Repository navigation
docs(spec): split the install door's residual clause 1 into its closed version half and open type half - #19935
Conversation
…and version halves The PackageInstallBodySchema docblock recorded "a manifest missing `type` and/or `version`" as one class the install door answers 201 to. The door now parses ManifestSchema.shape.version by reference and answers 400 / VALIDATION_ERROR to a manifest missing `version`, so only the `type` half is still residual. - package-api.zod.ts (docblock only): clause 1 splits into 1a (`version`, marked closed, naming the door-side pin) and 1b (`type`, still open). The count sentence says class 1 stays open through its `type` half. The paragraph quoting the runtime's door drives quotes the duplicate-id drive's current body, which carries a `version`. - package-api.test.ts: the registry drive transcription matches the body the drive posts now. The residual list is hoisted and gains a pin that every body in it carries a declared-valid `version`, with a lit control. - The pending 18058 changeset restated the same clause; its sentence is split the same way so the next CHANGELOG entry is true. - New patch changeset for @objectstack/spec: files[] ships the docblock. Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr Co-authored-by: Claude <noreply@anthropic.com>
check-empty-changeset's foreign-changeset rule refuses a PR that modifies a pending changeset it did not add. Correcting another PR's pending release note is a release decision that needs a person to confirm it, so the restated clause in that note is reported to the seat instead of being rewritten here. The file is byte-identical to the merge base again. Claude-Session: https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
Contract reviewServed-tier: 61/61 Isolated at-tier reviewer subagent, run by the ① Derived judgmentsScope read:
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Blocking: the registry-drive re-transcription Generated by Claude Code |
The install door's residual docblock, its pinning test and the changeset
transcribed the duplicate-id drive in domain-handler-registry.test.ts as
{ id: 'pkg-a', name: 'A', version: '1.0.0' }. That drive stopped posting
that body at PR #19473, which made the door parse the manifest's id leg
and repaired the fixture's id to com.example.pkg-a. The old body is the
REVERSED pin beside it and is answered 400. It had been filed as a 201
residual under clause 1b.
Read at the merge base fdeeea0 and at origin/main 2c1011b (the file
is byte-identical at both): the drive posts
{ id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }, answered 409 and
then 201 on ?overwrite=true. The declaration refuses it on `type` alone.
Clause 1b's "both door drives above" is true of that body, so 1b is
unchanged.
- package-api.zod.ts: the drives paragraph quotes the real body and
credits both repairs, the version to PR #19326 and the id to PR #19473.
- package-api.test.ts: DOOR_DRIVE_REGISTRY transcribes the real body.
The control that rested on the stale id now asserts that the registry
drive parses once `type` is added, like the conflict drive. The old
`pkg-a` reading is kept, pointed at the old body: it is refused on the
id alone.
- changeset: the drives sentence names the body the drive posts.
Prose and test only. No schema, accept set, export or runtime change.
Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d
Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: 62/62 Isolated at-tier reviewer subagent, run by the ① Derived judgmentsScope read:
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19327
Clause-②: no
PackageInstallBodySchema's docblock inpackages/spec/src/api/package-api.zod.tslists the bodiesPOST /api/v1/packagesanswers201to while the declaration refuses them. Its clause 1 recorded "a manifest missingtypeand/orversion" as ONE class. PR #19326 made the door parseManifestSchema.shape.versionby reference, so only thetypehalf of that clause is still true. This PR splits the clause.The diff is the docblock, one spec test file and one changeset. No schema, accept set, export or runtime code moves, and residual classes 2 to 5 are untouched.
Both halves re-measured at
origin/mainfdeeea0cc9, before any editBoth halves read exactly as the card says.
error.codeversionversionVALIDATION_ERRORversionversionVALIDATION_ERRORtypetypetypetypeHow it was driven. I used a one-shot probe, deleted right after the reading and never committed. It was a vitest file at
packages/runtime/src/domains/probe-19327-oneshot.test.tswith the harness ofpackages-install-manifest-version.test.ts: a realHttpDispatcher, spied protocol and registry install writers, andOS_HOMEredirected. It calledhandlePackages('', 'POST', body, {}, admin)once per row and printed status, code and whether either writer was called. It ran in one invocation with the existing drives that the docblock's parenthesis «both door drives above» points at:typehalf, from both existing drives. Each drive posts a manifest with notype, and each asserts that the door answers201:package-door-namespace-conflict-code.test.tsposts{ id, name: id, namespace, version: '1.0.0' }.domain-handler-registry.test.tsposts{ id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }(:600):409first, then201on?overwrite=true. The body{ id: 'pkg-a', name: 'A', version: '1.0.0' }has been that file's REVERSED pin since PR fix(runtime): POST /api/v1/packages parses the manifest's id leg #19473 and is answered400(:622-623), so it is not a residual.versionhalf, from fix(runtime): POST /api/v1/packages parses the manifestversionleg instead of installing anything it is handed #19326's door test. §1 ofpackages/runtime/src/domains/packages-install-manifest-version.test.tspins400+VALIDATION_ERROR+ neither writer called, on both body forms. It is cited, not duplicated.What changed
packages/spec/src/api/package-api.zod.ts(docblock only)version: marked CLOSED, naming PR fix(runtime): POST /api/v1/packages parses the manifestversionleg instead of installing anything it is handed #19326 and the door-side pin.type: still OPEN, answered201, with «both door drives above».versionleg instead of installing anything it is handed #19326's own body, so the two records read the same way.typehalf. The count sentence now says «class 1 in itstypehalf only, since PR fix(runtime): POST /api/v1/packages parses the manifestversionleg instead of installing anything it is handed #19326». Numbering 1 to 5 is unchanged, so no citation of clauses 2 to 5 moves.{ id: 'pkg-a', name: 'A' }and said «the second carries noversioneither». That drive was repaired twice: PR fix(runtime): POST /api/v1/packages parses the manifestversionleg instead of installing anything it is handed #19326 gave it aversion, and PR fix(runtime): POST /api/v1/packages parses the manifest's id leg #19473 replaced its idpkg-a, whichMANIFEST_ID_PATTERNrefuses. At the merge basefdeeea0cc9and atorigin/mainit posts{ id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }. The paragraph is clause 1b's antecedent, so left alone it would have filed a no-longer-posted body under the open residual. It now quotes the body the drive posts, says the declaration refuses both drives ontypealone and the door answers both201, credits both repairs, and says the door answers the oldpkg-abody400.packages/spec/src/api/package-api.test.ts(the pin of the clause)DOOR_DRIVE_REGISTRYnow transcribes the body the drive posts:{ id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }. Its comment credits both repairs, and itsittitle drops "noversion".typeis what decides it, for BOTH drives — the registry drive's old id is refused on its own». It asserts that the registry drive parses oncetypeis added, as the conflict drive does. The oldpkg-areading is kept, pointed at the old body:{ ...registryKeysCompleted, id: 'pkg-a' }is refused on the id alone, and the green parse just above it is its lit control.DOOR_201_RESIDUALS, and the existing "the door answers 201 to all of them anyway" assertion is unchanged over it.versionthe declaration accepts». It checks each residual's manifest withManifestSchema.shape.version. Its lit control is thatundefinedis refused. It cites the door-side pin rather than repeating it, because this package cannot import the door..changeset/19327-install-door-residual-split.md:patchfor@objectstack/specA changeset is owed, not
skip-changeset, becausefiles[]shipssrc/**/*.zod.tsand the docblock is also emitted into the built declarations. After the build, the new clause text is indist/api/index.d.tsanddist/api/index.d.mts, and the old spelling "and/orversion" hits 0 acrossdistandsrc. The bump ispatchbecause it is a text correction in a released package, with no API change.Note that npm
@objectstack/spec@17.4.0has noPackageInstallBodySchemaat all: grep 0, withPackageInstallRequestSchema= 4 as the control in the same file. The stale clause has therefore not shipped yet. The next release would be the first to carry it.Verification, round 1 (head
826e612b39)Reverse verification. The fix was committed first. I then restored the old drive transcription
{ id: 'pkg-a', name: 'A' }throughscripts/ablation-replace.mjs, with the anchor counted 1 → 0, the replacement 0 → 1, and the blobe976bff69a→5b952ed89a.ManifestSchema.idis a barez.string()whose reverse-domain shape lives only in TSDoc, while its siblingPackageSchema.manifestIdenforces that shape with a regex — one identifier, two declarations, only one of them machine-readable #17534 lit control in "the missing keys are what decide it", which now takes the drive'sversionthrough the spread.Tests 2 failed | 73 passed (75), exactly those two.e976bff69a, equal to HEAD, andgit diff HEADis empty. Restore was proven by hash, not by exit code.Spec package
pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2givesTest Files 527 passed (527),Tests 15504 passed | 1 todo, exit 0.pnpm --filter @objectstack/spec run typecheckexits 0.check:test-typecheckreports OK.tsc -p tsconfig.test.json --listFilesOnlylistssrc/api/package-api.test.ts(1 hit), and that file has no debt-ledger entry.turbo run build --filter=@objectstack/specexits 0.git statuswas clean afterwards, so no generated artifact moved.Gates. From
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat this head: 82 derived, run one by one with each exit code captured before any pipe.--ranreports: «✓ dispatch-gates --ran: 82 derived famil(ies) accounted for — 82 run, 0 NOT-MEASURED (a DERIVED zero — all 82 recorded an exit code and none of them is 3)».check:dual-build-cjs-loadsandcheck:type-check-debt. Afterturbo run build --filter='./packages/*' --filter='./packages/*/*'(72 tasks, exit 0), both measured 0:Lint (a narrowed pass, stated as one). I ran
eslint --no-inline-config --format jsonon the three touched paths. The JSON has 3 results. The two.tsfiles show 0 errors and 0 warnings. The.mdis outside eslint's own population ("File ignored because no matching configuration was supplied"). This narrowing covers everything the full run would:eslint.config.mjsnever enables type-aware linting (its note at lines 327-328: noparserOptions.project, no typed rules), so this diff cannot move the verdict of any untouched file. The fullpnpm lintunion is CI's.History. The first commit
b685c67ad2also rewrote the pending.changeset/18058-install-door-contract-rebind.md.check:empty-changeset's foreign-changeset rule refused that with exit 1. The second commit826e612b39restores the file byte-identical to the merge base, and the gate reads exit 0 at head. Acceptance note 1 below has the details.Patch round: the registry drive, transcribed as it posts (head
0251069c5)The at-tier contract review of head
826e612b39(record5808378321) failed one item. This PR had transcribed the registry drive as{ id: 'pkg-a', name: 'A', version: '1.0.0' }and filed it under the open201residual. That drive stopped posting that body at PR #19473; the body is now the drive file's REVERSED pin, answered400. Commit0251069c5corrects every copy in one round. The clause split, 1a's CLOSED marking, the count sentence and thepatchlevel are unchanged.Read at both refs before any edit.
packages/runtime/src/domain-handler-registry.test.tsandpackages/runtime/src/package-door-namespace-conflict-code.test.tsare each byte-identical at the merge basefdeeea0cc9and atorigin/main2c1011b01b:{ id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }(:600). It is answered409, then201on?overwrite=true(:601-604).{ id: 'pkg-a', name: 'A', version: '1.0.0' }(:622) and asserts400(:623).{ id, name: id, namespace, version: '1.0.0' }(:83) and asserts201on the first install.Clause 1b re-judged against both drives. Both still show it, so 1b stays byte-unchanged.
tsxprobe onsrc(not committed): both real bodies failManifestSchemaontypealone, and both parse oncetype: 'app'is added. The stale body fails onidandtype.0251069c5:pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 --reporter=verbose src/domain-handler-registry.test.ts src/package-door-namespace-conflict-code.test.tsgivesTests 57 passed (57). That includes the duplicate-id case (409, then201), the REVERSED pin (400) and namespace section 1 (first install201).What changed
package-api.zod.ts, the drives paragraph only:typealone while the door answers both201.versionrepair to PR fix(runtime): POST /api/v1/packages parses the manifestversionleg instead of installing anything it is handed #19326 and the id repair to PR fix(runtime): POST /api/v1/packages parses the manifest's id leg #19473.pkg-abody400, so that body is not part of the residual.package-api.test.ts:DOOR_DRIVE_REGISTRYholds the real body.typeis added. It keeps the oldpkg-areading, refused on the id alone.DOOR_201_RESIDUALSand its doc comment are unchanged: with the corrected constant, every entry is a body the door answers201..changeset/19327-install-door-residual-split.md: the drives sentence names the body the drive posts, and thepkg-abody the door answers400.Reverse verification. Run after the fix was committed, through
node scripts/ablation-replace.mjs, which put the stale transcription back.37e99f04ca→0331f39bc4.Tests 1 failed | 74 passed (75). The failure is atpackage-api.test.ts:945(expected false to be true), the parse of the real body plustype.37e99f04ca, equal to HEAD, andgit diff HEADis empty.Verification at
0251069c5package-api.test.ts:Tests 75 passed (75). Onorigin/main2c1011b01bplus this branch's patch it givesTests 79 passed (79); the patch applies cleanly beside feat(spec)!: remove the three unmounted PackageApiContracts entries (upgrade / resolve-dependencies / upload) #19937's changes to the same two files.--project localgivesTest Files 527 passed (527)andTests 15504 passed | 1 todo. The threerepo-project tests that read.changeset/or mentionpackage-apigive136 passed.pnpm --filter @objectstack/spec run typecheckexits 0.package-api.test.tsis intsconfig.test.json's program and has no debt entry.dist/api/index.d.tsand.d.mtseach carry the real body once and the stale body zero times.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 82. Deriving onorigin/mainplus the patch gives the same 82, with an empty set difference both ways.--ranon the record as captured: «82 derived famil(ies) accounted for — 80 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)».check:dual-build-cjs-loadsandcheck:type-check-debt, with PREREQUISITE NOT MET on a fresh worktree.turbo run build --filter='./packages/*' --filter='./packages/*/*'(72 tasks, exit 0), separate re-runs measured both at exit 0: «104 published require entry point(s) across 67 package(s) load» and «4 ledger entr(ies) re-measured … none above its recorded number».eslint --no-inline-config --format jsonon the three paths gives 3 results..tsfiles have 0 errors and 0 warnings. The.mdis outside eslint's population.eslint.config.mjsenables no type-aware linting (its note at:327-328), so this diff cannot move any untouched file's verdict.pnpm lintis CI's.Acceptance notes
.changeset/18058-install-door-contract-rebind.mdrestates the same clause.typeand/orversion(both of the runtime's own door drives post one)». That has been half false since fix(runtime): POST /api/v1/packages parses the manifestversionleg instead of installing anything it is handed #19326.@objectstack/spec,@objectstack/runtimeand@objectstack/clientCHANGELOGs at the next release.check:empty-changesetroutes to a person, and that gate stays red on any PR that makes the edit. The proposed replacement sentence goes to the seat in the report.400to a whitespace-onlyid«this declaration admits».ManifestSchema.idcarriesMANIFEST_ID_PATTERNand the declaration refuses it.package-api.test.tsalready pins that refusal.packages/runtime/src/domains/packages-install-manifest-version.test.tscounts the docblock's five classes differently from the docblock.version,type, unknown keys, string-typed options and bare-form options.type/versionas one class and the whitespaceidas class 5.Generated by Claude Code