Repository navigation
fix(spec)!: PackageInstallRequestSchema's wrapped branch refuses an unknown top-level key by name - #20277
Conversation
…unknown top-level key by name
The wrapped install body closes from strip to strict (strictObject), so
{ manifest, enabledOnInstall: false } is refused naming the misspelled key
instead of installing the package ENABLED with the key dropped. The docblock's
retired no-strict sentence is replaced by decision batch #227 item 3, letter A
(ruling record 5856869656) and its reason.
Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ
Co-authored-by: Claude <noreply@anthropic.com>
…and pin it at both doors Adds the semantic entry package-install-request-unknown-keys-refused and the regenerated migrations/registry.ts region, the minor BREAKING changeset with its registered disposition, and turns the runtime install-body contract's parity pin into a status pin for the wrapped top level. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…ct reference's defaulted format Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…wer before the declaration's Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…-request-wrapped-strict
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 141 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ec6f45b3b7f56a8efae2a6de382a59990d7aceba && git checkout ec6f45b3b7f56a8efae2a6de382a59990d7aceba
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e6b7d8c8612dc0842e0153089e99a99c4c5837a4 0b352e98aa1b9f641219c60296aea4b0b8ea3612 && git checkout -B drift-repro e6b7d8c8612dc0842e0153089e99a99c4c5837a4 && git merge --no-ff 0b352e98aa1b9f641219c60296aea4b0b8ea3612
node scripts/docs-audit/affected-docs.mjs --json e6b7d8c8612dc0842e0153089e99a99c4c5837a4
|
Contract reviewServed-tier: Read: card #20249 body and its 3 comments (claim 5857174683, dev report 5858350043, amendment 5858369774); ruling record 5856869656 (#19328, batch #227 item 3, letter A — verified it is that ruling) and the retired record 5716042643 (#18058, batch #148 item 4); ruling B on #17152 (5615360777); PR #20277 object, body, 6 files, 5 commits, 35 check-runs; the full diff against merge-base a9fb83e; at the head: package-api.zod.ts, package-api.test.ts, packages.ts (:70–85, :675–775, :970–1075, :1098–1110, :1190–1205), packages-install-body-contract.test.ts, the D3 entry and its registry region, the changeset, strict-object.ts, suggestions.zod.ts, polarity-axes.ts, the three changeset-gate headers, pr-automation's WHICH LEVEL prose, the strictness-ledger gate and counts, the SDK install call, README and docs examples, the CLI integration/e2e bodies, hotcrm at 2f7b2326 (read-only). Ran, in a detached worktree at 5941e9a after pnpm install --frozen-lockfile, under os-verify-lock (VERDICT command-exit 0, held 62s): a tsx spec-door probe; turbo build of the runtime closure (29/29 cache hits); spec package-api.test.ts 84/84; runtime packages-install-body-contract.test.ts 32/32; ablation 1 (.strip() at the dev's anchor) and ablation 2 (bare z.object().strict(), helper removed), both restored to the HEAD blob with clean porcelain. NOT MEASURED: the objectui package dialog's body (no ../objectui checkout, console dist gitignored and unbuilt — taken from the ruling record); out-of-repo callers; the derived gate families and the full spec/runtime suites (CI's, read from the check-runs). ① Derived judgments
② Semver levelDeclared: ③ Boundary flagsBlocking: none CI at this head: 35 check-runs on 5941e9a, 0 pending — 32 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)); all seven required contexts success: Lint & Repo Gates, TypeScript Type Check, Test Core (6/6 shards), Dogfood Regression Gate (3/3 shards), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard; Check Changeset, Spec property liveness, Type Check · source/consumer/debt/workspace all success. The PR is a draft, not governed (no governed path among its 6 files), size/m at +352/−49, merge base a9fb83e. Implemented-by: VERDICT: PASS |
… level is closed, and item 2 cites its ruling record The install branch comment in packages/runtime/src/domains/packages.ts said PackageInstallRequestSchema was strip mode and forbidden to close; since the wrapped branch became a strictObject that is false. It now states the close, the door's 400 VALIDATION_ERROR with nothing installed, and cites ruling record 5856869656. The residual item-2 clause in package-api.zod.ts gains the same record id inline. Comments only; no code token moves. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Delta from 5941e9a (PASS 5858576330): exactly one commit, ① Derived judgments
② Semver levelUnchanged and right. The changeset ③ Boundary flagsBlocking: none CI at this head: 42 check-runs on 0b352e9, 0 pending — 37 success, 5 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in), and the 19:33:58Z edited-event re-runs of Auto Label and Check PR Size); all seven required contexts success: Lint & Repo Gates, TypeScript Type Check, Test Core (6/6 shards), Dogfood Regression Gate (3/3 shards), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard; Check Changeset, Spec property liveness, Type Check · source/consumer/debt/workspace, Dogfood Verify CLI, the four claim guards all success. 17 workflow runs, all completed, none failed. Implemented-by: VERDICT: PASS |
Fixes #20249
Clause-②: no (narrowing)
Dispatched by the
domain:specseat 4 PM, sessionsession_01CiCTczDo7tGhafXjf61dUJ, round 1, on claim5857174683. Ruling carried: decision batch #227 item 3, letter A (ruling record5856869656, maintainer 「同意」).What changed
PackageInstallRequestSchema, the wrapped branch ofPackageInstallBodySchema(packages/spec/src/api/package-api.zod.ts):z.object(strip) becamestrictObject, with surfacethis package install request. An unknown top-level key is now refused by name, in the same envelope the manifest's and the bare form's unknown keys get. The key is named, a near-miss is offered the declared key, and the history sentence carries the remedy: remove the key, or spell the declared option it meant. There is no alias and no grace window.{ manifest, enabledOnInstall: false }now answers:Docblock. The sentence 「⛔ Do NOT close the wrapped branch with
.strict()… (ruling A)」 and the asymmetry paragraph around it are gone. In their place are this ruling's citation and its reason. The retired premise, «the declaration must not refuse a body the door answers201to», held only while the door did not parse its body. Sincec02fa1276the door answers what this union declares, so the premise is circular. The heading now reads "disjoint — and BOTH are closed". The request schema's own docblock gains a short "CLOSED" section that points at the union.The install door's code is not edited. It refuses the body because it asks the declaration. Its install-branch COMMENT was made true in patch round 1 (⚠️ What the union does NOT refuse, deliberately」 and now opens 「⭐ The TOP LEVEL of the wrapped form is closed too」. It states that
0b352e98a, under claim amendment5858369774). It used to open 「PackageInstallRequestSchemais astrictObject, that an unknown top-level key fails the union by name, and that the door answers400/VALIDATION_ERRORand installs nothing. It cites ruling record5856869656. Residual item 2 inpackage-api.zod.tsnow cites5856869656inline. Both edits are comment-only: for both files, the parser's leaf-token stream (JSDoc excluded) and the comment-free print are identical at5941e9aa5and0b352e98a, and a positive control reads DIFFER.ADR-0087 D3 entry
package-install-request-unknown-keys-refused(packages/spec/src/migrations/entries/semantic/18.…ts), plus the regeneratedmigrations/registry.tsregion.spec-changes.jsonanddocs/protocol-upgrade-guide.mdwere regenerated and did not change, because those projections do not render major-18 entries yet.Changeset
.changeset/20249-install-request-wrapped-strict.md:@objectstack/specminor, with a BREAKING for callers of the install door banner.Clause-②: no (narrowing)on its own line, and the ADR-0087 markerregistered package-install-request-unknown-keys-refused.Pins
packages/spec/src/api/package-api.test.ts, the wrapped-branch rows):enabledOnInstall, a private_source, and an unknown key beside declared options. Each asserts that the bound union refuses the body. Each also asserts the wrapped branch's single issue: codeunrecognized_keys, path[],keysequal to exactly that key, and the key named in the message. The misspelling also asserts the offeredenableOnInstall.manifestalone;manifestplus every declared option, with each value carried through. The option list is read off.shape, so a new option cannot slip past the fixture.packages/runtime/src/domains/packages-install-body-contract.test.ts):{ manifest, enabledOnInstall: false }answers400 VALIDATION_ERROR, naming`enabledOnInstall`and offering`enableOnInstall`. It is not installed, and it is not recorded as disabled._sourcegets the same answer, and nothing is installed.enableOnInstall: falseanswers201and is DISABLED in the registry and in the durable record.Evidence
e46218674.origin/maina9fb83ef0was merged in before this PR as5941e9aa5. The merge was clean,registry.tswas regenerated, andcheck:migration-registrypassed with 267 semantic entries. The build closure was then rebuilt and every run below was repeated at5941e9aa5.turbo run build --filter='@objectstack/client^...' --filter=@objectstack/client: 33/33 tasks successful.5941e9aa5, all exit 0:@objectstack/spec--project local: 550 files, 16244 passed, 1 todo.@objectstack/runtime--project local: 281 files, 4019 passed, 1 skipped.@objectstack/client: 50 files, 636 passed. It imports both schemas inreadme-package-install-example.test.ts.tsc --noEmitandcheck:test-typecheck): spec, runtime and client all exit 0.469f5e3a7, throughscripts/ablation-replace.mjsin wrap mode with an EXIT/INT/TERM trap. The merge touched neither ablated file nor either pin file.}).describe('Install package request'));became}).strip().describe('Install package request'));. Anchor count went 1 → 0, the replacement 0 → 1, and the blobc4f6b506b477→70f87496fc7b.the bound union refuses it: expected true to be false); the controls stayed green.expected 201 to be 400, so the door installed under strip mode, and §0's premise failed too. The §5 control stayed green.c4f6b506b477) andgit diff HEADis empty.dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat5941e9aa5): 88 derived, 86 exit 0, and 2 NOT MEASURED because their prerequisite is not met (exit 3).--ranreconciliation reports 0 UNRUN.check:dual-build-cjs-loadsandcheck:type-check-debtneed a whole-workspace build. CI runs them.check-adr-0087-registration --base origin/main(it reads this changeset as[BREAKING+clause-②-narrowing] registered package-install-request-unknown-keys-refused (new here)),check-changeset-no-major,check-empty-changeset,check:api-surface,check:authorable-surface,check:docs,check:strictness-ledger,check:migration-registry,check:spec-changes,check:upgrade-guide,check:doc-authoringandcheck:nul-bytes.eslint --no-inline-config --format jsonover the 5 changed.tsfiles): 5 files, 0 errors, 0 warnings.--print-configresolves all five. The changeset.mdis outside eslint's configured population.eslint.config.mjsenables no type-aware linting (noparserOptions.projectorprojectService), so this diff cannot change the verdict on any file it does not touch.pnpm lintis left to CI.Measured against the dispatch's assumptions
PackageInstallBodySchemaand refuses a wrapped unknown key without any runtime source edit. The door pin in §5 is the evidence.PackageInstallBodySchemaresidual section ([finding] once PR #20218 lands,PackageInstallBodySchema's published docblock still says the install door answers 201 to residual classes 1b, 2, 3 and 4, which it then refuses 400 #20219's region; [finding] once PR #20218 lands,PackageInstallBodySchema's published docblock still says the install door answers 201 to residual classes 1b, 2, 3 and 4, which it then refuses 400 #20219 is not addressed here):201either way". This change made "silently dropped on the wrapped one" false. The item now reads "refused by name on the bare branch and, since decision batch 🔗 Broken links detected in documentation #227 item 3, on the wrapped one too,201either way".201either way", the "additionally answers201to five classes" lead-in, and items 1b, 3 and 4 were already stale before this PR. They are left exactly as they were for [finding] once PR #20218 lands,PackageInstallBodySchema's published docblock still says the install door answers 201 to residual classes 1b, 2, 3 and 4, which it then refuses 400 #20219's dev.PackageInstallBodySchema's published docblock still says the install door answers 201 to residual classes 1b, 2, 3 and 4, which it then refuses 400 #20219's text.Acceptance notes
packages/runtime/src/domains/packages.ts, around lines 1003 to 1011, the install branch comment says 「PackageInstallRequestSchemais a plainz.object(strip mode) and its docblock forbids closing it …」. Resolved in this PR: the seat widened the claim to that one comment block (amendment5858369774), and patch round 1 (0b352e98a) made it true; see What changed.package-api.test.ts, the test "the door answers 201 to all of them anyway — so this declaration is a SUBSET of the door" (theDOOR_201_RESIDUALSlist) has been false sincec02fa1276. It belongs with [finding] once PR #20218 lands,PackageInstallBodySchema's published docblock still says the install door answers 201 to residual classes 1b, 2, 3 and 4, which it then refuses 400 #20219's docblock class. This PR's diff does not change its truth.settings,enableOnInstall,overwrite,platformVersionandartifactRef. The control covers all five, read off.shape.manifest.idpositionally and never parses the body throughManifestSchema— POST /packages answers 201 to ids thatMANIFEST_ID_PATTERN(spec,defineStack,os build, the publish face) refuses #19417 for the disposition. Those two changesets carrynot-required (no-migration-prescription), notregistered. This changeset follows the ruling'sregistereddisposition, because its body carries a FROM → TO prescription and the gate refusesno-migration-prescriptionbeside one. It borrows those two changesets' prose shape instead: the BREAKING banner, "What is not affected", and the remedy arriving with the400.5859104116). The at-tier review is5858576330(PASS at5941e9aa5).client.packages.installsends onlymanifest,settings,enableOnInstallandoverwrite.{ manifest }), measured by the dev who closed the door's other residuals. It was not re-measured here, because this session has no objectui checkout.2f7b2326: zero install-door POST callers (a read-only grep).Generated by Claude Code