Measured while implementing #10078 (step ③ residue of the #9180 ruling). Filed unassigned; recording only, not claiming.
What is wrong
The /meta boundary folds a URL type segment through META_URL_TO_SINGULAR — the complete map, which embeds every manifest spelling and every registry-derived one (canonicalizeMetaRequestType in packages/metadata-protocol/src/protocol.ts). The REST doors that sit in front of that fold decide the request's organization scope from the raw segment instead, through organizationIdForMetaRead / organizationIdForMetaWrite (packages/rest/src/rest-server.ts:2618, :4079, :4914, and the write twin), whose declaresOrgOverride (packages/metadata-core/src/meta-write-org-scope.ts:98) folds through PLURAL_TO_SINGULAR — the manifest-collection map, which is incomplete by design.
Two maps, two answers. Executed on origin/main (bf7cc1302) against the built @objectstack/metadata-core and @objectstack/spec/meta-spelling:
spelling in URL map protocol fold REST org READ REST org WRITE
object false object undefined undefined
objects true object undefined undefined
view false view org_1 org_1
views true view org_1 org_1
dashboard false dashboard org_1 org_1
dashboards true dashboard org_1 org_1
translation false translation org_1 org_1
translations true translation undefined undefined
email_template false email_template org_1 org_1
email_templates true email_template undefined undefined
emailTemplates true email_template org_1 org_1
field false field undefined undefined
fields true field undefined undefined
views / dashboards / emailTemplates agree with their singular because they are manifest keys. translations and email_templates are registry-derived spellings — limb 2 of META_URL_TO_SINGULAR — and have no manifest key, so declaresOrgOverride cannot see them.
Why it matters
translation and email_template are both allowOrgOverride: true. With an active organization:
#7894's own module header anticipated the shape and forbade the tempting repair: "⛔ Do not 'fix' a future instance of this by teaching a predicate one layer down (isNestedArtifactField and friends) to accept a plural." The correction here is the same direction as #7894's: the decision should be taken on the folded type, at or after the boundary fold — not by widening a second map.
A second, smaller site of the same class
GET /meta/:type/:name/published (packages/rest/src/rest-server.ts:6280) consults getMetaItemLayered (which folds) and, when the overlay layer is null, falls back to svc.getPublished(type, name) with the raw segment. A code/package-published item therefore answers 404 for a recognised plural and 200 for the singular.
Deliberately not a site: listDrafts applies no fold, and its comments say why (it matches the draft row's storedtype). Recorded here so nobody "fixes" it as if it were the same thing.
Why this is filed instead of fixed
It surfaced as the blocker for #10078's site 1 (normalize the client SDK's variable-typed item routes toward singular). That card carries a verbatim trip-wire: "If any slice of your diff turns out to change what a request is answered with (not merely what we emit or teach), STOP and report the fork — that slice belongs at the claude-fable-5 floor and is not this card." Folding the SDK's emitted segment moves an org-active caller between those two rows, so it stops there; PR #10337 ships that card's docs half and reports the fork.
Both limbs here change what a request is answered with and where a write lands, so this is accept/serve behaviour: claude-fable-5 floor, not the consumer-sweep tier.
Related: #10078 · #9180 · #7894 · #9454
Measured while implementing #10078 (step ③ residue of the #9180 ruling). Filed unassigned; recording only, not claiming.
What is wrong
The
/metaboundary folds a URL type segment throughMETA_URL_TO_SINGULAR— the complete map, which embeds every manifest spelling and every registry-derived one (canonicalizeMetaRequestTypeinpackages/metadata-protocol/src/protocol.ts). The REST doors that sit in front of that fold decide the request's organization scope from the raw segment instead, throughorganizationIdForMetaRead/organizationIdForMetaWrite(packages/rest/src/rest-server.ts:2618,:4079,:4914, and the write twin), whosedeclaresOrgOverride(packages/metadata-core/src/meta-write-org-scope.ts:98) folds throughPLURAL_TO_SINGULAR— the manifest-collection map, which is incomplete by design.Two maps, two answers. Executed on
origin/main(bf7cc1302) against the built@objectstack/metadata-coreand@objectstack/spec/meta-spelling:views/dashboards/emailTemplatesagree with their singular because they are manifest keys.translationsandemail_templatesare registry-derived spellings — limb 2 ofMETA_URL_TO_SINGULAR— and have no manifest key, sodeclaresOrgOverridecannot see them.Why it matters
translationandemail_templateare bothallowOrgOverride: true. With an active organization:GET /api/v1/meta/translations/:nameresolves the env-wide overlay row;GET /api/v1/meta/translation/:nameresolves the org-scoped one. Same item, same fold below, two answers — this is Org-overridable metadata (view,dashboard) is accepted with a 200state:'active'receipt but served by no read door #9454's failure mode ("persisted, receipted as live, and served by nothing") reachable purely by how the caller spelled the type.PUT /api/v1/meta/translations/:namelands an env-wide row wherePUT /api/v1/meta/translation/:namelands an org-scoped one. That is a second namespace for one item, addressed by spelling — the meta overlays: unnormalized type segment creates phantom rows that shadow the code-authored listing and cannot be deleted #4432 / meta-plural-url-bypass:PUT /meta/fields/<name>walks around the whole two-tier registry gate — 4 registry types have no entry inPLURAL_TO_SINGULAR#7894 defect exactly, one layer down from where meta-plural-url-bypass:PUT /meta/fields/<name>walks around the whole two-tier registry gate — 4 registry types have no entry inPLURAL_TO_SINGULAR#7894 closed it.#7894's own module header anticipated the shape and forbade the tempting repair: "⛔ Do not 'fix' a future instance of this by teaching a predicate one layer down (
isNestedArtifactFieldand friends) to accept a plural." The correction here is the same direction as #7894's: the decision should be taken on the folded type, at or after the boundary fold — not by widening a second map.A second, smaller site of the same class
GET /meta/:type/:name/published(packages/rest/src/rest-server.ts:6280) consultsgetMetaItemLayered(which folds) and, when the overlay layer is null, falls back tosvc.getPublished(type, name)with the raw segment. A code/package-published item therefore answers404for a recognised plural and200for the singular.Deliberately not a site:
listDraftsapplies no fold, and its comments say why (it matches the draft row's storedtype). Recorded here so nobody "fixes" it as if it were the same thing.Why this is filed instead of fixed
It surfaced as the blocker for #10078's site 1 (normalize the client SDK's variable-typed item routes toward singular). That card carries a verbatim trip-wire: "If any slice of your diff turns out to change what a request is answered with (not merely what we emit or teach), STOP and report the fork — that slice belongs at the
claude-fable-5floor and is not this card." Folding the SDK's emitted segment moves an org-active caller between those two rows, so it stops there; PR #10337 ships that card's docs half and reports the fork.Both limbs here change what a request is answered with and where a write lands, so this is accept/serve behaviour:
claude-fable-5floor, not the consumer-sweep tier.Related: #10078 · #9180 · #7894 · #9454