Skip to content

A draft row stored under a manifest-absent plural (fields, seeds, external_catalogs, translations) is promotable by publishPackageDrafts and lands ACTIVE in the second namespace #8908

Description

@hotlong

Measured while verifying #8858's premise, at origin/main 8664a2c99. Filed rather than fixed: the fix is a producer-side decision, not a line inside #8858's declared region.

What was measured

promoteDraftForPublish folds the stored draft's type through the manifest map (PLURAL_TO_SINGULAR[request.type] ?? request.type, protocol.ts:13198) — the map #7894 moved the /meta URL boundary OFF, because four registry types are legitimately absent from it (field, seed, external_catalog, translation; they are not stack collections). The batch route is not a URL boundary: its input is sys_metadata rows at rest, so #7894's fix never reaches it.

Two consequences, measured with the real repository and the real protocol over a stub engine:

  1. A manifest-ABSENT plural row at rest promotes successfully. With one draft row at rest carrying type = 'fields' (residue minted through the pre-meta-plural-url-bypass: PUT /meta/fields/<name> walks around the whole two-tier registry gate — 4 registry types have no entry in PLURAL_TO_SINGULAR #7894 plural URL door, which nothing rewrites on upgrade):
publishPackageDrafts({ packageId: 'app.demo' })
  → { success: true, publishedCount: 1, published: [{ type: 'fields', name: 'legacy_field' }] }
active row: { type: 'fields', name: 'legacy_field', package_id: 'app.demo' }
audit row:  { type: 'fields', name: 'legacy_field', outcome: 'allowed', code: 'ok' }

Identical result for translations. The fold is a no-op for these four, so the lookup key equals the stored spelling and the row is found.

  1. Contrast the manifest-PRESENT class (objects): the fold turns it into object, whereFor emits that spelling with no at-rest fallback, the promote raises NO_DRAFT and the all-or-nothing batch aborts — already pinned in protocol.publish-side-effects-canonical-type.test.ts and re-measured here. So the four manifest-absent types are exactly the class this route still lets through.

Downstream of the promote, runPublishSideEffects receives singularType = 'fields', so the registry write-through registers the item under the plural key as well — the #4432 shadowing shape, minted at publish time instead of at the URL. The ADR-0010 audit row records 'fields' too: recordMetadataAudit re-folds through PLURAL_TO_SINGULAR (protocol.ts:10577), which covers manifest-present plurals and misses these four — a fact rollbackMetaItem's own header already states.

Why it is a defect

Suggested disposition — a decision, not a one-line fold

Folding at the producer through the URL/registry map (canonicalMetaUrlType) would make these four behave like objects — i.e. the row becomes unpromotable (NO_DRAFT) and the whole batch aborts on it. That is fail-closed but it strands the residue with an unhelpful verdict. So the real question is what a legacy second-namespace row at rest should DO on publish:

  • (a) refuse loudly at the pre-flight, naming the row and pointing at the stored-migration path (fail-closed, actionable, batch-atomic — the shape the namespace-prefix gate already uses); or
  • (b) migrate it (_migrate-stored / a boot reconciliation step) so the residue is converted once rather than refused forever.

Both are producer-side and neither is a rider on another card.

Note for triage: #8858 proposes a related-looking one-line change to this method's ADR-0010 audit row (p.d.typep.singularType). That change is a provable no-op — see the dev report on #8858 — and it does not touch this class, because p.singularType is folded through the same manifest map that misses these four. If #8858 is re-scoped toward the producer rather than closed as not-a-defect, THIS issue is the shape that re-scope should take: merge them rather than running both.

Related: #7894 (the URL door, one layer above), #8868 (diffMetaItem — same manifest-absent class, different verb), #8858 (the measurement that surfaced this).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions