diff --git a/apps/api/src/services/p0_cicd_baseline_source_readiness.py b/apps/api/src/services/p0_cicd_baseline_source_readiness.py index fc7f4542..45a7f67f 100644 --- a/apps/api/src/services/p0_cicd_baseline_source_readiness.py +++ b/apps/api/src/services/p0_cicd_baseline_source_readiness.py @@ -45,7 +45,7 @@ def _enrich_source_presence(payload: dict[str, Any], repo_root: Path) -> None: for source in required_sources: item = _dict(source) relative_path = str(item.get("path") or "") - present = bool(relative_path) and (repo_root / relative_path).is_file() + present = bool(relative_path) and _source_present(repo_root, relative_path) item["present"] = present if present: present_ids.append(str(item.get("id") or relative_path)) @@ -72,6 +72,18 @@ def _enrich_source_presence(payload: dict[str, Any], repo_root: Path) -> None: ) +def _source_present(repo_root: Path, relative_path: str) -> bool: + if (repo_root / relative_path).is_file(): + return True + + api_prefix = "apps/api/" + if relative_path.startswith(api_prefix): + runtime_relative_path = relative_path.removeprefix(api_prefix) + return (repo_root / runtime_relative_path).is_file() + + return False + + def _require_schema(payload: dict[str, Any], label: str) -> None: actual = payload.get("schema_version") if actual != _SCHEMA_VERSION: diff --git a/apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py b/apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py index 4000718f..97596cb1 100644 --- a/apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py +++ b/apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py @@ -1,5 +1,8 @@ from __future__ import annotations +import json +from pathlib import Path + from fastapi import FastAPI from fastapi.testclient import TestClient @@ -32,6 +35,14 @@ from src.services.p0_cicd_baseline_source_readiness import ( load_latest_p0_cicd_baseline_source_readiness, ) +_REPO_ROOT = Path(__file__).resolve().parents[3] +_SNAPSHOT_PATH = ( + _REPO_ROOT + / "docs" + / "operations" + / "p0-cicd-baseline-source-readiness.snapshot.json" +) + def test_p0_cicd_baseline_source_readiness_loader_reports_ready_sources(): payload = load_latest_p0_cicd_baseline_source_readiness() @@ -53,6 +64,34 @@ def test_p0_cicd_baseline_source_readiness_loader_reports_ready_sources(): assert payload["operation_boundaries"]["secret_read_allowed"] is False +def test_p0_cicd_baseline_source_readiness_supports_runtime_image_layout(tmp_path): + operations_dir = tmp_path / "docs" / "operations" + operations_dir.mkdir(parents=True) + payload = json.loads(_SNAPSHOT_PATH.read_text(encoding="utf-8")) + (operations_dir / _SNAPSHOT_PATH.name).write_text( + json.dumps(payload), + encoding="utf-8", + ) + + for source in payload["required_sources"]: + relative_path = source["path"] + if relative_path.startswith("apps/api/"): + runtime_path = tmp_path / relative_path.removeprefix("apps/api/") + runtime_path.parent.mkdir(parents=True, exist_ok=True) + runtime_path.write_text("# runtime image source\n", encoding="utf-8") + else: + repo_path = tmp_path / relative_path + repo_path.parent.mkdir(parents=True, exist_ok=True) + repo_path.write_text("{}", encoding="utf-8") + + data = load_latest_p0_cicd_baseline_source_readiness(operations_dir) + + assert data["status"] == "ready_for_template_copy_apply_gate" + assert data["rollups"]["present_required_source_count"] == 11 + assert data["rollups"]["missing_required_source_count"] == 0 + assert data["rollups"]["blocked_source_ids"] == [] + + def test_recreated_onboarding_sources_keep_apply_gates_closed(): response = { "accept_warning_only_scope": True, diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index f32c18f3..017da625 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,18 @@ +## 2026-06-29 — 13:22 P0-004 source readiness production layout readback 修正 + +**完成內容**: +- `cd.yaml #3879` / `d128f298` 已讀回 `Success`,deploy marker `5920c95ad chore(cd): deploy d128f29 [skip ci]` 已進 Gitea main。 +- Production health 為 `healthy`,但 `/api/v1/agents/p0-cicd-baseline-source-readiness` 仍讀回 `present=3`、`missing=8`。 +- 定位原因:production API image 使用 `/app/src/services/...` layout;P0 source readiness snapshot 記錄的是 repo layout `apps/api/src/services/...`,loader 未映射 production image path。 +- `p0_cicd_baseline_source_readiness` 新增 runtime image path fallback:`apps/api/` 不存在時,改查 production image 的 ``。 +- 測試新增 tmp-path production layout 模擬,確認 source readiness 在 runtime image layout 下仍為 `11/11`、`100%`。 + +**驗證目標**: +- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`。 +- `python3.11 -m py_compile apps/api/src/services/p0_cicd_baseline_source_readiness.py`、`git diff --check`。 + +**邊界**:只修 readback path mapping;未 workflow_dispatch;未讀 token / `.runner` / cookie / session / secret / auth / `.env`;未操作 host / Docker / K8s / DB;workflow modification / trigger gate 維持 false。 + ## 2026-06-29 — 13:05 P0-002 product.awoooi.yaml manifest standard readiness **完成內容**: