Skip to content

Declare the metadata item-name grammar in spec and refuse it loudly at the publish door (#12176 stage 1) - #12244

Merged
os-trump merged 11 commits into
mainfrom
claude/issue-12194-item-name-grammar
Aug 25, 2026
Merged

Declare the metadata item-name grammar in spec and refuse it loudly at the publish door (#12176 stage 1)#12244
os-trump merged 11 commits into
mainfrom
claude/issue-12194-item-name-grammar

Conversation

@os-trump

Copy link
Copy Markdown
Collaborator

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.ts now declares METADATA_ITEM_NAME_PATTERN (lowercase snake_case segments, optionally dot-qualified — the regex family the card names) and MetadataItemNameSchema, built from the same ITEM_NAME_SEGMENT source as the dot-REQUIRED QUALIFIED_ITEM_NAME_PATTERN that ViewItemNameSchema (ui/view.zod.ts) now derives from — no second grammar minted; the view-item identity's behavior is byte-identical. SaveMetaItemRequestSchema.name and PublishMetaItemRequestSchema.name carry 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.refuseUngrammaticalMetaItemName in packages/metadata-protocol/src/protocol.ts runs in saveMetaItem (draft AND publish modes) and publishMetaItem, before the store-backed type probe: an off-grammar name earns 400 INVALID_REQUEST with 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/:name fold in rest-server.ts) and every dispatcher write (runtime/domains/meta.ts folds) reach protocol.saveMetaItem; publishPackageDrafts/rollbackMetaItem/duplicatePackage operate on stored rows (which the shut save door keeps grammatical); migrateStoredMetadata calls saveMetaItem per 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 while name='a' was 400) now refuses, and the docblock's stated residue (PUT /meta/fieldz/a/b accepted) 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-registration green), changeset minor for spec + metadata-protocol with the marker registered 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,…}.ts across examples/, platform-objects, plugins, services) + 68 define* 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)", and authored-translation-sync's LOCALE_LIKE tolerates dashed/uppercase stored names on the READ side. zh-CN fails 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 is new_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, respelled zh_cn here). No capability is lost: locale is 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.json names (positive control: 460 JSON files carry a name key), authored name: literals with a slash in non-test sources: 0, and all 11 literal crm/task/views/all_leads occurrences 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)

Verification (final union at the merged head f6cb548c, quoted from that run)

  • Red-before-fix: the new suites ran against origin/main@22c42c9b sources (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.
  • Union at f6cb548c: metadata-protocol full 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.
  • Derived gates (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, provenance line naming this tree at f6cb548c): 38/38 green, including check:generated (14/14 after regen), check:adr-0087-registration, strictness/liveness ledgers, check:engine-double-contract (ledger learned the new suite's double), and check:type-check-debt re-measured over the fully built closure.
  • Merged origin/main twice (second brought spec: two ComponentPropsMap entries contradict their ruled authorities — object-grid.data vs ViewDataSchema, element:number.filter vs the platform filter form (rulings ui#6207-A, ui#6206-B) #12039 Key 1 — both semantic:18 entries verified present after gen:migration-registry; the ADR-0122 pin-count conflict resolved semantically, both intents stack).

Generated by Claude Code

…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
…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
@github-actions

github-actionsBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/spec, touching 29 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/shared.json, packages/spec/export-origins/shared.json, packages/spec/json-schema.manifest/shared.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

50 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 3ddad51b5c9d745908cd918f0be1af95ee270709.

6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/shared.json, packages/spec/export-origins/shared.json, packages/spec/json-schema.manifest/shared.json) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 177: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 107 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.

Coarse fallback — 127 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 3ddad51b5c9d745908cd918f0be1af95ee270709packageMentionDocs.

Which tree this was computed on

This run read content/docs from 20f693001143a19216beec4466c490231cab0232 — the merge of head 289d920f3b6d441db621a055b7f10972699b8e95 into base 3ddad51b5c9d745908cd918f0be1af95ee270709, 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 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

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

… 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
@os-trump
os-trump disabled auto-merge August 25, 2026 17:30
@os-trump
os-trump enabled auto-merge August 25, 2026 17:30
…em-name-grammar
# Conflicts:
#	packages/spec/src/type-alias-convention.pin.test.ts
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:uisize/xlteststooling

Projects

None yet

2 participants

@os-trump@claude