You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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.
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. Fresh read on origin/main191e0430ef.
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.
The dataset door already tells the author to "declare a dependency … in the dependencies of P's manifest".
A Studio- or Setup-created package has no door that applies that remedy, so the author is walled: the only ways out are to recreate the package or bind elsewhere.
A consumer follow-up is p1 when its absence breaks users (filing-gate.md:40), and here it does.
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).
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/main3a931fca1; line numbers move; stop on a breach and explain it in the report):
packages/runtime/src/domains/packages.ts, the PATCH /packages/:id arm only (about :2247–:2286). It builds patch: { name?, description?, version? } at :2259, so manifest.dependencies is never persisted after create. The arm accepts dependencies, judged by ManifestSchema's dependencies row (read from @objectstack/spec, never restated), on both its branches: the protocol branch and the in-memory fallback.
packages/objectql/src/registry.ts, SchemaRegistry.updatePackageManifest only (about :6150): the in-memory merge, which today copies only the three keys. This file is domain:engine's, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in this act.
packages/metadata-protocol/src/protocol.ts, updatePackage only (about :32341): its patch type and the persisted manifest. This file is also domain:engine's, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367.
Pins beside each, an ablation for the persisted edit, and changesets: minor on @objectstack/runtime (a widening); objectql and metadata-protocol take whatever the export-list measurement says.
⛔ Hold-offs:
packages/spec/**. The IObjectQLEngine registry contract (packages/spec/src/contracts/objectql-engine.ts:131) still declares the three-key patch. If a gate or a type forces that line to move, stop and report it: packages/spec is the spec seat's.
objectui's edit dialog, which is filed there once this door accepts the row.
Every other /packages arm.
Container & model: M (three packages over two lanes, one door widened), mode:subagent, model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate")
Clause-②: yes
Basis for the Clause-② line: the PATCH /packages/:id door accepts a key it drops today. It is a widening of the door's accept set, as triage graded it (6279169921). It owes the lane seat's TIER_DEFAULT review of record before the queue.
Responsibility: this lane's runtime door (packages/runtime), with domain:engine's registry and protocol merge carrying the same three-key shape. No platform path covers it: the only ways out are recreating the package or binding elsewhere. It reaches every author whose existing package meets the closure refusal that prescribes "declare a dependency … in the dependencies of P's manifest" (the dataset door today, and the list-view door with PR fix(lint)!: the save door refuses a list view written into a package and bound outside its closure #24630).
Thread-read: 6279169921
Serial constraints cleared:
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"
]
}
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.
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"
]
}
Activity
objectstack-fleet commentedon Oct 19, 2026
Triage: p1 +
target:v18·bug· lanedomain:cli·area:studio·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-19T22:56ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. Fresh read on
origin/main191e0430ef.Premise holds.
packages/runtime/src/domains/packages.ts:2259buildspatch: { name?, description?, version? }and nothing else, so a package'smanifest.dependenciescannot change after create.Why p1 + v18. This is the remedy half of a refusal that ships in 18.0.
dependenciesof P's manifest".columnsfields are accepted (200 active, draft → publish) where the same body is refused 422list-view-field-unknown#24563, p1,target:v18) adds the same sentence at the list-view door.filing-gate.md:40), and here it does.Lane. The runtime door is
packages/runtime, which isdomain: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.
PATCHacceptsdependencies, judged byManifestSchema'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).findingcomes off with this grade.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
Claim: PM loop round 1 ·
domain:cliseat 2 · p1 +target:v18· 2026-10-19T23:12ZSession:
session_01LmjU2x1faLZ39DVTFyVca6Account:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24670-package-dependencies-patchWorktree:
objectstack-issue-24670Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on
origin/main3a931fca1; line numbers move; stop on a breach and explain it in the report):packages/runtime/src/domains/packages.ts, thePATCH /packages/:idarm only (about:2247–:2286). It buildspatch: { name?, description?, version? }at:2259, somanifest.dependenciesis never persisted after create. The arm acceptsdependencies, judged byManifestSchema'sdependenciesrow (read from@objectstack/spec, never restated), on both its branches: the protocol branch and the in-memory fallback.packages/objectql/src/registry.ts,SchemaRegistry.updatePackageManifestonly (about:6150): the in-memory merge, which today copies only the three keys. This file isdomain:engine's, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in this act.packages/metadata-protocol/src/protocol.ts,updatePackageonly (about:32341): itspatchtype and the persisted manifest. This file is alsodomain:engine's, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367.minoron@objectstack/runtime(a widening);objectqlandmetadata-protocoltake whatever the export-list measurement says.packages/spec/**. TheIObjectQLEngineregistry contract (packages/spec/src/contracts/objectql-engine.ts:131) still declares the three-key patch. If a gate or a type forces that line to move, stop and report it:packages/specis the spec seat's./packagesarm.Container & model:
M(three packages over two lanes, one door widened),mode:subagent,model: default tier(dispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: yes
Basis for the Clause-② line: the
PATCH /packages/:iddoor accepts a key it drops today. It is a widening of the door's accept set, as triage graded it (6279169921). It owes the lane seat'sTIER_DEFAULTreview of record before the queue.Responsibility: this lane's runtime door (
packages/runtime), withdomain:engine's registry and protocol merge carrying the same three-key shape. No platform path covers it: the only ways out are recreating the package or binding elsewhere. It reaches every author whose existing package meets the closure refusal that prescribes "declare a dependency … in thedependenciesof P's manifest" (the dataset door today, and the list-view door with PR fix(lint)!: the save door refuses a list view written into a package and bound outside its closure #24630).Thread-read: 6279169921
Serial constraints cleared:
pm:dispatchedclaim namespackages.ts; seat 1's four (rest: every GET /api/v1/data/OBJECT still runs a full zod safeParse of the object's definition (the API-exposure gate's by-name read is decorated with diagnostics) — 11.8% of a HotCRM read's CPU #24535, runtime: two hand-built 400 refusals put off-catalog codes ondetails.fields[].code(unrecognized_keys,invalid_string), which ADR-0114's closedFieldErrorCodecatalog forbids #24597, hourly full run: red on main (CI) #24645, runtime: the organization-ownership boot refusals prescribe--out PLAN/--apply --plan-file PLAN, which 18.0 refuses on every database (#24637, domain:cli row) #24651) are disjoint. Read in this act.registry.tsat:1465and:3246, and PR perf(rest): GET /data/OBJECT no longer computes metadata diagnostics; the exposure gate reads getMetaItemForExecution #24666 (rest: every GET /api/v1/data/OBJECT still runs a full zod safeParse of the object's definition (the API-exposure gate's by-name read is decorated with diagnostics) — 11.8% of a HotCRM read's CPU #24535) editsprotocol.tsat:11210–:11999. Both are other regions of a shared file, so ordinary concurrency applies: whichever lands second mergesmain. No open PR touchespackages.ts.packages/runtimeshare-links-refusal-order.test.ts› dispatcher twin › "[#23372] email audience: the refusal is the one an unknown token and a password-less email link answer" times out at vitest's 5000 ms default #24633 (onepackages/runtimetest file) and rest: a write refused by an unevaluable hook condition answers a generic500 INTERNAL_ERRORinstead of a coded refusal (#24619 row b) #24662 (REST andtypeserror classification). Both are disjoint.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
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 undermanifest, 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
dependencieson 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,
dependencieson 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'sClause-②: yesplus 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
objectstack-fleet commentedon Oct 20, 2026
ACCEPT → PR #24703 comment
6280623704·domain:cliseat 2 · sessionsession_01LmjU2x1faLZ39DVTFyVca6· 2026-10-20T00:44Z · head0fcd8e42c. Review of record6280612601(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
objectstack-fleet commentedon Oct 20, 2026
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'swrapped/src/patchtype /dependencieshandling 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^...' buildVERDICT command-exit 0 (built dist re-checked: objectql index.mjs carriesmanifest.dependencies = {x1, metadata-protocol index.d.ts carries the optionaldependencieskey 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 scripttsc --noEmit && pnpm check:test-typecheckran, 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
objectstack-fleet commentedon Oct 20, 2026
Landed: PR #24703 →
f7005cbd8(Fixes #24670)domain:cliseat 2 ·os-justin· sessionsession_01LmjU2x1faLZ39DVTFyVca6· 2026-10-20T04:26Zadded_to_merge_queue03:54Z)origin/mainf7005cbd8is on it, with a single parent (squash). 7 files, +645 / −17, matching the ACCEPTed change set (carried to7a8557cffby addendum6282684786)gh-readonly-queue/main/*pr-24703*head remainscompletedby the PR'sFixes. In the stroke after this comment,pm:dispatchedcomes off6279409111→ report → ACCEPT6280623704with review of record6280612601;6280948917(main-red hourly full run: red on main (CI) #24700, and packages:PATCH /api/v1/packages/:idedits a read-only package's manifest (200), where disable, publish, revert, delete and install-overwrite refuse it422 WRITABLE_PACKAGE_REQUIRED#24705 first);mainafter PR fix(runtime)!: PATCH /packages/:id refuses a read-only package with 422 WRITABLE_PACKAGE_REQUIRED #24719) → review of record6282679051on7a8557cff→ addendum6282684786.PATCH /api/v1/packages/:idaccepts the manifest'sdependenciesrow, judged byManifestSchema's row and replaced whole. A malformed row answers400 VALIDATION_ERRORand nothing is written. Onmainthe ADR-0070 gate (packages:PATCH /api/v1/packages/:idedits a read-only package's manifest (200), where disable, publish, revert, delete and install-overwrite refuse it422 WRITABLE_PACKAGE_REQUIRED#24705) precedes it, so only a writable package's row can be edited. The save refused with "declare a dependency … in thedependenciesof P's manifest" now has a door.6279169921: Studio / Setup package edit dialog cannot editdependencies:PackageFormDialogmarks the rowimmutableand its edit mode PATCHes only name / description / version, thoughPATCH /api/v1/packages/:idaccepts the row since objectstack#24670 objectui#13121. Studio'sPackageFormDialogkeeps the rowimmutableand its edit mode sends three keys, so the remedy is still not reachable in the UI.IObjectQLEngineregistry contract's three-key patch line (packages/spec, the spec seat's on its next touch);PackagesDomainProtocol.updatePackagetypedany.Generated by Claude Code