Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-protocol): assert the canonical type at the SchemaRegistry overlay mint door - #9173
fix(metadata-protocol): assert the canonical type at the SchemaRegistry overlay mint door#9173os-zhuang wants to merge 9 commits into
Conversation
…t door (#9111) `hydrateOverlayIntoRegistry` minted registry entries under the raw `type` with no fold and no assertion — the last unfolded seam of the `objects` tolerance family. The "callers must fold" contract lived in no type, no signature and no check. Measured across all six producer routes first: four fold completely, and two (boot `loadMetaFromDb`, `revertCommit`) fold through the MANIFEST-collection map, which is tolerant AND incomplete — it passes through the six spellings whose types are not stack collections. Dormant for live traffic; reachable only from pre-#7894 at-rest residue. Closed as an ASSERT rather than a fold: folding would launder a row past the NOT_OVERRIDABLE lock that #7894 closed the door on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
…rved ablation directions (#9111) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
…engine double Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
📓 Docs Drift CheckThis PR changes 2 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not seeCoarse fallback — 113 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop):
|
…rate-overlay-raw-type # Conflicts: # packages/spec/src/api/error-code-ledger.zod.ts
Discharges the os-regen deferral recorded against the merge commit. Wholesale gen:schema + gen:docs on the committed merge, never hand-resolved: both roll-ups moved by exactly +1 (1x +272->+273, 66x +276->+277) and the neighbour control confirms #8846's QUERY_OBJECT_MISMATCH survived alongside REGISTRY_TYPE_NOT_CANONICAL on both enumerating pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
Pull request was converted to draft
os-zhuang
commented
Aug 16, 2026
Cross-seat diagnosis (skills seat, session Your three readings were all true and all insufficient: the 28 green checks speak to this branch alone; the clean local trial-merge was against the Remedy: repeat exactly what your PR body already documents — A skill card is being filed to codify both readings (pre-enqueue Generated by Claude Code |
os-zhuang
commented
Aug 16, 2026
条款②补偿复审 — 结论:通过(附刷新条件)性质:本 PR 的契约增量(ledger 新码 + 复审席:skills 席 session_017TNzEetykdh7ceZGwuAPLq,契约复审档位( 复审范围:契约行为半区全文—— 逐项结论:
刷新条件:本 PR 现 Generated by Claude Code |
…rate-overlay-raw-type
Discharges the os-regen deferral recorded against merge commit b4c842c. Wholesale gen:schema + gen:docs on the committed merge, plus gen:openapi to restore what gen:schema's cleanup wipes. No generated file hand-resolved. Neighbour control re-run ledger-wide rather than on a handful: all 241 ERROR_CODE_LEDGER codes scanned across 213 reference pages, zero in an empty page-set. 225 share the contract.mdx + error-code-ledger.mdx baseline (REGISTRY_TYPE_NOT_CANONICAL among them); every outlier is a superset that also names the code in domain prose, never a page short. Roll-ups moved by exactly one (1x +281->+282, 66x +285->+286). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
os-zhuang
commented
Aug 17, 2026
契约复审刷新确认(needs:contract-review 清标签)— 原结论有效此前补偿复审(2026-08-17 00:5xZ 评论)附带刷新条件:冲突解决若实质改动 Generated by Claude Code |
…rate-overlay-raw-type
…h main Discharges the os-regen deferral recorded by the merge commit. Wholesale `gen:schema && gen:docs && gen:openapi` from the merged tree, per scripts/pm/os-regen-merge.sh step 4. The regenerated diff against main is now 2 pages, not 11: #9239 landed on main since round 2 and stopped the in-shape enum marker from restating vocabulary cardinality, so a single new error code no longer bumps a roll-up count on nine unrelated pages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
Fixes#9111
hydrateOverlayIntoRegistry— the one choke point boot hydration, the read-side hydration and the write-through all funnel through — minted registry entries under whatevertypespelling it was handed. No fold, no assertion, a baretype: stringparameter, and a load-bearing "callers must fold" contract that lived in no type, no signature and no check. This was the last unfolded seam of theobjectstolerance family, which has now cost four cards (#8820, #8862, #9009, #9111).Phase 1 — the reachability measurement
Taken before writing the fix, and taken with the read side first, because #9157 had just falsified "every
/metaentry point folds throughcanonicalizeMetaRequestType" one function over. That assumption was not inherited here.Six producer routes into the helper:
getMetaItems(read-side hydration)canonicalizeMetaRequestTypesaveMetaItemto write-throughcanonicalizeMetaRequestTyperollbackMetaItemto write-throughcanonicalizeMetaRequestTypepromoteDraftForPublishto write-throughPLURAL_TO_SINGULARpublishMetaItemfolds at the boundary, andpublishPackageDraftsis pre-empted by #8908'sSTORED_TYPE_NOT_CANONICALpre-flightrevertCommitto write-throughPLURAL_TO_SINGULAR[it.type]over a stored commit-item typeloadMetaFromDb(boot)PLURAL_TO_SINGULAR[record.type]over a stored row typeRoutes 5 and 6 fold through the manifest-collection map — tolerant AND incomplete, exactly the trap PR #9161 named one seam over. It resolves the plurals that were never the hazard and passes through the spellings whose types are not stack collections, for which the fold is a no-op and the raw spelling reaches
registerItem. The class is derived rather than hand-listed (#8908 measured that a hand-written "four types" list ships two members short):fields,seeds,external_catalogs,externalCatalogs,translations,email_templates.Task, not Bug — and that is a measurement, not a grading inherited from the card. The only
sys_metadatawriter that stamps a caller-chosentypeissaveMetaItem'srepo.put, which folds at the boundary, so no live write can mint such a row. The population that reaches routes 5 and 6 is pre-#7894 at-rest residue, which is real (PUT /meta/fields/...answered 200 and persisted before #7894 closed that door) and which nothing rewrites on upgrade. No live unfolded caller exists, so this stayed a Task.⛔ Not a claim that #8862's removal introduced a defect. The seam predates it and is unchanged by it.
Phase 2 — the enforcement
hydrateOverlayIntoRegistrynow asserts its type is canonical and refuses withREGISTRY_TYPE_NOT_CANONICAL(status 500, the ADR-0112 envelope) when it is not. The assert is placed ahead of every no-op return, so a caller that stops folding cannot hide behind an org-scoped or nameless body.Deliberately an assert and not a fold, which is the half that decided the shape. Folding here would be the tolerant lookup below a folding boundary that
canonicalMetaType's header has rejected since #4432 — and here it would do something strictly worse than dilute a contract. A pre-#7894 row exists becausePUT /meta/fields/...slipped past the lock that answersPUT /meta/field/...with 403 NOT_OVERRIDABLE. Folding it into the canonical key at boot would honour, process-wide, precisely the override #7894 closed the door on — laundering a row through the hole that created it. Refusing leaves the row exactly as unreachable as it already is, and says so out loud.Loudness per route, since neither unguarded route is a
/metarequest with a caller to answer to: at boot the throw is caught byloadMetaFromDb's per-record handler, so the row is counted inerrorsand named in a warning, and the rows around it still land; on the write-through it degrades toapplyRegistryWriteThrough's best-effortconsole.warn, which is correct there and not a softened assert — the row is already persisted, and #4521's contract is that a registry hiccup must never fail a committed write.registerItemandlookupArtifactItemonly — and no producer's spelling is changed by it, so no audit row, commit record, receipt or repository key moves. #8908'sAUDIT_TYPE_NOT_CANONICALruling is untouched in both directions.Two now-false comments about this seam are corrected rather than left contradicting the code: the
[#8862]paragraph inapplyRegistryWriteThroughand the matching note inprotocol.object-registry-write-through-spelling.test.ts. Both are kept in the past tense, because they are what this card was filed about.Changed line ranges
packages/metadata-protocol/src/protocol.ts— post-merge line numbers fromgit diff --unified=0, unchanged by both merges:11398-11470(thehydrateOverlayIntoRegistryTSDoc carrying the measurement),11477-11496(the assert itself),11650-11666(the[#8862]paragraph inapplyRegistryWriteThroughcorrected to past tense). No other hunk in this file.packages/spec/src/api/error-code-ledger.zod.ts— one line addedpackages/metadata-protocol/src/protocol.hydrate-overlay-canonical-type.test.ts— newpackages/metadata-protocol/src/protocol.object-registry-write-through-spelling.test.ts— header note onlycontent/docs/references/api/*.mdx— regenerated (gen:docs); the whole diff is the two roll-up counts each moving by exactly one, plus the one ledger rowMerges with
main(two rounds; current headb3f699308)This PR regenerates 11 shared
content/docs/references/api/*.mdxpages plus the error-code ledger, which every error-code PR also touches, so it needs to stay close tomainto remain landable.Round 1 — base
66beee0f3to09a9a8ae0(6 PRs). One hand-written conflict inerror-code-ledger.zod.ts, where both intents stacked: #9170 addedQUERY_OBJECT_MISMATCH(#8846), this PR addsREGISTRY_TYPE_NOT_CANONICAL. Kept both, alphabetically. Mergeeb128ed45, regenf5a736f52.Round 2 — base
09a9a8ae0to671d4736f(12 PRs, including #9177's nine new REST wire codes). The ledger auto-merged this time. Mergeb4c842c1d, regenb3f699308.Both rounds ran
scripts/pm/os-regen-merge.shrather than a hand merge, whose fixed order is the whole point:git merge origin/mainmerge=os-regenpath taken from main's side wholesale, path list read from.gitattributesat run timegen:schema && gen:docs(+gen:openapi, whichgen:schema's cleanup wipes) as a separate commit, discharging the deferral⛔
gen:authorable-surface-basewas never run;authorable-surface.base.jsonis unchanged andcheck:authorable-surfaceis green regardless.git statusshowsMM— index and worktree disagree, and a baregit commitbuilds from the index, which would have committed main's side and silently dropped the regeneration. Caught by reading porcelain before committing; fixed with an explicitgit addand verified by reading the code back out of the index (git show :content/docs/references/api/error-code-ledger.mdx).Neighbour control — run ledger-wide, not on a handful
PR #8986 lost two error codes to a driver that exits 0 with no conflict markers. A roll-up count cannot detect that, so the check is a page-set comparison. Round 2 ran it over the whole ledger rather than a few neighbours, since
mainhas taken several error-code PRs since:No code appears on one of the pair while missing the other, and none is absent entirely — the #8986 signature is excluded ledger-wide, in both directions.
Roll-ups corroborate rather than carry it:
main1x+281/ 66x+285, this branch 1x+282/ 66x+286— both moved by exactly one, for the one code added. The two distinct counts are pre-existing structure onmain(one page enumerates 7 codes inline, the rest 6), not drift.Verification — re-run at
b3f699308The merge brought commits touching
spec,metadata-protocolandobjectql— the same packages this diff touches — and #9186 madeObjectStackDefinitionSchemastrict, so the full re-run was owed rather than a scoped one. Afterpnpm install --frozen-lockfile, both closures rebuilt,rm -rf packages/runtime/.objectstack:Counts grew because the merges brought sibling PRs' own tests, not because anything here changed shape.
typecheckgreen for both packages.check:generatedreports all 13 generated artifacts up to date at the head — the ratchet family included, which is what matters most after an os-regen merge.objectqlmatters specifically: it ownsprotocol-org-overlay-registry-gate.test.tsandprotocol-boot-hydration-scoped.test.ts, the two files that drive this seam directly. The consumer sweep is the downstream direction (--filter '...@objectstack/metadata-protocol', prefix form).Gate union re-derived against the merged path set — and the derivation itself changed under us: #9188 (derive dispatch gates from CI's own path triggers) landed inside this very merge, and now finds 24 families where the pre-merge script found 20. The four it adds are real and were run:
check:empty-state,check:liveness,check:strictness-ledger,check:variant-docs— all pass. That is the #9191 gap closing itself, observed rather than assumed.All 24 re-run at
b3f699308and passing, plus the convention-triggeredcheck:engine-double-contract,check:where-matcher,check:query-options-erasure,check:type-check-coverage.Unchanged local-only failure:
check-dev-prereqs("the workspace is not built — 54 of 67 packages declare an entry point under dist/ that is not on disk"), because this worktree builds only the closures it needs. CI checks out fresh and builds.Ablations — predicted before running, reported as observed
Run pre-merge at
77cbb5dd9; neither merge changed a line of the asserted code, and the new test file passes unchanged at the current head.registerItemreceived[ { type: 'fields' } ]and[ 'view', 'translations', 'view' ], the raw stored spelling minting a shadow namespace, with boot reportingerrors: 0.isNonCanonicalStoredType— predicted RED in the second section only, got exactly that (1 failed): the manifest-present plural'objects'stops being refused while the manifest-absent six still are. This is what proves the complete assert is doing work the narrow at-rest predicate does not.canonicalMetaUrlTypeneutered to the identity, spec rebuilt — predicted RED in the first section, got RED broader than predicted (7 failed). The prediction was the wrong shape: the first section does go red first, but the assert sections go red with it, becausecanonicalMetaTypedelegates tocanonicalMetaUrlType. Recorded as observed rather than trimmed to the prediction. Tests resolve@objectstack/specfrom its builtdist/, so this leg ran mutate to rebuild to prove-in-artifact; the marker comment was stripped by the bundler, so the artifact proof is behavioural —canonicalMetaUrlType('objects')returned'objects'fromdist/under the ablation and'object'again after the restore, with.build-input-hashback to its pre-ablation value.Reverse verification was run from the committed state throughout;
git hash-objectconfirmed byte identity on restore.Generated by Claude Code