Skip to content

ADR-1343: Statically check that every dev-container stage copies the files it reads

  • Status: Accepted
  • Date: 2026-09-26
  • Deciders: lusoris
  • Tags: ci, build, dev-container, supply-chain

Context

ADR-0819 keeps the pull-request container gate under the runner time budget by building dev/Containerfile only up to the libvmaf-build stage. The final dev-mcp stage is never built in CI. When the pre-RC1 train landed (dd51d00db), that stage started reading /build/vmaf/requirements/locks/package-build.txt for its hash-locked pip install, but no stage copied requirements/. Every check was green, and the first local dev/scripts/dev-mcp-up.sh failed with "Could not open requirements file". Since the container is canonical for published artifacts (ADR-1102), an unbuildable final stage is a release risk that the existing gates could not see.

Decision

We add scripts/ci/check-dev-container-stage-inputs.py, a static contract that needs no Docker daemon. It resolves each stage's parent lineage, WORKDIR and non---from COPY instructions, then requires that every pip requirement or constraint file a RUN reads under /build/vmaf/ is provided by a COPY into that stage or a parent stage and maps to an existing repository file, and that every non---from COPY source exists. It reuses the instruction parser of scripts/ci/check-container-image-references.py, now exposed as logical_instructions(). It runs from a pre-commit and pre-push hook, and therefore in the required Pre-Commit CI job, whenever the Containerfile, a lock file or the checker changes. ADR-0819's build target is unchanged.

Alternatives considered

Option Pros Cons Why not chosen
Static stage-input contract (chosen) Runs in seconds without Docker; catches the missing-input and missing-source defect classes in every stage, including ones CI never builds Does not execute the stage, so a failing command with present inputs still passes Covers the defect that actually escaped at negligible cost
Build the dev-mcp target in the PR-time CI job Catches every defect the build can hit Tens of runner minutes per container-touching PR on top of the libvmaf build, against the budget ADR-0819 set Cost is disproportionate to the defect class seen so far
Keep the gap deferred (T-DEV-CONTAINER-DEVMCP-STAGE-UNGATED-2026-09-26 open) No work The same defect can recur silently Rejected by the maintainer

Consequences

  • Positive: a stage that reads an uncopied lock file, or copies a missing path, now fails locally and in CI before merge. The shared parser means one implementation of Dockerfile continuation and escape handling.
  • Negative: the check models only pip requirement and constraint reads; other input kinds need an explicit extension.
  • Neutral / follow-ups: a full dev-mcp build in CI remains possible under a future ADR if a defect escapes this contract.

References

  • Popup, 2026-09-26: "CI never builds the final dev-mcp container stage (ADR-0819 stops at libvmaf-build), which is how the broken stage reached master green. How should that gap be closed?" Answer: "Cheap static check (Recommended)".
  • ADR-0819 — PR-time container gate scope.
  • ADR-1102 — the container is canonical for published artifacts.
  • ADR-1231 — base-image reference gate whose parser this check reuses.