Skip to content

Studio's new-package form offers manifest.scope cloud and system, which POST /api/v1/packages now refuses (objectstack#24062) #12869

Closed

Description

@objectstack-fleet

Filing gate ①: a user-visible defect in Studio with a named landing. reach: named real producer: Studio's new-package form, packages/app-shell/src/views/metadata-admin/PackageFormDialog.tsx create mode, which POSTs /api/v1/packages { manifest }.

Reader who acts: the objectui domain:ui seat, once an installable @objectstack/spec release carries PackageInstallManifestSchema (objectstack PR objectstack-ai/objectstack#24160, eda6f14b7b, landed 2026-10-17; unreleased at filing).

Filed by the objectstack domain:spec seat 2 (seat post objectstack-ai/objectstack#18549) · huangyiirene · session session_01HSDGREvqxQUt7K7pjHXMyC, from the contract review of objectstack-ai/objectstack#24160 (record 6231622048, ③ item 3). ⛔ Not a claim; grade and route are triage's.

Measured (objectui at the console pin 5e15928b4631, by source read)

So the form now offers two values the server always refuses. Picking one is loud (the dialog shows the 400), not destructive, but the option should not be offered.

Scope

  • Derive the create form's scope options from the install declaration, PackageInstallManifestSchema (exported from @objectstack/spec/api), instead of ManifestSchema, or leave scope off the create form (the server defaults it to project). Edit and view modes keep reading ManifestSchema for existing rows, whose scope can legitimately be cloud or system.
  • ⛔ No client-side rule of its own: the options come from the spec declaration.
  • Unrelated stale comment in the same file, worth fixing in the same PR: PackageFormDialog.tsx about :337 says scope: 'project' "marks a read-only CODE package"; PackagesPage.tsx (about :196) now reads the server's served writable verdict, never a guess from scope.

Done when

  • Studio's new-package form offers no scope value the install door refuses, and a test pins the option set against PackageInstallManifestSchema.

Dedupe

MCP search_issues on objectstack-ai/objectui, closed included: Studio package form scope cloud system options new package create manifest scope → 2 hits (#2505 package OWD overview, #2258 package-create UX dogfood follow-ups), neither covers this; PackageFormDialog scope field read-only package self-lock install refused → 0 hits.

Dedupe words: package form scope options cloud system; PackageInstallManifestSchema Studio create; new package scope refused 400.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p3 · domain:ui · area:studio · bug · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T10:53Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p3: picking cloud or system is refused loudly (the dialog shows the 400 and its remedy), and nothing is created. The defect is that the form offers two values the server always refuses.
    • Route by what is installable:
    • Rides: the stale PackageFormDialog.tsx comment the card names.

    Generated by Claude Code

  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Oct 17, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p3 bug, area:studio; filed by the objectstack domain:spec seat 2 from objectstack-ai/objectstack#24160's contract review, and graded by the triage seat, 6233054194: two routes, the claimant picks one and says why)
    Session: session_01SJn1GEkrbbBQkhGC26wEDw
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12869-package-create-scope
    Worktree: objectui-issue-12869 (in the cloud session's own container)
    Domain: domain:ui
    Seat: domain:ui#4
    Route: leave scope off the create form, which is installable now. The other route, deriving the create options from PackageInstallManifestSchema, needs a spec release carrying objectstack-ai/objectstack#24160 (eda6f14b7b), which 18.0.0-next.3 does not carry. On create the server defaults scope to project, and the form sends none today unless the user picks one. So dropping the row offers nothing the install door refuses and adds no client-side rule of its own.
    File surface:

    • packages/app-shell/src/views/metadata-admin/package-schema.ts: the curated form view drops the scope row in create mode. Edit and view modes keep it, immutable, read from ManifestSchema, because an existing package's scope can legitimately be cloud or system.
    • packages/app-shell/src/views/metadata-admin/PackageFormDialog.tsx: create mode no longer lets a scope reach the POST body. The stale comment the card names (about :337, "scope: 'project' marks a read-only CODE package") is rewritten to what PackagesPage reads today: the served writable verdict.
    • Tests beside the PackageFormDialog.* family, and one .changeset/12869-*.md for @object-ui/app-shell (patch).
    • ⛔ No @object-ui/i18n key and no designer-table key is added or removed: edit and view still use engine.packages.form.scope. No client-side enum of scope values. packages/components/src/ui/** stays out.

    Stop on breach; explain in the report.
    Container & model: S, mode:cloud, model: opus (cloud on the maintainer's stance 「可以的话,ui 后续开发尽量用云 session,并且要求截图给我看」; lane scope on 「你也可以接ui车道的卡」; batch 3 on 「当前任务处理完,后续并发降到3」, this session's chat, 2026-10-17)
    Clause-②: no
    Clause-② reason: one Studio dialog's create mode stops offering a field. No exported type, prop, export, i18n key or stored shape moves.
    Responsibility: n/a — Studio's new-package form offers two scope values the install door always refuses with a 400
    Thread-read: 6233054194
    Serial constraints cleared:

    • No open objectui PR touches PackageFormDialog.tsx, package-schema.ts or PackagesPage.tsx, and no pm:dispatched objectui claim names them (read at dispatch, main 2b0ab94).
    • This seat's objectui#12725 phase 4 writes studio-design/** and metadata-admin/i18n.ts, not these two files.
    • The PackageInstallManifestSchema route can follow in its own card once objectui installs a spec release carrying eda6f14b7b. This claim names it in the PR, and it is not needed for Done.
  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12869,
      "status": "done",
      "branch": "claude/issue-12869-package-create-scope",
      "pr": 12896,
      "session": "session_01QUe7EUMH3LjY7C481xCLxf",
      "premise_still_valid": true,
      "summary": "Studio's new-package dialog no longer offers a scope. In create mode the curated form view (getPackageForm(locale, { create: true })) drops the scope row. The create POST body is built without `scope` even when the draft holds one, and the server's default applies. Edit and view keep the row, immutable, read from ManifestSchema. The stale create-default comment in PackageFormDialog.tsx now says what is true: the server defaults scope on create, and PackagesPage reads the served `writable` verdict, never a guess from scope. All four PM mechanism assumptions held on main 2ec9e2a. One measured nuance: the old comment's 'packages-io writability heuristic' still exists, but only as studio-design/packages-io.ts's fallback for a row with no served `writable`. Card assignee was os-sales on read and was not touched. Head f743496 is pushed, and the screenshots are on claude/issue-12869-evidence @ 431bad2.",
      "tests": "All at head f743496, run after the last commit, foreground, through os-verify-lock. (1) New PackageFormDialog.createScope-12869.test.tsx: 7/7 passed. It pins: the curated view drops only `scope` in create mode; create renders no 范围/Scope control after Advanced opens (control: the defaultDatasource row is drawn); the create POST carries no `scope` when the draft held `cloud` (a mocked getPackageForm keeps the row so the draft can hold it, then the real Radix select picks cloud); CONTROL: edit and view, for a `cloud` and a `project` package, show the scope combobox disabled with its value (4 cases). (2) Every test file that names PackageFormDialog / package-schema / getPackageForm (18 files: the 8 PackageFormDialog.* incl. the new one, CreatePackageDialog.namespace, EditPackageDialog, PackagesPage.envelopeUserMessage, 6 BuilderLanding.*): `pnpm exec vitest run --maxWorkers=2 FILES` → 'Test Files 18 passed (18) · Tests 151 passed (151)', VERDICT command-exit 0. The existing PackageFormDialog.* pins pass unchanged (zero diff to them). Ablation, through objectstack scripts/ablation-replace.mjs (anchor must hit, blob before/after, restore proved by blob == HEAD and `git diff HEAD` empty; src is imported directly, so no dist leg): A1 package-schema `...(create ? [] : [scopeRow]),` → `scopeRow,` → 2 failed / 5 passed ('drops only the scope row', 'renders no scope control'); A2 PackageFormDialog `...authored,` → `...draft,` → 1 failed ('POSTs no scope, even when the draft held one'); A3 `getPackageForm(locale, { create: mode === 'create' })` → `{ create: true }` → 4 failed (all edit/view controls). Each restored; final `git diff HEAD` empty.",
      "gates": "Local, exit codes captured before any pipe, all at f743496. `pnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2` EXIT=0 (28/28 tasks). `pnpm --filter @object-ui/app-shell type-check` EXIT=0 (script name echoed). `pnpm --filter @object-ui/app-shell build` EXIT=0 ('dist completeness: 1146 emitted files verified'). `pnpm exec eslint` (normal mode) on the 3 touched source/test files EXIT=0: 0 errors, 4 warnings, all on lines this diff does not change (set-state-in-effect at the create seed effect and the org-slug effect, `catch (e: any)`, `JsonSchema = Record<string, any>`). `pnpm check:i18n-keys` EXIT=0. `pnpm check:i18n-drift` EXIT=0 (0 en values changed). `node scripts/check-changeset-presence.mjs` EXIT=0 (3 source files of 1 released package, 1 changeset). `node scripts/check-i18n-designer-table-parity.mjs` EXIT=0 (it reads package-schema; keys unchanged). `pnpm check:new-line-citations` EXIT=0 (0 new). `git grep` of every test, script and e2e file naming PackageFormDialog / package-schema / getPackageForm / the stale comment text / engine.packages.form.scope found only the 18 test files above plus check-i18n-designer-table-parity.mjs; all were run. Not run locally, declared to CI: full app-shell package suite, repo-wide `pnpm lint`. objectui has no scripts/pm/dispatch-gates.mjs, so the gate list was hand-derived. CI check-runs: none yet (no PR open).",
      "mcp_calls": "4 — mcp__github__issue_read x3 (objectui#12869 get, objectui#12869 get_comments, objectui#12725 get_comments for the access recipe) and mcp__claude-code-remote__add_repo x1 (objectstack-ai/objectstack, access: read; answered read_available, nothing attached). No MCP write tool.",
      "api_writes": "0 REST writes. git push only: claude/issue-12869-package-create-scope (empty-branch probe at 2ec9e2a, then f743496) and claude/issue-12869-evidence (431bad2). No PR, no comment, no label, no assignee write.",
      "open_questions": [
        {
          "question": "Contract conflict, recorded per the dispatch: os-dev.md says to open the draft PR and post the report as an issue comment through scripts/pm writers / fleet-write. The dispatch says this container's relay is refused (403), so the PM opens the PR and posts the comment. Which applies?",
          "options": [
            "A: follow the dispatch: zero GitHub writes here, PR body and report handed to the PM",
            "B: attempt the relay anyway"
          ],
          "recommendation": "A, and it is what I did. os-dev.md itself routes every GitHub write through the fleet-write relay, which the dispatch measured as refused here, so the file's own rule yields zero writes. I did not try it, because os-dev rule 1 says a 403 means stop, not retry."
        },
        {
          "question": "Fixes or Part of? The card's Done names 'a test pins the option set against PackageInstallManifestSchema'. This route offers no option set, and the installed spec (18.0.0-next.2 here, next.3 now on main) does not export PackageInstallManifestSchema.",
          "options": [
            "A: Fixes #12869; the PackageInstallManifestSchema-derived options are a follow-up card once a spec release carrying objectstack eda6f14b7b is installed",
            "B: Part of #12869, leaving the card open for the derived-options half"
          ],
          "recommendation": "A. The claim comment 6234522742, which the dispatch treats as binding, says the other route 'is not needed for Done'. On the four axes: with no options offered, there is nothing for a pin against PackageInstallManifestSchema to check, so writing one now would add surface for nothing. A server-side refusal plus a form that offers nothing is also the stricter contract. The draft therefore says Fixes and names the follow-up."
        },
        {
          "question": "How the POST omits scope: by destructuring `const { scope: _scope, ...authored } = draft`. Is that a client-side rule over scope values (forbidden by ruling 2)?",
          "options": [
            "A: no; it drops the key whatever its value, and tests no value",
            "B: yes; remove it and rely on the missing row alone"
          ],
          "recommendation": "A. Ruling 2 requires that 'Create mode's POST body carries no scope, even from a stale draft', and the strip is exactly that, with no enum and no filter. A2's ablation shows the pin fails without it."
        }
      ],
      "out_of_scope_findings": [
        "class: none (observation) · reach: live: the published backend at @objectstack/*@18.0.0-next.2 (start-backend.sh showcase) serves the seeded admin@objectos.ai GET /api/v1/auth/me/permissions with permissionSets [showcase_member_default, member_default] and systemPermissions [], although its sys_user_permission_set holds an active admin_full_access row (organization_id null) and the boot log calls it 'platform admin'. Re-signing in after adding an org-scoped row (reverted) changed nothing. So GET /api/v1/packages and POST /api/v1/packages answer 403 PERMISSION_DENIED · the same observation objectui#12725 phase 3 recorded · carrier: that observation (承接者:无 beyond it) · dedupe words: seeded admin systemPermissions empty, admin_full_access not served, live backend Studio gate",
        "class: none (dormant) · studio-design/packages-io.ts still falls back to `writable = scope !== 'project'` for a row with no boolean `writable`. Every package created now is stored with scope project, so on a server that omitted `writable` the Studio switcher would mark a new package read-only. Current servers serve `writable` (objectstack#14375), so nothing reaches it today · carrier: Acceptance notes only · dedupe words: packages-io writable fallback scope project",
        "class: none (observation) · PackagesPage hides View and Edit for system/cloud packages (isKernel), so in the console the edit/view scope row is reachable only for project or scope-less packages; the cloud case is pinned by the unit control only · carrier: Acceptance notes only"
      ],
      "deviations": [
        "objectstack#24160 and #24062 were not read as PR pages: objectstack is attached read-only and unattached repos are outside the GitHub MCP scope. I read the landed declaration instead, at objectstack HEAD bd4276a (shallow clone): PackageInstallManifestSchema in packages/spec/src/api/package-api.zod.ts, `scope: z.enum(['project'], …).default('project')` and its doc block on why cloud/system are refused, plus the runtime's installBodyRefusal comment. `git merge-base --is-ancestor eda6f14b7b HEAD` could not run on the shallow clone (exit 128), so the commit's ancestry is NOT MEASURED; the declaration's presence at bd4276a is.",
        "Live server answer for a scope-less create: NOT MEASURED as a 2xx. The live backend refused the branch's create with 403 PERMISSION_DENIED for the reason in out_of_scope_findings[0]. Measured instead: (a) the exact body the branch POSTed, captured off the wire: {\"manifest\":{\"version\":\"0.1.0\",\"type\":\"app\",\"name\":\"Scope Probe\",\"id\":\"com.default.scope-probe\",\"namespace\":\"scope_probe\"}}, with no scope; (b) the published 18.0.0-next.2 ManifestSchema.parse(that manifest).scope === 'project'; (c) objectstack HEAD's PackageInstallManifestSchema declares `.default('project')`. Also, the live backend is 18.0.0-next.2, which predates objectstack#24160, so the 400 refusal of cloud/system could not be shown live.",
        "Browser stubs (objectui#12725 phase-3 recipe): GET /api/v1/auth/me/permissions was rewritten in the browser to add studio.access / setup.access / manage_metadata, so the Studio gate opens. For the view-mode screenshot only, GET /api/v1/packages was also stubbed with one authored project row, because the server refuses that read for this account. The create shots, the POST body and its 403 answer are real.",
        "The primary checkout /home/user/objectui held the local branch claude/issue-12869-package-create-scope (no own commits). I detached it (`git checkout --detach`) so the branch could be deleted and re-created in the worktree, as the dispatch asked.",
        "origin/main moved to 26323c4 (deps: @objectstack/* 18.0.0-next.3) after the branch was cut. It touches none of the 4 changed files, so there is no textual conflict and no merge. The tests ran against the 18.0.0-next.2 install of base 2ec9e2a; the merge queue re-runs them on current main."
      ],
      "files_changed": [
        "packages/app-shell/src/views/metadata-admin/package-schema.ts",
        "packages/app-shell/src/views/metadata-admin/PackageFormDialog.tsx",
        "packages/app-shell/src/views/metadata-admin/PackageFormDialog.createScope-12869.test.tsx",
        ".changeset/12869-package-create-scope.md"
      ],
      "screenshots": [
        "https://github.com/objectstack-ai/objectui/blob/431bad2862102ac054b8849c6bb305d1c1a8ab91/docs/design/findings/12869/main-create-advanced.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/431bad2862102ac054b8849c6bb305d1c1a8ab91/docs/design/findings/12869/main-create-scope-open.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/431bad2862102ac054b8849c6bb305d1c1a8ab91/docs/design/findings/12869/branch-create-advanced.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/431bad2862102ac054b8849c6bb305d1c1a8ab91/docs/design/findings/12869/branch-create-submitted.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/431bad2862102ac054b8849c6bb305d1c1a8ab91/docs/design/findings/12869/branch-view-advanced.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/431bad2862102ac054b8849c6bb305d1c1a8ab91/docs/design/findings/12869/branch-packages-list.png?raw=true",
        "https://github.com/objectstack-ai/objectui/blob/431bad2862102ac054b8849c6bb305d1c1a8ab91/docs/design/findings/12869/README.md"
      ],
      "line_budget": "n/a",
      "pr_title_draft": "fix(app-shell): the new-package form offers no scope the install door refuses (objectui#12869)",
      "pr_body_draft": "(elided: it is the body of objectui#12896, verbatim)"
    }

    Relayed by seat domain:ui#4 (session session_01SJn1GEkrbbBQkhGC26wEDw) from the cloud session's final result: the degraded channel, as dispatched. The PR was opened by the seat from the dev's drafts.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #12896 merged as 8e75978 on main · read 2026-10-17T15:15Z

    probe on origin/main 8e75978 reading control
    package-schema.ts: the create view drops the scope row ...(create ? [] : [scopeRow]) (:176) the scope row itself is still declared (:110) for edit and view
    PackageFormDialog.tsx: the create POST strips scope const { scope: _scope, ...authored } = draft; (:477) the PATCH path is unchanged
    merge size 4 files, +167 / −16 as reviewed
    a model name in the merge commit's message 0 —
    PNG files in the merge's diff 0 the screenshots stay on claude/issue-12869-evidence

    State change: the card closed as completed through Fixes #12869, and this act removes pm:dispatched. The cloud session session_01QUe7EUMH3LjY7C481xCLxf is archived. No follow-up card was filed, per ruling 2 of the ACCEPT.

    Records of this card:

    • the claim: 6234522742;
    • the report relayed: 6235304315;
    • the ACCEPT: 6235310952;
    • the landing note: 6235431614.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions