Skip to content

docs(spec): record that the record-stage manifest type is deliberately an index signature, and the runtime schema is the contract - #20191

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-19324-assembled-manifest-docblock
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-19324-assembled-manifest-docblock

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #19324

Clause-②: no

Ruling-ref: 5805795339 (batch #218 item 4, letter 丙). This PR carries out ruling item 1. It adds one docblock on RecordStagePackageBodySchema, at its ZodRawShape cast, and one beside AssembledInstalledPackageSchema.manifest. Both record the four points the ruling names. ⛔ No type change, ⛔ no schema change. Ruling item 2 holds: the client gap pin stays, and only its comment text moves. Per ruling item 3, the card is done when this lands.

What changed

  • packages/spec/src/stack.zod.ts, RecordStagePackageBodySchema
    • Its published docblock gains a section, "Its published type is deliberately an index signature", which carries all four points.
    • The internal note above the declaration now says what the as unknown as z.ZodObject(z.ZodRawShape) cast costs, and that the cast emits nothing.
  • packages/spec/src/api/package-api-assembled.zod.ts, AssembledInstalledPackageSchema
    • The docblock ends with a new section, "manifest's published type is deliberately an index signature", directly above the manifest line. It replaces the old one-paragraph pointer.
    • Since c23cfb346a the assembled declarations live in this file, not in package-api.zod.ts.
  • packages/client/src/return-type-precision.test.ts
    • The two comments that cited packages/spec/src/stack.zod.ts:1283 now cite RecordStagePackageBodySchema by name.
    • They no longer say the gap is this card's to close: it is the accepted static contract, and the pin reddens the day the body is typed precisely.
    • Comment text only. No assertion, no @ts-expect-error and no binding moved.
  • packages/client/src/index.ts, ObjectStackClient.packages.list (patch round 2, a2be2ff440)
  • .changeset/19324-record-stage-index-signature-docblock.md: @objectstack/spec patch and @objectstack/client patch (b9b0d32d2f).

Which of the four points were already there

Read at 3bd28e2b2e, before the edit.

Ruling item 1 point RecordStagePackageBodySchema AssembledInstalledPackageSchema
① the published type is deliberately an index signature Partial. Only an internal "ANNOTATED structurally" comment, which the published .d.ts drops, pointing up the file. Partial. "The body half is deliberately typed Record(string, unknown)". No consequence stated, and not called the accepted contract.
② the runtime Zod schema is the enforced contract Absent at this site. It was said only in AssembledPackageBodySchema's internal note. Present in substance: "a wrong-shaped body is refused exactly as it is there". Kept, restated in the new section.
③ why (TS7056 / #14513) Pointer only, in the unpublished comment. Pointer only, and it named AssembledPackageBodySchema rather than the schema manifest is built from.
④ A2 is the precise form Absent Absent

Premise re-measured against the BUILT declarations

Spec built at 3bd28e2b2e (lock verdict command-exit 0).

  • dist/api-assembled/index.d.ts and .d.mts declare manifest as z.ZodType of Record(string, unknown) on both sides.
  • dist/index.d.ts and .d.mts declare RecordStagePackageBodySchema the same way, at line 23236.
  • A probe program ran tsc 6.0.3 (strict, NodeNext) through the package exports. --listFiles shows 9 spec dist files and 0 spec src files.
    • Readings, exit 0 (all five compile):
      • R1: InstalledPackage assigns to AssembledInstalledPackage.
      • R2: the whole union assigns to the assembled arm.
      • R3: any object assigns to the manifest.
      • R4: string extends keyof the manifest (an index signature).
      • R5: a { bogus: 1, objects: 'not-an-array' } manifest compiles against the union.
    • Lit controls, exit 2, TS2322 twice: the reverse assignment, and a string manifest.
  • Runtime control: InstalledPackageAtEitherStageSchema.safeParse answers true for a valid authoring row, and false for the bogus row, both on the union and on the assembled arm.
  • The same readings hold at head 17f1e3d41c after the edit, so the types did not move.

The TS7056 reading behind the "why", re-measured after the split

M1 is the decision round's reading at d1ca8741dd. This PR re-ran it at 3bd28e2b2e.

  • Mutation: drop the artifact and record stages' structural annotations and the ZodRawShape cast.
  • Result: spec build exit 1 with exactly one error, src/api/package-api-assembled.zod.ts(222,14): error TS7056. Line 222 is PackageApiContracts.
  • How: through scripts/ablation-replace.mjs, with each anchor hitting 1 then 0, and blob 3c09282f16 → ac2e5f6b0b → 7823e5e634.
  • Restore proven: blob == HEAD 3c09282f16, git diff HEAD empty, git status --porcelain empty.

The #14513 history was read from commit 7085f90531. It covers TS7056 on the inferred type, and a named alias that turned stack.zod into a shared chunk. That chunk added 42,622 definition lines to the qa/http-conformance type-check program and pushed it past the then 4096 MB ceiling.

The docblock says plainly that the named-alias reading is inherited for the record stage and was not re-measured. A2's cost is quoted with its commit (d1ca8741dd), as is the fact that it was not measured on the http-conformance program.

One bounded fix in the same docblock

The AssembledInstalledPackageSchema docblock said the assembled stage was "built from AssembledPackageBodySchema". That has been false since #19373. The row's manifest is RecordStagePackageBodySchema, which extends the artifact stage, and the artifact stage is ManifestSchema.extend({ ...assembledPackageBodyShape(), … }). It now reads "built from the same body shape as AssembledPackageBodySchema … at the record stage the next section describes".

This was the decision round's own carrier note (comment 5800545221), and it names this PR. Same docblock, same class (the text beside manifest misdescribing its declaration), inside the claimed file surface, with no new gate.

Changeset, not skip-changeset: the edit ships

Measured on the rebuilt dist at 17f1e3d41c:

probe files
new RecordStagePackageBodySchema section heading dist/index.d.ts, dist/index.d.mts
new AssembledInstalledPackageSchema section heading dist/api-assembled/index.d.ts, dist/api-assembled/index.d.mts
lit control: an existing line of the RecordStagePackageBodySchema docblock the same 2 files
the replaced site-2 sentence 0
the internal cast note (not TSDoc) 0, dropped from dist as expected

Both edited files also ship as source, because @objectstack/spec's files[] carries src/**/*.zod.ts. So the edit is published and takes a patch changeset, carrying Clause-②: no. packages/client publishes only dist, README.md and CHANGELOG.md, so its test-file comment ships nothing. The packages.list paragraph does ship: measured on the rebuilt client dist at b9b0d32d2f, the new phrase is in index.d.ts, index.d.mts, index.js and index.mjs. 'Tracked as #19324' and stack.zod.ts:1283 are in 0 files, and the lit control, the unchanged Array.isArray warning, is in the same 4 files. So the changeset also carries @objectstack/client: patch (b9b0d32d2f).

Verification

The head is b9b0d32d2f. packages/spec did not move between 17f1e3d41c and b9b0d32d2f (git diff on it is empty), so the spec readings below, taken at 17f1e3d41c, hold at the head. The client and gate readings were re-taken at b9b0d32d2f.

  • @objectstack/spec build: exit 0. pnpm --filter @objectstack/spec check:generated: all 15 generated artifacts up to date.
  • @objectstack/spec typecheck: exit 0 (tsc, scripts and test layer).
  • @objectstack/spec tests, targeted:
    • 16 files / 613 tests in --project local and 7 files / 99 tests in --project repo, all passed.
    • The set: every test on the edited declarations (package-api, stack-json-stage-package-body, assembled-package-body, api-entry-graph.pin, split-entries), the tests that read stack.zod.ts as text, and every spec test that walks and reads source files.
    • ⊘ NOT MEASURED locally: the full spec suite. It passed the 560 s foreground ceiling with no verdict (exit 124). CI runs it.
  • @objectstack/client (at b9b0d32d2f): build exit 0 (check-dts-emitted 1/1).
    • typecheck exit 0, with check:test-typecheck OK at 0 files / 0 errors / 0 pinned. Its test program compiles return-type-precision.test.ts (--listFiles: 1 hit, against spec/dist/api-assembled).
    • tests: 50 files / 635 passed.
  • Cross-package tests on the assembled row: runtime packages-read-delete-response-conformance 17 passed, objectql registry-package-manifest-serializable 16 passed.
  • node scripts/pm/dispatch-gates.mjs --ran (re-derived and re-run at b9b0d32d2f; the new path added no family): 85 families derived. 83 run, every one exit 0.
    • ⊘ NOT MEASURED: check:dual-build-cjs-loads, which exits 3 until every workspace package is built.
    • ⊘ UNRUN: check:type-check-debt, which re-measures tsc per ledger entry over the whole built workspace. The only test-file edit is comment text, and the client test layer holds 0 errors.
    • CI runs both.
  • ESLint, narrowed to the 4 changed .ts files (at b9b0d32d2f):
    • Scope: --print-config applies 6 rules to index.ts and 5 to each of the other three. --format json reports 4 files, 0 errors, 0 warnings.
    • Why untouched files cannot change: eslint.config.mjs never enables type-aware linting (no parserOptions.project), so this diff cannot move the verdict on any other file.

Acceptance notes

Body corrected by domain:spec seat 2 after patch round 2 (b9b0d32d2f), from the dev's reported deviations: the client paragraph, the client changeset line, the stale Acceptance note removed, and the verification anchor.


Generated by Claude Code

…e accepted static contract

RecordStagePackageBodySchema and AssembledInstalledPackageSchema now state,
in their published docblocks, that manifest's static type is deliberately
Record<string, unknown> (an index signature), that the runtime Zod schema is
the enforced contract, why the type is not inferred (TS7056 at
PackageApiContracts; the named-alias shared-chunk heap failure recorded
beside AssembledPackageBodySchema), and that the A2 form is the precise one
if the schema depth ever allows it. The ZodRawShape cast gets a note on what
it costs and that it is type-only.

The client gap pin's two comments cite RecordStagePackageBodySchema by name
instead of a stale stack.zod.ts line number, and say the gap is the accepted
static contract; the pin itself is unchanged.

No type, schema or runtime change.

Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Sep 27, 2026
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 2 changed file(s) yielded no anchor (packages/spec/src/api/package-api-assembled.zod.ts, packages/spec/src/stack.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 2 changed package(s)).

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/src/api/package-api-assembled.zod.ts, packages/spec/src/stack.zod.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 139 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 e4471e643eae5e49eb493170ccb58da01603503f → packageMentionDocs.

…accepted static contract

The packages.list TSDoc called the type/runtime asymmetry a known gap
tracked on an open card and cited a stale stack.zod.ts line. The card
was ruled the other way: the index signature on the record-stage body
is the accepted static contract, the runtime Zod schema is the enforced
one, and the A2 form is recorded beside RecordStagePackageBodySchema for
the day the schema depth allows it. The paragraph now says so and cites
the schema by name. Comment text only.

Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Co-authored-by: Claude <noreply@anthropic.com>
…ent takes a patch line

Measured on the rebuilt packages/client dist: the rewritten packages.list
paragraph lands in index.d.ts, index.d.mts, index.js and index.mjs, and
@objectstack/client publishes dist.

Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: 55/55 CONTRACT_REVIEW_TIER
Head-sha: b9b0d32d2f49ab7a6a3eeaee57b0adbdcd2b3377

① Derived judgments

  1. Head unchanged (base 3bd28e2, 3 commits, 5 files). CI at head: every required lane success (Build Core, four Type Check lanes incl. debt ledger, Lint & Repo Gates, Test Core, Check Changeset); one duplicate Check Changeset run still in_progress.
  2. All four ruling points are in both spec docblocks: (1) "declared z.ZodType of Record(string, unknown) ... so the published type of a record-stage body is an index signature" / "this row's manifest publishes as an index signature"; (2) "The RUNTIME schema is the enforced contract ... Narrow a record-stage body by parsing it, never by trusting its static type"; (3) "TS7056 ... feat(cli+spec): compile a project of N packages into one packages[] artifact, with the assembled package body declared (ADR-0130 D4 producer, #14242 B) #14513 measured that on the assembled body ... fires at PackageApiContracts"; (4) "The precise form, if the schema depth ever allows it, is ... A2". Neither weaker nor stronger than the ruling: A2 stays conditional, its cost and the unmeasured http-conformance program are named, and "that reading is inherited, not re-measured" is honest. The client TSDoc mirrors it ("the ACCEPTED static contract, not a gap waiting to close").
  3. Facts at head: the annotation and the as unknown as z.ZodObject cast sit at stack.zod.ts:1504-1506; it is manifest of AssembledInstalledPackageSchema (package-api-assembled.zod.ts:7,143; export ./api-assembled); PackageApiContracts is below at :236 (main :222, the reported TS7056 site). Chain Record → Artifact → ManifestSchema.extend({...assembledPackageBodyShape(), functions, hooks}) makes "same body shape as AssembledPackageBodySchema" true and the old "built from" false since fix(spec,objectql): declare the inert-JSON artifact and registry-record package body stages, and stop the record under-reporting functions #19373. feat(cli+spec): compile a project of N packages into one packages[] artifact, with the assembled package body declared (ADR-0130 D4 producer, #14242 B) #14513 = 7085f90 (on main): TS7056 on the inferred type; a named alias made stack.zod a shared chunk, +42,622 definition lines past 4096 MB, recorded beside AssembledPackageBodySchema (stack.zod.ts:1268-1310) as the new prose says. A2 figures (+20,209 then-./api, +44,342 root, four typeof annotations, http-conformance unmeasured) match the decision-round readings at d1ca874 (on main). c23cfb3 (feat(spec)!: split the assembled-stage package API declarations off ./api into @objectstack/spec/api-assembled #20052, after the ruling) moved the declarations, so the ruling's package-api.zod.ts site is correctly relocated.
  4. Stale framing: packages/spec/src, packages/client/src, content/docs carry no stack.zod.ts:1283, no "tracked as [finding] AssembledInstalledPackage.manifest erodes to an index-signature type in the published .d.ts, so the assembled arm absorbs the authoring arm #19324", no "KNOWN GAP rather than a design". Remaining [finding] AssembledInstalledPackage.manifest erodes to an index-signature type in the published .d.ts, so the assembled arm absorbs the authoring arm #19324 mentions (index.ts:138, test:708/786) are pointers. Only the foreign 17536 changeset survives (③).
  5. Client edits: 21 + 33 changed lines, each begins * or // (scripted check); no assertion, @ts-expect-error or binding moved.
  6. Changeset truth, measured on the published 17.4.0 tarballs: spec files[] has src/**/*.zod.ts and the tarball ships src/stack.zod.ts; a JSDoc followed by a plain block comment reaches dist/index.d.ts and .d.mts (AssembledPackageBodySchema's "Never re-open this surface" = 1, its plain "Both halves were measured" = 0), so the new sections ship and the cast note does not. Client files[] = dist, README, CHANGELOG; tsup keeps JSDoc in all four dist files (468/468/339/339 blocks; the packages.list docblock at index.d.ts:1820 and index.js:1271). Every sentence holds.
  7. Non-blocking nits: (a) stack.zod.ts:1484 "the four declarations that embed the body" — the file header lists five embedders and A2 leaves AssembledInstalledPackageSchema inferred, so "four of the five" is exact; (b) "left the runtime bundles byte-identical" — root bundles were identical, api bundles differed only by the probe's own marker comments; (c) test:763 heading still says "the KNOWN GAP" and :776 "stays until the body is typed precisely", mildly against "not a gap waiting to close", though the ruling itself says "gap pin".

② Semver level

patch for both is right: only TSDoc and comments change, no type, schema, export or payload moves, yet the text ships in both packages' dist (and spec src), so skip-changeset would be wrong (AGENTS.md rule 3). Clause-②: no with no arm is correct: nothing widens or narrows; check-changeset-no-major demands minor only for yes. No BREAKING banner, no ADR-0087 marker needed. Frontmatter valid.

③ Boundary flags

Implemented-by: claude/issue-19324-assembled-manifest-docblock
Reviewed-by: session_01QcAS3qiYYZNezaxZxaUdMV

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 08:31
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 6cc8dcd Sep 27, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-19324-assembled-manifest-docblock branch September 27, 2026 08:52
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

Development

Successfully merging this pull request may close these issues.

[finding] AssembledInstalledPackage.manifest erodes to an index-signature type in the published .d.ts, so the assembled arm absorbs the authoring arm

2 participants