diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 85819d6ce..71ab5061d 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -4758,6 +4758,64 @@ "steps": "Steps" } }, + "ownerResponseValidationDetailBoundary": { + "title": "Owner Response Validation Detail Boundary", + "subtitle": "The single-run detail view mirrors the S4.13 validation rollup and S4.9-S4.12 response packets as read-only state; this is not approval, remediation, MCP execution, repo, refs, workflow / secret, or runtime authorization for this Run.", + "badge": "Read-only detail boundary", + "openIwooos": "Open IwoooS", + "detailRefsTitle": "Detail validation references", + "boundaryLabel": "Validation / detail boundary", + "boundaryTitle": "No owner-response validation detail action can run yet", + "boundaryDetail": "This section only explains how this Run detail understands the four packets, 22 response templates, received / accepted / rejected still at 0, and the gap between validation context and the execution timeline. It does not create approval records, start MCP or remediation, create platform runs, link the execution router, create repos, change refs, modify workflow / secrets, collect secret values, switch the primary source, or open a runtime gate.", + "metrics": { + "packets": { + "label": "Response packets", + "detail": "S4.9-S4.12 remain waiting for owner responses." + }, + "templates": { + "label": "Response templates", + "detail": "22 templates are future intake formats, not responses received by this Run." + }, + "received": { + "label": "Received", + "detail": "Still 0; the detail page must not turn visibility into intake state." + }, + "accepted": { + "label": "Accepted", + "detail": "Still 0; this can only change after redacted evidence passes validation." + }, + "validationRuns": { + "label": "Validation runs", + "detail": "Still 0; this detail card does not create a platform run." + }, + "displaySections": { + "label": "Display sections", + "detail": "8 sections only explain validation flow and the detail boundary." + } + }, + "detailRefs": { + "validationRollup": { + "title": "S4.13 Validation Rollup", + "detail": "Pins the four packets, cross-packet validation, evidence routing, review checklist, and result lanes, but does not create approval or remediation execution for this Run." + }, + "giteaAttestation": { + "title": "S4.9 Gitea Inventory Owner Attestation", + "detail": "5 templates still wait for owner response; the detail page can only mark the next intake focus." + }, + "githubTarget": { + "title": "S4.10 GitHub Target Owner Decision", + "detail": "7 target owner / visibility / standard responses remain unaccepted; repos or execution records must not be created automatically." + }, + "refsTruth": { + "title": "S4.11 Refs Truth Owner Response", + "detail": "5 truth categories still wait for redacted responses; refs must not be synced, deleted, or force-pushed." + }, + "workflowSecret": { + "title": "S4.12 Workflow / Secret Name Owner Response", + "detail": "5 name and redacted-evidence categories still wait for responses; only name inventories are allowed, never raw secret values." + } + } + }, "statuses": { "blocked": "Blocked", "cancelled": "Cancelled", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 90697d167..c0fbf912a 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -4759,6 +4759,64 @@ "steps": "步驟" } }, + "ownerResponseValidationDetailBoundary": { + "title": "負責人回覆驗收詳情邊界", + "subtitle": "單一執行詳情同步顯示 S4.13 驗收彙整與 S4.9-S4.12 四包來源回覆仍只可讀;這不是此 Run 的審批、補救、MCP 執行、專案庫、分支 / 標籤參照、工作流程 / 機密設定或執行期授權。", + "badge": "只讀詳情邊界", + "openIwooos": "開啟 IwoooS", + "detailRefsTitle": "詳情驗收參照", + "boundaryLabel": "驗收 / 詳情邊界", + "boundaryTitle": "目前沒有負責人回覆驗收詳情可執行動作", + "boundaryDetail": "這個區塊只說明此 Run 詳情如何理解四包、22 個回覆範本、已收到 / 已接受 / 已拒收仍為 0,以及驗收資訊與執行時間線尚未連成授權;不建立審批紀錄、不啟動 MCP 或補救、不建立平台執行、不接執行路由器、不建立專案庫、不改分支 / 標籤參照、不改工作流程 / 機密設定、不收機密明文值、不切主要來源,也不開執行期閘門。", + "metrics": { + "packets": { + "label": "回覆包", + "detail": "S4.9-S4.12 四包仍等待負責人回覆。" + }, + "templates": { + "label": "回覆範本", + "detail": "22 個範本只代表未來可收件格式,不代表此 Run 已收到回覆。" + }, + "received": { + "label": "已收到", + "detail": "目前仍為 0;詳情頁不得把可視性改寫成收件狀態。" + }, + "accepted": { + "label": "已接受", + "detail": "目前仍為 0;只有脫敏證據通過驗收後才能改變。" + }, + "validationRuns": { + "label": "驗收執行", + "detail": "目前仍為 0;此詳情卡不建立新的平台執行。" + }, + "displaySections": { + "label": "顯示區塊", + "detail": "8 個顯示區塊只用於說明驗收流程與詳情邊界。" + } + }, + "detailRefs": { + "validationRollup": { + "title": "S4.13 驗收彙整", + "detail": "固定四包、跨包驗收、證據路由、審查清單與結果分流,但不產生此 Run 的審批或補救執行。" + }, + "giteaAttestation": { + "title": "S4.9 Gitea 清冊負責人證明", + "detail": "5 個範本仍等待負責人回覆;詳情頁只能標記下一個收件焦點。" + }, + "githubTarget": { + "title": "S4.10 GitHub 目標負責人決策", + "detail": "7 個目標負責人 / 可見性 / 標準回覆仍未接受,不得自動建立專案庫或執行紀錄。" + }, + "refsTruth": { + "title": "S4.11 分支 / 標籤真相負責人回覆", + "detail": "5 類真相判定仍等待脫敏回覆,不得同步、刪除或強制推送分支 / 標籤參照。" + }, + "workflowSecret": { + "title": "S4.12 工作流程 / 機密名稱負責人回覆", + "detail": "5 類名稱與脫敏證據仍等待回覆;只允許名稱清冊,不允許機密明文值。" + } + } + }, "statuses": { "blocked": "已阻擋", "cancelled": "已取消", diff --git a/apps/web/src/app/[locale]/awooop/runs/[run_id]/page.tsx b/apps/web/src/app/[locale]/awooop/runs/[run_id]/page.tsx index 43504cb8f..d775c25d7 100644 --- a/apps/web/src/app/[locale]/awooop/runs/[run_id]/page.tsx +++ b/apps/web/src/app/[locale]/awooop/runs/[run_id]/page.tsx @@ -146,6 +146,12 @@ interface RemediationHistoryItem { checks?: Array<{ name?: string; passed?: boolean; detail?: string }>; } +type OwnerResponseValidationDetailRef = { + phase: string; + key: string; + contract: string; +}; + interface RunRemediationHistory { schema_version?: string; source?: string; @@ -183,6 +189,53 @@ interface RunDetailResponse { const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ""; const AUTO_REFRESH_INTERVAL = 30_000; +const ownerResponseValidationDetailRefs: OwnerResponseValidationDetailRef[] = [ + { + phase: "S4.13", + key: "validationRollup", + contract: "source_control_owner_response_validation_rollup_v1", + }, + { + phase: "S4.9", + key: "giteaAttestation", + contract: "gitea_inventory_owner_attestation_response_v1", + }, + { + phase: "S4.10", + key: "githubTarget", + contract: "github_target_owner_decision_response_v1", + }, + { + phase: "S4.11", + key: "refsTruth", + contract: "source_control_ref_truth_owner_response_v1", + }, + { + phase: "S4.12", + key: "workflowSecret", + contract: "source_control_workflow_secret_name_owner_response_v1", + }, +]; + +const ownerResponseValidationDetailBoundaries = [ + "owner_response_validation_received_count=0", + "owner_response_validation_accepted_count=0", + "owner_response_validation_rejected_count=0", + "run_detail_owner_response_linked=false", + "run_detail_approval_record_created=false", + "mcp_execution_authorized=false", + "remediation_execution_authorized=false", + "platform_run_creation_authorized=false", + "execution_router_linked=false", + "repo_creation_authorized=false", + "refs_sync_authorized=false", + "workflow_modification_authorized=false", + "secret_value_collection_allowed=false", + "github_primary_switch_authorized=false", + "runtime_execution_authorized=false", + "action_buttons_allowed=false", +]; + const STATUS_STYLE: Record = { completed: "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]", success: "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]", @@ -386,6 +439,92 @@ function RunActionPanel({ ); } +function OwnerResponseValidationDetailBoundaryPanel() { + const t = useTranslations("awooop.runDetail.ownerResponseValidationDetailBoundary"); + const metrics = [ + { label: t("metrics.packets.label"), value: "4", detail: t("metrics.packets.detail") }, + { label: t("metrics.templates.label"), value: "22", detail: t("metrics.templates.detail") }, + { label: t("metrics.received.label"), value: "0", detail: t("metrics.received.detail") }, + { label: t("metrics.accepted.label"), value: "0", detail: t("metrics.accepted.detail") }, + { label: t("metrics.validationRuns.label"), value: "0", detail: t("metrics.validationRuns.detail") }, + { label: t("metrics.displaySections.label"), value: "8", detail: t("metrics.displaySections.detail") }, + ]; + + return ( +
+
+
+
+
+

{t("subtitle")}

+
+
+ + {t("badge")} + + + {t("openIwooos")} +
+
+ +
+ {metrics.map((item) => ( +
+

{item.label}

+

{item.value}

+

{item.detail}

+
+ ))} +
+ +
+
+

{t("detailRefsTitle")}

+
+ {ownerResponseValidationDetailRefs.map((item) => ( +
+ {item.phase} +
+

+ {t(`detailRefs.${item.key}.title` as never)} +

+

+ {t(`detailRefs.${item.key}.detail` as never)} +

+

{item.contract}

+
+
+ ))} +
+
+ +
+
+
+

{t("boundaryTitle")}

+

{t("boundaryDetail")}

+
+ {ownerResponseValidationDetailBoundaries.map((item) => ( +
+ {item} +
+ ))} +
+
+
+
+ ); +} + function EventDossierPanel({ dossier, locale, @@ -907,6 +1046,8 @@ export default function RunDetailPage({ + + None: awooop_runs_page = ( root / "apps" / "web" / "src" / "app" / "[locale]" / "awooop" / "runs" / "page.tsx" ).read_text(encoding="utf-8") + awooop_run_detail_page = ( + root / "apps" / "web" / "src" / "app" / "[locale]" / "awooop" / "runs" / "[run_id]" / "page.tsx" + ).read_text(encoding="utf-8") awooop_approvals_page = ( root / "apps" / "web" / "src" / "app" / "[locale]" / "awooop" / "approvals" / "page.tsx" ).read_text(encoding="utf-8") @@ -311,6 +314,7 @@ def validate(root: Path) -> None: "s2_75_awooop_approvals_owner_response_validation_boundary", "s2_76_awooop_tenants_owner_response_validation_scope", "s2_77_awooop_runs_owner_response_validation_boundary", + "s2_78_awooop_run_detail_owner_response_validation_boundary", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -455,6 +459,11 @@ def validate(root: Path) -> None: [item["action_id"] for item in rollup["next_safe_actions"] if isinstance(item, dict)], "show_awooop_runs_owner_response_validation_boundary", ) + assert_contains( + "rollup.next_safe_actions.action_ids", + [item["action_id"] for item in rollup["next_safe_actions"] if isinstance(item, dict)], + "show_awooop_run_detail_owner_response_validation_boundary", + ) assert_contains( "rollup.next_safe_actions.action_ids", [item["action_id"] for item in rollup["next_safe_actions"] if isinstance(item, dict)], @@ -5966,6 +5975,85 @@ def validate(root: Path) -> None: key, ) + assert_text_contains( + "awooop_run_detail_page.owner_response_validation_detail_boundary_panel", + awooop_run_detail_page, + "OwnerResponseValidationDetailBoundaryPanel", + ) + assert_text_contains( + "awooop_run_detail_page.owner_response_validation_detail_refs", + awooop_run_detail_page, + "ownerResponseValidationDetailRefs", + ) + assert_text_contains( + "awooop_run_detail_page.owner_response_validation_detail_iwooos_link", + awooop_run_detail_page, + 'href="/iwooos"', + ) + for text in [ + "source_control_owner_response_validation_rollup_v1", + "gitea_inventory_owner_attestation_response_v1", + "github_target_owner_decision_response_v1", + "source_control_ref_truth_owner_response_v1", + "source_control_workflow_secret_name_owner_response_v1", + "owner_response_validation_received_count=0", + "owner_response_validation_accepted_count=0", + "owner_response_validation_rejected_count=0", + "run_detail_owner_response_linked=false", + "run_detail_approval_record_created=false", + "mcp_execution_authorized=false", + "remediation_execution_authorized=false", + "platform_run_creation_authorized=false", + "execution_router_linked=false", + "repo_creation_authorized=false", + "refs_sync_authorized=false", + "workflow_modification_authorized=false", + "secret_value_collection_allowed=false", + "github_primary_switch_authorized=false", + "runtime_execution_authorized=false", + "action_buttons_allowed=false", + ]: + assert_text_contains("awooop_run_detail_page.owner_response_validation_detail_boundary", awooop_run_detail_page, text) + for key in [ + "title", + "subtitle", + "badge", + "openIwooos", + "detailRefsTitle", + "boundaryLabel", + "boundaryTitle", + "boundaryDetail", + "metrics", + "detailRefs", + ]: + assert_contains( + "web_messages.zh-TW.awooop.runDetail.ownerResponseValidationDetailBoundary", + list(web_messages_zh["awooop"]["runDetail"]["ownerResponseValidationDetailBoundary"].keys()), + key, + ) + assert_contains( + "web_messages.en.awooop.runDetail.ownerResponseValidationDetailBoundary", + list(web_messages_en["awooop"]["runDetail"]["ownerResponseValidationDetailBoundary"].keys()), + key, + ) + for key in [ + "validationRollup", + "giteaAttestation", + "githubTarget", + "refsTruth", + "workflowSecret", + ]: + assert_contains( + "web_messages.zh-TW.awooop.runDetail.ownerResponseValidationDetailBoundary.detailRefs", + list(web_messages_zh["awooop"]["runDetail"]["ownerResponseValidationDetailBoundary"]["detailRefs"].keys()), + key, + ) + assert_contains( + "web_messages.en.awooop.runDetail.ownerResponseValidationDetailBoundary.detailRefs", + list(web_messages_en["awooop"]["runDetail"]["ownerResponseValidationDetailBoundary"]["detailRefs"].keys()), + key, + ) + assert_text_contains("awooop_approvals_page.security_owner_response_panel", awooop_approvals_page, "SecurityOwnerResponseGatePanel") assert_text_contains("awooop_approvals_page.iwooos_link", awooop_approvals_page, 'href="/iwooos"') for text in [