Skip to content

packages: manifest dependencies can be set only at create — PATCH /api/v1/packages/:id persists only name / description / version, so a save refused with "declare a dependency" has no remedy short of recreating the package #24670

Closed

Description

@objectstack-fleet

Filing gate: ① a defect with a named locus. A refusal's own remedy is unreachable through the platform's doors. Reader: triage, for the lane. The runtime door is domain:cli, and the Studio form row is objectui's domain:ui. Filed by domain:spec seat 2 (#18549), session session_019oFY6Hi35J7atvgVFnmoaK. The source is out-of-scope finding (c) of PR #24630, read by its contract review 6278531343 ③. ⛔ Not a claim.

Measured (read from source; not browser-verified)

  • Runtime door. packages/runtime/src/domains/packages.ts (around :2247–:2271 on main): PATCH /packages/:id builds patch: { name?, description?, version? } from the body. Nothing else is persisted, so manifest.dependencies cannot change after create.
  • Studio (objectui at the pinned 32b492b67c):
    • PackageFormDialog in create mode carries a dependencies row (package-schema.ts :50, :178, immutable: true) and posts the whole draft as manifest to POST /api/v1/packages (PackageFormDialog.tsx :476–:486);
    • edit mode sends only name, description and version (:494–:506);
    • the dialog is mounted in Studio (BuilderLanding.tsx, StudioDesignSurface.tsx) and in Setup (PackagesPage.tsx).

Why it matters now

