Problem
Re-anchoring a document changes its source locale after checking only the latest version's completeness. The published version may be older, and nothing checks it. When the published version is incomplete in the new source, public requests in that locale can render partial content with missing fields.
This does not affect ordinary changes to i18n.content.defaultLocale. Existing documents keep their source locale when the installation default changes. The problem arises only through the explicit re-anchor maintenance operation (apps/webapp/byline/scripts/re-anchor.ts → reAnchorDocument in packages/db-postgres/src/modules/storage/storage-commands.ts).
Scenario
- A document has English as its source. Its published version has complete English and partial French.
- A newer draft completes French.
- Re-anchoring to French passes, because the eligibility check reads the latest (draft) version's ledger. The operation flips
source_locale, moves the path row, and copies the draft into another draft.
- Public reads still select the older published version, which is now interpreted against a French source.
Expected consequence (traced, not yet reproduced)
- Public French request: the fallback chain is
[fr], because the requested locale equals the new floor. The resolver treats the floor as always available, so the published version renders in French with the untranslated fields missing.
- Public English request: the chain is
[en, fr]. English covers the partial French paths, so it resolves to complete English. Checking only the old source URL therefore misses the problem.
- Historical and version reads of versions created before the re-anchor are interpreted against the new source in the same way.
This is traced from the read code, not reproduced at runtime. The first task is to reproduce it.
Relationship to #102
The #102 public language rule treats the source locale as always eligible (L == D.sourceLocale), assuming the source is complete on the published version. Re-anchoring can break that assumption. #102 does not cause this problem, and its checkbox gate does not guard against it.
Proposed interim guard
Refuse re-anchoring when a published version exists that is incomplete in the proposed source, even if the latest version is complete.
- Check inside the existing re-anchor transaction, before changing the source or path.
- Keep the locale-agnostic exception (the ledger's
all marker).
- Report a distinct result (for example
skipped-published-incomplete) so the script's backlog report distinguishes it from skipped-incomplete.
- Keep
--dry-run reporting the same outcome.
This protects current public delivery without settling how historical versions should be interpreted. Re-anchoring is a Postgres-only maintenance operation outside the shared adapter contract; MySQL does not implement it.
Open design question (not required for the interim guard)
Which source should define each version's interpretation? Options recorded in the architecture doc:
- store the source locale used when each version's ledger was computed; or
- restrict re-anchoring whenever retained or published versions cannot support the change.
No schema choice is settled. Do not rewrite old ledgers as an incidental fix.
Acceptance criteria
References
Problem
Re-anchoring a document changes its source locale after checking only the latest version's completeness. The published version may be older, and nothing checks it. When the published version is incomplete in the new source, public requests in that locale can render partial content with missing fields.
This does not affect ordinary changes to
i18n.content.defaultLocale. Existing documents keep their source locale when the installation default changes. The problem arises only through the explicit re-anchor maintenance operation (apps/webapp/byline/scripts/re-anchor.ts→reAnchorDocumentinpackages/db-postgres/src/modules/storage/storage-commands.ts).Scenario
source_locale, moves the path row, and copies the draft into another draft.Expected consequence (traced, not yet reproduced)
[fr], because the requested locale equals the new floor. The resolver treats the floor as always available, so the published version renders in French with the untranslated fields missing.[en, fr]. English covers the partial French paths, so it resolves to complete English. Checking only the old source URL therefore misses the problem.This is traced from the read code, not reproduced at runtime. The first task is to reproduce it.
Relationship to #102
The #102 public language rule treats the source locale as always eligible (
L == D.sourceLocale), assuming the source is complete on the published version. Re-anchoring can break that assumption. #102 does not cause this problem, and its checkbox gate does not guard against it.Proposed interim guard
Refuse re-anchoring when a published version exists that is incomplete in the proposed source, even if the latest version is complete.
allmarker).skipped-published-incomplete) so the script's backlog report distinguishes it fromskipped-incomplete.--dry-runreporting the same outcome.This protects current public delivery without settling how historical versions should be interpreted. Re-anchoring is a Postgres-only maintenance operation outside the shared adapter contract; MySQL does not implement it.
Open design question (not required for the interim guard)
Which source should define each version's interpretation? Options recorded in the architecture doc:
No schema choice is settled. Do not rewrite old ledgers as an incidental fix.
Acceptance criteria
--dry-runreports the new refusal.docs/08-internationalization/04-administering-locales.mdanddocs/03-architecture/06-content-history-and-document-controls.mdare updated to describe the guard as shipped behaviour.References
packages/db-postgres/src/modules/storage/storage-commands.ts(reAnchorDocument, eligibility check on the latest version)packages/db-postgres/src/modules/storage/storage-queries.ts(buildLocaleChain,resolveEffectiveLocale)