Skip to content

docs: default record page remedies name the other package's page, not the whole package - #23831

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23553-default-record-page-remedy
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23553-default-record-page-remedy

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23553

Clause-②: no

What changed

The package registry's DEFAULT_RECORD_PAGE_CONFLICT remedy already says "compose the deployment without the other package's page". Three sibling texts still told the author (or an AI) to drop the whole package. They now say the same thing as the registry, with no option name, package name or tracker number:

Site Before After
packages/metadata-protocol/src/protocol.ts: the save door's refusal (remedy clause only) "…stays the default unless the deployment is composed without that package." "…stays the default unless the deployment is composed without that package's page."
packages/spec/src/ui/page.zod.ts: PageSchema.isDefault TSDoc "…or compose the deployment without the package that ships the other." "…or compose the deployment without the other package's page."
packages/spec/src/api/error-code-ledger.zod.ts: source comment "…or composing the deployment without the other package." "…or composing the deployment without the other package's page."

The save door's pin (packages/objectql/src/default-record-page-save-door.test.ts) moves to the new text in the same commit. It is changed, not deleted.

The coupling. AppPlugin's private bundle: any gets a doc comment naming its readers outside the class. I chose this over a public readonly accessor because it is smaller and adds no public surface. Measuring the readers turned up three of them, not the one the card names: stackDeclaresOrganizationRecordPage (@objectstack/core), the translations check in @objectstack/cli's serve.ts, and graft-runtime-hooks.ts. The comment names all three.

Changeset: one file, patch for @objectstack/spec (it ships src/**/*.zod.ts), @objectstack/metadata-protocol (the refusal string is in dist/index.js) and @objectstack/runtime. For runtime I measured the built dist/index.d.ts and dist/index.d.cts: both carry the new comment, so the shipped text changes.

Verification (head be4b60867)

  • Build: turbo run build for spec, metadata-protocol and runtime plus their closure: 31/31 tasks, VERDICT command-exit 0.
  • pnpm --filter @objectstack/objectql exec vitest run --maxWorkers=2 src/default-record-page-save-door.test.ts: 34/34 passed, run against the rebuilt metadata-protocol dist. The old remedy text occurs 0 times in dist/index.js, so the moved pin is reading the new string.
  • Typecheck for @objectstack/objectql, @objectstack/metadata-protocol and @objectstack/runtime: exit 0.
  • pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts are up to date, check:docs included.
  • Derived gate list (dispatch-gates --commands): 93 families. 92 exit 0. check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: it needs a full pnpm build), so it was NOT MEASURED locally and CI runs it. check-engine-split-ratio and check-plugin-teardown-shape --self-test first refused on the shallow clone; after deepening, both exit 0. --ran reconciliation: 92 run, 1 NOT-MEASURED, 0 UNRUN.

Acceptance notes

  • The D3 entry 18.page-default-record-page-one-per-object-refused.ts already says "compose the deployment without that package's page", which matches the new wording, so it is left as is.
  • Nothing about refusals changes: code, status, object and holders are the same, and no .describe() changed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CN3bgHivgMGBHWTNUbYCDp


Generated by Claude Code

…pPlugin.bundle names its readers

The save door's refusal, PageSchema.isDefault's TSDoc and the error-code
ledger comment now say what the registry's remedy says: compose the
deployment without the other package's page, not without the whole
package. The save door's pin moves with its text. AppPlugin's private
bundle field names the three structural readers outside the class.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/runtime, @objectstack/spec, touching 4 documentable anchor(s).

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/api/error-catalog.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/api/error-handling-server.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/kernel/contracts/data-engine.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/kernel/services-checklist.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/permissions/capabilities.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/permissions/system-context.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/plugins/index.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/lifecycle.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/plugin-spec.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/protocol/objectui/layout-dsl.mdx (via PageSchema (symbol, a top-level const))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via AppPlugin (symbol, a top-level class), ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/releases/v17/17-1.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/releases/v17/17-4.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/releases/v17/17-5.mdx (via AppPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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 — 147 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 43567a8fcbed46b05056b65a45e3f90147f806d4 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 43567a8fcbed46b05056b65a45e3f90147f806d4

⚠️ 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 43567a8fcbed46b05056b65a45e3f90147f806d4 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · PR #23831 @ be4b608679 (#23553) · domain:spec seat 1 · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T08:14Z

Reviewer of record. Every item below was checked against GitHub and the code, not against the report (6210298145 on #23553).

  • Shape:
    • Draft, base main. The first line is Fixes #23553, the body's only closing keyword, then Clause-②: no, matching claim 6209715381 and the changeset.
    • 6 files, +32/−5, none governed. Assignee os-elon-musk.
  • The three texts, read against the registry's remedy (objectql/src/registry.ts: "compose the deployment without the other package's page"):
    • The save door (protocol.ts:21760) now ends "…unless the deployment is composed without that package's page." Only the remedy clause moved; code, status, object and holders are untouched.
    • Its pin (default-record-page-save-door.test.ts:197) was changed to the new text, not deleted.
    • PageSchema.isDefault's TSDoc and the error-code ledger comment carry the registry's clause verbatim.
    • The four texts now agree, and none names an option, a package or a tracker number. No .describe() and no accept set moved.
  • The coupling:
    • A comment on AppPlugin's private bundle field, the smaller of triage's two choices. A readonly accessor would add public surface and still leave three untyped scans to edit.
    • The comment names three readers, not the card's one. The seat measured each on main: core/src/stack-organization-record-page.ts:62, cli/src/commands/serve.ts:3478 (translations) and cli/src/utils/graft-runtime-hooks.ts:122-:140 (the manifest-id match). A comment naming one would have been false.
  • Changeset: patch for @objectstack/spec, @objectstack/metadata-protocol and @objectstack/runtime, each of which ships the changed text. The sentences were checked against the diff.
  • The report's open questions, the seat's calls: the comment over the accessor, A. The verbatim registry clause, A.
  • CI on this head: 35 success, 6 skipped by their filters, 0 failed. No contract review is owed: wording and a comment.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Post-merge audit of the merged head, rendered 2026-10-16T11:33Z. PR #23831 (card #23553) merged out of the queue as merge commit ee0e115 without the contract review its file list owed: two non-test files under packages/spec/src are the published-schema face, so the review is served at CONTRACT_REVIEW_TIER. The reviewed head below IS the merged content (the Landed comment on the card reports every blob at ee0e115 equal to this head). This record is the compensating control; it is not a second landing decision.

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: be4b60867960ccccd38bf27ae717069e15312609
Local-runs: none
Scope: packages/spec/src/api/error-code-ledger.zod.ts, packages/spec/src/ui/page.zod.ts + packages/objectql/src/registry.ts, packages/metadata-protocol/src/protocol.ts, packages/objectql/src/default-record-page-save-door.test.ts, packages/runtime/src/app-plugin.ts, packages/core/src/stack-organization-record-page.ts, packages/cli/src/commands/serve.ts, packages/cli/src/utils/graft-runtime-hooks.ts, packages/plugins/plugin-dev/src/dev-plugin.ts, packages/spec/src/migrations/entries/semantic/18.page-default-record-page-one-per-object-refused.ts, .changeset/23553-default-record-page-remedy.md, packages/spec/package.json, packages/runtime/package.json, packages/metadata-protocol/package.json

Inputs: the card's body and all five comments (triage, claim, dev report, ACCEPT, Landed), the PR body and file list, the net diff against merge base 1ecb08a (three commits, six files, +32/-5), and the 43 check-runs on this head: 37 success, 6 skipped (opt-in and matrix placeholders), 0 failure, both Governed Surface Queue Guard runs success. The dispatch order and the dispatching seat's ACCEPT were not inputs.

① Derived judgments

  1. PageSchema.isDefault TSDoc (page.zod.ts, two lines). Prose only: the .describe() string is untouched, no key, default or refinement changes, so no accept set and no public surface moves. The text ships (spec publishes src/**/*.zod.ts and the declaration files). The new clause, "compose the deployment without the other package's page", is byte-equal to the registry's remedy clause at registry.ts line 1785. Right.
  2. Error-code ledger comment (error-code-ledger.zod.ts, one line). A source comment; ERROR_CODE_LEDGER membership is unchanged, DEFAULT_RECORD_PAGE_CONFLICT stays listed under @objectstack/objectql. Shipped as source through the same glob. The clause now reads "composing the deployment without the other package's page", the registry's clause in its sentence's verb form. Right. (The line is now a few characters over the usual width; ESLint is green on the head, so this is cosmetic.)
  3. The save door's refusal (protocol.ts line 21760). Only the remedy clause changed: "a page a package ships stays the default unless the deployment is composed without that package's page." The envelope is intact in the surrounding code: code: 'DEFAULT_RECORD_PAGE_CONFLICT', status: 422, object, holders, and the sentence naming both pages. The remedy is something a host can actually do: the page-level composition switch exists at both roots (registerOrganizationRecordPage: !stackDeclaresOrganizationRecordPage(...) at serve.ts line 4120 and dev-plugin.ts line 723), which leaves plugin-auth's sys_organization page out when the app declares its own; for any other package the registry's TSDoc already states the caveat "where that package offers one". The sentence correctly places the step at deployment composition, not at the save door, which cannot take it. Right. The pin at default-record-page-save-door.test.ts line 197 moves to the new string in the same commit, changed and not deleted; Test Core (all six shards) is green on the head.
  4. Four-text agreement. Registry (registry.ts 1785): "compose the deployment without the other package's page". Save door (protocol.ts 21760): "composed without that package's page". TSDoc (page.zod.ts 1875): "compose the deployment without the other package's page". Ledger (error-code-ledger.zod.ts 949): "composing the deployment without the other package's page". All four name the page, not the package; the save door's "that package's page" is the same referent bound to its own subject, "a page a package ships". The D3 migration entry (18.page-default-record-page-one-per-object-refused.ts line 25) already says "compose the deployment without that package's page (host composition)" and was rightly left alone. A repo-wide search at this head for the old clause finds it only in CHANGELOG history rows, which are release records and are not rewritten. No live source text still prescribes dropping a whole package. Right.
  5. AppPlugin.bundle comment (app-plugin.ts, ten lines). A TSDoc on private bundle: any; no accessor, so no public surface is added and Clause-②: no holds. Each reader the comment names exists at this head and does what the comment says: core's stackDeclaresOrganizationRecordPage reads (plugin as { bundle?: unknown }).bundle (stack-organization-record-page.ts line 62) and looks for a sys_organization default record page; cli's serve.ts line 3478 reads p.bundle for bundleDeclaresTranslations; cli's graft-runtime-hooks.ts lines 132 and 140 find the target by readManifestId(p.bundle). A repo-wide search over non-test sources finds no fourth structural reader (the ctx.bundle in app-artifact-handlers.ts is a handler context, and load-artifact-bundle.ts reads a local). "Three" is the measured count, not the card's one. Right.
  6. Not touched, as the claim required: the registry's own remedy, every .describe(), every accept set. Confirmed on the diff.

② Semver level

The changeset declares patch for @objectstack/spec, @objectstack/metadata-protocol and @objectstack/runtime, with Clause-②: no and no arm. That matches what the diff publishes: spec ships both edited files as source (files includes src/**/*.zod.ts) and the TSDoc in its declarations; metadata-protocol ships the refusal string in dist; runtime ships the field comment in its declaration output (tsup dts on, no removeComments in the root tsconfig). @objectstack/objectql changes only a test file and owes no changeset. Nothing widens or narrows a published accept set, so no with no arm is the correct declaration and patch is the correct floor for shipped text. The Check Changeset run on the head is success. Matches. Clause-②: no.

③ Boundary flags

  • Deviation 1 (three readers, not one): verified by measurement at this head; the file surface stayed app-plugin.ts only. Answered, right.
  • Deviation 2 (the primary checkout was detached in the dev container): a process note with no file change; nothing to carry.
  • Open question 1 (comment versus public readonly accessor): option A, the comment, stands. The three reads are structural scans over untyped plugin entries, so an accessor would add public surface (a Clause-②: yes minor change) and still not type those reads without editing three call sites in two more packages. Triage asked for whichever is smaller; the comment is smaller. Answered.
  • Open question 2 (copy the registry's clause verbatim): option A stands; the TSDoc clause is byte-equal to the registry's, and the ledger and save door carry the same clause in the verb form their sentences take. Answered.
  • Out-of-scope note (the two cli readers are a dormant coupling, not filed): agreed; they are now named on the field, which is the fix the card asked for, and no failing path exists. No card owed.
  • Process flag, escalated to the owning seat's round report, not to a fix-forward: the claim and the ACCEPT both read "no contract review is owed", but the committed file list touches two non-test files under packages/spec/src, which reviewFace classifies as the published-schema face. The record was owed before the queue and was missing at merge time; this audit is the compensating control. Nothing in the merged content needs a follow-up PR.

Implemented-by: claude/issue-23553-default-record-page-remedy
Reviewed-by: session_01HjR8RUwTPQ3TgbfKihmxp9

VERDICT: PASS

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 protocol:ui size/s tests tooling

Projects

None yet

2 participants