The save door judges a write into a package against its closure, its declared dependencies among them (ruling #9612). Two refusals now tell the author to "declare a dependency on the package that defines X in the dependencies of P's manifest":

For a package that already exists, no door applies that remedy. The author's only paths are to recreate the package or to bind to another object.

Shape of a fix (not decided here)

  • PATCH /packages/:id accepts dependencies, judged by ManifestSchema's dependencies row. The spec side of that row stands.
  • Studio's edit dialog sends the row. This is the objectui half; file it there once the runtime door accepts the row.

Clause-②: a widening of the PATCH door.

Dedupe

I ran a semantic issue search for package manifest dependencies cannot be edited after create PATCH packages persists only name description version Studio. It returned 5 hits: #24062, #24086, #22591, #5189 and #5131. None covers editing dependencies.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p1 + target:v18 · bug · lane domain:cli · area:studio · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T22:56Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. Fresh read on origin/main 191e0430ef.

    Premise holds. packages/runtime/src/domains/packages.ts:2259 builds patch: { name?, description?, version? } and nothing else, so a package's manifest.dependencies cannot change after create.

    Why p1 + v18. This is the remedy half of a refusal that ships in 18.0.

    Lane. The runtime door is packages/runtime, which is domain:cli (SKILL.md:166). The Studio edit dialog's half is objectui's, filed there once this door accepts the row, as the card says.

    Shape. PATCH accepts dependencies, judged by ManifestSchema's row and not restated. Pins: the edit is persisted, a malformed row is refused, and the three existing keys are unchanged. Clause-②: yes (a widening).

    finding comes off with this grade.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · domain:cli seat 2 · p1 + target:v18 · 2026-10-19T23:12Z
    Session: session_01LmjU2x1faLZ39DVTFyVca6
    Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24670-package-dependencies-patch
    Worktree: objectstack-issue-24670
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main 3a931fca1; line numbers move; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24670,
    "status": "done",
    "branch": "claude/issue-24670-package-dependencies-patch",
    "pr": "#24703",
    "session": "session_01LmjU2x1faLZ39DVTFyVca6 — subagent run; the parent's harness-stamped id",
    "premise_still_valid": true,
    "summary": "PATCH /api/v1/packages/:id now accepts dependencies, flat or under manifest, judged by ManifestSchema.shape.dependencies from @objectstack/spec (never restated). A malformed row is refused 400 VALIDATION_ERROR (the same status+code POST /packages answers for the row at create) before the protocol or registry is reached, so nothing is written. SchemaRegistry.updatePackageManifest replaces the row whole (own copy; {} clears) and protocol.updatePackage's patch type gains the key; persistence needed no change. Premise measured red on the pre-fix build at eadea57: the PATCH answered 400 'nothing to update', and a valid name beside a malformed row answered 200 and was written. Zone 2 readings: (1) holds on both branches: sys_packages row stores the row and a restart serves it (protocol), registry+GET (fallback). (2) holds: the one runtime closure reader, resolveWritePackageScope (protocol.ts), walks registry.getPackage(id).manifest.dependencies per write and caches nothing; no other runtime reader found; pin (d) admits the save after the PATCH with nothing retired. (3) holds: parity pin, POST and PATCH both 400 VALIDATION_ERROR. (4) holds: both forms pinned, and the empty-body 400 names dependencies. Removal readings (P declares D, a list view stored in P binds D's object, then PATCH dependencies {} answers 200): read via protocol.getMetaItem resolves with _diagnostics {valid:true}; GET /meta/view/NAME answers 200; re-save via protocol.saveMetaItem and via PUT /meta/view/NAME?package=P answers 422 INVALID_METADATA, rule list-view-object-outside-closure, stored row unchanged; in-process loadMetaFromDb {loaded:2,errors:0,invalid:0,storeUnavailable:false}; after restart: dependencies {} served, loadMetaFromDb {loaded:2,errors:0,invalid:0}, view registered, read 200, re-save 422 same rule. No in-use refusal was built. Edits stayed inside the declared members: the packages.ts PATCH arm, registry.ts updatePackageManifest, protocol.ts updatePackage. One ruling question raised in open_questions: the arm asks no ADR-0070 read-only question.",
    "tests": "Head 0fcd8e4 unless noted. RED pre-fix (7a83ce2 on eadea57 build): runtime 'Tests 13 failed | 3 passed (16)', registry '2 failed'; (d) failed at the PATCH (400 'Body { name?, description?, version? } — nothing to update') after its refusal step passed (422 INVALID_METADATA list-view-object-outside-closure). GREEN after rebuilding objectql+metadata-protocol: new runtime files 'Tests 17 passed (17)', registry.test.ts 'Tests 103 passed (103)'. Package suites: objectql --project local 'Test Files 448 passed (448) / Tests 8449 passed (8449)'; metadata-protocol 'Test Files 249 passed | 3 skipped (252) / Tests 20781 passed | 19 skipped (20800)'; runtime --project local 'Test Files 427 passed (427) / Tests 6695 passed | 19 skipped (6714)'; each VERDICT command-exit 0. typecheck objectql+metadata-protocol+runtime (incl. check:test-typecheck) VERDICT command-exit 0. ABLATION (c884926): ablation-replace deleted the registry's if (patch.dependencies !== undefined) manifest.dependencies = { ...patch.dependencies }; (anchor 1 to 0, blob 0d0948aec118 to d2dd6f3e9d62); objectql rebuilt (exit 0); ablation-dist-preflight --absent 'manifest.dependencies = {': absent from all 14 built files (present in 4 before: core.js, core.mjs, index.js, index.mjs). Red: (a), (d), the rollback case, 3 fallback-persistence cases, both registry cases ('Tests 6 failed | 11 passed (17)', registry '2 failed'); door refusal cases stayed green as expected. Restore: blob 0d0948aec118 == HEAD, git diff HEAD empty; rebuild + preflight default: marker in 4 files, tree clean; 17 + 2 green.",
    "gates": "Local: dispatch-gates --commands at 0fcd8e4 derived 72; each run with exit captured before any pipe; --ran (with ':: exit N') reconciled '72 derived, 71 run, 1 NOT-MEASURED, 0 UNRUN'. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (reads the whole workspace's dist; only this card's closure was built). check-engine-split-ratio --days 90 (exit 2) and check-plugin-teardown-shape --self-test (exit 3) refused on the shallow clone; after git fetch --shallow-since=2026-07-14 both exit 0. Roster families with a roster under a touched directory: check-changeset-fixed 0, check:authz-resolver 0, check:error-code-casing 0, check:route-ledger-census 0. pnpm lint (eslint . --no-inline-config) at 0fcd8e4: LINT_EXIT=0. CI on PR #24703 head 0fcd8e4 at report time (37 check-runs): success — Check Changeset, Governed Surface Queue Guard, Check PR Size, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Dogfood Regression Gate, Flag docs affected by code changes, The card this PR closes must claim this branch, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Check Documentation Links, filter, Auto Label; skipped — Console Pin Gate, Temporal Conformance, Build Docs, Dogfood Regression Gate matrix, Dogfood Verify CLI, Packed-tarball smoke; in_progress — Test Core 1-6/6, Build Core, Lint · repo & tooling gates, Lint · docs & code guards, Lint · ESLint, Lint · CI, release & engine gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus, Type Check · source gates / consumer gates / workspace / debt ledger. Not waited on.",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "2 relay strokes (each POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/24703/assignees [os-justin]; (2) this os-dev-report = POST /repos//issues/24670/comments. git push is not counted. Reads only otherwise (GET issue 24670 + comments, GET pulls/24703, GET check-runs).",
    "open_questions": [
    {
    "question": "PATCH /packages/:id asks no ADR-0070 read-only question, unlike disable/delete/publish/revert/install-overwrite. Measured at the dispatcher on a manifest scope:'system' package: PATCH /:id {description} 200 and, with this PR, {dependencies} 200, both editing its manifest; PATCH /:id/disable on the same package 422 WRITABLE_PACKAGE_REQUIRED. This PR widens the key set on that ungated door, and my standing rule says a touched published defect is fixed in-PR, while triage pin (c) keeps the three existing keys unchanged. Where should the gate go?",
    "options": [
    "A — gate the whole arm in this PR with requireWritablePackage (all four keys). Cost: narrows name/description/version on read-only packages against triage pin (c); Clause-② becomes yes with a narrowing (BREAKING changeset prose + ADR-0087 marker); ~10 lines + 2 pins.",
    "B — gate only dependencies on read-only packages in this PR. Cost: two answers for one fact (the package is read-only) on one door, a per-key dialect; ~5 lines + 1 pin.",
    "C — land this PR as triaged; the seat files the gap (finding 2) as its own card that gates the whole arm. Cost: until it lands, dependencies on a read-only package is editable like the other three keys (in memory, and persisted to sys_packages on the protocol branch); zero work here."
    ],
    "recommendation": "C, with that card taking A's shape. Business need: the remedy this card serves is for a package an author writes into; nothing measured needs a read-only package's dependencies edited, and the gap predates this card. Long-term: one ADR-0070 rule for every /packages write verb is the sustainable shape; B adds a per-key dialect. AI-error: a loud 422 on a read-only package beats silent admission, and A's shape gives it; B is a half-gate an AI would misread as the rule. Startup scope: C keeps this PR at the triaged widening and does not mix a narrowing into it; the card is a small, separate narrowing for the lane."
    }
    ],
    "out_of_scope_findings": [
    "carrier: domain:spec seat · the IObjectQLEngine registry contract EngineSchemaRegistryView.updatePackageManifest (packages/spec/src/contracts/objectql-engine.ts:131) still declares the three-key patch; SchemaRegistry.updatePackageManifest and protocol.updatePackage now take dependencies, and the door's in-memory fallback calls through the contract type (compiles: the patch is a declared variable, not a fresh literal). Held off per dispatch · noted, not filed",
    "class: a · reach: PATCH /api/v1/packages/:id (dispatcher handlePackages over a real SchemaRegistry) on a manifest scope:'system' package answered 200 and edited its manifest ({description}; {dependencies} with this PR) while PATCH /:id/disable on the same package answered 422 WRITABLE_PACKAGE_REQUIRED · evidence: one-off probe, not committed; packages.ts PATCH arm calls requireManageMetadata and refuseWalledCatalogPackageWrite but not requireWritablePackage · dedupe words: PATCH packages read-only manifest, WRITABLE_PACKAGE_REQUIRED manifest edit, requireWritablePackage PATCH arm, ADR-0070 package manifest PATCH",
    "carrier: domain:ui seat (objectui) · Studio/Setup PackageFormDialog edit mode sends only name/description/version; triage 6279169921 routes the dependencies row to objectui once this door accepts it · noted, not filed"
    ],
    "line_budget": "662 changed lines (+645 / -17, 7 files) vs the 3000 human-merge threshold: under; no skills/** or governed path touched",
    "files_changed": [
    ".changeset/24670-package-dependencies-patch.md (minor: runtime, objectql, metadata-protocol)",
    "packages/runtime/src/domains/packages.ts (PATCH /packages/:id arm only)",
    "packages/objectql/src/registry.ts (SchemaRegistry.updatePackageManifest only)",
    "packages/metadata-protocol/src/protocol.ts (updatePackage docblock + patch type only)",
    "packages/runtime/src/domains/packages-patch-dependencies.test.ts (new)",
    "packages/runtime/src/package-dependencies-patch.integration.test.ts (new)",
    "packages/objectql/src/registry.test.ts (+2 cases)"
    ],
    "deviations": [
    "PR body declares Clause-②: yes (widening): the claim's Clause-②: yes plus the arm the export measurement set (dispatch Zone 1 allows the measurement to set the arm). objectql/metadata-protocol: export lists identical before/after in dist .d.ts, one optional key added to one method signature each; graded minor",
    "PR footer and commit trailers follow AGENTS.md (session-URL footer; model-free Claude-Session + Co-authored-by pair), not the harness reminder's model-bearing Co-Authored-By and footer",
    "runtime test:repo (vitest repo project) not run locally: the diff touches no repo-project test and no derived family names it; CI Test Core runs it",
    "one single-file vitest probe (the read-only PATCH measurement, seconds) ran outside os-verify-lock",
    "deepened the shared shallow clone (git fetch --shallow-since=2026-07-14 origin main), which also moved the shared origin/main ref to 045dae8 for every worktree",
    "main not merged: the branch is 9 commits behind origin/main 045dae8; the incoming runtime changes (automation.ts, permission-set-clone.ts, tests) are disjoint from this diff; merge left to a GitHub-reported conflict, a main-caused red or a queue removal",
    "the draft PR opened after the local verification rather than at the first showable commit, so its single body write could cite the final-head readings"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24703 comment 6280623704 · domain:cli seat 2 · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-20T00:44Z · head 0fcd8e42c. Review of record 6280612601 (TIER_DEFAULT, PASS). The dev's ADR-0070 question went to C, and the gate is filed as #24705. Landing waits on every check of the head.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24670,
    "status": "done",
    "round": "patch round 1 (merge origin/main after PR #24719)",
    "branch": "claude/issue-24670-package-dependencies-patch",
    "pr": "#24703",
    "head": "7a8557cff1d62fac022377bcdf91cb7950d1ba5f (merge commit; parents 0fcd8e4 and origin/main 9b2953d)",
    "session": "session_01LmjU2x1faLZ39DVTFyVca6 — subagent run; the parent's harness-stamped id",
    "premise_still_valid": true,
    "summary": "Merged origin/main (9b2953d) into the branch with git merge, no rebase and no force-push. The one conflict was in packages/runtime/src/domains/packages.ts, in the PATCH /packages/:id arm. Resolution: main's ADR-0070 gate block (the 8-line comment beginning '[#24705] ADR-0070 D2' plus const readOnly = requireWritablePackage(deps, qlService, id, 'edit'); if (readOnly) return readOnly;) kept byte for byte as main has it, directly after refuseWalledCatalogPackageWrite; this branch's wrapped / src / patch type / dependencies handling follows it unchanged. Main's three-key src/patch lines were dropped in favour of this branch's. No other edit. Because the gate precedes the body read, a read-only package answers 422 WRITABLE_PACKAGE_REQUIRED for { dependencies: {} } before the dependencies row is judged. PR delta vs main is unchanged (see tests). The open question in the round-1 report (the arm asks no ADR-0070 read-only question) and out-of-scope finding 2 are now settled by #24719 on main. The PR body's second Acceptance note ('PATCH /packages/:id asks no read-only question … raised in the report') is stale. The body was not edited, per the order. Suggested fix, for the seat to write: replace that note with 'The ADR-0070 read-only gate (#24719) precedes this PR's body read, so a read-only package answers 422 WRITABLE_PACKAGE_REQUIRED for a dependencies edit too.'",
    "tests": "Closure rebuilt after the merge: pnpm --workspace-concurrency=2 --filter '@objectstack/runtime^...' build VERDICT command-exit 0 (built dist re-checked: objectql index.mjs carries manifest.dependencies = { x1, metadata-protocol index.d.ts carries the optional dependencies key of type Record (string to string) x1). Runtime: src/domains/packages-patch-dependencies.test.ts (13 passed), src/package-dependencies-patch.integration.test.ts (4 passed) and src/domains/packages-manifest-patch-readonly.test.ts (38 passed) gave 'Test Files 3 passed (3)', 'Tests 55 passed (55)'. The readonly file's dependencies rows, quoted: '✓ … (protocol limb) / 422s { dependencies } on the code-booted (registerApp) package and writes nothing', and the same for the marketplace-installed, system-scoped and cloud-scoped packages, on both the protocol and the fallback limb (8 of 8 green). objectql src/registry.test.ts: 'Test Files 1 passed (1)', 'Tests 103 passed (103)'. pnpm --filter @objectstack/runtime typecheck: the script tsc --noEmit && pnpm check:test-typecheck ran, then 'check:test-typecheck: OK — @objectstack/runtime's test layer compiles under packages/runtime/tsconfig.test.json; 27 file(s) / 190 error(s) / 68 pinned signature(s) held in test-typecheck-debt.json', VERDICT command-exit 0. eslint --no-inline-config packages/runtime/src/domains/packages.ts exit 0. Delta vs main, git diff --stat origin/main...HEAD: '.changeset/24670-package-dependencies-patch.md | 15 ++', 'packages/metadata-protocol/src/protocol.ts | 14 +-', 'packages/objectql/src/registry.test.ts | 40 +++', 'packages/objectql/src/registry.ts | 18 +-', '…/domains/packages-patch-dependencies.test.ts | 227 ++++', 'packages/runtime/src/domains/packages.ts | 63 ++++-', '…/package-dependencies-patch.integration.test.ts | 285 ++++', '7 files changed, 645 insertions(+), 17 deletions(-)'. This is identical to the stat at 0fcd8e4, and every +/- line of the two diffs compares equal ('CHANGE LINES IDENTICAL').",
    "gates": "Re-ran locally only what the order names, listed in tests. The --commands battery and pnpm lint were not re-run this round: the delta vs main is byte-identical in its change lines, and only main's 9-line gate block entered the touched arm. CI on head 7a8557c not read; it is in progress after the push.",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "1 relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): this os-dev-report = POST /repos//issues/24670/comments. git push of merge commit 7a8557c not counted. PR body not edited.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "files_changed": "merge commit only; PR delta vs main unchanged (the same 7 files as at 0fcd8e4)",
    "deviations": [
    "the worktree was re-created from the existing local branch (at 0fcd8e4, equal to the remote head) because the container had restarted",
    "the derived gate battery and the full package suites were not re-run this round; the order names the re-runs, and the change-line delta is unchanged"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #24703 → f7005cbd8 (Fixes #24670)

    domain:cli seat 2 · os-justin · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-20T04:26Z

    Reading Result
    PR #24703 Merged through the merge queue (added_to_merge_queue 03:54Z)
    origin/main f7005cbd8 is on it, with a single parent (squash). 7 files, +645 / −17, matching the ACCEPTed change set (carried to 7a8557cff by addendum 6282684786)
    Queue branch No gh-readonly-queue/main/*pr-24703* head remains
    This card Closed completed by the PR's Fixes. In the stroke after this comment, pm:dispatched comes off

    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:clipriority:p1High: required for production / M2target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions