Skip to content

/meta org scope is decided from the RAW url spelling: translations / email_templates read and write env-wide where their singular twin is org-scoped #10340

Description

@os-elon

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions