Skip to content

fix(metadata-protocol): refusals, hints and log lines state each decision in words instead of a tracker number (stage 2) - #20830

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20513-stage2-metadata-protocol
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20513-stage2-metadata-protocol

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20513
Clause-②: no

Stage 2 of 5 of this lane (metadata-protocol), under the maintainer's A / A ruling on the card. The card stays open for stages 3-5, so this PR carries no closing keyword. Text only: no error code, field name, HTTP status, export or control flow moves. Every changed source line is a string-literal line (108 changed lines in 11 .ts files, checked line by line against the merge base).

What this does

The metadata protocol's refusals, hints and log lines sent the reader to a tracker number for the reason behind them. Each rewritten string now says that reason in words (form D, as the migration-entry rewrite and stage 1 applied it). Where the sentence already stated what was decided, only the citation goes. Where it did not, the decision is added in words:

Where Cited The sentence now says
protocol.ts insertManyData refusal (thrown) 3172 insertMany is the partial-success batch insert: an outcome per row, so a bad row neither fails the whole batch nor makes the good rows run their beforeInsert hooks twice.
protocol.ts unknown metadata type refusal (400) 8586 A plugin cannot declare a type either: additionalTypes was retired because nothing ever read it.
protocol.ts stored non-canonical type refusals on publish and on revert (STORED_TYPE_NOT_CANONICAL) 7894, 8957 The /meta URL door now folds a type to its canonical spelling before it writes, so such a row predates that; the stored migration's skipped report "with that same reason" loses only its citation.
runtime-authoring-gate.ts schedule-flow organization_id hint 6153 The author's value wins, and the engine fills only an organization the run resolved, so for a schedule run the author is the one source.
plugin.ts the three kernel:ready "migration skipped" warnings 5839, 8629, 8686 What the migration that did not run would have ensured: view-name uniqueness among ACTIVE rows only; the NULL-safe sys_setting row identity on tenant and global rows; the adoption of untenanted seed rows and their autonumber counter.
sys-metadata-repository.ts history-counter abort (error) 4867 The old path answered 1 because it took a failed read for an empty table.
protocol.ts publish-closure degrade (warn) 10377 Validation falls back to the LIVE declarations without the batch's own drafts in the closure.
protocol.ts cold-boot org-scoped audit (warn) 6190, 6992 The write refusal it points at covers declared types only; the trailing "See" keeps ADR-0005.
everything else: the overlay, sys_view_definition and sys_setting index migration messages (ADR-0120 D4 stays), the seed/API tenancy repair, its receipt and its skips, the batch-row withhold, the object-existence gate's no-registry warning, the nested-select, overlay and non-canonical-registry refusals, and the live-MySQL testkit error 6418, 8725, 8629, 5839, 6417, 8686, 9451, 9261, 8502, 3770, 4196, 6190, 4432, 10382 The sentence already said what was decided; only the citation goes.

Each claim was checked against today's code, not only against the cited card: saveMetaItem folds the request type before it persists; additionalTypes is a retiredKey() tombstone in packages/spec; the org-scoped write refusal is live in orgScopedWriteRefusal. One cited number (10382) answers 404 and was read through its landing commit ee09d2119; the testkit sentence already says what it guards.

Order inside the stage

All 46 id-bearing literals (50 occurrences) fit one PR, under the stop line, so the stage lands whole, in three commits in the ordered sequence:

  1. dc8a1a112 author-visible text: 10 literals (thrown refusals, the stored-type refusals, the hint);
  2. 730cbcaf6 log lines: 35 literals, plus the two re-pinned tests;
  3. 442473234 the src/-shipped testkit string, and the changeset.

Each commit recomputes the ledger, so every commit on the branch is green on check:doc-authoring.

Pins re-pinned: 3 assertion lines in 2 test files

  • migrations/view-definition-active-index.test.ts 342-343 asserted the two numbers in the MySQL degradation line. They now assert the two gaps in words: "an archived view keeps occupying its name slot" and "two same-name ACTIVE shared views (owner NULL)".
  • protocol.batch-row-driver-text.test.ts 396 asserted the number in the withhold warning. It now asserts "must not be quoted back on response data", the decision itself.

A one-off mutation proves each new pin can fail, run on the committed head with scripts/ablation-replace.mjs (anchor hit once, disk-verified) under a shell trap. Changing "name slot" gives 1 failed / 27 passed. Changing "(owner NULL)" gives 1 failed / 27 passed. Changing "quoted back" gives 2 failed / 14 passed: the re-pin, and a knock-on in the next test, because the failed test never reached its mockRestore. After each leg, the blob equals HEAD and git diff HEAD is empty. The tree is clean after the run, and no test file was left behind.

No string here is compared byte for byte with a twin in another package. The consumer pins outside the package read unchanged fragments: runtime's batch-row-driver-text-real-driver.integration.test.ts reads the withhold prefix, seed-tenancy-autonumber-split.integration.test.ts reads "backfill skipped", and meta-field-overlay-lock.test.ts, objectql's protocol-meta.test.ts and rest's meta-unknown-type-read-refusal.test.ts read "is not a metadata type". I ran the three runtime files against the rebuilt dist/, and they passed.

Ledger burn-down

scripts/doc-authoring-prose-id.baseline.json was regenerated with node scripts/check-doc-authoring.mjs --census-ledger into a scratch file, so the growth refusal ran against the checked-in baseline, and then copied into place. Only metadata-protocol rows moved, and every one of them leaves:

File Occurrences before after (file, id) pairs before after
metadata-protocol/src/protocol.ts 15 0 (row leaves) 11 0
metadata-protocol/src/migrations/seed-tenancy-backfill.ts 14 0 (row leaves) 3 0
metadata-protocol/src/migrations/view-definition-active-index.ts 7 0 (row leaves) 3 0
metadata-protocol/src/migrations/overlay-index.ts 4 0 (row leaves) 2 0
metadata-protocol/src/migrations/sys-setting-identity-index.ts 4 0 (row leaves) 1 0
metadata-protocol/src/plugin.ts 3 0 (row leaves) 3 0
metadata-protocol/src/migrations/live-mysql-database.testkit.ts 1 0 (row leaves) 1 0
metadata-protocol/src/runtime-authoring-gate.ts 1 0 (row leaves) 1 0
metadata-protocol/src/sys-metadata-repository.ts 1 0 (row leaves) 1 0
whole ledger 861 811 572 546

The ledger's file count goes from 224 to 215, and other packages' rows moved: 0. The census's 40 messages reconcile with the ledger's 50 occurrences. The gate counts 46 string literals: 45 in the census population plus the testkit string, which the census filed as test-facing. The census folds a + chain into one message, so 5 two-literal chains make 45 literals into 40 messages. Four literals carry two ids each, which makes 46 literals into 50 occurrences.

Verification (head 442473234)

  • build: turbo over @objectstack/runtime... (30/30), then the whole workspace (72/72), then @objectstack/metadata-protocol directly. The new sentences are in dist/index.js, and the only citations left in dist/ are docblocks.
  • @objectstack/metadata-protocol test: Test Files 190 passed, 3 skipped (193); Tests 2792 passed, 19 skipped. The skips are the live MySQL/PostgreSQL files: this container has no server.
  • @objectstack/metadata-protocol typecheck: exit 0; tsc --listFiles reads 193 of 193 test files.
  • @objectstack/runtime, the three consumer files above: 3 files, 33 tests passed.
  • node scripts/pm/dispatch-gates.mjs --commands (no paths; 13 paths against merge base 261c529f0): 70 commands, all exit 0. check:dual-build-cjs-loads and check:type-check-debt first answered exit 3 PREREQUISITE NOT MET on the partial build. After the full build (and a direct rebuild of this package, whose dist a turbo cache hit had left older than the restored sources), both exit 0; check:dts-closure and check:lean-entry-closure were re-run there too. --ran: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN, exit 0.
  • check:doc-authoring: sibling-package prose ids hold the baseline, no growth, no burn-down unrecorded.
  • narrowed lint: eslint --no-inline-config --format json over the 11 touched .ts files reports 11 files, 0 errors and 0 warnings. The resolved parserOptions for these files are ecmaVersion and sourceType only, with no project or projectService. So no type-aware rule can move an untouched file. The repo-wide pnpm lint is CI's.
  • NOT MEASURED locally (CI's): the live PG/MySQL files, whose messages changed only ahead of the pinned MySQL statement lead-in; the Test Core shards; Dogfood; the workspace type-check lanes.

Acceptance notes


Generated by Claude Code

…ds instead of a tracker number (stage 2, author-visible)

The thrown refusals, the stored-type preflight and revert refusals, and the
schedule-flow organization hint no longer send the reader to a tracker number:
each says what was decided, or loses only the citation where the sentence
already said it. Text only: no code, field, status or export moves. The
prose-id ledger is recomputed with --census-ledger; only metadata-protocol
rows move.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
…each decision in words instead of a tracker number (stage 2, log lines)

The kernel:ready index migrations, the seed/API tenancy repair and its
receipt, the three migration-skipped warnings, and the protocol's warn and
error lines no longer cite a tracker number. Where the sentence already said
what was decided, only the citation goes; the three skipped warnings now say
what the migration that did not run would have ensured. Two tests that pinned
a number now pin the sentence. Text only. The ledger is recomputed; only
metadata-protocol rows move.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
…s its tracker number; changeset (stage 2)

The last metadata-protocol row leaves the prose-id ledger: the testkit's
isolation error already says what it guards, so only the citation goes. The
ledger is recomputed with --census-ledger and holds no metadata-protocol row.
Changeset: @objectstack/metadata-protocol patch.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
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 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 23 documentable anchor(s).

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

  • content/docs/api/error-catalog.mdx (via additionalTypes (literal, a string literal in refuseUnmintableMetaType))
  • content/docs/kernel/contracts/data-engine.mdx (via insertManyData (symbol, a method of class ObjectStackProtocolImplementation))
  • content/docs/kernel/services-checklist.mdx (via assembleMetadataProtocol (symbol, a top-level function))
  • content/docs/plugins/adding-a-metadata-type.mdx (via additionalTypes (literal, a string literal in refuseUnmintableMetaType))

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

  • content/docs/releases/v17/17-1.mdx (via publishPackageDrafts (symbol, a method of class ObjectStackProtocolImplementation), additionalTypes (literal, a string literal in refuseUnmintableMetaType))
  • content/docs/releases/v17/17-4.mdx (via insertManyData (symbol, a method of class ObjectStackProtocolImplementation))
  • content/docs/releases/v17/17-5.mdx (via insertManyData (symbol, a method of class ObjectStackProtocolImplementation))

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 — 11 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 73155fedcacc215565c4eef6d9899977e0707010 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 73155fedcacc215565c4eef6d9899977e0707010

⚠️ 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 73155fedcacc215565c4eef6d9899977e0707010 → 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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 442473234551d7cb909e2a927d1adc446a9b4e35
Local-runs: none

Read-only, at tier, adversarial to the dispatch. Inputs: card #20513 (body and all 17 comments — the census 5900801368, the ruling 5902360492 「20513 A」 A / A, the lane checklist 5902678544, the stage-2 claim 5907730876, the stage-2 os-dev-report 5908549372), PR #20830 (body, 13-file list, the net diff origin/main...442473234 at merge base 261c529f0), PR #20795 at df67985b0 and its record 5906164285 as the prior application of the same ruling, the head's check-runs, the 22 cited cards (21 readable; 10382 through its landing commit ee09d2119), and the head's own source through git show / git grep. Nothing built, run or re-run. The PR head was confirmed as 442473234551d7cb909e2a927d1adc446a9b4e35 before the read and had not moved.

Check-runs on 442473234, read once for this record and not polled: 31 check-runs — 13 completed / success (Check Changeset, Check PR Size, Part-of PR must not also close its card, the claim and single-writer guards, Flag docs affected by code changes, Check Documentation Links, Governed Surface Queue Guard, Type Check · source gates, Dogfood Verify CLI, Auto Label, filter), 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke — path-filtered), 15 in_progress (Test Core 1-6, Dogfood Regression Gate 1-3, Build Core, Temporal Conformance (live PG + MySQL), Lint & Repo Gates, Type Check · workspace / debt ledger / consumer gates), 0 failure. Their conclusions are the gate verdicts: the runs still going carry check:doc-authoring (Lint & Repo Gates), the three re-pinned assertions (Test Core) and the live-server leg the dev did not measure (Temporal Conformance). This record judges the contract on the diff, the cards and the head's source; a failure among those runs, if one lands, is a new fact for the adopting session, not one this record has read.

① Derived judgments

Scope against the ruling and the claim — right. The ruling orders stages per package, all three categories, form D, --census-ledger in the same PR, author-visible text first and log lines last; the claim scopes stage 2 to packages/metadata-protocol/src/**, the pins, the ledger and one patch changeset, and forbids wire code / field / status changes, comment or docblock edits (#20595) and other packages' ledger rows. The 13-file list is exactly that surface: 9 source files, 2 test files, the ledger, the changeset. No governed path. "Author-visible first" is honoured inside the stage as commit order (dc8a1a112 the refusals and the hint, 730cbcaf6 the log lines, 442473234 the testkit string), and a stage that lands whole has nothing left to split.

Text only — right, checked line by line. All 108 changed lines in the 11 .ts files (54 removed, 54 added) are inside string literals: template pieces in the five migration files and runtime-authoring-gate.ts, the '…' + chains in protocol.ts and the testkit, the three ctx.logger.warn('…') literals in plugin.ts, the console.error template in sys-metadata-repository.ts. The one-line throw new Error('insertManyData requires…') changes only its literal; the three rejoined boundaries (seed-tenancy object(s): + untenanted, view-definition cannot. + MySQL, and the catch-all whose trailing (#5839 / #6417). line folded into the sentence before it — hence 6 added / 7 removed on that file) move no non-literal token. err.code, err.status, code: 'STORED_TYPE_NOT_CANONICAL', organizationId, every logger.* / console.* call shape and every export are context lines. The two test-file changes are the 3 pin lines only. Comments and docblocks are untouched: the only tracker ids left outside comments in the 9 files on the head are trailing // [#3770], // [#7823], // [#7539] comments on code lines, which the ledger does not read.

Accept-set and public surface — nothing moves — right. No schema, route, wire code, HTTP status, field name, default or export changes; a caller sees different prose in the same envelopes. No test outside the package asserts any of the 22 removed numbers (a git grep of the head over every *.test.ts finds one trailing // [#8502] comment beside an unchanged assertion, nothing else), and the consumer fragments the dev names — the withhold prefix, "backfill skipped", "is not a metadata type" — are unchanged on the head.

Ledger diff — right, exact. Nine packages/metadata-protocol/src/** rows leave and nothing else moves. I reconciled every removed occurrence against a citation deleted in the diff: protocol.ts 15 (#6190 ×3 — the NOT_OVERRIDABLE "See", the cold-boot audit body and its "See" line; #7894 ×2 and #8957 ×2 — the publish and revert refusals; #8502, #3770, #4196, #3172, #4432, #8586, #10377, #6992 once each); seed-tenancy-backfill.ts 14 (#8686 ×9, #9451 ×4, #9261 ×1); view-definition-active-index.ts 7 (#5839 ×3, #6417 ×3, #8725 ×1); overlay-index.ts 4 (#6418 ×3, #8725 ×1); sys-setting-identity-index.ts 4 (#8629 ×4); plugin.ts 3 (#5839, #8629, #8686); runtime-authoring-gate.ts 1 (#6153); sys-metadata-repository.ts 1 (#4867); the testkit 1 (#10382). 50 occurrences, 26 (file, id) pairs, 9 files — the whole-ledger deltas 861 to 811, 572 to 546, 224 to 215 follow. check:doc-authoring inside Lint & Repo Gates is the mechanical confirmation, pending at read time.

Form D, string by string — right. Every cited card was read: 20 closed completed issues, one merged PR (#6153, landed as bdc8e709a), and #10382 through ee09d2119 (each live-MySQL suite's database derived from its own file path; the drop database in afterAll is why sharing one is unsafe — what the testkit sentence still says). No cited decision has been reversed. The 11 literals that gained words, each judged against the card and the head's code:

Pins — right, 3 lines in 2 files. view-definition-active-index.test.ts now asserts "an archived view keeps occupying its name slot" and "two same-name ACTIVE shared views (owner NULL)"; both phrases are contiguous in the head's joined template (…occupying its + name slot, and two same-name ACTIVE shared views (owner NULL) or environment-level …). protocol.batch-row-driver-text.test.ts asserts "must not be quoted back on response data", present in the head's chain. The dev's mutation legs (1 / 27, 1 / 27, 2 / 14 with the mockRestore knock-on) are its own measurement; what this record verified is that each pinned phrase is on the head.

② Semver level

.changeset/20513-metadata-protocol-runtime-strings-state-the-decision.md: '@objectstack/metadata-protocol': patch — right. The package is released (17.5.0, not private); the diff publishes changed prose in that one package and nothing else — no key, export, envelope code, status or default moves — so this is a fix-class patch, never skip-changeset. The body is CHANGELOG-fit: it names each message family, says which sentences gained words and what they now say, and closes "Text only: no error code, field name, status or behaviour changes"; each claim in it matches a string in the diff (the insertMany description, additionalTypes retired having never been read, the fold-before-write door, "a schedule resolves none", the failed read taken for an empty table). No model identifier in the changeset, the three commit trailers (Claude-Session plus a model-free Co-authored-by) or the PR body (session-URL footer). Check Changeset is success.

Clause-②: no — right. Nothing an author can write is widened or narrowed; the PR body carries Clause-②: no at line start and the changeset repeats it.

③ Boundary flags

Dev flags (report 5908549372), each answered:

  1. Census reconciliation (46 literals / 50 occurrences / 40 messages) — holds, recomputed independently. The census lists 40 messages for this package with 49 per-message-deduplicated id occurrences, plus the testkit string filed as test-facing. Five messages hold two id-bearing literals each (the MySQL view degradation, the ambiguous-organization skip with its #9261 NOTE, the publish and revert refusals, the cold-boot audit): 40 + 5 = 45, plus the testkit = 46. Four literals carry two ids (#6418, #8725; #6417, #8725; #5839 / #6417; #6190 / #6992): 46 + 4 = 50; the census's 49 is the ledger's 50 less the testkit, the audit's second #6190 having been folded by the per-message dedupe. Exact.
  2. 3 re-pinned assertions and their mutation legs — verified in ①. The 2-failed leg is the failing test never reaching warn.mockRestore(), a knock-on inside the same file, not a second pin.
  3. No cross-package byte-parity twin, nothing held — accepted. A git grep of the head over every test file finds no assertion on any of the 22 numbers outside the package, and no test compares these sentences with another package's copy; the driver-sql stage's held rows are a different family. The claim's "no other package's ledger rows" holds: the ledger diff touches nine rows, all under packages/metadata-protocol/src/.
  4. In-file twin pair rewritten alike — verified. The publish (about 18670) and revert (about 20876) refusals carry the identical parenthetical; no test on the head asserts the rewritten parenthetical, and the fragments tests do read (STORED_TYPE_NOT_CANONICAL, the re-author remedy) are unchanged.
  5. PR fix(runtime,metadata-protocol): the /automation write doors keep the packaged-base lock the /meta door keeps (#20679) #20817 also edits protocol.ts — accepted as stated. Whichever lands second merges main and recomputes with --census-ledger; the growth refusal makes a stale ledger red, so the order cannot go wrong silently. Carrier: the seat that lands the second one.
  6. The out-of-scope note on recordSeedTenancyReceipt — yes, a sentence this PR rewrote makes that claim. The shared prefix at seed-tenancy-backfill.ts about 1149 — "the seed/API tenancy repair ran and rewrote stored rows, but this deployment has NO durable record that it did" — lost its (#8686) in this diff and heads all three not-recorded lines. The code comment at about 1538 says the receipt is written for every applied run "including one where every stamp failed (objectsStamped === 0)", and a failed stamp skips that object's counter merge, so on such a run nothing was rewritten and the prefix over-claims — only when every stamp failed AND the receipt could not be written. The words pre-date this PR; the ruling's rewrite takes the citation out of a sentence that already states the decision, and the stage-1 record treated the same class as a wording note. Not a FAIL item. Carrier: the seat, as a finding or a rider on the next PR that touches this file — derive the prefix from result.objectsStamped, or say "ran" and let the receipt's own objectsStamped say what moved.
  7. Live PG / MySQL NOT MEASURED locally — CI's; Temporal Conformance is in_progress at read time. The three live files pin only the unchanged MySQL statement lead-in.

open_questions: none in the stage-2 report; none raised here. The card's lane children (#20749, #20751, #20753, Blocked-by: #20513) are unaffected by this stage.

One boundary note, not a FAIL item — a docs page quotes the old sentence. content/docs/api/error-catalog.mdx about 695 reproduces the unknown-metadata-type refusal verbatim as a sample 400, including "since #8586 retired 'additionalTypes'"; it is the only page under content/docs that quotes any of the 46 rewritten literals. The Docs Drift Check on this PR lists that page (via the additionalTypes literal) and is advisory by its own text; AGENTS.md's Documentation Guardrails bind no code PR to it, and the claim forbids riders outside packages/metadata-protocol/src/**. Carrier: the docs-accuracy-audit scoped by the drift comment, or a docs-only PR — the example should read the sentence the runtime now emits.

Implemented-by: claude/issue-20513-stage2-metadata-protocol
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 10:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit fe463b4 Sep 30, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20513-stage2-metadata-protocol branch September 30, 2026 10:42
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.

2 participants