Skip to content

spec: PackageInstallRequestSchema's wrapped branch refuses top-level unknown keys by name — ruling A on #19328 #20249

Closed

Description

@objectstack-fleet

Filed by the director seat (summon #30 续, session_01AsCNgFBs8HCjwhyHQsFbx3) from the maintainer's ruling on #19328 (batch #227 item 3, letter A, maintainer 「开始总监决裁」). Reader who acts: the domain:spec execution seat. ⛔ Not a claim. Grading beyond what is written here is triage's.

What the ruling settles

POST /api/v1/packages answers exactly what PackageInstallBodySchema declares since PR #20218 (packages/runtime/src/domains/packages.ts: const declaredBody = PackageInstallBodySchema.safeParse(body), one hit on origin/main ab820016b3). Three of the four residual classes on #19328 are closed by that landing. The last one is the wrapped form's top level: PackageInstallRequestSchema's wrapped branch (packages/spec/src/api/package-api.zod.ts, docblock at :381 「⛔ Do NOT close the wrapped branch with .strict() … (ruling A)」) is declared strip-mode, so a caller who writes { manifest, enabledOnInstall: false } (a misspelling of enableOnInstall) has the key dropped silently and the package installs enabled.

The docblock's ruling A (5716042643, #18058, batch #148 item 4) rested on 「the declaration must not refuse a body the door answers 201 to」 at a time when the door did not parse the body at all. Since PR #20218 the door answers what the declaration says, so that premise is circular and no longer constrains the choice. The manifest and the bare form are already strict; the maintainer ruled the wrapped top level joins them: one rule for the whole install contract (the Kubernetes fieldValidation=Strict / Terraform provider-schema idiom).

Work

  1. PackageInstallRequestSchema, wrapped branch: strip → strict. An unknown top-level key is refused by name, with the same envelope the manifest's and the bare form's unknown keys already get (400 VALIDATION_ERROR, the key named, the remedy: remove it or spell the declared option). ⛔ No alias, no grace window.
  2. The docblock at package-api.zod.ts:381: replace the ruling-A sentence with this ruling's citation ([finding] four residual classes the package install door still answers 201 to, measured unchanged by #19326 (triage: likely four cards) #19328 batch 🔗 Broken links detected in documentation #227 item 3, letter A) and the reason above; do not leave the retired reasoning in place.
  3. Pins: package-api.test.ts's wrapped-branch rows assert the refusal (a misspelled option, a private key) and the controls (manifest alone; manifest + the four declared options round-trip). PR fix(runtime): the package install door parses its whole body through PackageInstallBodySchema #20218's §5 alignment pin (「the door answers what the declaration says」) flips from strip to refusal on its own: re-read its expectation and correct it in the same PR if it asserted the strip.
  4. Changeset: @objectstack/spec minor, **BREAKING** banner, Clause-②: no (narrowing), ADR-0087 registered disposition (the wording of [finding] POST /api/v1/packages installs a manifest with NO version and answers 201, while its published declaration requires one — the door parses nothing #19120 / [finding] the HTTP install door reads manifest.id positionally and never parses the body through ManifestSchema — POST /packages answers 201 to ids that MANIFEST_ID_PATTERN (spec, defineStack, os build, the publish face) refuses #19417). Reach measured first-party by the [finding] four residual classes the package install door still answers 201 to, measured unchanged by #19326 (triage: likely four cards) #19328 dev: the SDK sends only manifest / settings / enableOnInstall / overwrite; the objectui package dialog sends { manifest }. Out-of-repo callers are NOT MEASURED; say so in the changeset.
  5. [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 (the spec docblocks PR fix(runtime): the package install door parses its whole body through PackageInstallBodySchema #20218 made false) touches the same docblock region; the two may land as one PR if the same dev holds both. Neither is a claim on the other.

Acceptance

Clause-②: no (narrowing)

Activity

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01CiCTczDo7tGhafXjf61dUJ
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20249-install-request-wrapped-strict
    Worktree: objectstack-issue-20249
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface:

    The narrowing grade (Clause-②: no (narrowing), minor + **BREAKING**, and an ADR-0087 registered disposition) is the card's own execution text (item 4). This claim line carries the closed spelling only.

    Ruling carried: #19328 batch #227 item 3, letter A (maintainer 「开始总监决裁」), as this card's body records it. The wrapped top level joins the manifest and the bare form: strict, with an unknown key refused by name, ⛔ no alias and no grace window.

  2. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20249,
      "status": "done",
      "branch": "claude/issue-20249-install-request-wrapped-strict",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20277",
      "session": "session_01CiCTczDo7tGhafXjf61dUJ",
      "premise_still_valid": true,
      "summary": "The wrapped branch of PackageInstallBodySchema, PackageInstallRequestSchema, moved from z.object (strip) to strictObject (surface 'this package install request'). An unknown top-level key is now refused by name, in the envelope the manifest's and the bare form's unknown keys already get. A near-miss is offered the declared key (enabledOnInstall → enableOnInstall), and the history sentence carries the remedy: remove the key or spell the declared option. There is no alias and no grace window. The docblock's retired no-strict sentence and asymmetry paragraph were replaced by ruling record 5856869656 (batch #227 item 3, letter A) and its reason: the old premise is circular since c02fa1276 made the door parse its whole body. POST /api/v1/packages now answers 400 VALIDATION_ERROR naming the key and installs nothing, with no runtime source edit. Also added: the ADR-0087 D3 entry package-install-request-unknown-keys-refused, the regenerated registry.ts region, and the changeset (@objectstack/spec minor, BREAKING banner, Clause-②: no (narrowing) line-leading, registered disposition, FROM → TO migration, out-of-repo callers NOT MEASURED). PR #20277 is draft; its body line 1 is Fixes #20249 and line 2 is Clause-②: no (narrowing); the body read back byte-identical after the write. origin/main a9fb83ef0 was merged in before the PR (AGENTS.md §10). The merge was clean, registry.ts was regenerated, the closure was rebuilt, and everything was re-run at head 5941e9aa5. The worktree was removed after the PR opened.",
      "tests": "All at final head 5941e9aa5, under os-verify-lock, VERDICT command-exit 0 (build → tests → typechecks in one held run). BUILD: turbo run build --concurrency=2 --filter='@objectstack/client^...' --filter=@objectstack/client → exit 0, 33 of 33 tasks successful. TESTS, direction stated (the schemas' consumers found by git grep: spec, runtime, client): '@objectstack/spec exec vitest run --project local' exit 0, 550 files, 16244 passed, 1 todo; '@objectstack/runtime exec vitest run --project local' exit 0, 281 files, 4019 passed, 1 skipped; '@objectstack/client exec vitest run' exit 0, 50 files, 636 passed (readme-package-install-example.test.ts imports both schemas). TYPECHECK: spec, runtime and client 'run typecheck' (tsc --noEmit plus check:test-typecheck) all exit 0. The test:repo projects and the runtime integration layers were left to CI. ABLATION at 469f5e3a7 via scripts/ablation-replace.mjs in wrap mode, plus the script's own EXIT/INT/TERM trap with an absolute path. The merge touched neither ablated file nor either pin file, and resolution runs through src in both suites (spec imports relatively; runtime aliases @objectstack/spec to src), so no dist leg is needed. Mutation: anchor \"}).describe('Install package request'));\" became \"}).strip().describe('Install package request'));\"; anchor count 1 → 0, injected text 0 → 1, blob c4f6b506b477 → 70f87496fc7b. Result: spec package-api.test.ts had 3 failed and 81 passed, the three refusal pins failing with 'the bound union refuses it: expected true to be false' while the controls stayed green. Runtime packages-install-body-contract.test.ts had 3 failed and 29 passed: §5's misspelled and _source pins failed with 'expected 201 to be 400', meaning the door installed under strip, and §0's premise failed too; the §5 control stayed green. Restore: blob == HEAD blob c4f6b506b477 and git diff HEAD is empty. GATES: dispatch-gates --commands --repo objectstack-ai/objectstack at 5941e9aa5 derived 88; 86 exit 0 and 2 exit 3 (PREREQUISITE NOT MET: whole-workspace build), which is NOT MEASURED. The --ran reconciliation exit 0 reads '88 derived, 86 run, 2 NOT-MEASURED, 0 UNRUN'. LINT, narrowed on purpose: eslint --no-inline-config --format json over the 5 changed .ts files gave 5 files, 0 errors, 0 warnings. --print-config resolves all five; the .md changeset matches no config. eslint.config.mjs has no parserOptions.project or projectService (not type-aware), so no untouched file's verdict can move. Repo-wide pnpm lint is left to CI.",
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "3 REST writes, each a fleet-write relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls, draft forced (run 36338761159, PR #20277); (2) label-write --issue 20277 --assign os-sales → POST /repos/objectstack-ai/objectstack/issues/20277/assignees (run 36338800164), read back as assignees [os-sales], labels untouched; (3) this os-dev-report → POST /repos/objectstack-ai/objectstack/issues/20249/comments. Not REST: git pushes of claude/issue-20249-install-request-wrapped-strict (the empty-branch probe, 5 commit pushes, 1 merge push). Reads used REST GET single-card/list endpoints only.",
      "open_questions": [
        {
          "question": "This change makes one comment false in a file the claim fences out. packages/runtime/src/domains/packages.ts, about lines 1003-1011, the install branch comment: 「⚠️ What the union does NOT refuse, deliberately: an unknown key at the TOP LEVEL of the wrapped form. PackageInstallRequestSchema is a plain z.object (strip mode) and its docblock forbids closing it …」. The rule 本轮改动令其变假…必修 and the claim's fence (⛔ Not packages.ts source) point opposite ways, so I left the comment untouched. Should the fix ride this PR or go elsewhere?",
          "options": [
            "A: the seat widens this claim's surface to that one comment block, and a single comment-only commit lands on PR #20277 (a patch round). It changes no behaviour and no gate family.",
            "B: route it to a separate comment-only follow-up, or to #20219's lane if the seat folds it there."
          ],
          "recommendation": "A. The comment is this change's own falsity. Keeping the sentence and the close in one landing means main never carries a runtime comment that contradicts the declaration. It is one comment, no code moves, and no new verification surface is needed."
        }
      ],
      "out_of_scope_findings": [
        "carrier: this PR's patch round if the seat picks A above, else 承接者:无 · packages/runtime/src/domains/packages.ts:1003-1011 comment says the wrapped top level is strip mode and forbidden to close; false at 5941e9aa5 · recorded in PR #20277 Acceptance notes · dedupe words: wrapped top level strip comment packages.ts, PackageInstallRequestSchema plain z.object comment",
        "carrier: #20219's dev · packages/spec/src/api/package-api.test.ts test 'the door answers 201 to all of them anyway — so this declaration is a SUBSET of the door' (DOOR_201_RESIDUALS) has been false since c02fa1276, where the door answers 400. Same stale-201 class as the residual docblock; this PR neither touched it nor changed its truth · noted, not filed · dedupe words: DOOR_201_RESIDUALS stale 201, install residual 201 pin"
      ],
      "gates_head": "5941e9aa5",
      "gates": [
        "exit 0 @ 5941e9aa5 · node scripts/check-adr-0087-registration.mjs --base origin/main",
        "exit 0 @ 5941e9aa5 · node scripts/check-adr-0087-registration.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-changeset-no-major.mjs --base origin/main",
        "exit 0 @ 5941e9aa5 · node scripts/check-changeset-no-major.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-ci-filter-parity.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-closing-keyword-parity.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-closing-keyword-parity.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-comment-mask-adoption.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-comment-mask-adoption.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-comment-mask-corpus.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-dev-prereqs.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-empty-changeset.mjs --base origin/main",
        "exit 0 @ 5941e9aa5 · node scripts/check-empty-changeset.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-keyed-text-bounds.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-keyed-text-bounds.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-platform-object-tenancy-census.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-platform-object-tenancy-census.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-plugin-teardown-shape.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-plugin-teardown-shape.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-registry-log-declared.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-registry-log-declared.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-rest-log-spy-declared.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-rest-log-spy-declared.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-spec-docblock-symbol-anchors.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-spec-docblock-symbol-anchors.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-system-context-census.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-system-context-census.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/check-undeclared-dep-imports.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/check-undeclared-dep-imports.mjs --self-test",
        "exit 0 @ 5941e9aa5 · node scripts/docs-audit/check-affected-docs.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/docs-audit/check-drift-comment.mjs",
        "exit 0 @ 5941e9aa5 · node scripts/pm/release-rehearsal-clone.mjs --self-test",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:duration-unit-keys",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:empty-state",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:liveness",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:migration-registry",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:objectui-pin-citations",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:skill-refs",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:spec-changes",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:strictness-ledger",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:upgrade-guide",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:yaml-examples",
        "exit 0 @ 5941e9aa5 · pnpm check:changeset-gate-self-tests",
        "exit 0 @ 5941e9aa5 · pnpm check:cross-package-test-inputs",
        "exit 0 @ 5941e9aa5 · pnpm check:dispatcher-error-vocabulary",
        "exit 0 @ 5941e9aa5 · pnpm check:doc-authoring",
        "exit 0 @ 5941e9aa5 · pnpm check:driver-memory-census",
        "exit 0 @ 5941e9aa5 · pnpm check:engine-double-contract",
        "exit 0 @ 5941e9aa5 · pnpm check:future-spec-major",
        "exit 0 @ 5941e9aa5 · pnpm check:gitlink-declared",
        "exit 0 @ 5941e9aa5 · pnpm check:issue-citations",
        "exit 0 @ 5941e9aa5 · pnpm check:logger-receiver-detach",
        "exit 0 @ 5941e9aa5 · pnpm check:merge-driver",
        "exit 0 @ 5941e9aa5 · pnpm check:nul-bytes",
        "exit 0 @ 5941e9aa5 · pnpm check:objectql-double-limit",
        "exit 0 @ 5941e9aa5 · pnpm check:objectui-changeset",
        "exit 0 @ 5941e9aa5 · pnpm check:org-identifier",
        "exit 0 @ 5941e9aa5 · pnpm check:page-declaration-shape",
        "exit 0 @ 5941e9aa5 · pnpm check:pm-changeset-deadline-census",
        "exit 0 @ 5941e9aa5 · pnpm check:pm-prior-rulings",
        "exit 0 @ 5941e9aa5 · pnpm check:pm-widening-tells",
        "exit 0 @ 5941e9aa5 · pnpm check:query-options-erasure",
        "exit 0 @ 5941e9aa5 · pnpm check:refd-timer-probe",
        "exit 0 @ 5941e9aa5 · pnpm check:route-envelope",
        "exit 0 @ 5941e9aa5 · pnpm check:slot-lookup",
        "exit 0 @ 5941e9aa5 · pnpm check:spec-parsed-alias",
        "exit 0 @ 5941e9aa5 · pnpm check:test-source-alias",
        "exit 0 @ 5941e9aa5 · pnpm check:tier-file-adoption",
        "exit 0 @ 5941e9aa5 · pnpm check:watch-hint-literal",
        "exit 0 @ 5941e9aa5 · pnpm check:where-matcher",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/lint run check:doc-formula-expressions",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:api-surface",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:authorable-surface",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:browser-reachable-entries",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:docs",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:dual-source-exports",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:entry-nameability",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:export-origins",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:exported-any",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:llms-txt",
        "exit 0 @ 5941e9aa5 · pnpm --filter @objectstack/spec run check:variant-docs",
        "exit 0 @ 5941e9aa5 · pnpm check:dts-closure",
        "exit 3 @ 5941e9aa5 · pnpm check:dual-build-cjs-loads",
        "exit 0 @ 5941e9aa5 · pnpm check:lean-entry-closure",
        "exit 0 @ 5941e9aa5 · pnpm check:published-files",
        "exit 0 @ 5941e9aa5 · pnpm check:sourcemap-no-sources-content",
        "exit 0 @ 5941e9aa5 · pnpm check:type-check-coverage",
        "exit 3 @ 5941e9aa5 · pnpm check:type-check-debt"
      ],
      "line_budget": "6 files, +352 / -49 = 401 changed lines vs merge base a9fb83ef0 (dispatch-gates reading), under the 5000 human-merge threshold. No skills/** and no governed surface touched, so no skill line ratchet applies and the PR is not governed.",
      "files_changed": [
        ".changeset/20249-install-request-wrapped-strict.md (new)",
        "packages/spec/src/api/package-api.zod.ts",
        "packages/spec/src/api/package-api.test.ts",
        "packages/spec/src/migrations/entries/semantic/18.package-install-request-unknown-keys-refused.ts (new)",
        "packages/spec/src/migrations/registry.ts (generated region, via gen:migration-registry)",
        "packages/runtime/src/domains/packages-install-body-contract.test.ts"
      ],
      "deviations": [
        "PM assumption 2 held only in part. §5 was a PARITY pin, not a strip assertion, so it would have stayed green in both directions and would not flip on its own. It was converted to a status pin: the door is read first, the declaration last, plus a spelled-right control. The file header's 'deliberately NOT refused' section was rewritten to match, and §0 gained both bodies. Test file only.",
        "The #20219 region: exactly one clause was edited. Residual item 2's 'silently dropped on the wrapped one' became 'and, since decision batch #227 item 3, on the wrapped one too', because this change made that clause false. '`201` either way', the 'additionally answers `201` to five classes' lead-in, and items 1b, 3 and 4 were left exactly as they were for #20219's dev.",
        "Disposition: the card cites 'the wording of #19120 / #19417', but those changesets carry not-required (no-migration-prescription). I followed the ruling's `registered` disposition, since the changeset carries a FROM → TO prescription and the gate refuses no-migration-prescription beside one, and borrowed only their prose shape. check-adr-0087-registration reads '[BREAKING+clause-②-narrowing] registered … (new here)'.",
        "The card says 'the four declared options'; the schema declares five (settings, enableOnInstall, overwrite, platformVersion, artifactRef). The control covers all five, read off .shape.",
        "origin/main a9fb83ef0 was merged into the branch before the PR, per AGENTS.md Multi-agent §10: merge commit 5941e9aa5, registry.ts regenerated and check:migration-registry exit 0, pnpm install --frozen-lockfile, closure rebuilt, all tests, typechecks and gates re-run at 5941e9aa5. To merge first, I killed my own queued, not-yet-acquired full-test lock job (PID 23267, recorded) before it ran anything.",
        "The strictObject history sentence was reworded mid-work to drop the example key, so every refusal message stays discriminating: the key named in a message is always the caller's own key.",
        "NOT MEASURED: check:dual-build-cjs-loads and check:type-check-debt (exit 3, both need a whole-workspace build); the spec and runtime test:repo projects (declared to CI); repo-wide pnpm lint (declared to CI; narrowed eslint proof above).",
        "Reach: the SDK was re-read at this base, and hotcrm at 2f7b2326 has 0 install-door POST callers (read-only grep). The objectui dialog's { manifest } is taken from the ruling record and was not re-measured, because this session has no objectui checkout. Out-of-repo callers are NOT MEASURED, as the changeset says.",
        "A container restart killed the first ablation while it was still queued and had never acquired the lock (no mutation happened; the tree was verified clean at 9cb95eb3c). It was re-run twice: once at 9cb95eb3c, then at 469f5e3a7 after the runtime pins were reordered so the door assertion reddens first. The 469f5e3a7 run is the one cited."
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim 5857174683: file surface extended for a comment-only patch round on PR #20277 (2026-09-27T18:02Z)

    domain:spec seat 4 (session_01CiCTczDo7tGhafXjf61dUJ). ⛔ Not a new claim: branch, worktree, assignee and Clause-②: no are unchanged.

    Why. The dev report on #20249 raised an open question (option A recommended). The runtime install door's comment at packages/runtime/src/domains/packages.ts about :1003–1013 says PackageInstallRequestSchema "is a plain z.object (strip mode) and its docblock forbids closing it, so { manifest, bogus } parses green with bogus dropped". PR #20277 at 5941e9aa5 makes that sentence false: the wrapped branch now refuses an unknown top-level key by name, and the door answers 400. It is this change's own falsity, so it rides this PR. The seat answers A under the non-escalating class for pure wording corrections: a comment, no behaviour, no gate family.

    Serial check (read now). Among the 21 open PRs, the only one touching that file or package-api.zod.ts is #20277 itself. The File surface: lines of the newest claims on all 33 open pm:dispatched cards name packages/runtime/src/domains/packages.ts in none of them.

    Added to the surface: that one comment block in packages/runtime/src/domains/packages.ts (the paragraph beginning "What the union does NOT refuse"). It becomes the true sentence and cites the ruling record the spec docblock now cites. ⛔ No code token of packages.ts moves.

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20249,
      "status": "done",
      "branch": "claude/issue-20249-install-request-wrapped-strict",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20277",
      "session": "session_01CiCTczDo7tGhafXjf61dUJ",
      "round": "patch round 1 (comment-only), answering open question A per claim amendment 5858369774 and at-tier review 5858576330 non-blocking flags (1) and (4)",
      "premise_still_valid": true,
      "summary": "Patch round 1 is one commit, 0b352e98a (docs(runtime,spec)), on top of 5941e9aa5 and pushed; the remote head is 0b352e98a. In packages/runtime/src/domains/packages.ts I rewrote the paragraph beginning 「⚠️ What the union does NOT refuse」 to state the truth at the head. PackageInstallRequestSchema is a strictObject (ruling record 5856869656, decision batch #227 item 3, letter A). An unknown top-level key such as { manifest, enabledOnInstall: false } fails the union by name. The door answers 400 / VALIDATION_ERROR and installs nothing, without a line of its own for it. The retired strip-mode/forbids-closing reasoning is removed, not left beside the new text. In package-api.zod.ts, residual item 2's clause now cites ruling record 5856869656 inline; nothing else in #20219's region moved. No code token moved in either file: the parser comparison is identical, with a positive control. origin/main (ae8e3ca01, 10 commits past merge base a9fb83ef0) did not touch either file I edit, so I made no merge (step 4). I did not write the PR body. I removed the worktree and the private refs again after pushing.",
      "sentences_touched": [
        "packages.ts, replaced: 「⚠️ What the union does NOT refuse, deliberately: an unknown key at the TOP LEVEL of the wrapped form.」 It was false at 5941e9aa5.",
        "packages.ts, replaced: 「`PackageInstallRequestSchema` is a plain `z.object` (strip mode) and its docblock forbids closing it, so `{ manifest, bogus }` parses green with `bogus` dropped — and this door, reading only parsed keys, now does exactly what the declaration says.」 The first half was false. The true tail is kept in substance as 「reading only parsed keys, it does exactly what the declaration says」.",
        "packages.ts, restated: 「Refusing it here without the declaration would make the published contract admit a body the door refuses; that is the declaration's decision to make.」 It was still true, but its 'it' referred to the key the old text said was admitted. It is now the general rule: 「⛔ Never refuse or admit a key here that the declaration decides the other way: the published contract and this door would then answer differently, and which keys the body may carry is the declaration's decision.」",
        "packages.ts, re-read and judged still true, so untouched: the 「[#19328] ⭐ THE DOOR PARSES THE WHOLE BODY」 opener; the 「Until this line …」 history paragraph (its row 2 names the manifest and bare-body positions only); the 基本裁决原则 paragraph including 「⛔ Nothing in `packages/spec` moves, and neither branch is relaxed」 (it describes the door-parse change c02fa1276, which moved nothing in spec, and no branch is relaxed; this PR tightens); and 「The verdict is answered after the `id` and `version` legs below …」.",
        "package-api.zod.ts, residual item 2: 「since decision batch #227 item 3」 became 「since ruling record `5856869656` (decision batch #227 item 3)」. 「`201` either way」 and the rest of the #20219 region are byte-unchanged."
      ],
      "tests": "All at 0b352e98a, in one locked run (VERDICT command-exit 0). BUILD turbo client^... plus client: exit 0, 33 of 33. TYPECHECK: runtime 'run typecheck' exit 0 (tsc --noEmit plus check:test-typecheck OK); spec 'run typecheck' exit 0. TESTS: runtime packages-install-body-contract.test.ts 32 of 32 passed; spec package-api.test.ts 84 of 84 passed. TOKEN PROOF (step 3), command 'node tokcmp2.mjs 5941e9aa5 0b352e98a packages/runtime/src/domains/packages.ts packages/spec/src/api/package-api.zod.ts', exit 0. The script parses each git blob with typescript's createSourceFile, walks the leaf tokens of the parsed AST with JSDoc nodes skipped, and re-prints each file with createPrinter({ removeComments: true }). packages.ts: parse diagnostics 0 and 0; leaf tokens 7479 → 7479, IDENTICAL (sha 484ec2925dd6637c both); comment-free print IDENTICAL (sha 87b406412a664db8 both); raw file 139931 → 140170 chars. package-api.zod.ts: leaf tokens 1845 → 1845, IDENTICAL (sha fb5b4042ca85df5f); print IDENTICAL (sha 1231cdb26b0c6c66). Positive control, 'e46218674 5941e9aa5 package-api.zod.ts' (round 1's real code change), reads DIFFER (1821 → 1845 tokens; print sha d5e2488d76771d72 vs 1231cdb26b0c6c66), exit 1. My first attempt used a bare ts.createScanner, with no parser context; it desynchronised on a backtick inside a // comment and reported a false DIFFER. I discarded it as a failed instrument and did not read it as a finding. ESLint over both files: 0 errors, 0 warnings. No ablation this round: comment-only, no assertion changed.",
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "1 REST write, through the fleet relay repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) as objectstack-fleet[bot]: this os-dev-report → POST /repos/objectstack-ai/objectstack/issues/20249/comments. Not REST: one git push of 0b352e98a. The PR body was not written (step 7). Reads: REST GET of comments 5858369774 and 5858576330.",
      "pr_body_update_requested": "- **Comment made true in patch round 1 (`0b352e98a`, under claim amendment `5858369774`).** The install-branch paragraph in `packages/runtime/src/domains/packages.ts` used to open 「⚠️ What the union does NOT refuse, deliberately」. It now opens 「⭐ The TOP LEVEL of the wrapped form is closed too」 and states three things: `PackageInstallRequestSchema` is a `strictObject`, an unknown top-level key fails the union by name, and the door answers `400` / `VALIDATION_ERROR` and installs nothing. It cites ruling record `5856869656`. Residual item 2 in `package-api.zod.ts` now cites `5856869656` inline. Both are comment-only edits: for `packages.ts` and `package-api.zod.ts`, the parser's leaf-token stream (JSDoc excluded) and the comment-free print are identical at `5941e9aa5` and `0b352e98a`, while a positive control (`e46218674..5941e9aa5` on `package-api.zod.ts`) reads DIFFER.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "gates_head": "0b352e98a",
      "gates": [
        "exit 0 @ 0b352e98a · node scripts/check-adr-0087-registration.mjs --base origin/main",
        "exit 0 @ 0b352e98a · node scripts/check-adr-0087-registration.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-changeset-no-major.mjs --base origin/main",
        "exit 0 @ 0b352e98a · node scripts/check-changeset-no-major.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-ci-filter-parity.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-closing-keyword-parity.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-closing-keyword-parity.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-comment-mask-adoption.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-comment-mask-adoption.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-comment-mask-corpus.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-dev-prereqs.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-empty-changeset.mjs --base origin/main",
        "exit 0 @ 0b352e98a · node scripts/check-empty-changeset.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-keyed-text-bounds.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-keyed-text-bounds.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-platform-object-tenancy-census.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-platform-object-tenancy-census.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-plugin-teardown-shape.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-plugin-teardown-shape.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-registry-log-declared.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-registry-log-declared.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-rest-log-spy-declared.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-rest-log-spy-declared.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-spec-docblock-symbol-anchors.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-spec-docblock-symbol-anchors.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-system-context-census.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-system-context-census.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/check-undeclared-dep-imports.mjs",
        "exit 0 @ 0b352e98a · node scripts/check-undeclared-dep-imports.mjs --self-test",
        "exit 0 @ 0b352e98a · node scripts/docs-audit/check-affected-docs.mjs",
        "exit 0 @ 0b352e98a · node scripts/docs-audit/check-drift-comment.mjs",
        "exit 0 @ 0b352e98a · node scripts/pm/release-rehearsal-clone.mjs --self-test",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:duration-unit-keys",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:empty-state",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:liveness",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:migration-registry",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:objectui-pin-citations",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:skill-refs",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:spec-changes",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:strictness-ledger",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:upgrade-guide",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:yaml-examples",
        "exit 0 @ 0b352e98a · pnpm check:changeset-gate-self-tests",
        "exit 0 @ 0b352e98a · pnpm check:cross-package-test-inputs",
        "exit 0 @ 0b352e98a · pnpm check:dispatcher-error-vocabulary",
        "exit 0 @ 0b352e98a · pnpm check:doc-authoring",
        "exit 0 @ 0b352e98a · pnpm check:driver-memory-census",
        "exit 0 @ 0b352e98a · pnpm check:engine-double-contract",
        "exit 0 @ 0b352e98a · pnpm check:future-spec-major",
        "exit 0 @ 0b352e98a · pnpm check:gitlink-declared",
        "exit 0 @ 0b352e98a · pnpm check:issue-citations",
        "exit 0 @ 0b352e98a · pnpm check:logger-receiver-detach",
        "exit 0 @ 0b352e98a · pnpm check:merge-driver",
        "exit 0 @ 0b352e98a · pnpm check:nul-bytes",
        "exit 0 @ 0b352e98a · pnpm check:objectql-double-limit",
        "exit 0 @ 0b352e98a · pnpm check:objectui-changeset",
        "exit 0 @ 0b352e98a · pnpm check:org-identifier",
        "exit 0 @ 0b352e98a · pnpm check:page-declaration-shape",
        "exit 0 @ 0b352e98a · pnpm check:pm-changeset-deadline-census",
        "exit 0 @ 0b352e98a · pnpm check:pm-prior-rulings",
        "exit 0 @ 0b352e98a · pnpm check:pm-widening-tells",
        "exit 0 @ 0b352e98a · pnpm check:query-options-erasure",
        "exit 0 @ 0b352e98a · pnpm check:refd-timer-probe",
        "exit 0 @ 0b352e98a · pnpm check:route-envelope",
        "exit 0 @ 0b352e98a · pnpm check:slot-lookup",
        "exit 0 @ 0b352e98a · pnpm check:spec-parsed-alias",
        "exit 0 @ 0b352e98a · pnpm check:test-source-alias",
        "exit 0 @ 0b352e98a · pnpm check:tier-file-adoption",
        "exit 0 @ 0b352e98a · pnpm check:watch-hint-literal",
        "exit 0 @ 0b352e98a · pnpm check:where-matcher",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/lint run check:doc-formula-expressions",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:api-surface",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:authorable-surface",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:browser-reachable-entries",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:docs",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:dual-source-exports",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:entry-nameability",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:export-origins",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:exported-any",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:llms-txt",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run check:variant-docs",
        "exit 0 @ 0b352e98a · pnpm check:dts-closure",
        "exit 3 @ 0b352e98a · pnpm check:dual-build-cjs-loads",
        "exit 0 @ 0b352e98a · pnpm check:lean-entry-closure",
        "exit 0 @ 0b352e98a · pnpm check:published-files",
        "exit 0 @ 0b352e98a · pnpm check:sourcemap-no-sources-content",
        "exit 0 @ 0b352e98a · pnpm check:type-check-coverage",
        "exit 3 @ 0b352e98a · pnpm check:type-check-debt",
        "exit 0 @ 0b352e98a · eslint --no-inline-config --format json packages/runtime/src/domains/packages.ts packages/spec/src/api/package-api.zod.ts (2 files, 0 errors, 0 warnings)",
        "exit 0 @ 0b352e98a · pnpm check:issue-citations (self-test)",
        "exit 0 @ 0b352e98a · node scripts/check-issue-citations.mjs (1 citation judged across 4 files, resolves)",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/runtime run typecheck (tsc --noEmit plus check:test-typecheck OK)",
        "exit 0 @ 0b352e98a · pnpm --filter @objectstack/spec run typecheck",
        "exit 0 @ 0b352e98a · turbo run build --concurrency=2 --filter=@objectstack/client^... --filter=@objectstack/client (33 of 33 tasks)",
        "exit 0 @ 0b352e98a · runtime vitest src/domains/packages-install-body-contract.test.ts (32 of 32 passed)",
        "exit 0 @ 0b352e98a · spec vitest src/api/package-api.test.ts (84 of 84 passed)",
        "exit 0 @ 5941e9aa5..0b352e98a · node tokcmp2.mjs (comment-stripped parser comparison) packages/runtime/src/domains/packages.ts packages/spec/src/api/package-api.zod.ts",
        "exit 0 @ 0b352e98a · node scripts/pm/dispatch-gates.mjs --ran (88 derived, 86 run, 2 NOT-MEASURED, 0 UNRUN)"
      ],
      "line_budget": "This round: 2 files, +13 / -9 (packages.ts +11/-8, package-api.zod.ts +2/-1), comments only. Branch vs merge base a9fb83ef0: 7 files, +364 / -57 = 421 changed lines, under 5000; not governed.",
      "files_changed": [
        "packages/runtime/src/domains/packages.ts (comment paragraph only, about :1003-1013)",
        "packages/spec/src/api/package-api.zod.ts (residual item-2 clause only)"
      ],
      "deviations": [
        "dispatch-gates warned STALE TREE: 0b352e98a is 10 commits behind origin/main ae8e3ca01, and 5 gate or tool files changed across that range (scripts/check-error-status-conformance.mjs, scripts/doc-authoring-prose-id.baseline.json, scripts/pm/dispatch-gates.mjs, scripts/pm/post-stamped.mjs, scripts/pm/record-recognisers.mjs). Neither file I edit moved, so under step 4 I made no merge. The gate union was derived and run with this head's copies of those scripts; the upstream copies' verdicts on this diff are NOT MEASURED here and will show up in CI's merge-ref run. The path-specific derivation for packages.ts (47 families) is a subset of the 88-family union.",
        "NOT MEASURED (exit 3, whole-workspace build prerequisite), unchanged from round 1: check:dual-build-cjs-loads, check:type-check-debt.",
        "The instruction said to 'Re-create … from origin/claude/… (PR head 5941e9aa5 unless the review names a later one)'. The review names 5941e9aa5 as its head, and the local branch, the remote and the fetched ref all equalled 5941e9aa5 before editing."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #20277 (2026-09-27T19:51Z)

    domain:spec seat 4 (session_01CiCTczDo7tGhafXjf61dUJ). This is the review of record for the round claimed in 5857174683 and widened by amendment 5858369774. The dev reports are 5858350043 (round 1) and 5859104116 (the comment patch round).

    Checklist, read on GitHub:

    Findings, disposed one line each:

    Next: ready → auto-merge through the relay → merge queue. This card closes on the merge through Fixes.

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #20277 → 28ad7e4b37 (2026-09-27T20:15Z)

    domain:spec seat 4 (session_01CiCTczDo7tGhafXjf61dUJ). PR #20277 merged through the merge queue on 2026-09-27T20:15:09Z, and this card closed completed through its Fixes line. The readings that confirm the landing:

    • 28ad7e4b37 has one parent (6a6a17b62e), is an ancestor of origin/main, and the queue branch for the PR is gone.
    • Its git patch-id --stable equals the reviewed diff's: e2960425ee01 on both (delta record 5859274102 at 0b352e98aa, from merge base a9fb83ef06).
    • By content: the ruling record 5856869656 is cited 2 times in package-api.zod.ts at the merge and 0 on the parent.

    pm:dispatched and the assignee come off in the same act.

    Carried: #20219 (pm:blocked behind #19328) owns the residual section's stale 「201」 text and the DOOR_201_RESIDUALS pin title. Its dev starts from the one clause this PR edited, residual item 2, which now cites 5856869656.

  7. added a commit that references this issue on Sep 28, 2026
    28ad7e4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions