Skip to content

[finding] The generated-leaf provenance companion is now RECORDED in 9 of 9 bundle sets and READ at serving time in 1 — withSourceFallback was never wired outside platform-objects #12642

Description

@os-warren

Origin: measured while implementing #12559 (the --source-hashes rollout). Filed unassigned and recording-only — this is a boundary the rollout leaves behind, not a defect in it, and the hazard is again what a reader concludes.

The measurement

PR #12557 (#11671, maintainer ruling #12069 Option A) landed the provenance companion as two halves, and its own body says so:

withSourceFallback takes that table as a fourth argument and now judges objects / metadataForms alongside the hand-authored sections, so a leaf whose source moved underneath it serves the current source string instead of a superseded draft.

It does not only record. withSourceFallback reads the records at serving time and substitutes the current source, and findStaleFills reports them.

#12559 rolled out the recording half to the eight uncovered sets. The reading half did not move, and nothing in either PR's file surface would tell a reader that. Measured on the tree at the head of claude/issue-12559-i18n-source-hash-rollout:

grep -rn "withSourceFallback" --include=*.ts packages/ | grep -v node_modules | grep -v '\.test\.'

Every call site is in @objectstack/platform-objectssrc/apps/translations/setup.translation.ts and src/metadata-translations/index.ts. The other eight sets' src/translations/index.ts assemble their TranslationBundle from the raw generated modules and never consult the companion sitting next to them:

exportconstAuditTranslations: TranslationBundle={en: {objects: enObjects,messages: enMessages},'zh-CN': {objects: zhCNObjects,messages: zhCNMessages},
...
};

So after #12559: provenance recorded 9 of 9, provenance served 1 of 9.

Why it is worth writing down

It is the same inference hazard #12559 was filed about, one layer down. After that PR the repo has --source-hashes documented in all nine extract configs, 24 committed companions, a changeset announcing the rollout, and check:i18n reporting 7 bundles per set where it reported 4. The natural reading of all of that is "a stale generated leaf now serves the current source everywhere". For eight of nine sets it records the drift and still serves the superseded draft.

The gap is not hypothetical, and it was measured on a newly covered set during that PR's ablation. Revising the source label behind objects.sys_share_link.fields.token.label in plugin-sharing (a leaf recorded in es-ES only) and regenerating from one extract run:

observationresult
findStaleFills on the companiones-ES 1 stale, zh-CN 0, ja-JP 0 — the record identifies it exactly
check:i18nOK (9 package(s) — all bundles in sync…)
check:i18n-stale-fillOK (10 bundle set(s) — no new stale fills, 0 baselined) — blind by construction, one locale, no second witness
what es-ES servesthe superseded English draft

On platform-objects that same leaf would have been substituted at serving time. In plugin-sharing the evidence exists on disk and nothing reads it.

What resolving it would look like — not chosen here

  • Wire the eight — each src/translations/index.ts passes its three companions through withSourceFallback, the shape platform-objects takes. Cheap for seven of the eight, which already depend on @objectstack/platform-objects. @objectstack/plugin-webhooks does not (@objectstack/core, @objectstack/service-messaging, @objectstack/spec), so this route adds a dependency edge there or moves withSourceFallback somewhere both can reach — which is the part that makes this a decision rather than a mechanical follow-up.
  • Move the seam — have the i18n loader apply provenance centrally, so a bundle set opts in by committing a companion rather than by wiring a call. One place instead of nine, and no new package edges.
  • Leave recording-only and say so — the records are still worth having (they are kept fresh by check:i18n and carry forward across source revisions), and a future consumer inherits nine sets of history rather than one. But then the boundary needs to be written where a reader looks, exactly as [finding] Generated-leaf staleness detection covers 1 of 9 i18n bundle sets — the other 8 stay legacy-trusted, and nothing says so at the point a reader would look #12559 argued.

Choosing among these is a per-package architecture call, so it is triage's rather than a seat's. Filed unassigned; not claimed.

Dedup

Searched 2026-08-27 across open issues for this vocabulary (withSourceFallback, source-hashes, provenance companion); the positive control fired — the query returns #12559 itself. No existing card covers the serving-side half: #11671 is the mechanism, #12069 its ruling, #12559 the recording rollout, and none of them names the eight sets' index.ts.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions