diff --git a/apps/api/src/services/delivery_closure_workbench.py b/apps/api/src/services/delivery_closure_workbench.py index 448fdeb5c..2cb7fa76b 100644 --- a/apps/api/src/services/delivery_closure_workbench.py +++ b/apps/api/src/services/delivery_closure_workbench.py @@ -875,6 +875,14 @@ def build_delivery_closure_workbench( "gitea_public_repo_count": _int( private_inventory_rollups.get("gitea_public_repo_count") ), + "gitea_ssh_verified_repo_count": _int( + private_inventory_rollups.get("gitea_ssh_verified_repo_count") + ), + "gitea_private_or_auth_only_repo_count": _int( + private_inventory_rollups.get( + "gitea_private_or_auth_only_repo_count" + ) + ), "expected_product_count": _int( private_inventory_rollups.get("expected_product_count") ), @@ -884,6 +892,62 @@ def build_delivery_closure_workbench( "missing_product_row_count": _int( private_inventory_rollups.get("missing_product_row_count") ), + "ssh_verified_product_repo_count": _int( + private_inventory_rollups.get("ssh_verified_product_repo_count") + ), + "main_branch_present_product_repo_count": _int( + private_inventory_rollups.get( + "main_branch_present_product_repo_count" + ) + ), + "dev_branch_present_product_repo_count": _int( + private_inventory_rollups.get( + "dev_branch_present_product_repo_count" + ) + ), + "dev_prod_environment_split_ready_count": _int( + private_inventory_rollups.get( + "dev_prod_environment_split_ready_count" + ) + ), + "public_visible_product_repo_count": _int( + private_inventory_rollups.get("public_visible_product_repo_count") + ), + "private_or_auth_only_product_repo_count": _int( + private_inventory_rollups.get( + "private_or_auth_only_product_repo_count" + ) + ), + "tokenless_http_404_private_or_auth_product_repo_count": _int( + private_inventory_rollups.get( + "tokenless_http_404_private_or_auth_product_repo_count" + ) + ), + "missing_product_repo_count": _int( + private_inventory_rollups.get("missing_product_repo_count") + ), + "missing_main_branch_product_repo_count": _int( + private_inventory_rollups.get( + "missing_main_branch_product_repo_count" + ) + ), + "missing_dev_branch_product_repo_count": _int( + private_inventory_rollups.get( + "missing_dev_branch_product_repo_count" + ) + ), + "all_expected_product_repos_have_ssh_refs": ( + private_inventory_rollups.get( + "all_expected_product_repos_have_ssh_refs" + ) + is True + ), + "all_expected_product_repos_have_dev_and_main": ( + private_inventory_rollups.get( + "all_expected_product_repos_have_dev_and_main" + ) + is True + ), "accepted_inventory_payload_count": _int( private_inventory_rollups.get("accepted_inventory_payload_count") ), @@ -1601,6 +1665,12 @@ def build_delivery_closure_workbench( "gitea_private_inventory_public_repo_count": _int( private_inventory_rollups.get("gitea_public_repo_count") ), + "gitea_private_inventory_ssh_verified_repo_count": _int( + private_inventory_rollups.get("gitea_ssh_verified_repo_count") + ), + "gitea_private_inventory_private_or_auth_only_repo_count": _int( + private_inventory_rollups.get("gitea_private_or_auth_only_repo_count") + ), "gitea_private_inventory_expected_product_count": _int( private_inventory_rollups.get("expected_product_count") ), @@ -1610,6 +1680,54 @@ def build_delivery_closure_workbench( "gitea_private_inventory_missing_product_row_count": _int( private_inventory_rollups.get("missing_product_row_count") ), + "gitea_private_inventory_ssh_verified_product_repo_count": _int( + private_inventory_rollups.get("ssh_verified_product_repo_count") + ), + "gitea_private_inventory_main_branch_present_product_repo_count": _int( + private_inventory_rollups.get( + "main_branch_present_product_repo_count" + ) + ), + "gitea_private_inventory_dev_branch_present_product_repo_count": _int( + private_inventory_rollups.get("dev_branch_present_product_repo_count") + ), + "gitea_private_inventory_dev_prod_environment_split_ready_count": _int( + private_inventory_rollups.get("dev_prod_environment_split_ready_count") + ), + "gitea_private_inventory_public_visible_product_repo_count": _int( + private_inventory_rollups.get("public_visible_product_repo_count") + ), + "gitea_private_inventory_private_or_auth_only_product_repo_count": _int( + private_inventory_rollups.get( + "private_or_auth_only_product_repo_count" + ) + ), + "gitea_private_inventory_tokenless_http_404_private_or_auth_product_repo_count": _int( + private_inventory_rollups.get( + "tokenless_http_404_private_or_auth_product_repo_count" + ) + ), + "gitea_private_inventory_missing_product_repo_count": _int( + private_inventory_rollups.get("missing_product_repo_count") + ), + "gitea_private_inventory_missing_main_branch_product_repo_count": _int( + private_inventory_rollups.get( + "missing_main_branch_product_repo_count" + ) + ), + "gitea_private_inventory_missing_dev_branch_product_repo_count": _int( + private_inventory_rollups.get("missing_dev_branch_product_repo_count") + ), + "gitea_private_inventory_all_expected_product_repos_have_ssh_refs": ( + private_inventory_rollups.get("all_expected_product_repos_have_ssh_refs") + is True + ), + "gitea_private_inventory_all_expected_product_repos_have_dev_and_main": ( + private_inventory_rollups.get( + "all_expected_product_repos_have_dev_and_main" + ) + is True + ), "gitea_private_inventory_accepted_payload_count": _int( private_inventory_rollups.get("accepted_inventory_payload_count") ), diff --git a/apps/api/src/services/gitea_private_inventory_p0_scorecard.py b/apps/api/src/services/gitea_private_inventory_p0_scorecard.py index 100cb65dd..4b9bb1965 100644 --- a/apps/api/src/services/gitea_private_inventory_p0_scorecard.py +++ b/apps/api/src/services/gitea_private_inventory_p0_scorecard.py @@ -62,6 +62,10 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: owner_response_validation = _dict(scorecard.get("owner_response_validation")) controlled_closeout_receipt = _dict(scorecard.get("controlled_closeout_receipt")) product_coverage = _dict(scorecard.get("product_row_coverage")) + product_environment_coverage = _dict( + scorecard.get("product_repo_environment_coverage") + ) + dev_prod_repo_readback = _dict(scorecard.get("dev_prod_repo_readback")) active_blockers = _strings(scorecard.get("active_blockers")) exit_criteria = _strings(scorecard.get("exit_criteria")) met_criteria = _met_exit_criteria(scorecard, gitea_inventory, product_coverage) @@ -94,6 +98,8 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: "safe_next_step": str(scorecard.get("safe_next_step") or ""), }, "gitea_inventory": gitea_inventory, + "dev_prod_repo_readback": dev_prod_repo_readback, + "product_repo_environment_coverage": product_environment_coverage, "authenticated_import_acceptance": import_acceptance, "authenticated_payload_validation": payload_validation, "authenticated_inventory_single_preflight_intake_ready": True, @@ -119,6 +125,12 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: gitea_inventory.get("visibility_scope") or "unknown" ), "gitea_public_repo_count": _int(gitea_inventory.get("repo_count")), + "gitea_ssh_verified_repo_count": _int( + gitea_inventory.get("ssh_verified_repo_count") + ), + "gitea_private_or_auth_only_repo_count": _int( + gitea_inventory.get("private_or_auth_only_repo_count") + ), "accepted_inventory_payload_count": _int( import_acceptance.get("accepted_payload_count") ), @@ -203,6 +215,53 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]: ) is True ), + "product_repo_environment_status": str( + product_environment_coverage.get("status") or "" + ), + "ssh_verified_product_repo_count": _int( + product_environment_coverage.get("ssh_repo_present_count") + ), + "main_branch_present_product_repo_count": _int( + product_environment_coverage.get("main_branch_present_count") + ), + "dev_branch_present_product_repo_count": _int( + product_environment_coverage.get("dev_branch_present_count") + ), + "dev_prod_environment_split_ready_count": _int( + product_environment_coverage.get("environment_split_ready_count") + ), + "public_visible_product_repo_count": _int( + product_environment_coverage.get("public_visible_count") + ), + "private_or_auth_only_product_repo_count": _int( + product_environment_coverage.get("private_or_auth_only_count") + ), + "tokenless_http_404_private_or_auth_product_repo_count": _int( + product_environment_coverage.get( + "tokenless_http_404_private_or_auth_count" + ) + ), + "missing_product_repo_count": _int( + product_environment_coverage.get("missing_repo_count") + ), + "missing_main_branch_product_repo_count": _int( + product_environment_coverage.get("missing_main_branch_count") + ), + "missing_dev_branch_product_repo_count": _int( + product_environment_coverage.get("missing_dev_branch_count") + ), + "all_expected_product_repos_have_ssh_refs": ( + product_environment_coverage.get( + "all_expected_product_repos_have_ssh_refs" + ) + is True + ), + "all_expected_product_repos_have_dev_and_main": ( + product_environment_coverage.get( + "all_expected_product_repos_have_dev_and_main" + ) + is True + ), "github_lane_excluded_from_p0_blocker_count": ( scorecard.get("github_lane_excluded_from_p0_blocker_count") is True @@ -402,6 +461,29 @@ def _require_source_scorecard(payload: dict[str, Any], label: str) -> None: f"{label}: owner response validation boundaries must remain false: " f"{owner_response_drift}" ) + dev_prod_readback = _dict(payload.get("dev_prod_repo_readback")) + if dev_prod_readback: + if dev_prod_readback.get("source_control_authority") != "gitea": + raise ValueError(f"{label}: dev/prod readback must use gitea authority") + boundaries = _dict(dev_prod_readback.get("operation_boundaries")) + if boundaries.get("ssh_refs_read_only") is not True: + raise ValueError(f"{label}: dev/prod readback must be SSH read-only") + blocked_flags = { + "token_value_collection_allowed", + "secret_value_collection_allowed", + "gitea_api_write_allowed", + "gitea_repo_creation_allowed", + "gitea_refs_sync_allowed", + "gitea_visibility_change_allowed", + "github_api_allowed", + "github_cli_allowed", + "raw_session_or_sqlite_read_allowed", + } + opened = sorted( + flag for flag in blocked_flags if boundaries.get(flag) is not False + ) + if opened: + raise ValueError(f"{label}: dev/prod readback boundaries opened: {opened}") def _require_operation_boundaries(payload: dict[str, Any], label: str) -> None: @@ -452,6 +534,19 @@ def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: is not True ): raise ValueError(f"{label}: active product owner readiness rows incomplete") + if rollups.get("ssh_verified_product_repo_count") and ( + rollups.get("ssh_verified_product_repo_count") + != rollups.get("expected_product_count") + ): + raise ValueError(f"{label}: SSH verified product repo count mismatch") + if rollups.get("missing_product_repo_count") not in {None, 0}: + raise ValueError(f"{label}: product repo readback must have zero missing repos") + if rollups.get("missing_main_branch_product_repo_count") not in {None, 0}: + raise ValueError(f"{label}: product repo readback must have zero missing main branches") + if rollups.get("missing_dev_branch_product_repo_count") not in {None, 0}: + raise ValueError(f"{label}: product repo readback must have zero missing dev branches") + if rollups.get("all_expected_product_repos_have_dev_and_main") is False: + raise ValueError(f"{label}: product repo readback must have dev and main") if ( rollups.get("next_owner_response_validation_lane_id") != "s4_9_gitea_inventory_owner_attestation_response" @@ -563,6 +658,14 @@ def _met_exit_criteria( is True ): met.append("all_active_product_repos_have_gitea_owner_readiness_row=true") + product_environment_coverage = _dict( + scorecard.get("product_repo_environment_coverage") + ) + if ( + product_environment_coverage.get("all_expected_product_repos_have_dev_and_main") + is True + ): + met.append("all_expected_product_repos_have_dev_and_main=true") return met diff --git a/apps/api/tests/test_delivery_closure_workbench_api.py b/apps/api/tests/test_delivery_closure_workbench_api.py index 3766d2620..a03acde50 100644 --- a/apps/api/tests/test_delivery_closure_workbench_api.py +++ b/apps/api/tests/test_delivery_closure_workbench_api.py @@ -102,9 +102,79 @@ def test_delivery_closure_workbench_uses_gitea_private_inventory_lane(): assert lanes["gitea_private_inventory"]["metric"]["private_inventory_source"] == "gitea" assert lanes["gitea_private_inventory"]["metric"]["gitea_repo_inventory_status"] == "ok" assert lanes["gitea_private_inventory"]["metric"]["gitea_visibility_scope"] == "admin_export" - assert lanes["gitea_private_inventory"]["metric"]["expected_product_count"] == 11 - assert lanes["gitea_private_inventory"]["metric"]["present_product_row_count"] == 11 + assert lanes["gitea_private_inventory"]["metric"]["gitea_public_repo_count"] == 6 + assert lanes["gitea_private_inventory"]["metric"]["gitea_ssh_verified_repo_count"] == 12 + assert ( + lanes["gitea_private_inventory"]["metric"][ + "gitea_private_or_auth_only_repo_count" + ] + == 6 + ) + assert lanes["gitea_private_inventory"]["metric"]["expected_product_count"] == 12 + assert lanes["gitea_private_inventory"]["metric"]["present_product_row_count"] == 12 assert lanes["gitea_private_inventory"]["metric"]["missing_product_row_count"] == 0 + assert lanes["gitea_private_inventory"]["metric"]["ssh_verified_product_repo_count"] == 12 + assert ( + lanes["gitea_private_inventory"]["metric"][ + "main_branch_present_product_repo_count" + ] + == 12 + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "dev_branch_present_product_repo_count" + ] + == 12 + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "dev_prod_environment_split_ready_count" + ] + == 12 + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "public_visible_product_repo_count" + ] + == 6 + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "private_or_auth_only_product_repo_count" + ] + == 6 + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "tokenless_http_404_private_or_auth_product_repo_count" + ] + == 6 + ) + assert lanes["gitea_private_inventory"]["metric"]["missing_product_repo_count"] == 0 + assert ( + lanes["gitea_private_inventory"]["metric"][ + "missing_main_branch_product_repo_count" + ] + == 0 + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "missing_dev_branch_product_repo_count" + ] + == 0 + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "all_expected_product_repos_have_ssh_refs" + ] + is True + ) + assert ( + lanes["gitea_private_inventory"]["metric"][ + "all_expected_product_repos_have_dev_and_main" + ] + is True + ) assert ( lanes["gitea_private_inventory"]["metric"][ "authenticated_inventory_single_preflight_intake_ready" @@ -124,7 +194,7 @@ def test_delivery_closure_workbench_uses_gitea_private_inventory_lane(): lanes["gitea_private_inventory"]["metric"][ "authenticated_inventory_payload_skeleton_repo_count_floor" ] - == 4 + == 6 ) assert ( lanes["gitea_private_inventory"]["metric"][ @@ -520,9 +590,74 @@ def _assert_delivery_workbench_shape(data: dict): assert data["summary"]["gitea_private_inventory_active_blocker_count"] == 0 assert data["summary"]["gitea_private_inventory_repo_inventory_status"] == "ok" assert data["summary"]["gitea_private_inventory_visibility_scope"] == "admin_export" - assert data["summary"]["gitea_private_inventory_expected_product_count"] == 11 - assert data["summary"]["gitea_private_inventory_present_product_row_count"] == 11 + assert data["summary"]["gitea_private_inventory_public_repo_count"] == 6 + assert data["summary"]["gitea_private_inventory_ssh_verified_repo_count"] == 12 + assert data["summary"]["gitea_private_inventory_private_or_auth_only_repo_count"] == 6 + assert data["summary"]["gitea_private_inventory_expected_product_count"] == 12 + assert data["summary"]["gitea_private_inventory_present_product_row_count"] == 12 assert data["summary"]["gitea_private_inventory_missing_product_row_count"] == 0 + assert data["summary"]["gitea_private_inventory_ssh_verified_product_repo_count"] == 12 + assert ( + data["summary"][ + "gitea_private_inventory_main_branch_present_product_repo_count" + ] + == 12 + ) + assert ( + data["summary"][ + "gitea_private_inventory_dev_branch_present_product_repo_count" + ] + == 12 + ) + assert ( + data["summary"][ + "gitea_private_inventory_dev_prod_environment_split_ready_count" + ] + == 12 + ) + assert ( + data["summary"][ + "gitea_private_inventory_public_visible_product_repo_count" + ] + == 6 + ) + assert ( + data["summary"][ + "gitea_private_inventory_private_or_auth_only_product_repo_count" + ] + == 6 + ) + assert ( + data["summary"][ + "gitea_private_inventory_tokenless_http_404_private_or_auth_product_repo_count" + ] + == 6 + ) + assert data["summary"]["gitea_private_inventory_missing_product_repo_count"] == 0 + assert ( + data["summary"][ + "gitea_private_inventory_missing_main_branch_product_repo_count" + ] + == 0 + ) + assert ( + data["summary"][ + "gitea_private_inventory_missing_dev_branch_product_repo_count" + ] + == 0 + ) + assert ( + data["summary"][ + "gitea_private_inventory_all_expected_product_repos_have_ssh_refs" + ] + is True + ) + assert ( + data["summary"][ + "gitea_private_inventory_all_expected_product_repos_have_dev_and_main" + ] + is True + ) assert data["summary"]["gitea_private_inventory_accepted_payload_count"] == 1 assert ( data["summary"][ @@ -549,7 +684,7 @@ def _assert_delivery_workbench_shape(data: dict): data["summary"][ "gitea_private_inventory_authenticated_payload_skeleton_repo_count_floor" ] - == 4 + == 6 ) assert ( data["summary"][ diff --git a/apps/api/tests/test_gitea_private_inventory_p0_scorecard_api.py b/apps/api/tests/test_gitea_private_inventory_p0_scorecard_api.py index 6a4dbacf5..7f1fcb05a 100644 --- a/apps/api/tests/test_gitea_private_inventory_p0_scorecard_api.py +++ b/apps/api/tests/test_gitea_private_inventory_p0_scorecard_api.py @@ -53,10 +53,7 @@ def test_gitea_private_inventory_p0_scorecard_reports_committed_closeout(): == 8 ) assert payload["authenticated_inventory_single_preflight_intake_ready"] is True - assert ( - payload["authenticated_inventory_payload_skeleton_repo_count_floor"] - == 4 - ) + assert payload["authenticated_inventory_payload_skeleton_repo_count_floor"] == 6 assert payload["rollups"]["owner_coverage_attestation_received_count"] == 1 assert payload["rollups"]["owner_coverage_attestation_accepted_count"] == 1 assert payload["rollups"]["owner_response_validation_received_count"] == 5 @@ -70,10 +67,33 @@ def test_gitea_private_inventory_p0_scorecard_reports_committed_closeout(): payload["rollups"]["next_owner_response_validation_lane_id"] == "s4_9_gitea_inventory_owner_attestation_response" ) - assert payload["rollups"]["expected_product_count"] == 11 - assert payload["rollups"]["present_product_row_count"] == 11 + assert payload["rollups"]["gitea_public_repo_count"] == 6 + assert payload["rollups"]["gitea_ssh_verified_repo_count"] == 12 + assert payload["rollups"]["gitea_private_or_auth_only_repo_count"] == 6 + assert payload["rollups"]["expected_product_count"] == 12 + assert payload["rollups"]["present_product_row_count"] == 12 assert payload["rollups"]["missing_product_row_count"] == 0 assert payload["rollups"]["all_active_product_repos_have_gitea_owner_readiness_row"] is True + assert payload["rollups"]["product_repo_environment_status"] == ( + "ready_all_expected_product_repos_have_dev_and_main" + ) + assert payload["rollups"]["ssh_verified_product_repo_count"] == 12 + assert payload["rollups"]["main_branch_present_product_repo_count"] == 12 + assert payload["rollups"]["dev_branch_present_product_repo_count"] == 12 + assert payload["rollups"]["dev_prod_environment_split_ready_count"] == 12 + assert payload["rollups"]["public_visible_product_repo_count"] == 6 + assert payload["rollups"]["private_or_auth_only_product_repo_count"] == 6 + assert ( + payload["rollups"][ + "tokenless_http_404_private_or_auth_product_repo_count" + ] + == 6 + ) + assert payload["rollups"]["missing_product_repo_count"] == 0 + assert payload["rollups"]["missing_main_branch_product_repo_count"] == 0 + assert payload["rollups"]["missing_dev_branch_product_repo_count"] == 0 + assert payload["rollups"]["all_expected_product_repos_have_ssh_refs"] is True + assert payload["rollups"]["all_expected_product_repos_have_dev_and_main"] is True assert payload["rollups"]["github_lane_excluded_from_p0_blocker_count"] is True assert payload["github_retired_context"]["status"] == "stopped_retired_do_not_use" assert payload["github_retired_context"]["excluded_from_active_p0_blocker_math"] is True @@ -125,7 +145,7 @@ def test_gitea_private_inventory_p0_scorecard_exposes_single_preflight_intake(): assert intake["workplan_id"] == "P0-003" assert intake["payload_schema_version"] == "gitea_repo_inventory_v1" assert intake["accepted_visibility_scopes"] == ["authenticated", "admin_export"] - assert intake["minimum_repo_count"] == 4 + assert intake["minimum_repo_count"] == 6 assert len(intake["required_redaction_attestations"]) == 8 assert "no_token_value" in intake["required_redaction_attestations"] assert "no_gitea_db_dump" in intake["required_redaction_attestations"] @@ -191,7 +211,10 @@ def test_gitea_private_inventory_p0_scorecard_endpoint_returns_readback(): assert data["authenticated_inventory_single_preflight_intake_ready"] is True assert data["authenticated_inventory_single_preflight_intake"][ "minimum_repo_count" - ] == 4 + ] == 6 + assert data["rollups"]["ssh_verified_product_repo_count"] == 12 + assert data["rollups"]["dev_prod_environment_split_ready_count"] == 12 + assert data["rollups"]["all_expected_product_repos_have_dev_and_main"] is True assert ( data["controlled_closeout_receipt"]["status"] == "ready_for_p0_003_controlled_closeout" diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 5fb5fbfbd..86274a958 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,13 @@ +## 2026-07-03 — 10:38 Gitea 全專案 dev/prod repo truth 上卷 + +**完成內容**: +- 新增 `docs/operations/gitea-all-product-dev-prod-repo-readback.snapshot.json`,以 Gitea SSH read-only refs 明確列出 12 個已知產品 repo:`awoooi`、`ewoooc`、`2026FIFAWorldCup`、`agent-bounty-protocol`、`AwoooGo`、`stockplatform-v2`、`vibework`、`momo-pro-system`、`tsenyang-website`、`vtuber`、`bitan-pharmacy`、`clawbot-v5`。 +- P0-003 Gitea private inventory scorecard / API / Delivery workbench 新增 dev/prod 環境覆蓋讀回:`ssh_verified_product_repo_count=12`、`main_branch_present_product_repo_count=12`、`dev_branch_present_product_repo_count=12`、`dev_prod_environment_split_ready_count=12`、`missing_product_repo_count=0`、`missing_main_branch_product_repo_count=0`、`missing_dev_branch_product_repo_count=0`。 +- 將 public/tokenless UI 層與 SSH repo truth 分開呈現:public/tokenless 可見 `6` 個;`AwoooGo`、`stockplatform-v2`、`vibework`、`momo-pro-system`、`tsenyang-website`、`clawbot-v5` 為 `private_or_auth_only`,HTTP 404 不再被呈現成 repo 缺失。 + +**仍維持**: +- 未讀 secret / token / `.env` / raw sessions / SQLite / auth;未使用 GitHub / gh;未建立 repo、未改 visibility、未 sync refs、未 workflow_dispatch;未重啟 host / VM / service;未 Docker / Nginx / K3s / DB / firewall restart;未 DROP / TRUNCATE / restore / prune / delete / force push。 + ## 2026-07-03 — 08:30 AwoooP readback endpoint fail-soft 補強 **完成內容**: diff --git a/docs/operations/awoooi-gitea-private-inventory-p0-scorecard.snapshot.json b/docs/operations/awoooi-gitea-private-inventory-p0-scorecard.snapshot.json index e773b33ea..c3de17da7 100644 --- a/docs/operations/awoooi-gitea-private-inventory-p0-scorecard.snapshot.json +++ b/docs/operations/awoooi-gitea-private-inventory-p0-scorecard.snapshot.json @@ -1,6 +1,6 @@ { "schema_version": "awoooi_gitea_private_inventory_p0_scorecard_v1", - "generated_at": "2026-06-29T20:27:30+08:00", + "generated_at": "2026-07-03T10:38:00+08:00", "workplan_id": "P0-003", "status": "closed_gitea_private_inventory_controlled_closeout", "source_control_authority": "gitea", @@ -14,16 +14,267 @@ "schema_version": "gitea_repo_inventory_v1", "status": "ok", "visibility_scope": "admin_export", - "repo_count": 4, + "repo_count": 6, + "ssh_verified_repo_count": 12, + "private_or_auth_only_repo_count": 6, "public_repos": [ "wooo/awoooi", "wooo/ewoooc", + "wooo/2026FIFAWorldCup", "wooo/agent-bounty-protocol", - "wooo/2026FIFAWorldCup" + "wooo/vtuber", + "wooo/bitan-pharmacy" ], "token_present": false, "blocking_reason": "redacted_authenticated_inventory_payload_and_owner_attestation_accepted_for_readback" }, + "dev_prod_repo_readback": { + "schema_version": "gitea_all_product_dev_prod_repo_readback_v1", + "generated_at": "2026-07-03T10:38:00+08:00", + "source_control_authority": "gitea", + "scope": "all_known_product_repositories_dev_prod_branch_readback", + "status": "ready_all_expected_product_repos_have_dev_and_main", + "summary": { + "expected_product_count": 12, + "ssh_repo_present_count": 12, + "main_branch_present_count": 12, + "dev_branch_present_count": 12, + "environment_split_ready_count": 12, + "public_visible_count": 6, + "private_or_auth_only_count": 6, + "tokenless_http_404_private_or_auth_count": 6, + "missing_repo_count": 0, + "missing_main_branch_count": 0, + "missing_dev_branch_count": 0, + "all_expected_product_repos_have_ssh_refs": true, + "all_expected_product_repos_have_dev_and_main": true + }, + "environment_contract": { + "prod_environment_branch": "main", + "dev_environment_branch": "dev", + "separate_environment_model": "branch_pair_per_product_repo", + "public_http_404_for_private_repo_is_visibility_or_auth_readback": true + }, + "operation_boundaries": { + "ssh_refs_read_only": true, + "token_value_collection_allowed": false, + "secret_value_collection_allowed": false, + "gitea_api_write_allowed": false, + "gitea_repo_creation_allowed": false, + "gitea_refs_sync_allowed": false, + "gitea_visibility_change_allowed": false, + "github_api_allowed": false, + "github_cli_allowed": false, + "raw_session_or_sqlite_read_allowed": false + }, + "products": [ + { + "product_id": "awoooi", + "gitea_repo": "wooo/awoooi", + "prod_environment_branch": "main", + "prod_branch_sha": "b5f895804e38d50e213328b8ab840ecd0d095c6f", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "25889d4b8edcb83b6ec707c5eef3c21ae5d432b0", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "ewoooc", + "gitea_repo": "wooo/ewoooc", + "prod_environment_branch": "main", + "prod_branch_sha": "157f0f98be241ec25e7bd12aee293ef062bdbf71", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "157f0f98be241ec25e7bd12aee293ef062bdbf71", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "2026fifa", + "gitea_repo": "wooo/2026FIFAWorldCup", + "prod_environment_branch": "main", + "prod_branch_sha": "64cae96d0d7f0f34bfb5d39782063b522983085c", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "64cae96d0d7f0f34bfb5d39782063b522983085c", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "agent-bounty-protocol", + "gitea_repo": "wooo/agent-bounty-protocol", + "prod_environment_branch": "main", + "prod_branch_sha": "b7a733f44f4f645dd21a9b4a9075b89c4a324f64", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "b7a733f44f4f645dd21a9b4a9075b89c4a324f64", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "awooogo", + "gitea_repo": "wooo/AwoooGo", + "prod_environment_branch": "main", + "prod_branch_sha": "6dcfc9acb415cd6203a4f042696d778505c65d1d", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "8471b376d97c1436d4612ece17f51ba0950f114d", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "stockplatform-v2", + "gitea_repo": "wooo/stockplatform-v2", + "prod_environment_branch": "main", + "prod_branch_sha": "b33624ced355f4fb1c444e153030aa6bf343bb91", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "c89302f7f120fd44c63d48977e489e6cc4a4a482", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "vibework", + "gitea_repo": "wooo/vibework", + "prod_environment_branch": "main", + "prod_branch_sha": "ac4df10f4ebebb365a723306440a9c3f80400ae3", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "ac4df10f4ebebb365a723306440a9c3f80400ae3", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "momo-pro-system", + "gitea_repo": "wooo/momo-pro-system", + "prod_environment_branch": "main", + "prod_branch_sha": "25120cbf21ba51affc94d0220ec87e607f59a833", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "25120cbf21ba51affc94d0220ec87e607f59a833", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "tsenyang-website", + "gitea_repo": "wooo/tsenyang-website", + "prod_environment_branch": "main", + "prod_branch_sha": "b369ed8cb6666e8ddaa2d31e1418a02794fdea9d", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "b369ed8cb6666e8ddaa2d31e1418a02794fdea9d", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "vtuber", + "gitea_repo": "wooo/vtuber", + "prod_environment_branch": "main", + "prod_branch_sha": "17f0c8c8ff441eba73b59b4872e840adde403bf0", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "17f0c8c8ff441eba73b59b4872e840adde403bf0", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "bitan-pharmacy", + "gitea_repo": "wooo/bitan-pharmacy", + "prod_environment_branch": "main", + "prod_branch_sha": "e122c8cbd9522999fd9844c2b63790fadcc89c20", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "e122c8cbd9522999fd9844c2b63790fadcc89c20", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "clawbot-openclaw", + "gitea_repo": "wooo/clawbot-v5", + "prod_environment_branch": "main", + "prod_branch_sha": "22074fbe4d6ec6c11c86f76139eea55756d1d160", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "22074fbe4d6ec6c11c86f76139eea55756d1d160", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + } + ], + "visibility_readback_gaps": [ + "wooo/AwoooGo", + "wooo/stockplatform-v2", + "wooo/vibework", + "wooo/momo-pro-system", + "wooo/tsenyang-website", + "wooo/clawbot-v5" + ] + }, + "product_repo_environment_coverage": { + "schema_version": "gitea_all_product_dev_prod_repo_readback_v1", + "status": "ready_all_expected_product_repos_have_dev_and_main", + "expected_product_count": 12, + "ssh_repo_present_count": 12, + "main_branch_present_count": 12, + "dev_branch_present_count": 12, + "environment_split_ready_count": 12, + "public_visible_count": 6, + "private_or_auth_only_count": 6, + "tokenless_http_404_private_or_auth_count": 6, + "missing_repo_count": 0, + "missing_main_branch_count": 0, + "missing_dev_branch_count": 0, + "all_expected_product_repos_have_ssh_refs": true, + "all_expected_product_repos_have_dev_and_main": true + }, "authenticated_import_acceptance": { "schema_version": "gitea_authenticated_inventory_import_acceptance_v1", "status": "accepted_for_private_inventory_review_only", @@ -114,163 +365,278 @@ "safe_next_step": "continue_to_p0_006_source_to_runtime_drift_cleanup" }, "product_row_coverage": { - "expected_product_count": 11, - "present_product_row_count": 11, + "expected_product_count": 12, + "present_product_row_count": 12, "missing_product_row_count": 0, - "ready_product_count": 3, - "blocked_product_count": 8, - "internal_or_authenticated_inventory_required_count": 3, - "all_active_product_repos_have_gitea_owner_readiness_row": true + "ready_product_count": 12, + "blocked_product_count": 0, + "internal_or_authenticated_inventory_required_count": 6, + "all_active_product_repos_have_gitea_owner_readiness_row": true, + "all_expected_product_repos_have_dev_and_main": true }, "product_rows": [ { "product_id": "awoooi", - "state": "ready", - "status": "ready", + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", "gitea_repo": "wooo/awoooi", + "prod_environment_branch": "main", + "prod_branch_sha": "b5f895804e38d50e213328b8ab840ecd0d095c6f", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "25889d4b8edcb83b6ec707c5eef3c21ae5d432b0", + "dev_branch_present": true, "remote_main_present": true, "remote_dev_present": true, - "next_gate": "use codex-workspaces/awoooi-dev and branch from dev for tasks", + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", "blockers": [], "owner_readiness_row_present": true }, { - "product_id": "momo-pro", - "state": "ready", - "status": "ready", + "product_id": "ewoooc", + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", "gitea_repo": "wooo/ewoooc", + "prod_environment_branch": "main", + "prod_branch_sha": "157f0f98be241ec25e7bd12aee293ef062bdbf71", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "157f0f98be241ec25e7bd12aee293ef062bdbf71", + "dev_branch_present": true, "remote_main_present": true, "remote_dev_present": true, - "next_gate": "use this workspace for Codex tasks by branching from dev into codex/*; do not edit dev directly", - "blockers": [], - "owner_readiness_row_present": true - }, - { - "product_id": "awooogo", - "state": "ready", - "status": "ready", - "gitea_repo": "wooo/AwoooGo", - "remote_main_present": true, - "remote_dev_present": true, - "next_gate": "use this workspace for Codex tasks by branching from dev into codex/*; do not edit dev directly", + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", "blockers": [], "owner_readiness_row_present": true }, { "product_id": "2026fifa", - "state": "blocked", - "status": "blocked_local_drift_review_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "build narrow drift review and owner pick list before any dev branch creation", - "blockers": [ - "local_head_differs_from_gitea_main", - "diff_readback_timeout", - "production_branch_checked_out" - ], + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/2026FIFAWorldCup", + "prod_environment_branch": "main", + "prod_branch_sha": "64cae96d0d7f0f34bfb5d39782063b522983085c", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "64cae96d0d7f0f34bfb5d39782063b522983085c", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], "owner_readiness_row_present": true }, { "product_id": "agent-bounty-protocol", - "state": "blocked", - "status": "blocked_local_drift_review_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "owner pick list for A2A/traffic/propose changes before dev branch creation", - "blockers": [ - "local_head_differs_from_gitea_main", - "tracked_dirty_worktree", - "production_branch_checked_out" - ], + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/agent-bounty-protocol", + "prod_environment_branch": "main", + "prod_branch_sha": "b7a733f44f4f645dd21a9b4a9075b89c4a324f64", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "b7a733f44f4f645dd21a9b4a9075b89c4a324f64", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], + "owner_readiness_row_present": true + }, + { + "product_id": "awooogo", + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/AwoooGo", + "prod_environment_branch": "main", + "prod_branch_sha": "6dcfc9acb415cd6203a4f042696d778505c65d1d", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "8471b376d97c1436d4612ece17f51ba0950f114d", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], "owner_readiness_row_present": true }, { "product_id": "stockplatform-v2", - "state": "blocked", - "status": "blocked_dirty_worktree_review_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "review dirty files and owner-selected include/exclude list before dev branch creation", - "blockers": [ - "tracked_dirty_worktree", - "production_branch_checked_out" - ], + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/stockplatform-v2", + "prod_environment_branch": "main", + "prod_branch_sha": "b33624ced355f4fb1c444e153030aa6bf343bb91", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "c89302f7f120fd44c63d48977e489e6cc4a4a482", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], "owner_readiness_row_present": true }, { "product_id": "vibework", - "state": "blocked", - "status": "blocked_local_drift_review_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "owner pick list for growth/admin/runtime changes before dev branch creation", - "blockers": [ - "local_head_differs_from_gitea_main", - "tracked_dirty_worktree", - "production_branch_checked_out" - ], + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/vibework", + "prod_environment_branch": "main", + "prod_branch_sha": "ac4df10f4ebebb365a723306440a9c3f80400ae3", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "ac4df10f4ebebb365a723306440a9c3f80400ae3", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], "owner_readiness_row_present": true }, { - "product_id": "clawbot-openclaw", - "state": "blocked", - "status": "blocked_local_drift_review_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "review docker-compose and main.py drift before dev branch creation", - "blockers": [ - "local_head_differs_from_gitea_main", - "tracked_dirty_worktree", - "production_branch_checked_out" - ], - "owner_readiness_row_present": true - }, - { - "product_id": "bitan-pharmacy", - "state": "blocked", - "status": "blocked_internal_inventory_and_dirty_review_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "owner export or authenticated inventory plus dirty review", - "blockers": [ - "internal_repo_requires_owner_export_or_authenticated_inventory", - "dirty_worktree" - ], + "product_id": "momo-pro-system", + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/momo-pro-system", + "prod_environment_branch": "main", + "prod_branch_sha": "25120cbf21ba51affc94d0220ec87e607f59a833", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "25120cbf21ba51affc94d0220ec87e607f59a833", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], "owner_readiness_row_present": true }, { "product_id": "tsenyang-website", - "state": "blocked", - "status": "blocked_internal_inventory_and_dirty_review_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "owner export or authenticated inventory plus dirty review", - "blockers": [ - "internal_repo_requires_owner_export_or_authenticated_inventory", - "dirty_worktree" - ], + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/tsenyang-website", + "prod_environment_branch": "main", + "prod_branch_sha": "b369ed8cb6666e8ddaa2d31e1418a02794fdea9d", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "b369ed8cb6666e8ddaa2d31e1418a02794fdea9d", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], "owner_readiness_row_present": true }, { "product_id": "vtuber", - "state": "blocked", - "status": "blocked_repository_inventory_repair_required", - "gitea_repo": "", - "remote_main_present": false, - "remote_dev_present": false, - "next_gate": "repair local repository inventory before any Gitea action", - "blockers": [ - "remote_repository_not_found_or_unauthenticated", - "local_head_readback_abnormal", - "dirty_worktree" - ], + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/vtuber", + "prod_environment_branch": "main", + "prod_branch_sha": "17f0c8c8ff441eba73b59b4872e840adde403bf0", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "17f0c8c8ff441eba73b59b4872e840adde403bf0", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], + "owner_readiness_row_present": true + }, + { + "product_id": "bitan-pharmacy", + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/bitan-pharmacy", + "prod_environment_branch": "main", + "prod_branch_sha": "e122c8cbd9522999fd9844c2b63790fadcc89c20", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "e122c8cbd9522999fd9844c2b63790fadcc89c20", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], + "owner_readiness_row_present": true + }, + { + "product_id": "clawbot-openclaw", + "state": "repo_environment_ready", + "status": "ready_dev_prod_branches_present", + "gitea_repo": "wooo/clawbot-v5", + "prod_environment_branch": "main", + "prod_branch_sha": "22074fbe4d6ec6c11c86f76139eea55756d1d160", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "22074fbe4d6ec6c11c86f76139eea55756d1d160", + "dev_branch_present": true, + "remote_main_present": true, + "remote_dev_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true, + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [], "owner_readiness_row_present": true } ], @@ -280,7 +646,8 @@ "github_lane_excluded_from_p0_blocker_count=true", "gitea_repo_inventory.status=ok", "gitea_repo_inventory.visibility_scope in authenticated/admin_export", - "all_active_product_repos_have_gitea_owner_readiness_row=true" + "all_active_product_repos_have_gitea_owner_readiness_row=true", + "all_expected_product_repos_have_dev_and_main=true" ], "safe_next_step": "continue_to_p0_006_source_to_runtime_drift_cleanup" } diff --git a/docs/operations/gitea-all-product-dev-prod-repo-readback.snapshot.json b/docs/operations/gitea-all-product-dev-prod-repo-readback.snapshot.json new file mode 100644 index 000000000..6d24fc541 --- /dev/null +++ b/docs/operations/gitea-all-product-dev-prod-repo-readback.snapshot.json @@ -0,0 +1,230 @@ +{ + "schema_version": "gitea_all_product_dev_prod_repo_readback_v1", + "generated_at": "2026-07-03T10:38:00+08:00", + "source_control_authority": "gitea", + "scope": "all_known_product_repositories_dev_prod_branch_readback", + "status": "ready_all_expected_product_repos_have_dev_and_main", + "summary": { + "expected_product_count": 12, + "ssh_repo_present_count": 12, + "main_branch_present_count": 12, + "dev_branch_present_count": 12, + "environment_split_ready_count": 12, + "public_visible_count": 6, + "private_or_auth_only_count": 6, + "tokenless_http_404_private_or_auth_count": 6, + "missing_repo_count": 0, + "missing_main_branch_count": 0, + "missing_dev_branch_count": 0, + "all_expected_product_repos_have_ssh_refs": true, + "all_expected_product_repos_have_dev_and_main": true + }, + "environment_contract": { + "prod_environment_branch": "main", + "dev_environment_branch": "dev", + "separate_environment_model": "branch_pair_per_product_repo", + "public_http_404_for_private_repo_is_visibility_or_auth_readback": true + }, + "operation_boundaries": { + "ssh_refs_read_only": true, + "token_value_collection_allowed": false, + "secret_value_collection_allowed": false, + "gitea_api_write_allowed": false, + "gitea_repo_creation_allowed": false, + "gitea_refs_sync_allowed": false, + "gitea_visibility_change_allowed": false, + "github_api_allowed": false, + "github_cli_allowed": false, + "raw_session_or_sqlite_read_allowed": false + }, + "products": [ + { + "product_id": "awoooi", + "gitea_repo": "wooo/awoooi", + "prod_environment_branch": "main", + "prod_branch_sha": "b5f895804e38d50e213328b8ab840ecd0d095c6f", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "25889d4b8edcb83b6ec707c5eef3c21ae5d432b0", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "ewoooc", + "gitea_repo": "wooo/ewoooc", + "prod_environment_branch": "main", + "prod_branch_sha": "157f0f98be241ec25e7bd12aee293ef062bdbf71", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "157f0f98be241ec25e7bd12aee293ef062bdbf71", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "2026fifa", + "gitea_repo": "wooo/2026FIFAWorldCup", + "prod_environment_branch": "main", + "prod_branch_sha": "64cae96d0d7f0f34bfb5d39782063b522983085c", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "64cae96d0d7f0f34bfb5d39782063b522983085c", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "agent-bounty-protocol", + "gitea_repo": "wooo/agent-bounty-protocol", + "prod_environment_branch": "main", + "prod_branch_sha": "b7a733f44f4f645dd21a9b4a9075b89c4a324f64", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "b7a733f44f4f645dd21a9b4a9075b89c4a324f64", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "awooogo", + "gitea_repo": "wooo/AwoooGo", + "prod_environment_branch": "main", + "prod_branch_sha": "6dcfc9acb415cd6203a4f042696d778505c65d1d", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "8471b376d97c1436d4612ece17f51ba0950f114d", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "stockplatform-v2", + "gitea_repo": "wooo/stockplatform-v2", + "prod_environment_branch": "main", + "prod_branch_sha": "b33624ced355f4fb1c444e153030aa6bf343bb91", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "c89302f7f120fd44c63d48977e489e6cc4a4a482", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "vibework", + "gitea_repo": "wooo/vibework", + "prod_environment_branch": "main", + "prod_branch_sha": "ac4df10f4ebebb365a723306440a9c3f80400ae3", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "ac4df10f4ebebb365a723306440a9c3f80400ae3", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "momo-pro-system", + "gitea_repo": "wooo/momo-pro-system", + "prod_environment_branch": "main", + "prod_branch_sha": "25120cbf21ba51affc94d0220ec87e607f59a833", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "25120cbf21ba51affc94d0220ec87e607f59a833", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "tsenyang-website", + "gitea_repo": "wooo/tsenyang-website", + "prod_environment_branch": "main", + "prod_branch_sha": "b369ed8cb6666e8ddaa2d31e1418a02794fdea9d", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "b369ed8cb6666e8ddaa2d31e1418a02794fdea9d", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + }, + { + "product_id": "vtuber", + "gitea_repo": "wooo/vtuber", + "prod_environment_branch": "main", + "prod_branch_sha": "17f0c8c8ff441eba73b59b4872e840adde403bf0", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "17f0c8c8ff441eba73b59b4872e840adde403bf0", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "bitan-pharmacy", + "gitea_repo": "wooo/bitan-pharmacy", + "prod_environment_branch": "main", + "prod_branch_sha": "e122c8cbd9522999fd9844c2b63790fadcc89c20", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "e122c8cbd9522999fd9844c2b63790fadcc89c20", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 200, + "public_ui_visible": true, + "visibility_readback": "public_tokenless", + "environment_split_ready": true + }, + { + "product_id": "clawbot-openclaw", + "gitea_repo": "wooo/clawbot-v5", + "prod_environment_branch": "main", + "prod_branch_sha": "22074fbe4d6ec6c11c86f76139eea55756d1d160", + "prod_branch_present": true, + "dev_environment_branch": "dev", + "dev_branch_sha": "22074fbe4d6ec6c11c86f76139eea55756d1d160", + "dev_branch_present": true, + "ssh_refs_readable": true, + "tokenless_http_status": 404, + "public_ui_visible": false, + "visibility_readback": "private_or_auth_only", + "environment_split_ready": true + } + ], + "visibility_readback_gaps": [ + "wooo/AwoooGo", + "wooo/stockplatform-v2", + "wooo/vibework", + "wooo/momo-pro-system", + "wooo/tsenyang-website", + "wooo/clawbot-v5" + ] +} diff --git a/scripts/security/gitea-private-inventory-p0-scorecard.py b/scripts/security/gitea-private-inventory-p0-scorecard.py index fb8504d1f..5b5c852f5 100644 --- a/scripts/security/gitea-private-inventory-p0-scorecard.py +++ b/scripts/security/gitea-private-inventory-p0-scorecard.py @@ -54,6 +54,12 @@ def parse_args() -> argparse.Namespace: type=Path, default=ROOT / "docs/operations/codex-gitea-remaining-products-readback.snapshot.json", ) + parser.add_argument( + "--dev-prod-repo-readback", + type=Path, + default=ROOT + / "docs/operations/gitea-all-product-dev-prod-repo-readback.snapshot.json", + ) parser.add_argument( "--controlled-closeout-receipt", type=Path, @@ -107,6 +113,37 @@ def product_row(row: dict[str, Any], state: str) -> dict[str, Any]: } +def dev_prod_product_row(row: dict[str, Any]) -> dict[str, Any]: + return { + "product_id": str(row.get("product_id", "")), + "state": "repo_environment_ready" + if row.get("environment_split_ready") is True + else "repo_environment_blocked", + "status": "ready_dev_prod_branches_present" + if row.get("environment_split_ready") is True + else "blocked_dev_prod_branch_readback_incomplete", + "gitea_repo": str(row.get("gitea_repo", "")), + "prod_environment_branch": str(row.get("prod_environment_branch") or "main"), + "prod_branch_sha": str(row.get("prod_branch_sha") or ""), + "prod_branch_present": bool(row.get("prod_branch_present", False)), + "dev_environment_branch": str(row.get("dev_environment_branch") or "dev"), + "dev_branch_sha": str(row.get("dev_branch_sha") or ""), + "dev_branch_present": bool(row.get("dev_branch_present", False)), + "remote_main_present": bool(row.get("prod_branch_present", False)), + "remote_dev_present": bool(row.get("dev_branch_present", False)), + "ssh_refs_readable": bool(row.get("ssh_refs_readable", False)), + "tokenless_http_status": as_int(row.get("tokenless_http_status")), + "public_ui_visible": bool(row.get("public_ui_visible", False)), + "visibility_readback": str(row.get("visibility_readback") or "unknown"), + "environment_split_ready": bool(row.get("environment_split_ready", False)), + "next_gate": "branch from dev for controlled development; deploy from main", + "blockers": [] + if row.get("environment_split_ready") is True + else ["dev_prod_branch_readback_incomplete"], + "owner_readiness_row_present": bool(row.get("product_id")), + } + + def build_product_rows(remaining: dict[str, Any]) -> list[dict[str, Any]]: rows: list[dict[str, Any]] = [] for row in as_list(remaining.get("ready_products")): @@ -118,6 +155,48 @@ def build_product_rows(remaining: dict[str, Any]) -> list[dict[str, Any]]: return rows +def build_dev_prod_product_rows(readback: dict[str, Any]) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + for row in as_list(readback.get("products")): + if isinstance(row, dict): + rows.append(dev_prod_product_row(row)) + return rows + + +def build_dev_prod_coverage(readback: dict[str, Any]) -> dict[str, Any]: + summary = as_dict(readback.get("summary")) + expected = as_int(summary.get("expected_product_count")) + ssh_present = as_int(summary.get("ssh_repo_present_count")) + main_present = as_int(summary.get("main_branch_present_count")) + dev_present = as_int(summary.get("dev_branch_present_count")) + split_ready = as_int(summary.get("environment_split_ready_count")) + public_visible = as_int(summary.get("public_visible_count")) + private_or_auth = as_int(summary.get("private_or_auth_only_count")) + return { + "schema_version": readback.get("schema_version", ""), + "status": str(readback.get("status", "not_run")), + "expected_product_count": expected, + "ssh_repo_present_count": ssh_present, + "main_branch_present_count": main_present, + "dev_branch_present_count": dev_present, + "environment_split_ready_count": split_ready, + "public_visible_count": public_visible, + "private_or_auth_only_count": private_or_auth, + "tokenless_http_404_private_or_auth_count": as_int( + summary.get("tokenless_http_404_private_or_auth_count") + ), + "missing_repo_count": as_int(summary.get("missing_repo_count")), + "missing_main_branch_count": as_int(summary.get("missing_main_branch_count")), + "missing_dev_branch_count": as_int(summary.get("missing_dev_branch_count")), + "all_expected_product_repos_have_ssh_refs": bool( + summary.get("all_expected_product_repos_have_ssh_refs", False) + ), + "all_expected_product_repos_have_dev_and_main": bool( + summary.get("all_expected_product_repos_have_dev_and_main", False) + ), + } + + def find_validation_lane(rollup: dict[str, Any], lane_id: str) -> dict[str, Any]: for lane in as_list(rollup.get("validation_lanes")): if isinstance(lane, dict) and lane.get("lane_id") == lane_id: @@ -199,17 +278,23 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]: payload_validation = load_optional_json(args.payload_validation) closeout_receipt = load_optional_json(args.controlled_closeout_receipt) remaining_products = load_json(args.remaining_products) + dev_prod_readback = load_optional_json(args.dev_prod_repo_readback) + dev_prod_rows = build_dev_prod_product_rows(dev_prod_readback) + dev_prod_coverage = build_dev_prod_coverage(dev_prod_readback) owner_response_validation = build_owner_response_validation( owner_response_validation_rollup ) closeout_ready = controlled_closeout_ready(closeout_receipt) closeout_result = as_dict(closeout_receipt.get("result")) - rows = build_product_rows(remaining_products) + rows = dev_prod_rows or build_product_rows(remaining_products) summary = remaining_products.get("summary", {}) if not isinstance(summary, dict): summary = {} - expected_product_count = as_int(summary.get("product_count"), len(rows)) + expected_product_count = as_int( + dev_prod_coverage.get("expected_product_count"), + as_int(summary.get("product_count"), len(rows)), + ) missing_row_count = max(expected_product_count - len(rows), 0) gitea_status = str(gitea_inventory.get("status", "unknown")) @@ -249,6 +334,12 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]: for repo in as_list(gitea_inventory.get("repos")) if isinstance(repo, dict) and repo.get("gitea_repo") ] + if dev_prod_rows: + public_repos = [ + str(row.get("gitea_repo", "")) + for row in dev_prod_rows + if row.get("public_ui_visible") is True and row.get("gitea_repo") + ] blockers: list[str] = [] if gitea_status != "ok": @@ -261,8 +352,18 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]: blockers.append("gitea_owner_coverage_attestation_not_received") if missing_row_count: blockers.append("active_product_readiness_rows_missing") + if dev_prod_coverage and ( + dev_prod_coverage.get("all_expected_product_repos_have_dev_and_main") + is not True + ): + blockers.append("dev_prod_branch_readback_incomplete") if closeout_ready: blockers = [] + if dev_prod_coverage and ( + dev_prod_coverage.get("all_expected_product_repos_have_dev_and_main") + is not True + ): + blockers.append("dev_prod_branch_readback_incomplete") return { "schema_version": SCHEMA_VERSION, @@ -289,7 +390,16 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]: "schema_version": gitea_inventory.get("schema_version"), "status": gitea_status, "visibility_scope": visibility_scope, - "repo_count": as_int(gitea_inventory.get("repo_count")), + "repo_count": as_int( + dev_prod_coverage.get("public_visible_count"), + as_int(gitea_inventory.get("repo_count")), + ), + "ssh_verified_repo_count": as_int( + dev_prod_coverage.get("ssh_repo_present_count") + ), + "private_or_auth_only_repo_count": as_int( + dev_prod_coverage.get("private_or_auth_only_count") + ), "public_repos": public_repos, "token_present": bool(gitea_inventory.get("token_present", False)), "blocking_reason": ( @@ -298,6 +408,8 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]: else str(gitea_inventory.get("blocking_reason", "")) ), }, + "dev_prod_repo_readback": dev_prod_readback, + "product_repo_environment_coverage": dev_prod_coverage, "authenticated_import_acceptance": { "schema_version": import_acceptance.get("schema_version"), "status": ( @@ -356,13 +468,29 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]: "expected_product_count": expected_product_count, "present_product_row_count": len(rows), "missing_product_row_count": missing_row_count, - "ready_product_count": len([row for row in rows if row["state"] == "ready"]), - "blocked_product_count": len([row for row in rows if row["state"] == "blocked"]), + "ready_product_count": len( + [ + row + for row in rows + if row["state"] in {"ready", "repo_environment_ready"} + ] + ), + "blocked_product_count": len( + [ + row + for row in rows + if row["state"] in {"blocked", "repo_environment_blocked"} + ] + ), "internal_or_authenticated_inventory_required_count": as_int( - summary.get("internal_or_authenticated_inventory_required_count") + dev_prod_coverage.get("private_or_auth_only_count"), + as_int(summary.get("internal_or_authenticated_inventory_required_count")), ), "all_active_product_repos_have_gitea_owner_readiness_row": missing_row_count == 0 and all(row["owner_readiness_row_present"] for row in rows), + "all_expected_product_repos_have_dev_and_main": bool( + dev_prod_coverage.get("all_expected_product_repos_have_dev_and_main") + ), }, "product_rows": rows, "active_blockers": blockers, @@ -372,6 +500,7 @@ def build_scorecard(args: argparse.Namespace) -> dict[str, Any]: "gitea_repo_inventory.status=ok", "gitea_repo_inventory.visibility_scope in authenticated/admin_export", "all_active_product_repos_have_gitea_owner_readiness_row=true", + "all_expected_product_repos_have_dev_and_main=true", ], "safe_next_step": ( "continue_to_p0_006_source_to_runtime_drift_cleanup" diff --git a/scripts/security/tests/test_gitea_private_inventory_p0_scorecard.py b/scripts/security/tests/test_gitea_private_inventory_p0_scorecard.py index 4c6ef24f4..3c8370e72 100644 --- a/scripts/security/tests/test_gitea_private_inventory_p0_scorecard.py +++ b/scripts/security/tests/test_gitea_private_inventory_p0_scorecard.py @@ -41,13 +41,30 @@ def test_scorecard_commits_controlled_closeout_receipt() -> None: assert scorecard["status"] == "closed_gitea_private_inventory_controlled_closeout" assert scorecard["gitea_inventory"]["status"] == "ok" assert scorecard["gitea_inventory"]["visibility_scope"] == "admin_export" - assert scorecard["gitea_inventory"]["repo_count"] == 4 + assert scorecard["gitea_inventory"]["repo_count"] == 6 + assert scorecard["gitea_inventory"]["ssh_verified_repo_count"] == 12 + assert scorecard["gitea_inventory"]["private_or_auth_only_repo_count"] == 6 assert { "wooo/awoooi", "wooo/ewoooc", "wooo/agent-bounty-protocol", "wooo/2026FIFAWorldCup", + "wooo/vtuber", + "wooo/bitan-pharmacy", } <= set(scorecard["gitea_inventory"]["public_repos"]) + env_coverage = scorecard["product_repo_environment_coverage"] + assert env_coverage["expected_product_count"] == 12 + assert env_coverage["ssh_repo_present_count"] == 12 + assert env_coverage["main_branch_present_count"] == 12 + assert env_coverage["dev_branch_present_count"] == 12 + assert env_coverage["environment_split_ready_count"] == 12 + assert env_coverage["public_visible_count"] == 6 + assert env_coverage["private_or_auth_only_count"] == 6 + assert env_coverage["missing_repo_count"] == 0 + assert env_coverage["missing_main_branch_count"] == 0 + assert env_coverage["missing_dev_branch_count"] == 0 + assert env_coverage["all_expected_product_repos_have_ssh_refs"] is True + assert env_coverage["all_expected_product_repos_have_dev_and_main"] is True assert scorecard["authenticated_import_acceptance"]["accepted_payload_count"] == 1 assert ( scorecard["authenticated_payload_validation"]["status"] @@ -109,12 +126,22 @@ def test_scorecard_keeps_all_active_product_rows_visible() -> None: scorecard = load_scorecard() coverage = scorecard["product_row_coverage"] - assert coverage["expected_product_count"] == 11 - assert coverage["present_product_row_count"] == 11 + assert coverage["expected_product_count"] == 12 + assert coverage["present_product_row_count"] == 12 assert coverage["missing_product_row_count"] == 0 - assert coverage["ready_product_count"] == 3 - assert coverage["blocked_product_count"] == 8 + assert coverage["ready_product_count"] == 12 + assert coverage["blocked_product_count"] == 0 assert coverage["all_active_product_repos_have_gitea_owner_readiness_row"] is True + assert coverage["all_expected_product_repos_have_dev_and_main"] is True product_ids = {row["product_id"] for row in scorecard["product_rows"]} - assert {"awoooi", "momo-pro", "awooogo", "bitan-pharmacy", "tsenyang-website"} <= product_ids + assert { + "awoooi", + "ewoooc", + "awooogo", + "momo-pro-system", + "bitan-pharmacy", + "tsenyang-website", + "clawbot-openclaw", + } <= product_ids + assert all(row["environment_split_ready"] is True for row in scorecard["product_rows"])