Uh oh!
There was an error while loading. Please reload this page.
Declare the metadata item-name grammar in spec and refuse it loudly at the publish door (#12176 stage 1) - #12244
Conversation
…and refuse it at the publish door (#12194) Stage 1 of #12176: item names are lowercase snake_case segments, optionally dot-qualified (METADATA_ITEM_NAME_PATTERN, one segment source shared with ViewItemNameSchema's dot-required arity). saveMetaItem and publishMetaItem refuse an off-grammar name 400 INVALID_REQUEST with the dotted prescription; the slash bypass of refuseUnmintableMetaType closes as a consequence. Reads and deleteMetaItem stay open for residue. ADR-0087 semantic entry + changeset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB
…spec artifacts (#12194) The compound PUT suites pinned the acceptance direction the ruling reverses: they now pin the grammar refusal at the wire (400 INVALID_REQUEST with the dotted prescription, store untouched, no query spelling bypasses), the single-segment twin keeps its full contract, and the published-overlay compound READ fixture is seeded as store residue (reads stay open). The org-scope fixture's translation addressing name respells zh-CN -> zh_cn (locale keeps the BCP-47 spelling). api-surface/export-origins/docs regenerated for the new shared exports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB
…the pinned ledger (#12194) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB
…em-name-grammar # Conflicts: # packages/spec/src/type-alias-convention.pin.test.ts
gen:migration-registry re-concatenates both semantic:18 entries (object-grid-data-view-data-converged from #12039, metadata-item-name-grammar- enforced from this branch); api-surface/export-origins/docs regenerated on the merged tree per scripts/pm/os-regen-merge.sh step 4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB
📓 Docs Drift CheckThis PR changes 2 package(s): 50 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 127 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 20f693001143a19216beec4466c490231cab0232 && git checkout 20f693001143a19216beec4466c490231cab0232
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3ddad51b5c9d745908cd918f0be1af95ee270709 289d920f3b6d441db621a055b7f10972699b8e95 && git checkout -B drift-repro 3ddad51b5c9d745908cd918f0be1af95ee270709 && git merge --no-ff 289d920f3b6d441db621a055b7f10972699b8e95
node scripts/docs-audit/affected-docs.mjs --json 3ddad51b5c9d745908cd918f0be1af95ee270709
|
… the grammar suite's find double (#12194) Two objectql consumer tests proved central-registry validation through an ungrammatical item NAME, which the new grammar door now refuses first with INVALID_REQUEST 400 — before the schema gate could produce the INVALID_METADATA 422 they pin. Both probes now break `label` instead, so they still prove the registry wiring; name-grammar refusal keeps its own pins in metadata-protocol. The grammar suite's engine double now matches every provided where key by nullish-normalised equality and holds the caller's limit bound (check:objectql-double-limit: the fixed-key filter could never seat the control probe, so the double sat UNJUDGED — new files never join that baseline). Also merges origin/main. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9StxQgG2DPA26XzZZqnJB
…em-name-grammar # Conflicts: # packages/spec/src/type-alias-convention.pin.test.ts
… the item-name pin to Iso862 (#12194)
Fixes#12194
Stage 1 of #12176's maintainer-ruled retirement of compound-name addressing (2026-08-25; Q2 fork accepted as B — the dotted qualified form becomes enforced). D3 (#12195, REST/SDK re-addressing) and D4 (#12196, docs sweep) are out of scope here and untouched: the compound routes stay mounted; this PR shuts the door they write through.
What lands
1. The item-name grammar is declared in spec, from one segment source.
packages/spec/src/shared/identifiers.zod.tsnow declaresMETADATA_ITEM_NAME_PATTERN(lowercase snake_case segments, optionally dot-qualified — the regex family the card names) andMetadataItemNameSchema, built from the sameITEM_NAME_SEGMENTsource as the dot-REQUIREDQUALIFIED_ITEM_NAME_PATTERNthatViewItemNameSchema(ui/view.zod.ts) now derives from — no second grammar minted; the view-item identity's behavior is byte-identical.SaveMetaItemRequestSchema.nameandPublishMetaItemRequestSchema.namecarry the schema; the read/delete request shapes deliberately stay plain strings so residue rows stay listable and clearable.2. The publish door refuses it loudly.
refuseUngrammaticalMetaItemNameinpackages/metadata-protocol/src/protocol.tsruns insaveMetaItem(draft AND publish modes) andpublishMetaItem, before the store-backed type probe: an off-grammar name earns400 INVALID_REQUESTwith the grammar, the dotted prescription (crm_lead.pipeline) and the containment rule in the message. Enforcement sites bound (traced, not assumed): every REST write (PUT /meta/:type/:name, the compound:type/:section/:namefold in rest-server.ts) and every dispatcher write (runtime/domains/meta.tsfolds) reachprotocol.saveMetaItem;publishPackageDrafts/rollbackMetaItem/duplicatePackageoperate on stored rows (which the shut save door keeps grammatical);migrateStoredMetadatacallssaveMetaItemper row and is deliberately NOT exempt — an off-grammar stored row's rewrite fails loudly in the migrate report (outcome: 'failed'with this refusal's text), never silently.3. The measured slash bypass ends.
refuseUnmintableMetaType's exemption 1 (skip the type verdict when the name contains a slash) is deleted — the grammar verdict refuses every slash name before the type verdict runs, so the #12176 census's P10 (type=fieldz name='a/b'accepted and stored whilename='a'was 400) now refuses, and the docblock's stated residue (PUT /meta/fieldz/a/baccepted) ends with it. Anti-vacuity pinned:fieldz+ grammatical name still earns the TYPE refusal.4. ADR-0087 semantic entry + changeset.
migrations/entries/semantic/18.metadata-item-name-grammar-enforced.ts(registry regenerated;check:adr-0087-registrationgreen), changesetminorfor spec + metadata-protocol with the markerregistered metadata-item-name-grammar-enforced, stating the narrowing and what an author writes instead.Premise verification (binding, ran FIRST)
Corpus censused before landing the regex — the grammar accepts every legitimate existing item name: 146 authoring-file item names (
*.{object,view,flow,app,…}.tsacross examples/, platform-objects, plugins, services) + 68define*call names — 0 failures, 0 dots at file level; dotted legit names found in the broad sweep (showcase.export_data,auth.password_reset,ping.body) all fit. Junk controls all refuse; accept controls all pass.One qualification, reported rather than smoothed:
TranslationItemSchema's docblock says translation items are "conventionally named after their locale (zh-CN)", andauthored-translation-sync'sLOCALE_LIKEtolerates dashed/uppercase stored names on the READ side.zh-CNfails the grammar (uppercase + dash — both boundary cases the card rules out). Measured: zero in-repo addressing names use that spelling (the platform's create seed isnew_translation; bundles load into the i18n service keyed by locale and mint no item names; the one occurrence was a test fixture whose subject is org-scoping, respelledzh_cnhere). No capability is lost:localeis the type's REQUIRED identity and keeps the BCP-47 spelling; the read side is untouched, so dashed residue rows in out-of-repo stores keep resolving. If locale-named rows turn out load-bearing in deployments, that is D2 reopening — flagged in the report.Landing assert (absorbed D2)
Re-measured on this tree at the final commit: zero stored slash rows — the JSON corpus's only slash-in-name hits are npm
package.jsonnames (positive control: 460 JSON files carry anamekey), authoredname:literals with a slash in non-test sources: 0, and all 11 literalcrm/task/views/all_leadsoccurrences outside tests are docs pages (D4), SDK/fold docblocks (D3), and this PR's own declarations. No migration performed, none needed.Test triage (the compound-door pins reversed direction by ruling)
protocol.item-name-grammar.test.ts: accept probes (flat, dotted, multi-dot), 14 junk refusal probes asserting the ADR-0112 envelope (code+status, store untouched), the P10 pin + anti-vacuity control, promotion-door pins, reads/delete-stay-open pins.protocol.unrecognised-meta-type.test.ts: the compound-arity ACCEPT pin (it pinned the deleted exemption) replaced with the refusal pin; header updated.runtime/meta-compound-arity-mint-door.test.ts: the two live-route accept pins now pin the wire-level 400 + grammar message + no row.rest/meta-compound-save-{force,mode}-parity.test.ts: the twins now DIVERGE by design — single door keeps its full Two more faces of the DESTRUCTIVE_CHANGE 409 prescribe?force=trueon routes that never thread it — the compound-namePUT /meta/:type/:a/:band the runtime dispatcherPUT /meta#11095/The compound-namePUT /meta/:type/:section/:namenever threads?mode=draft, while its single-segment twin does — the fourth divergence closed, a fifth left open #11712 contract (pinned per spelling), compound door refuses before reading any query param (?force=truedoes not bypass;?mode=draftcannot stage a slash draft); repeated-param guard ordering pins kept.rest/meta-published-overlay.test.ts§6: fixture reseeded as store residue (reads stay open by design).runtime/meta-write-org-scope.test.ts: translation fixture addressing namezh-CN→zh_cn(locale keepszh-CN).spec/type-alias-convention.pin.test.ts: Iso859 pin for the new schema (ADR-0122), count merged with spec: two ComponentPropsMap entries contradict their ruled authorities —object-grid.datavs ViewDataSchema,element:number.filtervs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039's landing (834 + 1 = 835, verified by recount).Verification (final union at the merged head
f6cb548c, quoted from that run)origin/main@22c42c9bsources (restore under trap; mutation proven on disk — guard refs 0, dist marker 0 after rebuild, spec build exit 0): 19 failed / 24 passed, 18 with the "promise resolved" acceptance signature — the census's acceptances reproduced, accept-side green. Fix restored (markers 5/4, dist marker present), rebuilt: 43/43 green.f6cb548c:metadata-protocolfull suite 1944 passed | 10 skipped; triaged rest files 49 passed; triaged runtime files 25 passed; spec pin/migrations/artifact suites 150 passed; typecheck green for spec, metadata-protocol, rest, runtime.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, provenance line naming this tree atf6cb548c): 38/38 green, includingcheck:generated(14/14 after regen),check:adr-0087-registration, strictness/liveness ledgers,check:engine-double-contract(ledger learned the new suite's double), andcheck:type-check-debtre-measured over the fully built closure.origin/maintwice (second brought spec: two ComponentPropsMap entries contradict their ruled authorities —object-grid.datavs ViewDataSchema,element:number.filtervs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039 Key 1 — both semantic:18 entries verified present aftergen:migration-registry; the ADR-0122 pin-count conflict resolved semantically, both intents stack).Generated by Claude Code