Skip to content

metadata.register('object', ...) applies no namespace-prefix check — the ADR-0028 invariant is enforced caller-by-caller, not at the runtime write path #11237

Description

@os-sam

Found while working #11061 (the importObject name-override seam). Filed rather than
fixed there: #11061's triage scoped that card to the import seam, and its own body calls
this "a much wider question than this seam". Unassigned — recording, not starting.

What was measured

MetadataManager.register(type, name, data) (packages/metadata/src/metadata-manager.ts)
applies exactly two guards before writing: assertMetadataRegisterContract (name/data
agreement, document shape, canonical type) and the persistence.writable gate. It applies
no namespace check of any kind on register('object', ...)validateObjectNamespacePrefix
is not called, and no other name gate exists on that path.

The ADR-0028 prefix rule runs at exactly two gates today:

  • defineStack() / os validate — compile time (packages/spec/src/stack.zod.ts,
    validateNamespacePrefix).
  • the publish pre-flight — packages/metadata-protocol/src/protocol.ts
    (NAMESPACE_PREFIX violations, batch-refused with the validator's own message).

metadata.register is neither, so any runtime caller that persists an object through it
bypasses the declared invariant entirely.

Live exposure, as of #11061's fix

In-tree register('object', ...) callers measured:

So after #11061 the known live exposure is closed, but the invariant remains enforced
caller-by-caller rather than at the write path itself. The next runtime feature that
persists an object via register starts un-gated by default — the same
declared-vs-enforced shape #11061 closed, one layer down.

The question to grade

Should register('object', ...) itself gate the name (making declared = enforced at the
write path), or is the gate deliberately a publish/authoring-time concern with register
kept as the low-level primitive? Considerations pulling in both directions, stated so
triage does not have to rediscover them:

  • A gate at register needs a namespace to judge against; MetadataManager has no
    manifest concept (publishPackage's own doc says it indexes by packageId and cannot
    prove a namespace — that is why the publish gate takes namespace as an option).
  • A gate at register would also sit on the boot-time bridge writes and any rehydration
    path that reaches it, where refusing a legacy stored row is a different decision than
    refusing a fresh runtime write.

Related: #11061 (the import seam and its ruling that the prefix rule is declared
behavior), #10712 / PR #11059 (the draft half).

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions