Skip to content

Implement ADR-0029 D9 — register a tenant object overlay as its own contributor layer instead of splicing out the packaged owner #7277

Description

@os-zhuang

Implementation card for ADR-0029 D9, which was ratified when PR #7087 merged (fb5ebc5df, 2026-08-10). Filed by the domain:metadata PM seat as the designed follow-on to #6853; the design question is settled, this card is the code.

Why this exists

The maintainer's 2026-08-09 ruling on #6853 approved direction B in principle but required its semantics to be designed in an amendment, not guessed. That amendment is now merged as ADR-0029 D9. Nothing in this card re-opens the design — read D9 and implement it.

Measured today (see #6853's dev report, and #7012 which shipped the stop-the-bleed half): when an overlay row's package_id equals the packaged owner's id, registerObject splices the packaged contributor out, so the packaged definition is destroyed at write time rather than shadowed, and loadMetaFromDb replays that destruction on every boot with no gate and no log.

What D9 specifies

  • A tenant overlay registers as a third, NON-owning overlay contributor kind with replace semantics on the base layer — not a new ownership kind, and not an extender.
  • Resolution becomes base = overlay ?? own, with extenders folding on top exactly as today. This is bit-for-bit what the current splice already produces: the resolved schema, _provenance: 'org' included, does not move. Only what the registry remembers changes.
  • assertSingleOwnerPerObject is unchanged, literally — overlays are not owners, so D3's sentence keeps holding with no exemption clause (ADR-0028 D5/D6 depend on D3 being unconditional). One new violation class: an orphan overlay with no owner.
  • computeFQN untouched (identity today); the layer shares the owner's key.
  • Priority stays descriptive. A constant between owner (100) and extender (200) keeps list order, but base selection asks the kind, never "highest priority wins" — author-declared extender priority must not be able to re-rank which layer is the base.

Three things a naive implementation gets wrong — all named in D9

  1. getArtifactItem's object branch must read the OWNER contributor, not the merged object. The merged body deliberately keeps _provenance: 'org', so isArtifactBacked only stops lying if artifact identity is read from the owner's layer. This clause is not implied by the layering change and has to be done deliberately.
  2. provisionPrimary / provisionSearchCompanion are gated on own — that gate must become "is this the BASE layer", or nameField changes on every overlaid object. D9 carries a measured blast-radius table of every .ownership read site; work from it rather than from grep alone.
  3. An object overlay write over a packaged object is silently discarded by registerObject's ownership rule while saveMetaItem reports success #6995 is subsumed and must be implemented as ruled: the row's package_id is provenance on the layer, never an ownership claim — same package normal, package-less sentinel accepted (today's throw was an artefact of the borrowed slot), different package refused loudly at the producer. The reason for the last one is in D9: the overlay-uniqueness index can hold two rows for one (type, name) bound to two packages, which the object registry can never represent because computeFQN is identity.

Cost that is already accepted in the record

With the predicate honest, saveMetaItem refuses an overlay write to a packaged object with NOT_OVERRIDABLE every time, not only the first — object declares allowOrgOverride: false. Today the first write is refused and, by destroying the evidence, admits every later one through the allowRuntimeCreate tier. This is the declared contract enforced consistently rather than a new restriction, but a deployment living in the post-first-write state will feel it. D9.6 carries this with its migration note; do not soften it without a new ruling.

Left open by D9 on purpose

  • Late install — a package registering an object a tenant sys_metadata row already owns under the sentinel still hits "already owned by". D9 records the recommendation (the code layer becomes owner, the tenant contribution re-classified as its overlay layer) but explicitly leaves it to this card with its own measurement, because it is a second discriminator.
  • ADR-0005's "Amended by" cross-reference was deliberately not written while D9 was a proposal, since that line asserts acceptance. D9 is now accepted, so the one-line pointer is in scope here.

Family

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions