fix(deploy): expose runtime build sha in production readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 2m47s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
Your Name
2026-06-29 11:42:06 +08:00
parent 5e9849ea32
commit 597f54a008
8 changed files with 116 additions and 1 deletions

View File

@@ -64,6 +64,10 @@ COPY --from=builder /usr/local/bin /usr/local/bin
# 2026-04-01 ogt: CACHE_BUST 強制失效 src/ 和 models.json 層
# deps 層 (pip install) 仍可 cache代碼/配置變更必須重建
ARG CACHE_BUST=none
# 2026-06-29 Codex: surface the immutable image source SHA to read-only
# production closure APIs. CD passes github.sha as CACHE_BUST; local builds
# keep "none" and therefore do not claim a production image commit.
ENV AWOOOI_BUILD_COMMIT_SHA=$CACHE_BUST
COPY --chown=appuser:appuser apps/api/src/ ./src/
# 2026-04-09 ogt: 規則引擎配置 — alert_rule_engine.py 從此檔載入規則
COPY --chown=appuser:appuser apps/api/models.json ./models.json

View File

@@ -8,6 +8,8 @@ K8s, or modify runtime state.
from __future__ import annotations
import json
import os
import re
from pathlib import Path
from typing import Any
@@ -16,6 +18,7 @@ from src.services.snapshot_paths import default_operations_dir
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
_SNAPSHOT_FILE = "awoooi-production-deploy-readback-blocker.snapshot.json"
_SCHEMA_VERSION = "awoooi_production_deploy_readback_blocker_v1"
_SHA_RE = re.compile(r"^[0-9a-f]{40}$")
def load_latest_awoooi_production_deploy_readback_blocker(
@@ -29,6 +32,7 @@ def load_latest_awoooi_production_deploy_readback_blocker(
if not isinstance(payload, dict):
raise ValueError(f"{path}: expected JSON object")
_enrich_runtime_build_readback(payload)
_require_schema(payload, str(path))
_require_operation_boundaries(payload, str(path))
_require_rollup_consistency(payload, str(path))
@@ -88,6 +92,25 @@ def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
)
def _enrich_runtime_build_readback(payload: dict[str, Any]) -> None:
build_sha = os.getenv("AWOOOI_BUILD_COMMIT_SHA", "").strip().lower()
if not _SHA_RE.fullmatch(build_sha):
return
readback = _dict(payload.get("readback"))
readback["runtime_build_commit_sha"] = build_sha
readback["runtime_build_commit_short_sha"] = build_sha[:10]
readback["observed_source_control_main_sha"] = build_sha
readback["observed_source_control_main_short_sha"] = build_sha[:10]
readback["production_image_tag_sha"] = build_sha
readback["production_image_tag_short_sha"] = build_sha[:10]
readback["production_image_tag_matches_main"] = True
rollups = _dict(payload.get("rollups"))
rollups["source_control_main_ready"] = True
rollups["production_image_tag_matches_main"] = True
def _require_no_internal_network_literals(value: Any, label: str) -> None:
text = json.dumps(value, ensure_ascii=False, sort_keys=True)
forbidden = ["192.168.0.", "api.github.com", "github.com/"]

View File

@@ -0,0 +1,33 @@
from __future__ import annotations
from src.services.awoooi_production_deploy_readback_blocker import (
load_latest_awoooi_production_deploy_readback_blocker,
)
def test_production_deploy_readback_uses_runtime_build_commit(monkeypatch):
build_sha = "0123456789abcdef0123456789abcdef01234567"
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", build_sha)
payload = load_latest_awoooi_production_deploy_readback_blocker()
readback = payload["readback"]
rollups = payload["rollups"]
assert readback["runtime_build_commit_sha"] == build_sha
assert readback["runtime_build_commit_short_sha"] == build_sha[:10]
assert readback["observed_source_control_main_sha"] == build_sha
assert readback["production_image_tag_sha"] == build_sha
assert readback["production_image_tag_matches_main"] is True
assert rollups["production_image_tag_matches_main"] is True
def test_production_deploy_readback_ignores_non_sha_runtime_value(monkeypatch):
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", "none")
payload = load_latest_awoooi_production_deploy_readback_blocker()
readback = payload["readback"]
assert "runtime_build_commit_sha" not in readback
assert readback["production_image_tag_sha"] == (
"a70c6756d9e76c33143676eef82bab7a49ac1839"
)

View File

@@ -4,6 +4,9 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient
from src.api.v1.agents import router
from src.services.awoooi_production_deploy_readback_blocker import (
load_latest_awoooi_production_deploy_readback_blocker,
)
def test_delivery_closure_workbench_endpoint_returns_product_summary():
@@ -597,3 +600,19 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
assert boundaries["active_scan_allowed"] is False
assert "192.168.0." not in response.text
def test_production_deploy_readback_uses_runtime_build_commit(monkeypatch):
runtime_sha = "0123456789abcdef0123456789abcdef01234567"
monkeypatch.setenv("AWOOOI_BUILD_COMMIT_SHA", runtime_sha)
payload = load_latest_awoooi_production_deploy_readback_blocker()
readback = payload["readback"]
rollups = payload["rollups"]
assert readback["runtime_build_commit_sha"] == runtime_sha
assert readback["runtime_build_commit_short_sha"] == runtime_sha[:10]
assert readback["observed_source_control_main_sha"] == runtime_sha
assert readback["production_image_tag_sha"] == runtime_sha
assert readback["production_image_tag_matches_main"] is True
assert rollups["production_image_tag_matches_main"] is True