diff --git a/Dockerfile b/Dockerfile index 35148a5..93e8c79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,14 @@ COPY alembic ./alembic COPY alembic.ini ./ COPY scripts ./scripts -RUN sed -i "s|^BAKED_COMMIT_SHA: str = \"\"|BAKED_COMMIT_SHA: str = \"${COMMIT_SHA}\"|" api/app/buildinfo.py \ +# I-12: an EMPTY COMMIT_SHA must fail the build, not sail through it. +# Previously the sed replaced "" with "" (a no-op) and the grep then matched +# that same empty string, so a build with no COMMIT_SHA passed and shipped a +# container reporting commit_sha: null — exactly the "service cannot name its +# own commit" defect this project exists to prevent. Caught 2026-08-14 when +# /version went null after a deploy. +RUN test -n "${COMMIT_SHA}" || (echo "FATAL: COMMIT_SHA build arg is empty (I-12)" && false) \ + && sed -i "s|^BAKED_COMMIT_SHA: str = \"\"|BAKED_COMMIT_SHA: str = \"${COMMIT_SHA}\"|" api/app/buildinfo.py \ && sed -i "s|^BAKED_BUILT_AT: str = \"\"|BAKED_BUILT_AT: str = \"${BUILT_AT}\"|" api/app/buildinfo.py \ && grep -q "BAKED_COMMIT_SHA: str = \"${COMMIT_SHA}\"" api/app/buildinfo.py diff --git a/api/tests/test_invariants.py b/api/tests/test_invariants.py index 984008a..ab1f6b5 100644 --- a/api/tests/test_invariants.py +++ b/api/tests/test_invariants.py @@ -694,3 +694,12 @@ async def test_security_no_bearer_is_still_401(): with _pytest.raises(RepairPointer) as exc: await get_caller(req, authorization=None, x_service_token=None) assert exc.value.status_code == 401 + + +def test_i12_build_fails_when_commit_sha_is_empty(): + """The sed+grep pair silently accepted an empty COMMIT_SHA: it replaced "" + with "" and then matched that same empty string, shipping a container that + reported commit_sha: null. That is the exact defect I-12 exists to prevent, + and it happened on 2026-08-14.""" + df = (ROOT / "Dockerfile").read_text() + assert 'test -n "${COMMIT_SHA}"' in df