Skip to content

docs: the manifest surface no longer describes itself as an open object - #16327

Open
huangyiirene wants to merge 2 commits into
mainfrom
claude/issue-14721-manifest-open-object-prose
Open

docs: the manifest surface no longer describes itself as an open object#16327
huangyiirene wants to merge 2 commits into
mainfrom
claude/issue-14721-manifest-open-object-prose

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #14721

ManifestSchema became a strictObject at #14192. Five prose sites still described the surface it replaced. They are not inert: the packages/spec and packages/core docblocks land in the published declaration files (measured — the new text reaches 14 spec and 2 core dist files), so an author, or an AI writing metadata, reading those declarations was told the manifest tolerates undeclared keys while the runtime now rejects them by name and offers the declared spelling.

Prose that contradicts a tightened contract is the "tolerance hides bulk mistakes" shape, so the bar here was that each rewritten sentence teaches the current refusal — not merely that it stops teaching the old permission.

Clause-②: no — no schema, behaviour, export or test expectation changes.

The five live sites

# file the false claim what it says now
1 packages/spec/src/stack.zod.ts "NOT strictObject: ManifestSchema is an open object" the posture is inherited: the schema is ManifestSchema.extend(...), .extend() carries the base's unknown-key handling, so an assembled body refuses undeclared keys too
2 packages/cli/src/commands/compile.ts the superset under manifest is safe "because ManifestSchema is an open object" it is safe because nothing parses itrunAuthoringRules reads fields off the object and never hands it to a schema
3 packages/core/src/artifact-packages.ts "carries defaults and Zod strips undeclared keys" the defaults clause is kept (it still holds); the strip clause is retired — the key is now refused at that parse, never dropped from a clone
4 docs/audits/2026-07-...-ledger.md "api/, system/, kernel/ and cloud/ are wire surface by construction" only cloud/ is; the other three read mixed in this file's own table 270 lines above
6 content/docs/protocol/kernel/plugin-spec.mdx closed list omitted contributes.kinds[] names it, and adds that the closure is inherited by AssembledPackageBodySchema

Site 1 keeps the #14242 collection-shape rationale — that gate is unchanged and still the reason this declaration exists. What changed is that it is no longer the only gate at that seam.

Measured, not asserted

The rewrites claim a runtime behaviour, so it was measured against the built dist, each with its negative control:

claim reading
AssembledPackageBodySchema inherits the closed posture REFUSED code=unrecognized_keys — message byte-identical to ManifestSchema's, rename namesapce to namespace intact
...and that refusal is about the key, not a broken schema control: the same body clean, and with objects: [], both ACCEPTED
contributes.kinds[] entries are closed REFUSED at path contributes.kinds.0, rename descriptio to description; clean twin ACCEPTED
engine / engines / contributes closed all REFUSED naming the key; clean twins ACCEPTED
defaults still move bytes (site 3) parse adds exactly defaultDatasource: 'default', scope: 'project'

An earlier probe of contributes.kinds[] reported invalid_type on a missing required id — a masked reading, not a refusal of the unknown key. Re-run with otherwise-valid fixtures so unrecognized_keys is what fires.

Verification

All at 7a265fbb49.

  • pnpm --filter @objectstack/spec check:generated15/15 artifacts up to date, check:docs and check:api-surface included
  • typecheckspec, core, cli all green
  • tests — spec 482 files / 13102 passed · core 50 / 1215 · cli (unit tier) 181 / 2453 (+6 expected fail)
  • eslint . --no-inline-config — the whole population, 6216 files, 0 errors, 0 warnings (no narrowing claimed; the full run fit)
  • 12 further gates the change set derives: doc-frontmatter, docs-section-name, doc-route-spelling, comment-mask adoption + corpus, closing-keyword-parity, changeset-no-major, empty-changeset, section-landing-index, keyed-text-bounds, affected-docs, drift-comment — all exit 0
  • check:nul-bytes, check:doc-anchors, check:docs-single-h1, check:doc-authoring, check:docs-audit-scope, check:corpus-claim-drift — all exit 0

The remaining derived families are CI's farm run, not re-derived locally.

Two sites deliberately NOT edited

  • Site 5 (manifest.zod.ts, the main describe) — already discharged. The describe carries the honest wording the card prescribed. The card's premise does hold (ADR-0025 is 521 lines; grep -w main is 0 against 5 substring hits — control lit), but the surviving (ADR-0025 §3.4 step 1) citation in the JSDoc points at the pipeline's build step, which is exactly where os plugin build reads and rewrites the key — the same citation packages/cli/src/commands/plugin/build.ts:5 uses for the same step. Editing it would churn nine generated reference rows for no correction.
  • The seventh site (ledger.md:1344) — the same stale sentence, but quoted as a historical finding, in a row that says it "is reported for its owner rather than edited here". That is the ledger recording a residue correctly. Left alone.

Notes


Generated by Claude Code

…s five sites

#14192 closed ManifestSchema with strictObject. Five prose sites still taught the
old open-object posture; each is rewritten to teach the current refusal rather
than merely to stop teaching the old permission.

Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Co-authored-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
Co-authored-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/core, @objectstack/spec, touching 1 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/core/src/artifact-packages.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.

17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 3e270d4e296368f6600d71fcec9902f3a14c1698.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/core/src/artifact-packages.ts, packages/spec/src/stack.zod.ts) — pages documenting those are invisible to this run
  • 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.

Coarse fallback — 140 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 3e270d4e296368f6600d71fcec9902f3a14c1698packageMentionDocs.

Which tree this was computed on

This run read content/docs from e8dcf94aa876b4a4c042c78a1d7e57a9e848c6e4 — the merge of head 7a265fbb49ce16f35b8974a0b5c82938e5e4a74e into base 3e270d4e296368f6600d71fcec9902f3a14c1698, 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 e8dcf94aa876b4a4c042c78a1d7e57a9e848c6e4 && git checkout e8dcf94aa876b4a4c042c78a1d7e57a9e848c6e4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3e270d4e296368f6600d71fcec9902f3a14c1698 7a265fbb49ce16f35b8974a0b5c82938e5e4a74e && git checkout -B drift-repro 3e270d4e296368f6600d71fcec9902f3a14c1698 && git merge --no-ff 7a265fbb49ce16f35b8974a0b5c82938e5e4a74e

node scripts/docs-audit/affected-docs.mjs --json 3e270d4e296368f6600d71fcec9902f3a14c1698

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 3e270d4e296368f6600d71fcec9902f3a14c1698 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator Author

⚠️ The green this PR is armed on is STALE — it came from the pre-fix aggregator. Stating that before it lands, not after

domain:spec PM dispatch seat (session_01T6HeZvT9wdSJD1ZxJb5Eno), 2026-09-06T16:0xZ. Correcting the reading in my own ACCEPT (5560169906) — not the verdict, the reading it rests on.

What changed under it. PR #16316 (Part of #16157, the rollup fix) merged 14:48:55Z. This PR's CI started 14:28:13Z, on a base predating it. So the required Test Core aggregate that concluded success here at 14:58:53Z, over a Test Core (5/6) that was cancelled at the 30-minute wall, is the old behaviour — the one that treated a killed shard as owing no attestation and satisfied its roster check with an empty set.

Measured, on the sibling PR #16342 whose head merged main after the fix: same lane, same defect, same killed index 5/6, and the aggregate concluded failure at 15:53:47Z. ⇒ the fix works, and this PR's green is a reading the repo no longer produces.

⛔ What I am NOT doing

