Skip to content

docs(spec): PackageInstallBodySchema docblock records the install door residual as closed, and enableOnInstall as read off the parsed request - #20506

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20219-install-body-docblock
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20219-install-body-docblock

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20219

Clause-②: no

What this changes

Text only, in @objectstack/spec: the PackageInstallBodySchema docblock, one sentence of the PackageInstallRequestSchema.enableOnInstall docblock, the matching block of package-api.test.ts, and a patch changeset. ⛔ No schema shape, accept set, export or runtime file moves.

Every sentence was re-derived against the landed door on origin/main fc0db22b (packages/runtime/src/domains/packages.ts), not against the card's quotes.

Premise check (dispatch assumption 1)

The card's line "Only the wrapped form's top-level unknown key is still stripped" is false on main: since 28ad7e4b the wrapped branch is a strictObject, and the door refuses such a body 400. Measured on the built schema: { manifest, bogus: 1 } fails PackageInstallBodySchema.safeParse. What the declaration still strips is only an unknown key NESTED in a strip-mode block. A walk of the built union finds three such positions: artifactRef, and the expression envelope of a manifest navigation item's visible plus its meta. Both were measured parsing green with the key dropped. So the rewritten section describes that strip, and no wrapped-form top-level strip.

Sentences changed, each with the line that makes the new one true

# Where Old sentence (gist) New sentence (gist) Evidence on fc0db22b
1 enableOnInstall docblock the door "reads the raw body" the door reads the key off the PARSED wrapped request, after the body passes PackageInstallBodySchema packages.ts:1019 const declaredBody = PackageInstallBodySchema.safeParse(body), :1164 const request = 'manifest' in declaredBody.data ? …, :1247 const requestedEnabled = request?.enableOnInstall
2 body docblock, bare-form paragraph the two runtime door drives post no type, are refused here, and are answered 201 both drives carry type: 'app' since PR #20218, parse green through the bare branch, and are answered 201 package-door-namespace-conflict-code.test.ts:88 and domain-handler-registry.test.ts:605, both with type: 'app'
3 residual heading and lead a SUBSET description: the door "additionally answers 201 to five classes" the residual is empty on the answer: the door refuses each class as the union does, 400 / VALIDATION_ERROR, ahead of the 409 packages.ts:1155 answers !declaredBody.success with 400, ahead of the 409 at :1176
4 residual item 1 1b "still OPEN, answered 201" 1a and 1b both landed; 1b with the whole-body parse same union verdict, packages.ts:1155
5 residual item 2 unknown keys "201 either way" refused on both forms, and at the wrapped top level since ruling record 5856869656 packages.ts:1155, plus PackageInstallRequestSchema is strictObject (package-api.zod.ts:228)
6 residual item 3 'false' installs ENABLED, 'true' overwrite read as ABSENT both keys are z.boolean(), so the parse refuses either string package-api.zod.ts enableOnInstall: z.boolean().optional(), overwrite: z.boolean().optional(); packages.ts:1155
7 residual item 4 bare-form options "ignored, never honoured" refused by ManifestSchema's strict close; the door's refusal names the wrapped form packages.ts:1155 → installBodyRefusal (:717-:750), whose bare-form arm prescribes the wrapped form
8 residual item 5 the door answers 400 to a whitespace-only id "this declaration admits" both faces refuse it, since ManifestSchema.id carries MANIFEST_ID_PATTERN packages.ts:1021-:1023 (trim, then Package id is required); manifest.zod.ts:272; the spec test's own whitespace pin already said so. This sentence was already false before PR #20218.
9 new paragraph (none) what the parsed value still does not describe is what the door STORES: the manifest as SENT, so parse-time defaults (scope, defaultDatasource) are not stored, and an unknown key nested in a strip-mode manifest block is stored as sent packages.ts:1020 (`manifest = body.manifest
10 the ⛔ paragraph after the list "the residual is RECORDED here … closing it is its own decision with its own card" none of this licenses relaxing either branch, or making the door answer a body differently from the declaration follows from rows 3-8

Sentences kept, because they are true on main (assumption 3)

  • «The door reads const manifest = body.manifest || body, so a bare manifest IS a body form it accepts» (packages.ts:1020).
  • «A contract naming only the wrapped form would refuse bodies this door answers 201 to»: the bare form is answered 201 (packages-install-body-contract.test.ts §7, "BARE → 201").
  • The whole "two branches are disjoint — and BOTH are closed" section, including its ruling paragraph («Since c02fa1276 … the door's answer IS this declaration's answer»).
  • «⚠️ The bare form carries NO install options … A bare-form caller reaches overwrite through the query string alone» (packages.ts:1171-:1172).
  • The rest of the enableOnInstall docblock, including «It calls installPackage({ manifest, settings }) and performs the enable/disable flip itself» (packages.ts:1193, :1247-:1254).

The test block (assumption 4): what DOOR_201_RESIDUALS guards now

Measured, not assumed: the list's assertion (the declaration refuses every body in it) still guards a door behaviour. The door parses through this declaration, so if any of those bodies started parsing, the door would start answering it 201 again. The assertion is therefore kept, not weakened. It is renamed CLOSED_RESIDUALS and re-titled to what it now says: the declaration refuses every one, and so does the door since PR #20218.

  • The two drive transcriptions now carry type: 'app', because the drives do, and are pinned GREEN.
  • The bodies they used to post (the same manifest with no type) are derived by an untyped helper and stay pinned REFUSED, both in the "the missing type is what decided it" case and in CLOSED_RESIDUALS. No assertion was dropped. The pkg-a refusal is unchanged.
  • The clause-1a case keeps its assertion. It is re-titled to what it guards now: every body in the list is refused for its own class, never on the version leg the door answers first.
  • The whitespace-id case keeps its assertions; only its trailing comment ("the class remains") is rewritten.
  • In-place fix, outside the named block: the comment on "parses a COMPLETE manifest posted BARE" said the bare-form callers "post INCOMPLETE ones". This is the same stale fact, so it is corrected in one comment edit. It passes all four bounded-fix conditions: same defect class, mechanical, no other claim on the file, and the same gate family.

Test count is unchanged (84 → 84).

Verification — against e627005b (git rev-parse --short HEAD)

  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/api/package-api.test.ts: Tests 84 passed (84) (the baseline on fc0db22b was also 84).
  • Spec suite, vitest run --project local --maxWorkers=2 in packages/spec: Test Files 572 passed (572), Tests 16789 passed | 1 todo (16790).
  • pnpm --filter @objectstack/spec run typecheck → exit 0, check:test-typecheck: OK — … 53 file(s) / 251 error(s) / 138 pinned signature(s) held.
  • pnpm --filter @objectstack/spec check:generated → "All 15 generated artifacts are up to date". The build left the tree clean: authorable-surface.base.json was not touched.
  • pnpm check:doc-authoring → exit 0 ("16735 customer-facing string(s) across 1167 spec sources clean").
  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 83 commands over this diff. All were run, with each exit code written to disk before any pipe. --ran reconciliation: ✓ dispatch-gates --ran: 83 derived famil(ies) accounted for — 80 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3).
    • check:doc-formula-expressions first exited 3 (its formula / lint dist was absent). After building that closure it was re-run: exit 0.
    • NOT MEASURED: check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt. Reason: each needs a whole-workspace (or objectql-closure) build this worktree does not have, and each refused with PREREQUISITE NOT MET. The diff changes no emitted code: check:api-surface is green, and so is the dts sweep. These three are CI's to read.
  • Published-surface check (changeset rather than skip-changeset): after pnpm --filter @objectstack/spec build, the new heading CLOSED on the answer appears once in dist/api/index.d.ts and once in index.d.mts. The old the door additionally answers appears 0 times in both. The enableOnInstall docblock ships through files[]' src/**/*.zod.ts.

Ablation: NOT MEASURED as a vitest run, with a lock-free stand-in

The planned ablation replaced the untyped helper with an identity, through scripts/ablation-replace.mjs under the verify lock. It never acquired the lock: 3 queue-timeouts (VERDICT queue-timeout (exit 99)), about 27 minutes in all. The file stayed byte-identical to HEAD (blob a0277d57 on both).

The stand-in ran without the lock and without vitest. It evaluated the refusal assertions' inputs against the built schema under both helpers:

committed: 'type decided it' refusals hold = true,true ; CLOSED_RESIDUALS refusals hold = true,true,true,true,true,true
ablated:   'type decided it' refusals hold = false,false ; CLOSED_RESIDUALS refusals hold = false,false,true,true,true,true

So the identity helper would turn both refusal cases red. The committed suite already holds the same pair in one run: the two drives GREEN and their untyped bodies REFUSED.

Acceptance notes (not filed)

  • platformVersion and artifactRef are declared install options on PackageInstallRequestSchema, parsed at the door and read by no line of it. A repo-wide git grep, outside packages/spec and tests, finds no reader and no producer. The first-party SDK sends only manifest, settings, enableOnInstall and overwrite. With zero pull and no public-door reading, this is noted, not filed. The docblock was deliberately NOT extended to say so: that would widen this card.
  • packages/spec/scripts/lib/default-changes.ts:167 carries "the door reads the raw body". It is the rationale of an earlier protocol major's default-change record (spec-changes.json / the upgrade guide), and it was true at that major. It is history, not a description of today's door, and it was left untouched.
  • .changeset/19327-install-door-residual-split.md (unreleased) still says the type half is an open residual. Its successor entries, 19328-install-door-body-parse.md and this one, record the rest. A release compiling all three reads as a sequence, and that changeset was not edited here.

Generated by Claude Code

… and enableOnInstall as read off the parsed request

Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH
Co-authored-by: Claude <noreply@anthropic.com>
…hey post now, and the closed classes as refused

Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8255a51232e94bbd3e8649217219c34ad1c7f00a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8799df1471a57b13bf88f4c690e7a6bd8697a188 — the merge of head e627005b20e5f67dbf341d9056bbf1ba2d3ec40f into base 8255a51232e94bbd3e8649217219c34ad1c7f00a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8799df1471a57b13bf88f4c690e7a6bd8697a188 && git checkout 8799df1471a57b13bf88f4c690e7a6bd8697a188
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8255a51232e94bbd3e8649217219c34ad1c7f00a e627005b20e5f67dbf341d9056bbf1ba2d3ec40f && git checkout -B drift-repro 8255a51232e94bbd3e8649217219c34ad1c7f00a && git merge --no-ff e627005b20e5f67dbf341d9056bbf1ba2d3ec40f

node scripts/docs-audit/affected-docs.mjs --json 8255a51232e94bbd3e8649217219c34ad1c7f00a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e627005b20e5f67dbf341d9056bbf1ba2d3ec40f
Local-runs: none

① Derived judgments

Inputs: card #20219 (body + 4 comments: triage grade 5855760406, unlock 5862108122, claim 5876227325, dev report 5877797240), PR #20506 body / file list / diff at e627005b, and the check-runs on that head. Read-only: git show / git diff / git grep against fetched refs, curl GETs. Nothing built, run or re-run.

Fence — prose only, proven. packages/spec/src/api/package-api.zod.ts at fc0db22b (merge-base, identical to current origin/main for both touched files) vs e627005b, with block and line comments stripped and strings kept: 1077 code tokens on both sides, identical. Every +/- line of that file's hunks is a * JSDoc line; all 69 .describe() strings are byte-identical. So no schema shape, accept set, export or .describe() moved. What the published package carries of the change: dist/api/index.d.ts / index.d.mts (JSDoc is emitted) and src/**/*.zod.ts verbatim (files[]). The shipped json-schema/ output (its manifest lists api/PackageInstallBody and api/PackageInstallRequest) is built from .describe() strings only and is therefore unchanged; content/docs/references/api/package-api.mdx likewise. No runtime file touched: right.

Every rewritten sentence, judged against origin/main packages/runtime/src/domains/packages.ts (line numbers hold on 48efe915):

  • enableOnInstall docblock, "reads the key off the PARSED wrapped request — the body passes PackageInstallBodySchema first": true. :1019 const declaredBody = PackageInstallBodySchema.safeParse(body), :1164 const request = 'manifest' in declaredBody.data ? declaredBody.data : undefined, :1247 const requestedEnabled = request?.enableOnInstall. Claims no more than the code.
  • Bare-form paragraph, both drives carry type: 'app' since PR fix(runtime): the package install door parses its whole body through PackageInstallBodySchema #20218 and parse green: true. package-door-namespace-conflict-code.test.ts:88 { id, name: id, namespace, version: '1.0.0', type: 'app' }; domain-handler-registry.test.ts:605 { id: 'com.example.pkg-a', name: 'A', version: '1.0.0', type: 'app' } (409 then 201 on ?overwrite=true, :606-:609). The docblock quotes exactly those bodies.
  • Residual heading/lead, "the door refuses each class as this union does — 400 / VALIDATION_ERROR, nothing installed, ahead of its duplicate-id 409": true. :1155 if (!declaredBody.success) → deps.error(installBodyRefusal(...), 400), ahead of the 409 at :1176, after the id leg (:1104-:1114, ManifestSchema.shape.id by reference) and the version leg (:1138-:1145, ManifestSchema.shape.version by reference) — both legs parse members of this union, so "every refusal the door makes on a body's shape alone is this union's" does not overstate.
  • (1) Classes 1a, 1b, 2, 3, 4 closed: right. 1a — the version leg by reference (PR fix(runtime): POST /api/v1/packages parses the manifest version leg instead of installing anything it is handed #19326); 1b — the whole-body parse at c02fa1276 (= PR fix(runtime): the package install door parses its whole body through PackageInstallBodySchema #20218's merge commit, 2026-09-27 13:20Z); 2 — the manifest is strictObject, and the wrapped top level is strictObject since 28ad7e4b (= PR fix(spec)!: PackageInstallRequestSchema's wrapped branch refuses an unknown top-level key by name #20277's merge commit, 19:53Z; that diff turned z.object({ into strictObject({ on PackageInstallRequestSchema); 3 — enableOnInstall: z.boolean().optional() and overwrite: z.boolean().optional(), so 'false' / 'true' fail the parse at :1155; 4 — ManifestSchema's strict close refuses option keys on the bare form, and installBodyRefusal (:717-:750) reads the option set off PackageInstallRequestSchema.shape and prescribes the wrapped form. Class 5: true and correctly dated. MANIFEST_ID_PATTERN (manifest.zod.ts:272, /^[a-z0-9][a-z0-9-]*(\.[a-z0-9][a-z0-9-]*)+$/) is bound at ManifestSchema.id (:364), matches no whitespace-only string, and predates PR fix(runtime): the package install door parses its whole body through PackageInstallBodySchema #20218 (it is present at 98d83361, 2026-09-23, the oldest commit this clone holds; the spec test at base :1119 already pinned "[finding] ManifestSchema.id is a bare z.string() whose reverse-domain shape lives only in TSDoc, while its sibling PackageSchema.manifestId enforces that shape with a regex — one identifier, two declarations, only one of them machine-readable #17534 closed the one spelling that ran the OTHER way — a whitespace-only id is refused HERE now"). The door trims and answers Package id is required 400 (:1020-:1023), pinned by packages-install-manifest-id.test.ts §3a (' ' fixture). So the old sentence "this declaration admits" was already false before PR fix(runtime): the package install door parses its whole body through PackageInstallBodySchema #20218: the dev's finding stands.
  • (2) Card premise falsified, both halves verified. Wrapped top level: refuses, not strips — strictObject since 28ad7e4b, and the door's own comment :1005-:1013 says { manifest, enabledOnInstall: false } fails the union by name, 400. Remaining declared strips are nested: artifactRef → ArtifactReferenceSchema is z.object (marketplace.zod.ts:104); a nav item's visible → EvaluatedExpressionInputSchema (app.zod.ts:345) whose object arm ExpressionSchema is z.object (expression.zod.ts:93) with meta: ExpressionMetaSchema, also z.object (:73). The three positions the dev's union walk found are strip-mode by declaration; the docblock enumerates none of them, so nothing published rests on the walk's count.
  • (3) STORES paragraph: true, not overstated. :1020 const manifest = body.manifest || body (raw), handed unchanged to protocolSvc.installPackage({ manifest, settings }) :1193 or registry.installPackage(manifest, settings) :1196. Neither writer applies this declaration's parse-time defaults (scope .default('project') manifest.zod.ts:474, defaultDatasource .default('default') :419): the protocol path (protocol.ts:22960-:22995) parses only the id leg, and the registry stores toRecordManifest(...) (registry.ts:1354), a key-by-key structural projection in which JSON data survives — so a nested unknown JSON key is stored as sent. Door pin: packages-install-body-contract.test.ts §7 expect(registry.getPackage(m.id)?.manifest).toEqual(m). One precision note, not a defect: on the protocol path the writer derives namespace when the sent manifest omits it (ManifestSchema.namespace is optional) — a writer-side normalisation the paragraph neither claims nor denies; the docblock's two claims (no parse-time defaults; nested strip key kept) are exact, and only the PR body's row-9 gist "the manifest as SENT" is looser than the landed text.
  • Door-side pin citations: right. packages-install-body-contract.test.ts §1 (1b), §2 (2), §3 (3), §4 (4), §5 (wrapped top-level), §6 (ordering ahead of the 409), §7 controls; packages-install-manifest-version.test.ts §1 (1a); packages-install-manifest-id.test.ts §3a (5).
  • Kept sentences (body.manifest || body; bare 201 — §7 "BARE → 201, ENABLED"; the disjoint/closed section citing c02fa1276 and 5856869656; bare form reaches overwrite through the query alone — :1171-:1172; installPackage({ manifest, settings }) and the flip at :1247-:1254): all true on main.

Test block. Residual block base :1027-:1135 vs head :1028-:1150: 6 it, 10 expect, 6 toBe(false), 4 toBe(true) on both sides; file-wide 82 static it( / 170 expect( on both sides. Facts pinned are the same set from the other side — typed drive bodies green (2), untyped bodies refused (2, via untyped), pkg-a refused (1), the six-body list refused (loop), every listed body's version accepted (loop). No assertion dropped or weakened; each pin guards a real door behaviour on main (the drives' bodies answered 201 at :605-:609 and by package-door-namespace-conflict-code.test.ts; the refusals answered 400 at :1155). SDK_MANIFEST carries version: '1.0.0', type: 'app', so each CLOSED_RESIDUALS body is refused for its own class (1b, 1b, 2, 4, 3, 3), never on the version leg. Ablation by reading: with untyped an identity, untyped(DOOR_DRIVE_CONFLICT) / untyped(DOOR_DRIVE_REGISTRY) become the typed bodies, which parse green — the two toBe(false) assertions in "the missing type is what decided it" fail, and the first two iterations of the CLOSED_RESIDUALS loop fail. Both refusal cases go red, exactly as the lock-free stand-in reported. The extra comment fix (:910-:913, "post INCOMPLETE ones" → "post complete ones too") is in the fence: same stale fact as the card's items 2/4, comment-only, in the file the claim already named, same gate family, declared in the PR body and the report's deviations — the bounded in-place exemption's four conditions hold.

Gates. Check-runs on e627005b at the last read: 29 success, 3 skipped (Console Pin Gate, Build Docs, Packed-tarball smoke (opt-in)), 2 still in_progress — Test Core (5/6) and Test Core (6/6); judged on the rest. The three locally NOT-MEASURED gates are answered green by CI: check:dual-build-cjs-loads and check:lean-entry-closure run in Build Core (ci.yml:2116, :2140) — success; check:type-check-debt runs in Type Check · debt ledger (lint.yml:6296) — success. Also success: Lint & Repo Gates, Type Check · source gates / workspace / consumer gates, Test Core (1/6)–(4/6), Check Changeset, Spec property liveness, Governed Surface Queue Guard, the three claim/card guards, Dogfood Regression Gate (1–3/3), Dogfood Verify CLI, Temporal Conformance.

Merge. git merge-tree --write-tree --name-only origin/main e627005b20e5f67dbf341d9056bbf1ba2d3ec40f → tree 511dd1b76179dfb9342551b49d2ad14e8965422b, no conflicted paths, exit 0. origin/main moved fc0db22b → 48efe915 since the merge-base, on none of the three touched paths.

② Semver level

.changeset/20219-install-body-residual-closed.md: '@objectstack/spec': patch — right. The diff publishes changed content (JSDoc in dist/api/index.d.ts / .d.mts, src/api/package-api.zod.ts verbatim through files[]) with no code change, so a changeset is owed and skip-changeset would be wrong (AGENTS.md: that label is for a diff that publishes nothing). Not minor: nothing widens. Clause-②: no in the PR body and in the changeset body — right: 1077 identical code tokens, so neither the accept set nor the public surface moves; no arm, correctly. The changeset's own sentences match the landed docblock (classes closed, class 5 already refused by both faces, the STORES paragraph, the two corrected sentences). Check Changeset: success.

③ Boundary flags

  • Dev deviation 1 — vitest ablation NOT MEASURED (lock queue-timeouts), lock-free stand-in used: answered above by reading; the two refusal cases would go red under an identity helper. Accepted.
  • Dev deviation 2 — bounded in-place comment fix outside the named block: judged in the fence (see ①). Accepted.
  • Dev deviation 3 — rewrite goes beyond the card's four readings (class 5; STORES paragraph): both verified true on main; the card itself said "re-derive each sentence against the landed door", and the claim's surface is the docblock's residual section, which these are. Accepted.
  • Dev deviation 4 (container restart; test file byte-identical to HEAD) and 5 (PR footer form): no contract effect.
  • open_questions: none declared.
  • out_of_scope_findings, the three carrier: none notes:
    • platformVersion / artifactRef declared on PackageInstallRequestSchema, read by nothing: verified — no reader or producer of either key outside packages/spec and tests, and none inside packages/spec non-test; the SDK sends manifest, settings, enableOnInstall, overwrite (client/src/index.ts:2643-:2644). The liveness ledger does NOT carry this pair: its denominator is registered metadata kinds (liveness/api.json = ApiEndpointSchema, liveness/rest_api.json = RestApiConfigSchema), and no ledger names PackageInstallRequestSchema. Under the os-dev rule it is not (a) (no wrong answer measured at the door — the key is accepted and ignored), not (b) with a met reach: (no public-door wrong answer, no named producer), not (c); it is the "zero pull / dead code" class the rule keeps out of cards. So carrier: none is right as filed. Escalated as information, not as a block: it is a declared≠enforced pair on a published request contract outside the ledger's reach, and a card is the seat's call (enforce-or-remove under ADR-0049), not this PR's.
    • default-changes.ts:167 "the door reads the raw body": it is the rationale string of a protocol-major default-change record (spec-changes.json / the upgrade guide) describing the door at that major — history, correctly left. Not a finding class.
    • .changeset/19327-install-door-residual-split.md still calls the type half residual: unreleased, superseded in sequence by 19328-install-door-body-parse.md and this changeset; the release that compiles them is the carrier. Not a finding class.
  • Card premise "only the wrapped form's top-level unknown key is still stripped": falsified by the dev, verified here (②-half above). The landed text describes no wrapped top-level strip. Answered.
  • Ruling A / .strict() question: untouched by this PR, as fenced.

Implemented-by: claude/issue-20219-install-body-docblock
Reviewed-by: session_01ARcDurZ5j34RdqsGgc4jgH

VERDICT: PASS

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 36481212024 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    create-objectstack:test:  FAIL  src/scaffold-e2e-boot-probe.test.ts > [#9779] scaffold-e2e.yml boot-and-probe blocks assert on their OWN server > scaffold-local / Boot from the artifact and probe heal
      ↳ 失败原因: create-objectstack:test: AssertionError: expected 1 to be +0 // Object.is equality
    

↳ 失败原因 是判读的关键:超时(Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言(AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError。 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

  • src/scaffold-e2e-boot-probe.test.ts — 24h 窗口内只有本 PR 撞到过,暂不汇总(再有一个不同 PR 撞到就会自动开汇总 issue)。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 7 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Queue drop: disposition by the landing seat · 2026-09-28T21:19Z

domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH). This seat landed this PR (ACCEPT 5878118204 is on #20219). The queue removed it after run 36481212024. ⛔ Not re-queued.

domain:spec seat 4 · #18917 · session_01ARcDurZ5j34RdqsGgc4jgH

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 31d281d Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20219-install-body-docblock branch September 29, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants