feat(delivery): expose p0 cicd baseline readiness
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
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:
@@ -358,6 +358,9 @@ from src.services.offsite_escrow_readiness_status import (
|
||||
from src.services.package_supply_chain_inventory import (
|
||||
load_latest_package_supply_chain_inventory,
|
||||
)
|
||||
from src.services.p0_cicd_baseline_source_readiness import (
|
||||
load_latest_p0_cicd_baseline_source_readiness,
|
||||
)
|
||||
from src.services.product_code_review_gate import (
|
||||
load_latest_product_code_review_gate,
|
||||
)
|
||||
@@ -964,6 +967,37 @@ async def get_delivery_closure_workbench() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/p0-cicd-baseline-source-readiness",
|
||||
response_model=dict[str, Any],
|
||||
summary="取得 P0-004 CI/CD baseline source readiness",
|
||||
description=(
|
||||
"讀取已提交的 P0-004 dev/prod CI/CD baseline source readiness;"
|
||||
"此端點只檢查 baseline / warning step / apply gate 的 committed source 是否存在。"
|
||||
"它不修改 `.gitea/workflows`、不觸發 workflow、不建立 repo、不同步 refs、"
|
||||
"不呼叫 GitHub、不讀 secret、不操作 host / K8s。"
|
||||
),
|
||||
)
|
||||
async def get_p0_cicd_baseline_source_readiness() -> dict[str, Any]:
|
||||
"""回傳 P0-004 CI/CD baseline source readiness 只讀快照。"""
|
||||
try:
|
||||
payload = await asyncio.to_thread(
|
||||
load_latest_p0_cicd_baseline_source_readiness
|
||||
)
|
||||
return redact_public_lan_topology(payload)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error("p0_cicd_baseline_source_readiness_invalid", error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="P0-004 CI/CD baseline source readiness 快照無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/github-target-private-backup-evidence-gate",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -21,6 +21,9 @@ from src.services.backup_dr_readiness_matrix import (
|
||||
from src.services.gitea_workflow_runner_health import (
|
||||
load_latest_gitea_workflow_runner_health,
|
||||
)
|
||||
from src.services.p0_cicd_baseline_source_readiness import (
|
||||
load_latest_p0_cicd_baseline_source_readiness,
|
||||
)
|
||||
from src.services.runtime_surface_inventory import (
|
||||
load_latest_runtime_surface_inventory,
|
||||
)
|
||||
@@ -33,6 +36,7 @@ def load_delivery_closure_workbench() -> dict[str, Any]:
|
||||
status_cleanup = load_latest_awoooi_status_cleanup_dashboard()
|
||||
production_deploy = load_latest_awoooi_production_deploy_readback_blocker()
|
||||
github = _load_github_private_backup_evidence_gate()
|
||||
cicd_baseline = load_latest_p0_cicd_baseline_source_readiness()
|
||||
gitea = load_latest_gitea_workflow_runner_health()
|
||||
runtime = load_latest_runtime_surface_inventory()
|
||||
backup = load_latest_backup_dr_readiness_matrix()
|
||||
@@ -40,6 +44,7 @@ def load_delivery_closure_workbench() -> dict[str, Any]:
|
||||
status_cleanup=status_cleanup,
|
||||
production_deploy=production_deploy,
|
||||
github=github,
|
||||
cicd_baseline=cicd_baseline,
|
||||
gitea=gitea,
|
||||
runtime=runtime,
|
||||
backup=backup,
|
||||
@@ -51,6 +56,7 @@ def build_delivery_closure_workbench(
|
||||
status_cleanup: dict[str, Any],
|
||||
production_deploy: dict[str, Any],
|
||||
github: dict[str, Any],
|
||||
cicd_baseline: dict[str, Any],
|
||||
gitea: dict[str, Any],
|
||||
runtime: dict[str, Any],
|
||||
backup: dict[str, Any],
|
||||
@@ -65,6 +71,8 @@ def build_delivery_closure_workbench(
|
||||
github_preflight.get("internal_governance_writeback")
|
||||
or github.get("internal_governance_writeback")
|
||||
)
|
||||
cicd_baseline_readback = _dict(cicd_baseline.get("readback"))
|
||||
cicd_baseline_rollups = _dict(cicd_baseline.get("rollups"))
|
||||
production_deploy_readback = _dict(production_deploy.get("readback"))
|
||||
production_deploy_rollups = _dict(production_deploy.get("rollups"))
|
||||
gitea_status = _dict(gitea.get("program_status"))
|
||||
@@ -410,6 +418,51 @@ def build_delivery_closure_workbench(
|
||||
or _first_target_action(github.get("targets"))
|
||||
),
|
||||
},
|
||||
{
|
||||
"id": "cicd_baseline",
|
||||
"source_id": "p0_cicd_baseline_source_readiness",
|
||||
"completion_percent": _percent(
|
||||
cicd_baseline_rollups.get("source_readiness_percent")
|
||||
),
|
||||
"status": str(cicd_baseline.get("status") or "unknown"),
|
||||
"blocker_count": _int(
|
||||
cicd_baseline_rollups.get("missing_required_source_count")
|
||||
),
|
||||
"metric": {
|
||||
"kind": "source_readiness",
|
||||
"workplan_id": str(
|
||||
cicd_baseline_readback.get("workplan_id") or "P0-004"
|
||||
),
|
||||
"required_source_count": _int(
|
||||
cicd_baseline_rollups.get("required_source_count")
|
||||
),
|
||||
"present_required_source_count": _int(
|
||||
cicd_baseline_rollups.get("present_required_source_count")
|
||||
),
|
||||
"missing_required_source_count": _int(
|
||||
cicd_baseline_rollups.get("missing_required_source_count")
|
||||
),
|
||||
"source_readiness_percent": _int(
|
||||
cicd_baseline_rollups.get("source_readiness_percent")
|
||||
),
|
||||
"blocked_source_ids": _strings(
|
||||
cicd_baseline_rollups.get("blocked_source_ids")
|
||||
),
|
||||
"workflow_modification_allowed": _dict(
|
||||
cicd_baseline.get("operation_boundaries")
|
||||
).get("workflow_modification_allowed")
|
||||
is True,
|
||||
"workflow_trigger_allowed": _dict(
|
||||
cicd_baseline.get("operation_boundaries")
|
||||
).get("workflow_trigger_allowed")
|
||||
is True,
|
||||
"safe_next_step": str(
|
||||
cicd_baseline_readback.get("safe_next_step") or ""
|
||||
),
|
||||
},
|
||||
"href": "/deployments",
|
||||
"next_action": _first_string(cicd_baseline.get("next_actions")),
|
||||
},
|
||||
{
|
||||
"id": "gitea",
|
||||
"source_id": "gitea_ci_cd",
|
||||
@@ -521,6 +574,7 @@ def build_delivery_closure_workbench(
|
||||
_source_status("status_cleanup", status_cleanup),
|
||||
_source_status("production_deploy_readback", production_deploy),
|
||||
_source_status("github_private_backup", github),
|
||||
_source_status("p0_cicd_baseline_source_readiness", cicd_baseline),
|
||||
_source_status("gitea_ci_cd", gitea),
|
||||
_source_status("runtime_surface", runtime),
|
||||
_source_status("backup_dr", backup),
|
||||
@@ -569,6 +623,28 @@ def build_delivery_closure_workbench(
|
||||
"workflow_trigger_authorized"
|
||||
)
|
||||
is True,
|
||||
"p0_cicd_baseline_status": str(cicd_baseline.get("status") or ""),
|
||||
"p0_cicd_baseline_workplan_id": str(
|
||||
cicd_baseline_readback.get("workplan_id") or ""
|
||||
),
|
||||
"p0_cicd_baseline_source_readiness_percent": _int(
|
||||
cicd_baseline_rollups.get("source_readiness_percent")
|
||||
),
|
||||
"p0_cicd_baseline_required_source_count": _int(
|
||||
cicd_baseline_rollups.get("required_source_count")
|
||||
),
|
||||
"p0_cicd_baseline_present_required_source_count": _int(
|
||||
cicd_baseline_rollups.get("present_required_source_count")
|
||||
),
|
||||
"p0_cicd_baseline_missing_required_source_count": _int(
|
||||
cicd_baseline_rollups.get("missing_required_source_count")
|
||||
),
|
||||
"p0_cicd_baseline_blocked_source_ids": _strings(
|
||||
cicd_baseline_rollups.get("blocked_source_ids")
|
||||
),
|
||||
"p0_cicd_baseline_safe_next_step": str(
|
||||
cicd_baseline_readback.get("safe_next_step") or ""
|
||||
),
|
||||
"production_deploy_status": str(production_deploy.get("status") or ""),
|
||||
"production_deploy_source_control_main_ready": production_deploy_rollups.get(
|
||||
"source_control_main_ready"
|
||||
|
||||
133
apps/api/src/services/p0_cicd_baseline_source_readiness.py
Normal file
133
apps/api/src/services/p0_cicd_baseline_source_readiness.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""P0-004 CI/CD baseline source readiness.
|
||||
|
||||
Loads the committed source-readiness snapshot for the dev/prod CI/CD baseline.
|
||||
This is read-only: it checks committed source paths and does not modify
|
||||
workflows, trigger Gitea, create repos, sync refs, read secrets, or touch hosts.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from src.services.snapshot_paths import default_operations_dir, resolve_repo_root
|
||||
|
||||
_DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__))
|
||||
_SNAPSHOT_FILE = "p0-cicd-baseline-source-readiness.snapshot.json"
|
||||
_SCHEMA_VERSION = "p0_cicd_baseline_source_readiness_v1"
|
||||
|
||||
|
||||
def load_latest_p0_cicd_baseline_source_readiness(
|
||||
operations_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Load the committed P0-004 CI/CD baseline source readiness snapshot."""
|
||||
directory = operations_dir or _DEFAULT_OPERATIONS_DIR
|
||||
path = directory / _SNAPSHOT_FILE
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
payload = json.load(handle)
|
||||
|
||||
if not isinstance(payload, dict):
|
||||
raise ValueError(f"{path}: expected JSON object")
|
||||
repo_root = resolve_repo_root(path)
|
||||
_enrich_source_presence(payload, repo_root)
|
||||
_require_schema(payload, str(path))
|
||||
_require_operation_boundaries(payload, str(path))
|
||||
_require_rollup_consistency(payload, str(path))
|
||||
return payload
|
||||
|
||||
|
||||
def _enrich_source_presence(payload: dict[str, Any], repo_root: Path) -> None:
|
||||
sources = _list(payload.get("required_sources"))
|
||||
required_sources = [source for source in sources if _dict(source).get("required") is True]
|
||||
present_ids: list[str] = []
|
||||
missing_ids: list[str] = []
|
||||
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()
|
||||
item["present"] = present
|
||||
if present:
|
||||
present_ids.append(str(item.get("id") or relative_path))
|
||||
else:
|
||||
missing_ids.append(str(item.get("id") or relative_path))
|
||||
|
||||
required_count = len(required_sources)
|
||||
present_count = len(present_ids)
|
||||
missing_count = len(missing_ids)
|
||||
rollups = _dict(payload.get("rollups"))
|
||||
rollups["required_source_count"] = required_count
|
||||
rollups["present_required_source_count"] = present_count
|
||||
rollups["missing_required_source_count"] = missing_count
|
||||
rollups["source_readiness_percent"] = round(
|
||||
present_count / max(required_count, 1) * 100
|
||||
)
|
||||
rollups["blocked_source_ids"] = missing_ids
|
||||
rollups["hard_blocker_count"] = len(_list(payload.get("blockers")))
|
||||
rollups["next_action_count"] = len(_list(payload.get("next_actions")))
|
||||
payload["status"] = (
|
||||
"ready_for_template_copy_apply_gate"
|
||||
if missing_count == 0
|
||||
else "blocked_required_sources_missing"
|
||||
)
|
||||
|
||||
|
||||
def _require_schema(payload: dict[str, Any], label: str) -> None:
|
||||
actual = payload.get("schema_version")
|
||||
if actual != _SCHEMA_VERSION:
|
||||
raise ValueError(
|
||||
f"{label}: expected schema_version={_SCHEMA_VERSION}, got {actual!r}"
|
||||
)
|
||||
|
||||
|
||||
def _require_operation_boundaries(payload: dict[str, Any], label: str) -> None:
|
||||
boundaries = _dict(payload.get("operation_boundaries"))
|
||||
if boundaries.get("read_only_api_allowed") is not True:
|
||||
raise ValueError(f"{label}: read_only_api_allowed must be true")
|
||||
blocked_flags = {
|
||||
"workflow_modification_allowed",
|
||||
"workflow_trigger_allowed",
|
||||
"repo_creation_allowed",
|
||||
"refs_sync_allowed",
|
||||
"github_api_allowed",
|
||||
"host_or_k8s_write_allowed",
|
||||
"secret_read_allowed",
|
||||
"raw_session_or_sqlite_read_allowed",
|
||||
}
|
||||
allowed = sorted(flag for flag in blocked_flags if boundaries.get(flag) is not False)
|
||||
if allowed:
|
||||
raise ValueError(f"{label}: operation boundaries must remain false: {allowed}")
|
||||
|
||||
|
||||
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
|
||||
sources = [
|
||||
source
|
||||
for source in _list(payload.get("required_sources"))
|
||||
if _dict(source).get("required") is True
|
||||
]
|
||||
blockers = _list(payload.get("blockers"))
|
||||
next_actions = _list(payload.get("next_actions"))
|
||||
rollups = _dict(payload.get("rollups"))
|
||||
missing_ids = [
|
||||
str(_dict(source).get("id") or _dict(source).get("path") or "")
|
||||
for source in sources
|
||||
if _dict(source).get("present") is not True
|
||||
]
|
||||
if rollups.get("required_source_count") != len(sources):
|
||||
raise ValueError(f"{label}: required_source_count must match sources")
|
||||
if rollups.get("missing_required_source_count") != len(missing_ids):
|
||||
raise ValueError(f"{label}: missing_required_source_count mismatch")
|
||||
if rollups.get("blocked_source_ids") != missing_ids:
|
||||
raise ValueError(f"{label}: blocked_source_ids must match missing sources")
|
||||
if rollups.get("hard_blocker_count") != len(blockers):
|
||||
raise ValueError(f"{label}: hard_blocker_count must match blockers")
|
||||
if rollups.get("next_action_count") != len(next_actions):
|
||||
raise ValueError(f"{label}: next_action_count must match next_actions")
|
||||
|
||||
|
||||
def _dict(value: Any) -> dict[str, Any]:
|
||||
return value if isinstance(value, dict) else {}
|
||||
|
||||
|
||||
def _list(value: Any) -> list[Any]:
|
||||
return value if isinstance(value, list) else []
|
||||
@@ -19,14 +19,29 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "delivery_closure_workbench_v1"
|
||||
assert data["summary"]["source_count"] == 6
|
||||
assert data["summary"]["loaded_source_count"] == 6
|
||||
assert data["summary"]["source_count"] == 7
|
||||
assert data["summary"]["loaded_source_count"] == 7
|
||||
assert data["summary"]["runtime_execution_authorized"] is False
|
||||
assert data["summary"]["remote_write_authorized"] is True
|
||||
assert data["summary"]["repo_creation_authorized"] is True
|
||||
assert data["summary"]["visibility_change_authorized"] is True
|
||||
assert data["summary"]["refs_sync_authorized"] is True
|
||||
assert data["summary"]["workflow_trigger_authorized"] is True
|
||||
assert data["summary"]["p0_cicd_baseline_status"] == (
|
||||
"blocked_required_sources_missing"
|
||||
)
|
||||
assert data["summary"]["p0_cicd_baseline_workplan_id"] == "P0-004"
|
||||
assert data["summary"]["p0_cicd_baseline_source_readiness_percent"] == 27
|
||||
assert data["summary"]["p0_cicd_baseline_required_source_count"] == 11
|
||||
assert data["summary"]["p0_cicd_baseline_present_required_source_count"] == 3
|
||||
assert data["summary"]["p0_cicd_baseline_missing_required_source_count"] == 8
|
||||
assert (
|
||||
"warning_step_template_copy_apply_gate_service"
|
||||
in data["summary"]["p0_cicd_baseline_blocked_source_ids"]
|
||||
)
|
||||
assert data["summary"]["p0_cicd_baseline_safe_next_step"] == (
|
||||
"restore_or_recreate_tracked_warning_step_source_before_workflow_enablement"
|
||||
)
|
||||
assert data["summary"]["production_deploy_status"] == (
|
||||
"closure_verified"
|
||||
)
|
||||
@@ -274,6 +289,7 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
sources = {source["id"]: source for source in data["source_statuses"]}
|
||||
assert sorted(lanes) == [
|
||||
"backup",
|
||||
"cicd_baseline",
|
||||
"gitea",
|
||||
"github",
|
||||
"production_deploy",
|
||||
@@ -498,6 +514,23 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
== 0
|
||||
)
|
||||
assert lanes["github"]["metric"]["kind"] == "private_backup_verified"
|
||||
assert lanes["cicd_baseline"]["metric"]["kind"] == "source_readiness"
|
||||
assert lanes["cicd_baseline"]["status"] == "blocked_required_sources_missing"
|
||||
assert lanes["cicd_baseline"]["blocker_count"] == 8
|
||||
assert lanes["cicd_baseline"]["completion_percent"] == 27
|
||||
assert lanes["cicd_baseline"]["metric"]["workplan_id"] == "P0-004"
|
||||
assert lanes["cicd_baseline"]["metric"]["required_source_count"] == 11
|
||||
assert lanes["cicd_baseline"]["metric"]["present_required_source_count"] == 3
|
||||
assert lanes["cicd_baseline"]["metric"]["missing_required_source_count"] == 8
|
||||
assert (
|
||||
"warning_step_template_copy_apply_gate_service"
|
||||
in lanes["cicd_baseline"]["metric"]["blocked_source_ids"]
|
||||
)
|
||||
assert (
|
||||
lanes["cicd_baseline"]["metric"]["workflow_modification_allowed"]
|
||||
is False
|
||||
)
|
||||
assert lanes["cicd_baseline"]["metric"]["workflow_trigger_allowed"] is False
|
||||
assert lanes["gitea"]["metric"]["kind"] == "workflow_count"
|
||||
assert lanes["runtime"]["metric"]["kind"] == "surface_count"
|
||||
assert lanes["backup"]["metric"]["kind"] == "readiness_row_count"
|
||||
@@ -533,6 +566,7 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
assert lanes["backup"]["metric"]["credential_marker_write_authorized_count"] == 0
|
||||
assert lanes["backup"]["metric"]["credential_escrow_forbidden_true_field_count"] == 0
|
||||
assert sources["github_private_backup"]["loaded"] is True
|
||||
assert sources["p0_cicd_baseline_source_readiness"]["loaded"] is True
|
||||
assert sources["production_deploy_readback"]["loaded"] is True
|
||||
assert (
|
||||
sources["production_deploy_readback"]["schema_version"]
|
||||
@@ -543,6 +577,14 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
sources["github_private_backup"]["schema_version"]
|
||||
== "github_target_private_backup_evidence_gate_v1"
|
||||
)
|
||||
assert (
|
||||
sources["p0_cicd_baseline_source_readiness"]["schema_version"]
|
||||
== "p0_cicd_baseline_source_readiness_v1"
|
||||
)
|
||||
assert (
|
||||
sources["p0_cicd_baseline_source_readiness"]["missing_reason"]
|
||||
== ""
|
||||
)
|
||||
assert sources["github_private_backup"]["missing_reason"] == ""
|
||||
assert lanes["github"]["blocker_count"] == 5
|
||||
assert (
|
||||
|
||||
47
apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py
Normal file
47
apps/api/tests/test_p0_cicd_baseline_source_readiness_api.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.api.v1.agents import router
|
||||
from src.services.p0_cicd_baseline_source_readiness import (
|
||||
load_latest_p0_cicd_baseline_source_readiness,
|
||||
)
|
||||
|
||||
|
||||
def test_p0_cicd_baseline_source_readiness_loader_reports_missing_sources():
|
||||
payload = load_latest_p0_cicd_baseline_source_readiness()
|
||||
|
||||
assert payload["schema_version"] == "p0_cicd_baseline_source_readiness_v1"
|
||||
assert payload["status"] == "blocked_required_sources_missing"
|
||||
assert payload["priority"] == "P0-004"
|
||||
assert payload["readback"]["workplan_id"] == "P0-004"
|
||||
assert payload["readback"]["scorecard_completion_percent"] == 40
|
||||
assert payload["rollups"]["required_source_count"] == 11
|
||||
assert payload["rollups"]["present_required_source_count"] == 3
|
||||
assert payload["rollups"]["missing_required_source_count"] == 8
|
||||
assert payload["rollups"]["source_readiness_percent"] == 27
|
||||
assert (
|
||||
"warning_step_template_copy_apply_gate_service"
|
||||
in payload["rollups"]["blocked_source_ids"]
|
||||
)
|
||||
assert payload["operation_boundaries"]["read_only_api_allowed"] is True
|
||||
assert payload["operation_boundaries"]["workflow_modification_allowed"] is False
|
||||
assert payload["operation_boundaries"]["workflow_trigger_allowed"] is False
|
||||
assert payload["operation_boundaries"]["github_api_allowed"] is False
|
||||
assert payload["operation_boundaries"]["secret_read_allowed"] is False
|
||||
|
||||
|
||||
def test_p0_cicd_baseline_source_readiness_endpoint_returns_snapshot():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.get("/api/v1/agents/p0-cicd-baseline-source-readiness")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "p0_cicd_baseline_source_readiness_v1"
|
||||
assert data["status"] == "blocked_required_sources_missing"
|
||||
assert data["rollups"]["missing_required_source_count"] == 8
|
||||
assert data["operation_boundaries"]["workflow_modification_allowed"] is False
|
||||
@@ -1,3 +1,19 @@
|
||||
## 2026-06-29 — 12:06 P0-004 CI/CD baseline source readiness readback
|
||||
|
||||
**完成內容**:
|
||||
- 新增 `docs/operations/p0-cicd-baseline-source-readiness.snapshot.json`,把 P0-004 dev/prod CI/CD baseline 的 tracked source presence 轉成機器可讀 readback。
|
||||
- 新增 `p0_cicd_baseline_source_readiness` loader 與 `/api/v1/agents/p0-cicd-baseline-source-readiness` 只讀 API。
|
||||
- Delivery Workbench 新增 `cicd_baseline` lane / source,直接呈現 `status=blocked_required_sources_missing`、`required_source_count=11`、`present_required_source_count=3`、`missing_required_source_count=8`、`source_readiness_percent=27`。
|
||||
- 此步只補 P0-004 source readiness guard;不啟用 `.gitea/workflows`、不 workflow_dispatch、不建立 repo、不 sync refs、不使用 GitHub。
|
||||
|
||||
**驗證結果**:
|
||||
- `python3.11 -m json.tool docs/operations/p0-cicd-baseline-source-readiness.snapshot.json`:通過。
|
||||
- `python3.11 -m py_compile apps/api/src/services/p0_cicd_baseline_source_readiness.py apps/api/src/services/delivery_closure_workbench.py apps/api/src/api/v1/agents.py`:通過。
|
||||
- `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`:`4 passed`。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**邊界**:未讀 token / `.runner` 內容 / cookie / session / secret / auth / `.env`;未使用 GitHub;未操作 host / Docker / K8s;未修改 workflow;未 force push。
|
||||
|
||||
## 2026-06-29 — 11:35 non-110 CD closure production readback verified
|
||||
|
||||
**完成內容**:
|
||||
|
||||
123
docs/operations/p0-cicd-baseline-source-readiness.snapshot.json
Normal file
123
docs/operations/p0-cicd-baseline-source-readiness.snapshot.json
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"schema_version": "p0_cicd_baseline_source_readiness_v1",
|
||||
"generated_at": "2026-06-29T12:06:00+08:00",
|
||||
"status": "blocked_required_sources_missing",
|
||||
"priority": "P0-004",
|
||||
"scope": "dev_prod_cicd_baseline",
|
||||
"readback": {
|
||||
"workplan_id": "P0-004",
|
||||
"workplan_title": "補 dev / prod CI/CD baseline",
|
||||
"scorecard_completion_percent": 40,
|
||||
"baseline_matrix_present": true,
|
||||
"dev_missing_count": 10,
|
||||
"prod_cicd_gap_count": 10,
|
||||
"github_mirror_status": "removed_deleted_do_not_use",
|
||||
"safe_next_step": "restore_or_recreate_tracked_warning_step_source_before_workflow_enablement"
|
||||
},
|
||||
"required_sources": [
|
||||
{
|
||||
"id": "blocked_products_owner_response_preflight_snapshot",
|
||||
"kind": "committed_snapshot",
|
||||
"path": "docs/operations/codex-gitea-blocked-products-owner-response-intake-preflight.snapshot.json",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "blocked_products_owner_response_acceptance_snapshot",
|
||||
"kind": "committed_snapshot",
|
||||
"path": "docs/operations/codex-gitea-blocked-products-owner-response-acceptance.snapshot.json",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "blocked_products_owner_response_templates_snapshot",
|
||||
"kind": "committed_snapshot",
|
||||
"path": "docs/operations/codex-gitea-blocked-products-owner-response-templates.snapshot.json",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "warning_step_owner_package_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_gitea_onboarding_warning_step_owner_package.py",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "warning_step_owner_response_preflight_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_gitea_onboarding_warning_step_owner_response_preflight.py",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "warning_step_template_copy_execution_plan_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_gitea_onboarding_warning_step_template_copy_execution_plan.py",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "warning_step_template_copy_apply_gate_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_gitea_onboarding_warning_step_template_copy_apply_gate.py",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "warning_step_dashboard_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_gitea_onboarding_warning_step_dashboard.py",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "product_onboarding_guard_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_product_onboarding_guard.py",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "onboarding_reminder_contract_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_onboarding_reminder_contract.py",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"id": "new_product_onboarding_page_model_service",
|
||||
"kind": "api_service_source",
|
||||
"path": "apps/api/src/services/awoooi_new_product_onboarding_page_model.py",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"blockers": [
|
||||
"tracked_warning_step_source_files_missing",
|
||||
"workflow_enablement_blocked_until_source_readiness_green"
|
||||
],
|
||||
"next_actions": [
|
||||
"restore_or_recreate_warning_step_owner_package_preflight_plan_apply_gate_dashboard_sources",
|
||||
"add_focused_tests_for_recreated_sources_before_any_workflow_copy",
|
||||
"keep_workflow_modification_allowed_false_until_source_readiness_green"
|
||||
],
|
||||
"rollups": {
|
||||
"required_source_count": 11,
|
||||
"present_required_source_count": 3,
|
||||
"missing_required_source_count": 8,
|
||||
"source_readiness_percent": 27,
|
||||
"blocked_source_ids": [
|
||||
"warning_step_owner_package_service",
|
||||
"warning_step_owner_response_preflight_service",
|
||||
"warning_step_template_copy_execution_plan_service",
|
||||
"warning_step_template_copy_apply_gate_service",
|
||||
"warning_step_dashboard_service",
|
||||
"product_onboarding_guard_service",
|
||||
"onboarding_reminder_contract_service",
|
||||
"new_product_onboarding_page_model_service"
|
||||
],
|
||||
"hard_blocker_count": 2,
|
||||
"next_action_count": 3
|
||||
},
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": true,
|
||||
"workflow_modification_allowed": false,
|
||||
"workflow_trigger_allowed": false,
|
||||
"repo_creation_allowed": false,
|
||||
"refs_sync_allowed": false,
|
||||
"github_api_allowed": false,
|
||||
"host_or_k8s_write_allowed": false,
|
||||
"secret_read_allowed": false,
|
||||
"raw_session_or_sqlite_read_allowed": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user