Skip to content

fix(docker): source-attribution drift gate runs after generate-inventory-facts and build-handlers write untracked .js into scanned roots — every self-host docker build fails with ~472 stale-manifest errors #7435

Description

@MattJvanB

Variant

worldmonitor.app (Full / Geopolitical)

Affected area

Other

Bug description

Summary

The app Dockerfile builder stage runs, in order: npm ciCOPY . .node scripts/generate-inventory-facts.mjsnode docker/build-handlers.mjsnpm run build:pronpm run build:crawlable-corpus && npm run build:sitemap && npx tsc && npx vite build.

build:crawlable-corpus invokes the source-attribution drift gate (validateManifest in scripts/source-attribution.mjs), which scans SOURCE_ROOTS = ['scripts', 'server', 'api', 'src'] for files with extensions in SOURCE_EXTENSIONS (including .js) and requires every observed host's scan-derived kinds/references to deep-equal the committed manifest (shared/source-attribution-manifest.json).

By the time the gate runs, two earlier Dockerfile steps have written untracked .js into those same scanned roots: generate-inventory-facts.mjs (e.g. api/_inventory-facts.generated.js) and build-handlers.mjs (~130 compiled handler files in api/, each embedding the source URLs of the TypeScript it was compiled from). The scan sees hundreds of files the manifest was never generated against, nearly every entry drifts, and the build dies:

Error: source-attribution: invalid manifest (stale manifest entry for 24.hu: kind, references no longer match the source tree; run node scripts/source-attribution.mjs --write; ...)

~472 entries on our install, reproduced at both 2b1f9553 (2026-08-28) and d1b9d117 (2026-08-29 tip). The remedy in the error message (--write) is wrong here: regenerating against a build-polluted tree would commit the pollution.

Evidence the gate itself is sound

Running the gate against the pristine checkout inside the same container image passes cleanly:

docker run --rm -v <repo>:/x:ro -w /x --entrypoint node worldmonitor:latest scripts/source-attribution.mjs --check
# → source-attribution: 753 active hosts across 744 providers (...), exit 0

The failure is purely ordering: pristine tree passes, post-generator tree fails.

Reproduction

  1. Clean clone of main (verified at 2b1f9553 and d1b9d117).
  2. docker compose build worldmonitor.
  3. Builder step npm run build:crawlable-corpus ... exits 1 with hundreds of stale manifest entry errors.

Expected

The repo's own Dockerfile should be able to build the repo's own image. The attribution gate should either run against the pristine tree (before any generator step), or exclude generated artifacts from the scan (e.g. skip *.generated.* and build-handlers outputs), or both.

Suggested fix

Move npm run build:crawlable-corpus && npm run build:sitemap to immediately after COPY . . (before generate-inventory-facts and build-handlers), or teach walkSourceFiles to consult .gitignore/a generated-file denylist so the gate only ever sees tracked sources.

Workaround (in production on our install since 2026-08-29)

Dockerfile reordered exactly as above (corpus + sitemap run right after COPY . .; npx tsc && npx vite build stay in their original position). Full image build completes; gate passes at its new position.

Environment

Steps to reproduce

  1. Clean clone of main (reproduced at 2b1f9553, 2026-08-28, and at d1b9d117, 2026-08-29 tip)
  2. docker compose build worldmonitor per the self-hosting docs
  3. Builder reaches the step RUN npm run build:crawlable-corpus && npm run build:sitemap && npx tsc && npx vite build
  4. Build exits 1: Error: source-attribution: invalid manifest (stale manifest entry for 24.hu: kind, references no longer match the source tree; run node scripts/source-attribution.mjs --write; ...) — repeated for ~472 hosts
  5. Control experiment: docker run --rm -v <repo>:/x:ro -w /x --entrypoint node worldmonitor:latest scripts/source-attribution.mjs --check against the pristine checkout passes (753 active hosts, exit 0), confirming the gate fails only after generate-inventory-facts.mjs and build-handlers.mjs have written untracked .js into the scanned roots

Expected behavior

The repo's own Dockerfile should be able to build the repo's own image from a clean checkout. The source-attribution drift gate should either run against the pristine tree (before any generator step writes into the scanned roots), or exclude generated artifacts from the scan (e.g. skip *.generated.* and the build-handlers outputs), or both. An operator following the self-hosting docs should get a completed image, not ~472 stale-manifest errors pointing them at --write (which would regenerate the manifest against a build-polluted tree and commit the pollution).

Screenshots / Console errors

No response

Browser & OS

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions