You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
migrateStoredMetadata reports a row stored under a non-canonical type as canonical — the stored migration has no finish line for the second-namespace residue #8957
Measured while implementing #8908, at origin/main7b3c0335d. Filed rather than fixed: it is outside #8908's declared region (that card refuses the row at the publish pre-flight; it deliberately does not migrate it).
What was measured
migrateStoredMetadata (protocol.ts, the method behind POST /meta/_migrate-stored and os migrate meta --stored) opens each row with
— the manifest-collection map, i.e. the same lookup #7894 moved the /meta URL boundary OFF, and the same one #8908 is about. For a row stored under a spelling that map omits, the fold is a no-op, so:
singular stays 'fields' (or seeds, external_catalogs, externalCatalogs, translations, email_templates — the six spellings canonicalMetaUrlType folds and PLURAL_TO_SINGULAR does not);
convertStoredItemDetailed('fields', body) looks up ADR-0087 body conversions registered for a type named fields, finds none, and returns no notices;
changed is therefore false, and the row is recorded outcome: 'canonical'.
canonical is not itemised in the report at all — by design, because on a healthy deployment that is every row. So an operator who runs the stored migration over a database carrying this residue gets a clean report with the row counted as already correct.
Why it is a defect
The migration's contract is body canonicalization (#4327, #4454), and by that contract nothing here is broken. The defect is the verdict it publishes about a row it did not consider: canonical means "nothing to do", and there is something to do — the row is in a second namespace that no registry read and no compliance query on the canonical type can reach.
That verdict is now load-bearing in a way it was not before. #8908 makes the batch publish refuse these rows and hand the operator an actionable message. The natural next thing an operator reaches for is the stored migration — and it answers that the row is fine. The two doors disagree about the same row.
Scope note: migrateStoredMetadata rewrites BODIES; rewriting a stored type is a different operation (it changes the row's identity key, so it is a move, not an edit — new (org, type, name, package_id) key, history and audit continuity to decide, and a collision question when the canonical row already exists). That is why this is filed rather than folded into #8908.
Two shapes, for whoever triages this
Report honestly, migrate nothing. Cheapest and strictly an improvement: give the scan the canonical fold it is missing, and record a row whose stored spelling is non-canonical as skipped with the reason, so it appears in report.rows instead of vanishing into the canonical count. Keeps the method's contract exactly as it is.
Measured while implementing #8908, at
origin/main7b3c0335d. Filed rather than fixed: it is outside #8908's declared region (that card refuses the row at the publish pre-flight; it deliberately does not migrate it).What was measured
migrateStoredMetadata(protocol.ts, the method behindPOST /meta/_migrate-storedandos migrate meta --stored) opens each row with— the manifest-collection map, i.e. the same lookup #7894 moved the
/metaURL boundary OFF, and the same one #8908 is about. For a row stored under a spelling that map omits, the fold is a no-op, so:singularstays'fields'(orseeds,external_catalogs,externalCatalogs,translations,email_templates— the six spellingscanonicalMetaUrlTypefolds andPLURAL_TO_SINGULARdoes not);convertStoredItemDetailed('fields', body)looks up ADR-0087 body conversions registered for a type namedfields, finds none, and returns no notices;changedis thereforefalse, and the row is recordedoutcome: 'canonical'.canonicalis not itemised in the report at all — by design, because on a healthy deployment that is every row. So an operator who runs the stored migration over a database carrying this residue gets a clean report with the row counted as already correct.Why it is a defect
The migration's contract is body canonicalization (#4327, #4454), and by that contract nothing here is broken. The defect is the verdict it publishes about a row it did not consider:
canonicalmeans "nothing to do", and there is something to do — the row is in a second namespace that no registry read and no compliance query on the canonical type can reach.That verdict is now load-bearing in a way it was not before. #8908 makes the batch publish refuse these rows and hand the operator an actionable message. The natural next thing an operator reaches for is the stored migration — and it answers that the row is fine. The two doors disagree about the same row.
Scope note:
migrateStoredMetadatarewrites BODIES; rewriting a storedtypeis a different operation (it changes the row's identity key, so it is a move, not an edit — new(org, type, name, package_id)key, history and audit continuity to decide, and a collision question when the canonical row already exists). That is why this is filed rather than folded into #8908.Two shapes, for whoever triages this
skippedwith the reason, so it appears inreport.rowsinstead of vanishing into thecanonicalcount. Keeps the method's contract exactly as it is._migrate-storedlimb of 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's option (b), which that card's ruling explicitly did NOT adjudicate ("stays available as a follow-up needing its own appetite"). Needs the identity-move questions above answered first.Shape 1 is not a substitute for shape 2, but it is independent of it and does not presume it.
Related: #8908 (the publish-side refusal, which is what makes this visible), #7894 (the URL door), #4327 / #4454 (the stored migration's own cards).