* fix(relay): COPY _seed-envelope-source + _seed-contract into Dockerfile.relay
Root cause of chokepointFlows STALE_SEED (1911min stale, maxStaleMin=720):
since 2026-04-14 (PR #3097/#3101 landing), scripts/_seed-utils.mjs imports
_seed-envelope-source.mjs and _seed-contract.mjs. Dockerfile.relay COPY'd
_seed-utils.mjs but NOT its new transitive dependencies, so every execFile
invocation of seed-chokepoint-flows.mjs, seed-climate-news.mjs, and
seed-ember-electricity.mjs crashed at import with ERR_MODULE_NOT_FOUND.
The ais-relay loop kept firing every 6h but each child died instantly —
no visible error because execFile only surfaces child stderr to the
parent relay's log stream.
Local repro: node scripts/seed-chokepoint-flows.mjs runs fine in 3.6s
and writes 7 records. Same command inside the relay container would
throw at the import line because the file doesn't exist.
Fix:
1. Add COPY scripts/_seed-envelope-source.mjs and
COPY scripts/_seed-contract.mjs to Dockerfile.relay.
2. Add a static guard test (tests/dockerfile-relay-imports.test.mjs)
that BFS's the transitive-import graph from every COPY'd entrypoint
and fails if any reached scripts/*.mjs|cjs isn't also COPY'd. This
would have caught the original regression.
Matches feedback_dockerfile_relay_explicit_copy.md — we now have a test
enforcing it.
* fix(test): scanner also covers require() and createRequire(...)(...) — greptile P2
Review finding on PR #3132: collectRelativeImports only matched ESM
import/export syntax, so require('./x.cjs') in ais-relay.cjs and
createRequire(import.meta.url)('./x.cjs') in _seed-utils.mjs were
invisible to the guard. No active bug (_proxy-utils.cjs is already
COPY'd) but a future createRequire pointing at a new uncopied helper
would slip through.
Two regexes now cover both forms:
- cjsRe: direct require('./x') — with a non-identifier lookbehind so
'thisrequire(' or 'foorequire(' can't match.
- createRequireRe: createRequire(...)('./x') chained-call — the outer
call is applied to createRequire's return value, not to a 'require('
token, so the first regex misses it on its own.
Added a unit test asserting both forms resolve on known sites
(_seed-utils.mjs and ais-relay.cjs) so the next edit to this file
can't silently drop coverage.