Uh oh!
There was an error while loading. Please reload this page.
fix(metadata): route the three read-side /meta verbs through the canonical type boundary (#9157) - #9191
Conversation
…cal /meta type boundary (#9157) auditMetaItem, historyMetaItem and findReferencesToMeta derived their type key from PLURAL_TO_SINGULAR - the manifest-collection map #7894 moved this boundary off - instead of calling canonicalizeMetaRequestType, which nine sibling verbs already call. That call carries both the URL map and metaUrlSpellingRefusal, and the refusal is the half those three could never reach: it lives inside the function they skipped. Step 1 of the #9180 maintainer ruling (the /meta type segment is singular, always). Steps 2 and 3 stay unbundled and independently revertible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTKPDRoynY8i3HmdSFUxFj
…d-verb-canonical-fold
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
…d-verb-canonical-fold
…ent, not package edges The advisory resolved pages by PACKAGE DEPENDENCY -- "which hand-written docs reference @objectstack/x". That is a dependency-graph proxy answering a semantic question, and #9192 measured it wrong in BOTH directions on PR #9191 (the three metadata read verbs): 3 pages listed of which 1 was relevant, while the 2 pages that actually document the changed surface were absent, because they document it through the SDK -- which does not depend on the implementing package at all. A page is now listed when it NAMES something the change touched. Three anchor kinds: the documentable declaration enclosing each changed line (top-level, or a member of a top-level container -- a local inside a function body is not surface, which is what drops the measured `singular` false positive); the wire routes a changed symbol's registrar handler serves; and the client method the route ledgers bind to those routes, which is the hop that carries the derivation across the surface boundary the package graph cannot cross. Two guards keep it precision-first, both running BEFORE the route bridge so a weak name cannot mint route anchors from every handler that mentions it: a shape guard (a single lowercase word like `label` or `object` matched 82 and 113 of 178 pages) and a corpus-share guard (`ObjectQL` is code-shaped and genuinely changed, and names 59 of 178 pages). Everything either guard removes is published, as is every changed file that yielded no anchor -- silence from this tool must never be readable as absence, which is the whole subject of the card. Measured before/after across ten real PRs; the PR comment now also states what the run could not see, at the point of use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza
os-zhuang
commented
Aug 17, 2026
契约复审(needs:contract-review 清标签)— 通过复审席:skills 席 session_017TNzEetykdh7ceZGwuAPLq(契约复审档位,非派发席,过渡期代行)。范围:契约行为增量全文(protocol.ts 三处折叠接入 + 测试断言清单 + changeset 的 wire 变更表)。 结论:
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
…ent, not package edges (objectstack-ai#9229) The advisory resolved pages by PACKAGE DEPENDENCY -- "which hand-written docs reference @objectstack/x". That is a dependency-graph proxy answering a semantic question, and objectstack-ai#9192 measured it wrong in BOTH directions on PR objectstack-ai#9191 (the three metadata read verbs): 3 pages listed of which 1 was relevant, while the 2 pages that actually document the changed surface were absent, because they document it through the SDK -- which does not depend on the implementing package at all. A page is now listed when it NAMES something the change touched. Three anchor kinds: the documentable declaration enclosing each changed line (top-level, or a member of a top-level container -- a local inside a function body is not surface, which is what drops the measured `singular` false positive); the wire routes a changed symbol's registrar handler serves; and the client method the route ledgers bind to those routes, which is the hop that carries the derivation across the surface boundary the package graph cannot cross. Two guards keep it precision-first, both running BEFORE the route bridge so a weak name cannot mint route anchors from every handler that mentions it: a shape guard (a single lowercase word like `label` or `object` matched 82 and 113 of 178 pages) and a corpus-share guard (`ObjectQL` is code-shaped and genuinely changed, and names 59 of 178 pages). Everything either guard removes is published, as is every changed file that yielded no anchor -- silence from this tool must never be readable as absence, which is the whole subject of the card. Measured before/after across ten real PRs; the PR comment now also states what the run could not see, at the point of use. Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza Co-authored-by: Claude <noreply@anthropic.com>
Fixes#9157
Step ① of the maintainer ruling in #9180 (2026-08-16): the
/metatype segment is singular, always, no exception.auditMetaItem,historyMetaItemandfindReferencesToMetaeach opened by deriving their type key fromPLURAL_TO_SINGULAR— the MANIFEST-COLLECTION map #7894 moved this boundary off — instead of callingcanonicalizeMetaRequestType, which the nine sibling/metaverbs already call. That one call carries both the URL spelling map andmetaUrlSpellingRefusal, and the refusal is the half these three could never reach: it lives inside the function they skipped.⛔ Steps ② (the
/meta/objects/:name/state/:fieldroute rename) and ③ (docs + retiringPLURAL_TO_SINGULARas a fold) are not in this PR. #9180 requires the three steps to stay independently revertible.#9174remains open and is not addressed here.The filing card asserted that
PUT /meta/views/all_leadsis "refused 400 by the same deployment for the same spelling", and the dispatch summarised the deliverable as three GET routes going "200-empty → 400". The first is false and the second is true only for one of two classes. Measured onorigin/mainbefore writing any code:viewsis inMETA_URL_TO_SINGULAR, so it folds — on the write verbs today just as on the read verbs after this PR. The refusal class is misspellings of a declared type, not recognised plurals. The premise still stands where it matters (three verbs genuinely skip the boundary, and the empty-accumulator harm is real), so this is a correction rather than a falsification — but the delivered wire change is two-shaped, not one::typeviewes— unrecognised spelling of a declared typeINVALID_REQUEST, namingviewandviewstranslations/fields/seeds/external_catalogs— recognised plurals of the four manifest-ABSENT typesviews— recognised plural, manifest-PRESENTfieldz— reaches for no declared typeBoth rows close the harm #9180 named — "nothing depends on this" delivered right before a rename or delete. One does it by refusing loudly; the other by finally answering truthfully. Retiring the recognised plurals themselves is step ③, and a pin (
theRecognisedPluralsAreNotRefused) asserts they are still accepted so this PR cannot be read as having bundled it.⭐ The population, re-derived rather than taken from the card
The card hand-listed "nine fold, three do not". Derived mechanically from the source instead — every class method taking a request with a required
type: string, which is the shape a/meta/:typepath segment arrives as:getMetaItems,getMetaItem,getMetaItemLayered,getMetaItemCached,saveMetaItem,publishMetaItem,rollbackMetaItem,diffMetaItem,deleteMetaItemauditMetaItem,historyMetaItem,findReferencesToMetaThe count is three; the card was right. The derivation is now a test, so a tenth verb arriving unfolded goes red rather than being noticed a card later.
Three methods were deliberately excluded and the exclusion is stated so it can be contested:
getMetaDiagnostics,listDrafts(type?:) andmigrateStoredMetadata(types?: string[]) take the type as a query-string filter, not a path segment — a different contract, andmigrateStoredMetadatadocuments accepting both spellings on purpose.Two measured details that invert an intuition
historyMetaItemthe plural was a door AROUND a gate, not merely a wrong key.fielddeclares neitherallowOrgOverridenorallowRuntimeCreate, so the canonical spelling is refused by the overlay gate and never touches the store — whilefieldstookisRuntimeCreateAllowed's no-static-registry-entry arm (the plugin path, permissive by construction) and issued a realsys_metadata_historyread keyed'fields'. Same empty body, opposite path, which is why the pins assert the KEY the store was asked for and not only the body.findReferencesToMetathe refusal is the whole visible change. EveryREFERENCE_PATHSkey (object,view,tool,skill,flow,dashboard,page) is manifest-PRESENT and already folded, so a manifest-absent target still answers{ references: [] }after this PR. That residue is asserted rather than implied, and filed separately asfindReferencesToMetaanswers{references: []}for every target type absent fromREFERENCE_PATHS— the "Used by" panel is silent by construction for 20+ declared types #9190 (finding, unassigned) — it is a registry-coverage question, not a spelling one.Reverse verification
Direction predicted in writing before running: plain RED (not "more diagnostics", not inverted — the refusal is thrown by the boundary itself, not by a downstream schema, so there is no
??-chain fall-through to reroute it).Observed: plain RED, exactly as predicted. Ablating the three fold calls back to
PLURAL_TO_SINGULAR[request.type] ?? request.typeand re-running the pin file:Every predicted-green control stayed green in both directions: the anti-vacuity arm,
theRecognisedPluralsAreNotRefused, all four manifest-PRESENT / plugin-shaped controls, and the honestfindReferencesToMetascope pin. The fix was committed before the ablation, so restoring it wasgit checkout <branch> -- <path>andgit statuscame back clean — the restored tree is byte-identical to the committed one.Docs
No documentation change is needed, and that was established by sweeping the whole
content/docs/**corpus rather than a supplied page list./history,/auditand/referencesappear in exactly two pages, neither of which makes a spelling claim (api/client-sdk.mdx:187-188passes singular'object';kernel/contracts/metadata-service.mdx:85is a baretype: stringsignature). Every spelling statement in the corpus —api/metadata-api.mdx:26-31andprotocol/kernel/http-protocol.mdx:877-879— is scoped under aGET /meta/{type}heading, i.e. the listing verb, which already folded; so nothing was made false, and nothing was quietly moved toward truth either. Follow-ups filed by the PM as #9192 (the page list was wrong in both directions) and #9193 (the refusal envelope is undocumented for all twelve verbs).Changed ranges
Declared from
git diff --unified=0 origin/main, not from symbol addresses:packages/metadata-protocol/src/protocol.ts—6376-6420,13443-13476,17883-17912PR #9173 (#9111) is open on the same file at
11398-11470/11477-11496/11650-11666— no overlap.Verification — all at
e96538ab7(current head, base671d4736f, 0 commits behind)Re-verified after the second
origin/mainmerge. No commit in that merge touchespackages/metadata-protocol/**— butpackages/spec/**moved (#9186,ObjectStackDefinitionSchemagoes strict) and spec is upstream of this package, so the dependency closure was rebuilt and the suite re-run rather than carried over:The downstream consumer sweep (
--filter '...@objectstack/metadata-protocol'— dependents, not dependencies) was run at the previous headb8731f7a3and is unchanged by this refresh, since nothing in the merge touches this package or its consumers' use of it: objectql 213 files / 3755 tests, rest 122 / 2011, runtime 165 / 2463 — all passed.Gate union re-derived and re-run at
e96538ab7, all PASS. The derivation itself was upgraded by #9188 in this very merge (it now states outright that 94 of 103 families sit in workflows with nopull_requestpath filter, i.e. CI schedules them on every PR):The last six are not named by the derivation and were read off CI's own job list (#9171 discipline).
check:stack-collection-mapsis new to this run and was added because its script moved in this merge and it governsPLURAL_TO_SINGULARitself — the map this change moves off.Type-check ratchet re-measured at the new head, not carried over:
tsc --noEmitoverpackages/metadata-protocolreports 63 errors, still exactly the ledger's recorded number, with zero from the new test file.Generated by Claude Code