Not disarming auto-merge, and not forcing a re-run — and the reason is a mechanism rather than a preference. The merge-queue build runs on the gh-readonly-queue/main/… ref, which is current main plus this PR. main carries #16316. ⇒ the queue re-judges this PR with the fixed aggregator regardless of what its pull_request run concluded 90 minutes ago. If Test Core (5/6) trips the wall again there, this PR is ejected from the queue and stays open — which is the correct outcome, and I will handle it as a block rather than a disclosure.

So the honest gate is applied either way. What would have been wrong is letting the stale green stand as the reason it landed.

If it does land

Then Test Core (5/6) passed on the queue build and nothing is unexecuted — a better outcome than the one my ACCEPT disclosed. ⛔ The disclosure in that ACCEPT ("this PR will land with a sixth of the suite unexecuted") is therefore superseded, not confirmed: it was true of the pull_request run and will not be true of the merge that actually happens.

Why the diff is not the risk either way

Six files, 61 additions / 14 deletions, prose only — docblocks, one audit-ledger sentence, one .mdx callout. No schema, no export, no behaviour, no test expectation (Clause-②: no). Five of six shards passed, check:generated 15/15, all three typechecks green, eslint over the whole 6216-file population 0/0. The unexecuted shard was never the load-bearing evidence for a comment sweep.

Full posture change on #16173 (5560400009) — the shard-timing defect is now a hard block on the queue for every lane, not a cost anyone can disclose their way past. Confirmation of the fix on #16157 (5560401741), including the list of what this lane landed through the hole while it was open.

domain:spec PM dispatch seat · seat post #6017 · correcting the reading under my own ACCEPT


Generated by Claude Code


Generated by Claude Code

@huangyiirene
huangyiirene added this pull request to the merge queue Sep 6, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 6, 2026

Copy link
Copy Markdown
Collaborator Author

⛔ EJECTED from the merge queue — and this is the correct outcome, exactly as written above before it happened

domain:spec PM dispatch seat (session_01T6HeZvT9wdSJD1ZxJb5Eno), 2026-09-06T17:5xZ. Following through on 5560405799, which said: "If Test Core (5/6) trips the wall again there, this PR is ejected from the queue and stays open — which is the correct outcome, and I will handle it as a block rather than a disclosure."

It did, and it is.

The queue build — merge_group run 34047281740, gh-readonly-queue/main/pr-16327-4998efa717…

job conclusion window
Test Core (1/6) · (2/6) · (3/6) · (4/6) ✅ success 17:01:45 → 17:15:04
Test Core (6/6) ✅ success 17:01:47 → 17:29:17 (43s under the wall)
Test Core (5/6) cancelled 17:01:46 → 17:32:04 = 30m18s
Test Core (required) failure 17:32:06 → 17:32:24
the other 10 jobs ✅ success

Run cancelled 17:32:25Z; the queue re-formed without this PR at the same moment (sibling #16347 moved from base 3ac024afc to 4998efa71). ⇒ Removed from the queue. Still open, unmerged.

What this settles about the earlier readings on this PR

Status: BLOCKED, ⛔ not a disclosure

This PR is now in the same state as sibling #16342: finished work, refused by a defect its diff cannot cause. Six files, 61 additions / 14 deletions, prose only — no schema, no export, no behaviour, no test expectation. Five of six shards passed in the queue build; the sixth was killed by the 30-minute wall.

⛔ Not re-running the shard. ⛔ Not raising timeout-minutes. ⛔ Not touching scripts/test-shard-timings.json. ⛔ Not re-arming auto-merge on the theory that another roll of the dice is a fix — this PR's own queue build shows (6/6) finishing 43 seconds under the wall and (5/6) going 18 seconds over, which is what a coin flip looks like, not a flake worth re-spending.

It waits on the timings refresh tracked at #16173, where this run is recorded as measurement 11 (5561021414) — the first observation of the defect inside the merge queue, which is the only place that decides whether anything lands.

Card #14721 keeps pm:dispatched; ⛔ nothing is stroked, because nothing landed.


Generated by Claude Code


Generated by Claude Code

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/s tooling

Projects

None yet

2 participants