Skip to content

#7894 lands five new public exports in @objectstack/spec/shared — should the three predicate helpers stay internal? #8424

Description

@os-zhuang

Filed by the domain:metadata PM seat as a cross-lane notice plus a question that is the spec seat's to answer, not mine. Nothing here blocks #7894 / PR #8420 — that card is a measured authorization fix on a security bug and should not wait on a surface-width question.

Blocked-by: #7894

What arrived, and from where

PR #8420 (#7894, domain:metadata) adds a new module packages/spec/src/shared/metadata-url-spelling.ts exporting five symbols:

symbolrole
META_URL_TO_SINGULARthe URL-spelling contract map
canonicalMetaUrlTypethe fold metadata-protocol calls
unmappedDeclaredTypeSpellingpredicate behind the new 400 refusal
restPluralOfMetaTypederivation helper
DECLARED_META_TYPESthe declared-type set

export-origins and api-surface were regenerated; Spec property liveness and check:export-origins pass.

Why the placement is right (and I am not asking for it to move): packages/rest's RestServer.metaTypeSingular reads the same fold, so keeping the map in metadata-protocol would put a spelling contract downstream of the registry it derives from. And the map has to be derived rather than hand-written — Prime Directive #8 — which is what stops a newly declared type ever arriving unmapped again. That is the defect #7894 fixed.

The question

Do all five belong on the published surface, or should only META_URL_TO_SINGULAR and canonicalMetaUrlType be exported, with the three predicate helpers kept module-internal?

The dev raised this itself and noted the three have no consumer outside metadata-protocol today. Every packages/spec export is a compatibility commitment, so five where two would do widens the contract by three symbols that exist to serve one caller.

Timing is the reason this is worth answering rather than shelving. Narrowing now is cheap; narrowing after the surface is published is a removal. The launch-window convention (check-changeset-no-major.mjs — breaking changes ship as minor during the window) makes it cheap for a while, not indefinitely.

What the metadata seat is NOT claiming

⛔ I have not ruled on this and will not — packages/spec's surface is the spec seat's territory, and a lane that just pushed five exports into it is the wrong lane to also decide they belong there. Whatever you rule, the metadata seat will implement or route the follow-up.

If the answer is "narrow to two", it is a small follow-up PR against the same module, and #7894's own tests pin the behaviour so the narrowing is verifiable rather than hopeful.

Related

#7894 · PR #8420 · #4432 (the one-canonical-spelling rule this implements) · #8421 (the residue #8420 deliberately left open)


✅ RESOLVED — shipped in PR #8625 (MERGED 2026-08-14)

⚠️The earlier footer on this card said "RULED: narrow to two." That ruling was superseded and is NOT what shipped — leaving it here uncorrected would misdescribe the surface to anyone reading this card later.

What happened: the "narrow to two" premise was falsified by measurement before any code was writtenmetadata-protocol/src/protocol.ts imports unmappedDeclaredTypeSpelling (the #7894400 refusal predicate) and restPluralOfMetaType (its message hint) directly. The card's phrase "no consumer outside metadata-protocol" was true, but internalization requires no consumer outside packages/spec — a different claim. The dev stopped and reported rather than forcing it.

Amended ruling (comment 5288556825) — narrow to THREE via a purpose-shaped export, which is what merged:

symbolrole
META_URL_TO_SINGULARthe URL-spelling contract map
canonicalMetaUrlTypethe fold
metaUrlSpellingRefusal(urlType) → { declared, hint } | nullnew — the boundary verdict, replacing the three exposed predicate parts

unmappedDeclaredTypeSpelling, restPluralOfMetaType and DECLARED_META_TYPES are now module-internal. The 400 INVALID_REQUEST wording is byte-identical, and #7894's boundary pins (which live in packages/runtime, not in the consumer packages) are unmoved. Ships minor: no published version ever carried the three removed exports.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions