All checks were successful
CI / test (push) Successful in 16s
Per ADR-001 step 3 / AC-4: after the freshly rebuilt staging container is healthy, run staging_check.py --mode stub against the fresh 8501 stand BEFORE reporting success, so the EXACT artefact BUILD-ONCE retagged to prod is the one validated on staging. Fail-closed: staging_check rc!=0 -> exit 1 (not promoted). - Invoked inside the container (docker exec $TARGET_SERVICE) per the canonical signature in scripts/staging_check.py header, --base-url http://localhost:$TARGET_PORT. - Targets ONLY 8501 (staging), never 8500 (prod) - AC-9. - --mode stub: fast, deterministic, no LLM spend (ADR). - Static regression test test_tc07_build_staging_runs_staging_check_stub_after_health: asserts staging_check.py + --mode stub present, runs after health, before exit 0, fail-closed, and never hard-codes prod 8500.
134 lines
6.9 KiB
Python
134 lines
6.9 KiB
Python
"""ORCH-058 TC-07/08: static guarantees of the Strategy-B provenance plumbing.
|
|
|
|
These assert the *shape* of the deploy artefacts that can't be unit-tested by
|
|
running them (they shell out to docker/ssh on the host):
|
|
|
|
* TC-07 — the deploy hook fail-closes BEFORE `docker tag` when the staging
|
|
image's git-revision label != EXPECTED_REVISION (exit 1), and the
|
|
new `--build-staging` rebuild mode stamps GIT_SHA into the image.
|
|
* TC-08 — the Dockerfile declares `ARG GIT_SHA` and stamps it into the
|
|
`org.opencontainers.image.revision` OCI label (the anchor B reads).
|
|
"""
|
|
|
|
import pathlib
|
|
|
|
_ROOT = pathlib.Path(__file__).resolve().parents[1]
|
|
_HOOK = _ROOT / "scripts" / "orchestrator-deploy-hook.sh"
|
|
_DOCKERFILE = _ROOT / "Dockerfile"
|
|
|
|
|
|
def _build_staging_block() -> str:
|
|
"""Return only the body of the hook's ``--build-staging`` branch, so the
|
|
contract assertions below cannot be satisfied by lookalike strings elsewhere
|
|
in the script (e.g. the NORMAL DEPLOY recreate). The block runs from the
|
|
``--build-staging`` guard up to the NORMAL DEPLOY section header."""
|
|
text = _HOOK.read_text(encoding="utf-8")
|
|
start = text.index('"${1:-}" == "--build-staging"')
|
|
end = text.index("NORMAL DEPLOY mode", start)
|
|
return text[start:end]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TC-07: hook fail-closed provenance guard + --build-staging rebuild mode
|
|
# ---------------------------------------------------------------------------
|
|
def test_tc07_hook_has_fail_closed_provenance_guard():
|
|
text = _HOOK.read_text(encoding="utf-8")
|
|
# The label key the hook inspects must be the OCI revision label.
|
|
assert 'REVISION_LABEL="org.opencontainers.image.revision"' in text
|
|
# EXPECTED_REVISION is read (default unset -> backward compatible).
|
|
assert 'EXPECTED_REVISION="${EXPECTED_REVISION:-}"' in text
|
|
# The guard must inspect the source image's label and normalise <no value>.
|
|
assert "docker image inspect --format" in text
|
|
assert '"<no value>"' in text
|
|
# Fail-closed: empty OR mismatch -> abort with exit 1.
|
|
assert '-z "$IMG_REV" || "$IMG_REV" != "$EXPECTED_REVISION"' in text
|
|
|
|
|
|
def test_tc07_provenance_guard_precedes_docker_tag():
|
|
"""The fail-closed `exit 1` must sit BEFORE the `docker tag` retag line."""
|
|
text = _HOOK.read_text(encoding="utf-8")
|
|
guard = text.index("$EXPECTED_REVISION")
|
|
retag = text.index('docker tag "$SOURCE_IMAGE" "$TARGET_IMAGE"')
|
|
assert guard < retag, "provenance guard must run before the prod retag"
|
|
|
|
|
|
def test_tc07_build_staging_mode_stamps_git_sha():
|
|
text = _HOOK.read_text(encoding="utf-8")
|
|
# The new Strategy-A rebuild mode exists and is keyed on --build-staging.
|
|
assert '"${1:-}" == "--build-staging"' in text
|
|
# It rebuilds the staging image stamping the validated commit as a build-arg.
|
|
assert 'docker build --build-arg GIT_SHA="$GIT_SHA"' in text
|
|
|
|
|
|
def test_tc07_build_staging_builds_from_caller_context_not_repo():
|
|
"""Contract (caller <-> hook): --build-staging must build from the
|
|
caller-supplied BUILD_CONTEXT (the validated worktree), NOT the prod clone.
|
|
|
|
Regression guard for the P0 deadlock: the block must honour the caller's
|
|
GIT_SHA (BUILD_CONTEXT/GIT_SHA defaulting) and must NOT recompute the SHA
|
|
from the host clone's HEAD (`git rev-parse HEAD`) — on the
|
|
deploy-staging -> deploy edge `main` HEAD != validated SHA, which would
|
|
stamp the wrong revision label and deadlock the Strategy-B guard.
|
|
"""
|
|
block = _build_staging_block()
|
|
# Build context is the caller-supplied worktree, defaulting to $REPO.
|
|
assert 'BUILD_CONTEXT="${BUILD_CONTEXT:-$REPO}"' in block
|
|
assert 'docker build --build-arg GIT_SHA="$GIT_SHA" -t "$TARGET_IMAGE" "$BUILD_CONTEXT"' in block
|
|
# Honour the caller's GIT_SHA; never hard-build against the prod clone.
|
|
assert 'GIT_SHA="${GIT_SHA:-}"' in block
|
|
assert 'docker build --build-arg GIT_SHA="$GIT_SHA" -t "$TARGET_IMAGE" "$REPO"' not in block
|
|
# Must NOT recompute the validated SHA from the host clone's HEAD.
|
|
assert "git rev-parse HEAD" not in block
|
|
|
|
|
|
def test_tc07_build_staging_recreates_and_health_checks_8501():
|
|
"""AC-4: --build-staging must recreate the staging container on the fresh
|
|
image and validate it (health-check), so rebuild_staging_image's rc=0 truly
|
|
means "rebuilt AND healthy". A bare `docker build` + exit 0 would make the
|
|
freshness verdict a lie."""
|
|
block = _build_staging_block()
|
|
# Recreate the staging service on the freshly built image.
|
|
assert 'docker compose --profile "$COMPOSE_PROFILE" up -d --no-build "$TARGET_SERVICE"' in block
|
|
# Validate the fresh container before reporting success.
|
|
assert 'health_check 10 6 "build-staging-health"' in block
|
|
# Health failure surfaces as a non-zero exit (FAILED contract preserved).
|
|
assert "exit 1" in block
|
|
|
|
|
|
def test_tc07_build_staging_runs_staging_check_stub_after_health():
|
|
"""AC-4 / ADR-001 step 3: after the fresh staging container is healthy, the
|
|
--build-staging mode MUST run staging_check.py --mode stub against the fresh
|
|
8501 stand BEFORE reporting success, and fail-closed (exit 1) if it fails -
|
|
so the EXACT artefact promoted to prod is the one that passed staging."""
|
|
block = _build_staging_block()
|
|
# staging_check is invoked in --mode stub (fast, no LLM spend per ADR).
|
|
assert "staging_check.py" in block
|
|
assert "--mode stub" in block
|
|
# It targets the fresh STAGING stand (8501 / TARGET_PORT), never prod 8500.
|
|
assert '--base-url "http://localhost:$TARGET_PORT"' in block
|
|
# AC-9: the staging_check invocation must NOT hard-code the prod port (8500).
|
|
invocation_lines = [
|
|
ln for ln in block.splitlines()
|
|
if "staging_check.py" in ln or "--base-url" in ln
|
|
]
|
|
assert invocation_lines, "expected a staging_check.py invocation line"
|
|
assert all("8500" not in ln for ln in invocation_lines)
|
|
# Ordering: staging_check runs AFTER the health-check, BEFORE the final exit 0.
|
|
health_idx = block.index('health_check 10 6 "build-staging-health"')
|
|
check_idx = block.index("staging_check.py")
|
|
assert health_idx < check_idx, "staging_check must run after health_check"
|
|
exit0_idx = block.index("staging_check --mode stub PASS")
|
|
success_exit = block.index("exit 0", exit0_idx)
|
|
assert check_idx < success_exit, "staging_check must precede the success exit 0"
|
|
# Fail-closed: a non-zero staging_check surfaces as exit 1 (no prod promote).
|
|
assert "staging_check --mode stub FAILED" in block
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# TC-08: Dockerfile stamps the OCI revision label from a build-arg
|
|
# ---------------------------------------------------------------------------
|
|
def test_tc08_dockerfile_stamps_revision_label():
|
|
text = _DOCKERFILE.read_text(encoding="utf-8")
|
|
assert "ARG GIT_SHA" in text
|
|
assert "LABEL org.opencontainers.image.revision=$GIT_SHA" in text
|