diff --git a/scripts/orchestrator-deploy-hook.sh b/scripts/orchestrator-deploy-hook.sh index 75b855b..5d3d302 100755 --- a/scripts/orchestrator-deploy-hook.sh +++ b/scripts/orchestrator-deploy-hook.sh @@ -175,8 +175,21 @@ if [[ "${1:-}" == "--build-staging" ]]; then fi log "BUILD-STAGING: running health-check on port $TARGET_PORT (10x6s)" if health_check 10 6 "build-staging-health"; then - log "BUILD-STAGING: $TARGET_SERVICE healthy on the fresh image (exit 0)" - exit 0 + log "BUILD-STAGING: $TARGET_SERVICE healthy on the fresh image" + # AC-4 / ADR-001 step 3: validate the EXACT fresh artefact that will be + # BUILD-ONCE retagged to prod by running staging_check.py against the + # freshly recreated STAGING stand (8501, never prod 8500 - AC-9). + # --mode stub: fast, deterministic, no LLM spend (ADR). Run INSIDE the + # container so B6 reads the running instance own env (.env.staging). + log "BUILD-STAGING: running staging_check.py --mode stub against fresh 8501 (port $TARGET_PORT)" + if docker exec "$TARGET_SERVICE" \\ + python3 /repos/orchestrator/scripts/staging_check.py \\ + --base-url "http://localhost:$TARGET_PORT" --mode stub >> "$LOG" 2>&1; then + log "BUILD-STAGING: staging_check --mode stub PASS on fresh image (exit 0)" + exit 0 + fi + log "BUILD-STAGING: staging_check --mode stub FAILED on fresh image - not promoting (exit 1)" + exit 1 fi log "BUILD-STAGING: health FAILED after rebuild (exit 1)" exit 1 diff --git a/tests/test_deploy_hook_provenance.py b/tests/test_deploy_hook_provenance.py index 24416a1..b4652bb 100644 --- a/tests/test_deploy_hook_provenance.py +++ b/tests/test_deploy_hook_provenance.py @@ -95,6 +95,35 @@ def test_tc07_build_staging_recreates_and_health_checks_8501(): 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 # ---------------------------------------------------------------------------