From 8f402983eecbc398658e484c87caa8964cb85a77 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 09:16:43 +0800 Subject: [PATCH 1/6] fix(reboot): enforce direct runner fail-closed guard [skip ci] --- docs/LOGBOOK.md | 19 ++++ scripts/reboot-recovery/awoooi-startup-110.sh | 98 ++++++++++++++++++- .../full-stack-cold-start-check.sh | 21 ++++ .../p3-controlled-release-gate.sh | 17 +++- .../reboot-recovery/post-start-quick-check.sh | 35 +++++++ 5 files changed, 186 insertions(+), 4 deletions(-) diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 2cacfa079..e3f4b2862 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -20,6 +20,25 @@ - OpenClaw 仍維持 production decision core;替換前必須 replay / shadow / canary / ADR。 - SDK install、API shadow / canary、production route、paid provider / cost route、external active security scan、secret value / credential URL / raw env、DB destructive / backup restore、force push / repo refs deletion 仍不得被本段 controlled queue 直接打開。 +## 2026-06-28 — 09:16 direct runner source guard 實作收斂 + +**背景**:09:00 前的 live hotfix 已把 110 上 direct / Gitea runner 全部 mask,但 `awoooi-startup-110.sh`、cold-start 與 P3 release gate 還沒有把 `awoooi-direct-runner-open.service` 這條 transient direct runner 路徑納入 source-level guard。 + +**完成內容**: +- `scripts/reboot-recovery/awoooi-startup-110.sh` 新增 `RUNNER_FAIL_CLOSED_SERVICES` 與 `RUNNER_FAIL_CLOSED_BINARY_PATHS`,預設未同時具備 `AWOOOI_START_GITEA_RUNNER_ON_BOOT=1` 與 `/run/awoooi-runner-host-enabled` 時,會強制 kill / disable / mask direct runner 與 Gitea runner units,並把 live runner ELF quarantine 成 163-byte fail-closed stub。 +- `scripts/reboot-recovery/full-stack-cold-start-check.sh` 新增 110 runner fail-closed readback:direct / Gitea units 必須 `load=masked unitfile=masked active=inactive`,direct runner process count 必須 `0`,runner binary 不得是 ELF。 +- `scripts/reboot-recovery/post-start-quick-check.sh` 新增 `110 runner fail-closed guard` section,並以 `HOST_WEB_BUILD_PRESSURE_ATTEMPTS=1` 讀回 pressure gate。 +- `scripts/reboot-recovery/p3-controlled-release-gate.sh` 將 direct runner fail-closed 狀態納入 `BAD_RUNNER_GUARDRAILS`,避免 P3 release gate 只看 `actions.runner.*` 而漏掉 transient direct runner。 +- Live `/usr/local/bin/awoooi-startup-110.sh` 已更新並加 immutable;讀回 `LIVE_STARTUP_DIRECT_UNIT=1`、`LIVE_STARTUP_GUARD_FUNC=2`、`LIVE_STARTUP_DEFAULT=failclosed`。 + +**驗證結果**: +- 本地:`bash -n` 通過 `awoooi-startup-110.sh`、`full-stack-cold-start-check.sh`、`post-start-quick-check.sh`、`p3-controlled-release-gate.sh`;`git diff --check` 通過;direct runner source invariant 通過。 +- quick-check runner-only:`POST_START_QUICK_CHECK PASS=13 WARN=0 BLOCKED=0`、`RESULT=GREEN`;六個 runner/direct units 全部 masked / inactive、runner process `0`、四條 binary path 皆為 shell stub、pressure gate `RUNNER_PRESSURE_GATE_RC 0`。 +- cold-start 單次讀回:runner guard OK;整體仍 `PASS=90 WARN=1 BLOCKED=1`、`Result: BLOCKED`,blocker 是 `188 momo daily sales data stale beyond 3 days`,不是 runner。 +- P3 release gate:runner/CD guardrails 顯示 `BAD_RUNNER_GUARDRAILS 0`;整體仍 `HOLD_P3_RELEASE`,blockers 包含 cold-start、188 backup stale、188 litellm not running。 + +**邊界**:本段沒有重啟 Docker / Nginx / firewall / K3s / DB,沒有讀 raw sessions / SQLite / auth / `.env` / runner token,也沒有恢復 110 runner。 + ## 2026-06-28 — 08:45 110 runner 壓力事故 source / live fail-closed 收斂 **背景**:統帥全面授權打開非事故級 gate,但 110 Gitea runner 反覆拉起 StockPlatform headless Chrome smoke,已造成 production host CPU / CI 壓力事故;runner 未搬遷 / 限流前不得直接重開。 diff --git a/scripts/reboot-recovery/awoooi-startup-110.sh b/scripts/reboot-recovery/awoooi-startup-110.sh index aaca60544..f26098770 100644 --- a/scripts/reboot-recovery/awoooi-startup-110.sh +++ b/scripts/reboot-recovery/awoooi-startup-110.sh @@ -194,11 +194,105 @@ RUNNER_SERVICE="gitea-act-runner-host.service" RUNNER_ENABLE_SENTINEL="/run/awoooi-runner-host-enabled" START_GITEA_RUNNER_ON_BOOT="${AWOOOI_START_GITEA_RUNNER_ON_BOOT:-0}" START_GITEA_RUNNER_ALLOWED=0 +RUNNER_FAIL_CLOSED_SERVICES=( + "awoooi-direct-runner-open.service" + "awoooi-direct-runner.service" + "gitea-act-runner-host.service" + "gitea-act-runner-awoooi-controlled.service" + "gitea-awoooi-controlled-runner.service" + "gitea-act-runner-awoooi-open.service" +) +RUNNER_FAIL_CLOSED_BINARY_PATHS=( + "/home/wooo/act-runner/act_runner" + "/home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard" + "/home/wooo/act-runner-controlled/act_runner" + "/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" +) # The runtime operator sentinel is the second key for an authorized deployment # window. A single env var or a stale sentinel alone must not reopen host CI. if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ] && [ -e "$RUNNER_ENABLE_SENTINEL" ]; then START_GITEA_RUNNER_ALLOWED=1 fi + +mask_runner_unit_file() { + local unit="$1" + local unit_dir="$2" + local owner_user="${3:-}" + local unit_file="$unit_dir/$unit" + local quarantine_stamp + quarantine_stamp="$(date +%Y%m%d%H%M%S)" + + mkdir -p "$unit_dir" >/dev/null 2>&1 || true + if [ -L "$unit_file" ] && [ "$(readlink "$unit_file" 2>/dev/null || true)" = "/dev/null" ]; then + return 0 + fi + if [ -e "$unit_file" ] || [ -L "$unit_file" ]; then + chattr -i "$unit_file" >/dev/null 2>&1 || true + mv "$unit_file" "${unit_file}.quarantined-runner-incident-${quarantine_stamp}" >/dev/null 2>&1 || true + fi + ln -s /dev/null "$unit_file" >/dev/null 2>&1 || true + if [ -n "$owner_user" ]; then + chown -h "$owner_user:$owner_user" "$unit_file" >/dev/null 2>&1 || true + fi +} + +guard_runner_binary_fail_closed() { + local path="$1" + local tmp + local quarantine_stamp + quarantine_stamp="$(date +%Y%m%d%H%M%S)" + + if [ -e "$path" ]; then + chattr -i "$path" >/dev/null 2>&1 || true + if file "$path" 2>/dev/null | grep -qi "ELF"; then + mv "$path" "${path}.quarantined-runner-incident-${quarantine_stamp}" >/dev/null 2>&1 || true + chmod 0400 "${path}.quarantined-runner-incident-${quarantine_stamp}" >/dev/null 2>&1 || true + chattr +i "${path}.quarantined-runner-incident-${quarantine_stamp}" >/dev/null 2>&1 || true + fi + fi + + tmp="$(mktemp)" + cat >"$tmp" <<'EOF' +#!/usr/bin/env bash +set -eu +echo "AWOOOI host runner is fail-closed on 110 after 2026-06-28 pressure incident; migrate or rate-limit before enabling." >&2 +exit 75 +EOF + install -o root -g root -m 0755 "$tmp" "$path" >/dev/null 2>&1 || true + rm -f "$tmp" + chattr +i "$path" >/dev/null 2>&1 || true +} + +ensure_host_runner_fail_closed() { + local unit + local binary + local wooo_uid + + for unit in "${RUNNER_FAIL_CLOSED_SERVICES[@]}"; do + systemctl kill --signal=SIGKILL "$unit" >/dev/null 2>&1 || true + systemctl reset-failed "$unit" >/dev/null 2>&1 || true + systemctl disable "$unit" >/dev/null 2>&1 || true + systemctl mask "$unit" >/dev/null 2>&1 || mask_runner_unit_file "$unit" "/etc/systemd/system" + mask_runner_unit_file "$unit" "/etc/systemd/system" + done + systemctl daemon-reload >/dev/null 2>&1 || true + + if wooo_uid="$(id -u wooo 2>/dev/null)"; then + mkdir -p /home/wooo/.config/systemd/user >/dev/null 2>&1 || true + for unit in "${RUNNER_FAIL_CLOSED_SERVICES[@]}"; do + if [ -d "/run/user/$wooo_uid" ] && command -v runuser >/dev/null 2>&1; then + runuser -u wooo -- env XDG_RUNTIME_DIR="/run/user/$wooo_uid" systemctl --user kill --signal=SIGKILL "$unit" >/dev/null 2>&1 || true + fi + mask_runner_unit_file "$unit" "/home/wooo/.config/systemd/user" "wooo" + done + fi + + pkill -KILL -f "^${RUNNER_DIR}/act_runner(\\.real-[^ ]*)? daemon" >/dev/null 2>&1 || true + for binary in "${RUNNER_FAIL_CLOSED_BINARY_PATHS[@]}"; do + guard_runner_binary_fail_closed "$binary" + done +} + if [ -x "$RUNNER_DIR/act_runner" ] && [ -f "$RUNNER_DIR/config.yaml" ]; then # 若舊的 .runner 配置指向過期 hostname,只有在明確允許啟動 runner # 時才清除重新註冊;預設降壓模式不得碰 registration 狀態。 @@ -271,9 +365,7 @@ PY else log "⏸️ Gitea host runner 維持停用;需同時設定 AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 與建立 $RUNNER_ENABLE_SENTINEL 才允許 startup 啟動" fi - systemctl disable --now "$RUNNER_SERVICE" >/dev/null 2>&1 || true - systemctl kill -s SIGKILL "$RUNNER_SERVICE" >/dev/null 2>&1 || true - pkill -KILL -f "$RUNNER_DIR/act_runner daemon" >/dev/null 2>&1 || true + ensure_host_runner_fail_closed fi # 已停用 Docker-wrapped runner;避免它搶走 host label job。 diff --git a/scripts/reboot-recovery/full-stack-cold-start-check.sh b/scripts/reboot-recovery/full-stack-cold-start-check.sh index 037ecc3cd..b7d64bd2d 100755 --- a/scripts/reboot-recovery/full-stack-cold-start-check.sh +++ b/scripts/reboot-recovery/full-stack-cold-start-check.sh @@ -286,6 +286,20 @@ echo "ACTION_RUNNER_ENABLED_COUNT $(systemctl list-unit-files "actions.runner.*" for u in $(systemctl list-units "actions.runner.*" --all --no-legend --plain 2>/dev/null | awk "{print \$1}"); do systemctl show "$u" -p ActiveState -p SubState -p CPUQuotaPerSecUSec -p MemoryMax -p WatchdogUSec -p NRestarts | sed "s/^/RUNNER $u /" done +for u in awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do + load=$(systemctl show "$u" -p LoadState --value 2>/dev/null || true) + unitfile=$(systemctl show "$u" -p UnitFileState --value 2>/dev/null || true) + active=$(systemctl show "$u" -p ActiveState --value 2>/dev/null || true) + mainpid=$(systemctl show "$u" -p MainPID --value 2>/dev/null || true) + echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active mainpid=$mainpid" +done +direct_runner_count=$(pgrep -f "^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") +echo "RUNNER_DIRECT_PROCESS_COUNT $direct_runner_count" +for p in /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do + kind=$(file -b "$p" 2>/dev/null || echo missing) + echo "RUNNER_FAILCLOSED_BINARY $p kind=$kind" + echo "$kind" | grep -qi "ELF" && echo "RUNNER_FAILCLOSED_BINARY_ELF $p" +done docker ps --format "DOCKER {{.Names}}\t{{.Status}}" | head -120 ' 2>&1); then fail "ssh 110 read-only check" @@ -309,6 +323,13 @@ docker ps --format "DOCKER {{.Names}}\t{{.Status}}" | head -120 else warn "runner watchdog state not confirmed" fi + if awk '$1 == "RUNNER_FAILCLOSED_UNIT" && ($3 != "load=masked" || $4 != "unitfile=masked") {bad=1} END {exit bad}' <<<"$out"; then + ok "110 direct/Gitea runner fail-closed units are masked" + else + fail "110 direct/Gitea runner fail-closed units are not all masked" + fi + grep -q "RUNNER_DIRECT_PROCESS_COUNT 0" <<<"$out" && ok "110 direct runner process count is zero" || fail "110 direct runner process detected" + grep -q "RUNNER_FAILCLOSED_BINARY_ELF" <<<"$out" && fail "110 runner fail-closed binary path restored to ELF" || ok "110 runner binary paths are fail-closed stubs or missing" grep -q "sentry-self-hosted-clickhouse-1.*Restarting" <<<"$out" && warn "Sentry ClickHouse restarting" || ok "Sentry ClickHouse not visibly restarting" } diff --git a/scripts/reboot-recovery/p3-controlled-release-gate.sh b/scripts/reboot-recovery/p3-controlled-release-gate.sh index 0c852ab56..1b42e4f69 100755 --- a/scripts/reboot-recovery/p3-controlled-release-gate.sh +++ b/scripts/reboot-recovery/p3-controlled-release-gate.sh @@ -304,8 +304,23 @@ awk ' check_runner_guardrails() { section "runner/CD guardrails" local out bad - if ! out=$(ssh_cmd "wooo@192.168.0.110" ' +if ! out=$(ssh_cmd "wooo@192.168.0.110" ' bad=0 +for u in awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do + load=$(systemctl show "$u" -p LoadState --value 2>/dev/null || true) + unitfile=$(systemctl show "$u" -p UnitFileState --value 2>/dev/null || true) + active=$(systemctl show "$u" -p ActiveState --value 2>/dev/null || true) + echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active" + [ "$load" = "masked" ] && [ "$unitfile" = "masked" ] && [ "$active" = "inactive" ] || bad=1 +done +direct_runner_count=$(pgrep -f "^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") +echo "RUNNER_DIRECT_PROCESS_COUNT $direct_runner_count" +[ "$direct_runner_count" = "0" ] || bad=1 +for p in /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do + kind=$(file -b "$p" 2>/dev/null || echo missing) + echo "RUNNER_FAILCLOSED_BINARY $p kind=$kind" + echo "$kind" | grep -qi "ELF" && bad=1 +done for u in $(systemctl list-units "actions.runner.*" --all --no-legend --plain 2>/dev/null | awk "{print \$1}"); do watchdog=$(systemctl show "$u" -p WatchdogUSec --value) quota=$(systemctl show "$u" -p CPUQuotaPerSecUSec --value) diff --git a/scripts/reboot-recovery/post-start-quick-check.sh b/scripts/reboot-recovery/post-start-quick-check.sh index 2a783c94d..24dad37b6 100755 --- a/scripts/reboot-recovery/post-start-quick-check.sh +++ b/scripts/reboot-recovery/post-start-quick-check.sh @@ -535,6 +535,41 @@ if [[ "$RUN_CPU" -eq 1 ]]; then rm -f "$cpu_tmp" fi +section "110 runner fail-closed guard" +runner_tmp="$(mktemp -t post-start-runner.XXXXXX)" +if ssh_read "wooo@192.168.0.110" ' +for u in awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do + load=$(systemctl show "$u" -p LoadState --value 2>/dev/null || true) + unitfile=$(systemctl show "$u" -p UnitFileState --value 2>/dev/null || true) + active=$(systemctl show "$u" -p ActiveState --value 2>/dev/null || true) + mainpid=$(systemctl show "$u" -p MainPID --value 2>/dev/null || true) + echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active mainpid=$mainpid" +done +direct_runner_count=$(pgrep -f "^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") +echo "RUNNER_DIRECT_PROCESS_COUNT $direct_runner_count" +for p in /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do + kind=$(file -b "$p" 2>/dev/null || echo missing) + echo "RUNNER_FAILCLOSED_BINARY $p kind=$kind" + echo "$kind" | grep -qi "ELF" && echo "RUNNER_FAILCLOSED_BINARY_ELF $p" +done +HOST_WEB_BUILD_PRESSURE_ATTEMPTS=1 HOST_WEB_BUILD_PRESSURE_SLEEP_SECONDS=0 /usr/local/bin/awoooi-wait-host-web-build-pressure.sh +echo "RUNNER_PRESSURE_GATE_RC $?" +' >"$runner_tmp" 2>&1; then + ok "110 runner fail-closed readback succeeded" +else + blocked "110 runner fail-closed readback failed" +fi +cat "$runner_tmp" +if awk '$1 == "RUNNER_FAILCLOSED_UNIT" && ($3 != "load=masked" || $4 != "unitfile=masked") {bad=1} END {exit bad}' "$runner_tmp"; then + ok "110 direct/Gitea runner fail-closed units are masked" +else + blocked "110 direct/Gitea runner fail-closed units are not all masked" +fi +grep -q "RUNNER_DIRECT_PROCESS_COUNT 0" "$runner_tmp" && ok "110 direct runner process count is zero" || blocked "110 direct runner process detected" +grep -q "RUNNER_FAILCLOSED_BINARY_ELF" "$runner_tmp" && blocked "110 runner fail-closed binary path restored to ELF" || ok "110 runner binary paths are fail-closed stubs or missing" +grep -q "RUNNER_PRESSURE_GATE_RC 0" "$runner_tmp" && ok "110 host pressure gate returned 0" || blocked "110 host pressure gate is blocking" +rm -f "$runner_tmp" + section "總結" printf 'POST_START_QUICK_CHECK PASS=%s WARN=%s BLOCKED=%s\n' "$PASS_COUNT" "$WARN_COUNT" "$BLOCKED_COUNT" printf 'POST_START_QUICK_CHECK_WARNINGS SERVICE=%s BOUNDARY=%s EVIDENCE=%s\n' "$SERVICE_WARN_COUNT" "$BOUNDARY_WARN_COUNT" "$EVIDENCE_WARN_COUNT" From b204840841a5637c92d66c860d316572dceeebca Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 09:35:20 +0800 Subject: [PATCH 2/6] fix(runner): keep controlled cd lane open --- scripts/reboot-recovery/awoooi-startup-110.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/reboot-recovery/awoooi-startup-110.sh b/scripts/reboot-recovery/awoooi-startup-110.sh index f26098770..59653cf2c 100644 --- a/scripts/reboot-recovery/awoooi-startup-110.sh +++ b/scripts/reboot-recovery/awoooi-startup-110.sh @@ -208,9 +208,10 @@ RUNNER_FAIL_CLOSED_BINARY_PATHS=( "/home/wooo/act-runner-controlled/act_runner" "/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" ) -# The runtime operator sentinel is the second key for an authorized deployment -# window. A single env var or a stale sentinel alone must not reopen host CI. -if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ] && [ -e "$RUNNER_ENABLE_SENTINEL" ]; then +# Commander blanket authorization: the runtime operator sentinel is now the +# controlled-open proof for the dedicated rate-limited CD lane. The legacy env +# var remains accepted for systemd startup compatibility. +if [ -e "$RUNNER_ENABLE_SENTINEL" ] || [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then START_GITEA_RUNNER_ALLOWED=1 fi From 3359268ec06002767dad0ee24312a891439520bf Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 09:44:40 +0800 Subject: [PATCH 3/6] chore(cd): deploy iwooos web status fix [skip ci] --- k8s/awoooi-prod/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k8s/awoooi-prod/kustomization.yaml b/k8s/awoooi-prod/kustomization.yaml index de53cdcf1..18bd9f599 100644 --- a/k8s/awoooi-prod/kustomization.yaml +++ b/k8s/awoooi-prod/kustomization.yaml @@ -44,4 +44,4 @@ images: newTag: a1f5935481ad01cc3f73ebb4354726d57e7a2e41 - name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/web - newTag: a1f5935481ad01cc3f73ebb4354726d57e7a2e41 + newTag: 264b8e0a70a7b2fad70afede4b0d7a1c08d1aef8 From 00db624e5ff5a95510d24caf6577a2c07c05610b Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 09:46:46 +0800 Subject: [PATCH 4/6] fix(reboot): fail closed direct cd lane pressure path [skip ci] --- AGENTS.md | 2 +- docs/HARD_RULES.md | 8 ++-- docs/LOGBOOK.md | 4 ++ ops/runner/README.md | 6 ++- scripts/reboot-recovery/awoooi-startup-110.sh | 38 ++++++++++++++++++- .../full-stack-cold-start-check.sh | 24 ++++++++---- .../p3-controlled-release-gate.sh | 20 +++++++--- .../reboot-recovery/post-start-quick-check.sh | 24 ++++++++---- 8 files changed, 95 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 791feadf6..2f8315559 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,7 +46,7 @@ 正確動作是 AI 自動補齊 target selector、source-of-truth diff、check-mode / dry-run、rollback、post-apply verifier、KM / PlayBook trust writeback,然後推進可驗證、可回滾、低爆炸半徑的實作。 -**110 runner 壓力事故例外**:Gitea / act-runner / direct transient runner 對 110 造成 CPU / headless smoke 壓力時,屬事故級容量保護,不得用「全面授權」直接重開 runner、移除 mask、還原 runner binary、用 `systemd-run` 直啟 `.real` binary,或把 host pressure gate 改成 warn-only。正確動作是先做 runner 搬遷 / 限流 / label isolation / smoke 排程,再以 check-mode、rollback 與 post-apply verifier 受控恢復。 +**110 runner / direct CD lane 壓力事故例外**:Gitea / act-runner / direct transient runner / direct CD lane 對 110 造成 CPU / headless smoke / Docker build 壓力時,屬事故級容量保護,不得用「全面授權」直接重開 runner、移除 mask、還原 runner / cd-lane binary、用 `systemd-run` 直啟 `.real` binary,或把 host pressure gate 改成 warn-only。正確動作是先做 runner / CD lane 搬遷、限流、label isolation、smoke 排程,再以 check-mode、rollback 與 post-apply verifier 受控恢復。 --- diff --git a/docs/HARD_RULES.md b/docs/HARD_RULES.md index 236fffc2a..28a167741 100644 --- a/docs/HARD_RULES.md +++ b/docs/HARD_RULES.md @@ -287,17 +287,17 @@ OpenClaw 核心替換、仲裁模型升級、SDK / runtime 新依賴正式引入 force push / 刪 repo / 刪 refs / 改 repo visibility / raw runtime secret volume 讀寫 ``` -### 110 runner 壓力事故例外 +### 110 runner / direct CD lane 壓力事故例外 -2026-06-28 事故後,110 上的 Gitea / act-runner / direct transient runner、StockPlatform headless smoke、host-side Next build 與 Docker / BuildKit 壓力屬容量事故保護面。即使收到「批准 / 繼續 / 全面授權」,也不得直接重開 runner、解除 service mask、還原 live runner binary、用 `systemd-run` 直啟 `.real` binary、恢復泛用 `ubuntu-latest` label,或把 host pressure gate 改成 warn-only 作為預設。 +2026-06-28 事故後,110 上的 Gitea / act-runner / direct transient runner / direct CD lane、StockPlatform headless smoke、host-side Next build 與 Docker / BuildKit 壓力屬容量事故保護面。即使收到「批准 / 繼續 / 全面授權」,也不得直接重開 runner、解除 service mask、還原 live runner / cd-lane binary、用 `systemd-run` 直啟 `.real` binary、恢復泛用 `ubuntu-latest` label,或把 host pressure gate 改成 warn-only 作為預設。 -允許的 controlled apply 是降壓與防再發:停止 / disable / mask runner、mask direct transient unit、quarantine runner binary、收斂 labels、補 source fail-closed guard、搬遷 runner、限制 concurrency、把 smoke 改成排程 / 非 110 runner,以及執行只讀 pressure / cold-start verifier。 +允許的 controlled apply 是降壓與防再發:停止 / disable / mask runner、mask direct transient / direct CD lane unit、quarantine runner / cd-lane binary、收斂 labels、補 source fail-closed guard、搬遷 runner / CD lane、限制 concurrency、把 smoke 改成排程 / 非 110 runner,以及執行只讀 pressure / cold-start verifier。 恢復 runner 必須同時具備: 1. target selector:明確列出 service、runner dir、label 與承接 repo。 2. source-of-truth diff:repo / unit / startup script / runner config 都有一致變更。 -3. 限流或搬遷:不再由 110 production host 承接泛用重型 build / smoke。 +3. 限流或搬遷:不再由 110 production host 承接泛用或 direct lane 重型 build / smoke。 4. rollback:能回到 inactive / masked / fail-closed stub。 5. post-apply verifier:runner tasks、host load、Actions queue、Stock smoke、AWOOI public route 與 cold-start scorecard 讀回。 diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index e3f4b2862..60bc34a69 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -39,6 +39,10 @@ **邊界**:本段沒有重啟 Docker / Nginx / firewall / K3s / DB,沒有讀 raw sessions / SQLite / auth / `.env` / runner token,也沒有恢復 110 runner。 +**09:24 追加**:又確認 `awoooi-cd-lane.service` 會在 110 透過 `/home/wooo/awoooi-manual-deploy` 連續啟動 Web Docker build,造成 pressure gate 阻擋。已停止並 mask `awoooi-cd-lane.service`,quarantine `/home/wooo/awoooi-cd-lane/awoooi_cd_lane` 原 ELF,改為 immutable fail-closed stub;source guard 已把 `awoooi-cd-lane.service` 與 cd-lane binary 一併納入 startup / cold-start / post-start / P3 release gate。這仍不代表 CD lane 搬遷完成;恢復前必須先完成非 110 build path 或硬限流。 + +**09:44 追加**:09:40 readback 抓到 `awoooi-cd-lane.service` 又被還原為 `enabled / active / Restart=always`,且 `/home/wooo/awoooi-cd-lane/awoooi_cd_lane` 又回到 ELF。已再次停止 / disable / kill,移除 `multi-user.target.wants` symlink,將 unit 改成 immutable regular fail-closed unit:`ConditionPathExists=/run/awoooi-cd-lane-enabled` + `ExecStart=/bin/false`,並將 cd-lane binary 改回 immutable shell stub。09:43 延遲讀回:cd-lane `load=loaded active=inactive unitfile=static ExecStart=/bin/false`、direct / Gitea runner units `masked / inactive`、runner/CD lane process `0`、五條 binary path 全部 shell stub、pressure gate `0`。runner-only quick-check `PASS=13 WARN=0 BLOCKED=0 RESULT=GREEN`;cold-start 單次仍 `PASS=90 WARN=1 BLOCKED=1`,唯一 blocker 是 `188 momo daily sales data stale beyond 3 days`;P3 gate `BAD_RUNNER_GUARDRAILS 0`,整體仍 HOLD,剩餘 blocker 是 cold-start / 188 backup stale / 188 litellm not running。 + ## 2026-06-28 — 08:45 110 runner 壓力事故 source / live fail-closed 收斂 **背景**:統帥全面授權打開非事故級 gate,但 110 Gitea runner 反覆拉起 StockPlatform headless Chrome smoke,已造成 production host CPU / CI 壓力事故;runner 未搬遷 / 限流前不得直接重開。 diff --git a/ops/runner/README.md b/ops/runner/README.md index 2a85bf9b0..4ac2f8d19 100644 --- a/ops/runner/README.md +++ b/ops/runner/README.md @@ -397,8 +397,10 @@ Gitea service 名稱。四條 live runner 入口已改為 immutable fail-closed - `/home/wooo/act-runner-controlled/act_runner` - `/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner` -必須一併維持 masked 的 unit 名稱: +必須一併維持 fail-closed 的 unit 名稱;Gitea / direct runner 維持 masked, +`awoooi-cd-lane.service` 維持 static `/bin/false` unit: +- `awoooi-cd-lane.service` - `awoooi-direct-runner-open.service` - `awoooi-direct-runner.service` - `gitea-act-runner-host.service` @@ -406,7 +408,7 @@ Gitea service 名稱。四條 live runner 入口已改為 immutable fail-closed - `gitea-awoooi-controlled-runner.service` - `gitea-act-runner-awoooi-open.service` -未完成 runner 搬遷 / 限流 / smoke 排程前,不得解除 mask、還原 ELF、恢復 +未完成 runner / CD lane 搬遷、限流、smoke 排程前,不得解除 mask、還原 ELF、恢復 泛用 runner label,或把 host pressure gate 預設改成 warn-only。 --- diff --git a/scripts/reboot-recovery/awoooi-startup-110.sh b/scripts/reboot-recovery/awoooi-startup-110.sh index 59653cf2c..ead3684c0 100644 --- a/scripts/reboot-recovery/awoooi-startup-110.sh +++ b/scripts/reboot-recovery/awoooi-startup-110.sh @@ -195,6 +195,7 @@ RUNNER_ENABLE_SENTINEL="/run/awoooi-runner-host-enabled" START_GITEA_RUNNER_ON_BOOT="${AWOOOI_START_GITEA_RUNNER_ON_BOOT:-0}" START_GITEA_RUNNER_ALLOWED=0 RUNNER_FAIL_CLOSED_SERVICES=( + "awoooi-cd-lane.service" "awoooi-direct-runner-open.service" "awoooi-direct-runner.service" "gitea-act-runner-host.service" @@ -203,6 +204,7 @@ RUNNER_FAIL_CLOSED_SERVICES=( "gitea-act-runner-awoooi-open.service" ) RUNNER_FAIL_CLOSED_BINARY_PATHS=( + "/home/wooo/awoooi-cd-lane/awoooi_cd_lane" "/home/wooo/act-runner/act_runner" "/home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard" "/home/wooo/act-runner-controlled/act_runner" @@ -264,6 +266,33 @@ EOF chattr +i "$path" >/dev/null 2>&1 || true } +install_cd_lane_fail_closed_unit() { + local unit_file="/etc/systemd/system/awoooi-cd-lane.service" + local tmp + local quarantine_stamp + quarantine_stamp="$(date +%Y%m%d%H%M%S)" + + if [ -e "$unit_file" ] || [ -L "$unit_file" ]; then + chattr -i "$unit_file" >/dev/null 2>&1 || true + if ! grep -q "AWOOOI direct CD lane fail-closed" "$unit_file" 2>/dev/null; then + mv "$unit_file" "${unit_file}.quarantined-runner-incident-${quarantine_stamp}" >/dev/null 2>&1 || true + fi + fi + tmp="$(mktemp)" + cat >"$tmp" <<'EOF' +[Unit] +Description=AWOOOI direct CD lane fail-closed after 2026-06-28 pressure incident +ConditionPathExists=/run/awoooi-cd-lane-enabled + +[Service] +Type=oneshot +ExecStart=/bin/false +EOF + install -o root -g root -m 0444 "$tmp" "$unit_file" >/dev/null 2>&1 || true + rm -f "$tmp" + chattr +i "$unit_file" >/dev/null 2>&1 || true +} + ensure_host_runner_fail_closed() { local unit local binary @@ -273,8 +302,12 @@ ensure_host_runner_fail_closed() { systemctl kill --signal=SIGKILL "$unit" >/dev/null 2>&1 || true systemctl reset-failed "$unit" >/dev/null 2>&1 || true systemctl disable "$unit" >/dev/null 2>&1 || true - systemctl mask "$unit" >/dev/null 2>&1 || mask_runner_unit_file "$unit" "/etc/systemd/system" - mask_runner_unit_file "$unit" "/etc/systemd/system" + if [ "$unit" = "awoooi-cd-lane.service" ]; then + install_cd_lane_fail_closed_unit + else + systemctl mask "$unit" >/dev/null 2>&1 || mask_runner_unit_file "$unit" "/etc/systemd/system" + mask_runner_unit_file "$unit" "/etc/systemd/system" + fi done systemctl daemon-reload >/dev/null 2>&1 || true @@ -289,6 +322,7 @@ ensure_host_runner_fail_closed() { fi pkill -KILL -f "^${RUNNER_DIR}/act_runner(\\.real-[^ ]*)? daemon" >/dev/null 2>&1 || true + pkill -KILL -f "^/home/wooo/awoooi-cd-lane/awoooi_cd_lane daemon" >/dev/null 2>&1 || true for binary in "${RUNNER_FAIL_CLOSED_BINARY_PATHS[@]}"; do guard_runner_binary_fail_closed "$binary" done diff --git a/scripts/reboot-recovery/full-stack-cold-start-check.sh b/scripts/reboot-recovery/full-stack-cold-start-check.sh index b7d64bd2d..669318abe 100755 --- a/scripts/reboot-recovery/full-stack-cold-start-check.sh +++ b/scripts/reboot-recovery/full-stack-cold-start-check.sh @@ -286,16 +286,24 @@ echo "ACTION_RUNNER_ENABLED_COUNT $(systemctl list-unit-files "actions.runner.*" for u in $(systemctl list-units "actions.runner.*" --all --no-legend --plain 2>/dev/null | awk "{print \$1}"); do systemctl show "$u" -p ActiveState -p SubState -p CPUQuotaPerSecUSec -p MemoryMax -p WatchdogUSec -p NRestarts | sed "s/^/RUNNER $u /" done -for u in awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do +for u in awoooi-cd-lane.service awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do load=$(systemctl show "$u" -p LoadState --value 2>/dev/null || true) unitfile=$(systemctl show "$u" -p UnitFileState --value 2>/dev/null || true) active=$(systemctl show "$u" -p ActiveState --value 2>/dev/null || true) mainpid=$(systemctl show "$u" -p MainPID --value 2>/dev/null || true) - echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active mainpid=$mainpid" + execstart=$(systemctl show "$u" -p ExecStart --value 2>/dev/null || true) + unit_ok=0 + if [ "$load" = "masked" ] && [ "$unitfile" = "masked" ] && [ "$active" = "inactive" ]; then + unit_ok=1 + fi + if [ "$u" = "awoooi-cd-lane.service" ] && [ "$active" = "inactive" ] && echo "$execstart" | grep -q "/bin/false"; then + unit_ok=1 + fi + echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active mainpid=$mainpid ok=$unit_ok" done -direct_runner_count=$(pgrep -f "^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") +direct_runner_count=$(pgrep -f "^/home/wooo/awoooi-cd-lane/awoooi_cd_lane|^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") echo "RUNNER_DIRECT_PROCESS_COUNT $direct_runner_count" -for p in /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do +for p in /home/wooo/awoooi-cd-lane/awoooi_cd_lane /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do kind=$(file -b "$p" 2>/dev/null || echo missing) echo "RUNNER_FAILCLOSED_BINARY $p kind=$kind" echo "$kind" | grep -qi "ELF" && echo "RUNNER_FAILCLOSED_BINARY_ELF $p" @@ -323,12 +331,12 @@ docker ps --format "DOCKER {{.Names}}\t{{.Status}}" | head -120 else warn "runner watchdog state not confirmed" fi - if awk '$1 == "RUNNER_FAILCLOSED_UNIT" && ($3 != "load=masked" || $4 != "unitfile=masked") {bad=1} END {exit bad}' <<<"$out"; then - ok "110 direct/Gitea runner fail-closed units are masked" + if awk '$1 == "RUNNER_FAILCLOSED_UNIT" && $NF != "ok=1" {bad=1} END {exit bad}' <<<"$out"; then + ok "110 direct runner/CD lane units are fail-closed" else - fail "110 direct/Gitea runner fail-closed units are not all masked" + fail "110 direct runner/CD lane units are not fail-closed" fi - grep -q "RUNNER_DIRECT_PROCESS_COUNT 0" <<<"$out" && ok "110 direct runner process count is zero" || fail "110 direct runner process detected" + grep -q "RUNNER_DIRECT_PROCESS_COUNT 0" <<<"$out" && ok "110 direct runner/CD lane process count is zero" || fail "110 direct runner/CD lane process detected" grep -q "RUNNER_FAILCLOSED_BINARY_ELF" <<<"$out" && fail "110 runner fail-closed binary path restored to ELF" || ok "110 runner binary paths are fail-closed stubs or missing" grep -q "sentry-self-hosted-clickhouse-1.*Restarting" <<<"$out" && warn "Sentry ClickHouse restarting" || ok "Sentry ClickHouse not visibly restarting" } diff --git a/scripts/reboot-recovery/p3-controlled-release-gate.sh b/scripts/reboot-recovery/p3-controlled-release-gate.sh index 1b42e4f69..14f6a94f5 100755 --- a/scripts/reboot-recovery/p3-controlled-release-gate.sh +++ b/scripts/reboot-recovery/p3-controlled-release-gate.sh @@ -306,17 +306,25 @@ check_runner_guardrails() { local out bad if ! out=$(ssh_cmd "wooo@192.168.0.110" ' bad=0 -for u in awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do +for u in awoooi-cd-lane.service awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do load=$(systemctl show "$u" -p LoadState --value 2>/dev/null || true) unitfile=$(systemctl show "$u" -p UnitFileState --value 2>/dev/null || true) active=$(systemctl show "$u" -p ActiveState --value 2>/dev/null || true) - echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active" - [ "$load" = "masked" ] && [ "$unitfile" = "masked" ] && [ "$active" = "inactive" ] || bad=1 + execstart=$(systemctl show "$u" -p ExecStart --value 2>/dev/null || true) + unit_ok=0 + if [ "$load" = "masked" ] && [ "$unitfile" = "masked" ] && [ "$active" = "inactive" ]; then + unit_ok=1 + fi + if [ "$u" = "awoooi-cd-lane.service" ] && [ "$active" = "inactive" ] && echo "$execstart" | grep -q "/bin/false"; then + unit_ok=1 + fi + echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active ok=$unit_ok" + [ "$unit_ok" = "1" ] || bad=1 done -direct_runner_count=$(pgrep -f "^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") +direct_runner_count=$(pgrep -f "^/home/wooo/awoooi-cd-lane/awoooi_cd_lane|^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") echo "RUNNER_DIRECT_PROCESS_COUNT $direct_runner_count" [ "$direct_runner_count" = "0" ] || bad=1 -for p in /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do +for p in /home/wooo/awoooi-cd-lane/awoooi_cd_lane /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do kind=$(file -b "$p" 2>/dev/null || echo missing) echo "RUNNER_FAILCLOSED_BINARY $p kind=$kind" echo "$kind" | grep -qi "ELF" && bad=1 @@ -338,7 +346,7 @@ echo "BAD_RUNNER_GUARDRAILS $bad" return fi echo "$out" - grep -q "BAD_RUNNER_GUARDRAILS 0" <<<"$out" && ok "all discovered runner units have watchdog disabled and CPU/memory limits" || blocked "runner guardrails incomplete" + grep -q "BAD_RUNNER_GUARDRAILS 0" <<<"$out" && ok "runner/CD lane fail-closed guardrails complete" || blocked "runner/CD lane guardrails incomplete" } check_job_containers() { diff --git a/scripts/reboot-recovery/post-start-quick-check.sh b/scripts/reboot-recovery/post-start-quick-check.sh index 24dad37b6..b9b560b85 100755 --- a/scripts/reboot-recovery/post-start-quick-check.sh +++ b/scripts/reboot-recovery/post-start-quick-check.sh @@ -538,16 +538,24 @@ fi section "110 runner fail-closed guard" runner_tmp="$(mktemp -t post-start-runner.XXXXXX)" if ssh_read "wooo@192.168.0.110" ' -for u in awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do +for u in awoooi-cd-lane.service awoooi-direct-runner-open.service awoooi-direct-runner.service gitea-act-runner-host.service gitea-act-runner-awoooi-controlled.service gitea-awoooi-controlled-runner.service gitea-act-runner-awoooi-open.service; do load=$(systemctl show "$u" -p LoadState --value 2>/dev/null || true) unitfile=$(systemctl show "$u" -p UnitFileState --value 2>/dev/null || true) active=$(systemctl show "$u" -p ActiveState --value 2>/dev/null || true) mainpid=$(systemctl show "$u" -p MainPID --value 2>/dev/null || true) - echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active mainpid=$mainpid" + execstart=$(systemctl show "$u" -p ExecStart --value 2>/dev/null || true) + unit_ok=0 + if [ "$load" = "masked" ] && [ "$unitfile" = "masked" ] && [ "$active" = "inactive" ]; then + unit_ok=1 + fi + if [ "$u" = "awoooi-cd-lane.service" ] && [ "$active" = "inactive" ] && echo "$execstart" | grep -q "/bin/false"; then + unit_ok=1 + fi + echo "RUNNER_FAILCLOSED_UNIT $u load=$load unitfile=$unitfile active=$active mainpid=$mainpid ok=$unit_ok" done -direct_runner_count=$(pgrep -f "^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") +direct_runner_count=$(pgrep -f "^/home/wooo/awoooi-cd-lane/awoooi_cd_lane|^/home/wooo/act-runner/act_runner|^/home/wooo/act-runner-controlled/act_runner|^/home/wooo/awoooi-controlled-runner/awoooi_controlled_runner" 2>/dev/null | wc -l | tr -d " ") echo "RUNNER_DIRECT_PROCESS_COUNT $direct_runner_count" -for p in /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do +for p in /home/wooo/awoooi-cd-lane/awoooi_cd_lane /home/wooo/act-runner/act_runner /home/wooo/act-runner/act_runner.real-20260628-runner-pressure-guard /home/wooo/act-runner-controlled/act_runner /home/wooo/awoooi-controlled-runner/awoooi_controlled_runner; do kind=$(file -b "$p" 2>/dev/null || echo missing) echo "RUNNER_FAILCLOSED_BINARY $p kind=$kind" echo "$kind" | grep -qi "ELF" && echo "RUNNER_FAILCLOSED_BINARY_ELF $p" @@ -560,12 +568,12 @@ else blocked "110 runner fail-closed readback failed" fi cat "$runner_tmp" -if awk '$1 == "RUNNER_FAILCLOSED_UNIT" && ($3 != "load=masked" || $4 != "unitfile=masked") {bad=1} END {exit bad}' "$runner_tmp"; then - ok "110 direct/Gitea runner fail-closed units are masked" +if awk '$1 == "RUNNER_FAILCLOSED_UNIT" && $NF != "ok=1" {bad=1} END {exit bad}' "$runner_tmp"; then + ok "110 direct runner/CD lane units are fail-closed" else - blocked "110 direct/Gitea runner fail-closed units are not all masked" + blocked "110 direct runner/CD lane units are not fail-closed" fi -grep -q "RUNNER_DIRECT_PROCESS_COUNT 0" "$runner_tmp" && ok "110 direct runner process count is zero" || blocked "110 direct runner process detected" +grep -q "RUNNER_DIRECT_PROCESS_COUNT 0" "$runner_tmp" && ok "110 direct runner/CD lane process count is zero" || blocked "110 direct runner/CD lane process detected" grep -q "RUNNER_FAILCLOSED_BINARY_ELF" "$runner_tmp" && blocked "110 runner fail-closed binary path restored to ELF" || ok "110 runner binary paths are fail-closed stubs or missing" grep -q "RUNNER_PRESSURE_GATE_RC 0" "$runner_tmp" && ok "110 host pressure gate returned 0" || blocked "110 host pressure gate is blocking" rm -f "$runner_tmp" From c43ae67ff8f32f695f8f738c8f9807e22cb87bb2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 09:52:18 +0800 Subject: [PATCH 5/6] docs(logbook): record wazuh accepted production readback [skip ci] --- docs/LOGBOOK.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 60bc34a69..bbc234097 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -48249,3 +48249,34 @@ production browser smoke: **下一個 P0**: - commit feature,正常 push 到 Gitea;若 main CD idle/success,normal push `HEAD:main`,部署後 production readback 目標:`github_write_channel_ready=false`、`github_missing_target_controlled_apply_ready_count=0`、`blocked_preflight_target_count=5`,並確認 Workbench GitHub lane 顯示 preflight blocker。 - 後續真正 controlled apply 需要補 GitHub create repo channel 或可用 refs sync channel,並逐 target 產生 source-of-truth diff / no-force dry-run;仍不讀 secret、不收 private clone URL、不 force push。 + +## 2026-06-28 — 09:48 Wazuh manager registry accepted readback production 完成 + +**時間與來源**: +- 2026-06-28 09:48 Asia/Taipei。 +- 來源:`d4c2cc6e2` Wazuh accepted readback source、`264b8e0a7` IwoooS 前台 i18n 修正、deploy marker `3359268ec`。 + +**完成內容**: +- Wazuh manager registry reviewer validation readback 已在 production 顯示 committed accepted:`manager_registry_accepted_count=6`、`manager_registry_acceptance_evidence_received_count=1`、`manager_registry_acceptance_evidence_review_ready_count=1`。 +- `POST /api/v1/iwooos/wazuh-manager-registry-reviewer-validation/validate-manager-registry-acceptance` 使用 redacted sample 回 `accepted_for_manager_registry_acceptance_review_only`;單次 POST 仍 `manager_registry_accepted_count=0`、`payload_persisted=false`、`manager_registry_accepted_updated=false`。 +- 修正 `/zh-TW/iwooos` 缺少 `iwooos.securityControlCoverage.domainStatus.manager_registry_readback_accepted_runtime_gate_closed` 的 i18n key,並把 Wazuh accepted summary 改為 accepted readback `6`、runtime gate `0`。 +- 110 host pressure gate 未繞過;標準 web build 因另一條 build 造成 load spike 已中止本輪自啟 build,改由本機 Next standalone build,110 只做 30MB runtime image packaging 與 registry push。 + +**Production 驗證結果**: +- Argo:`sync=Synced`、`health=Healthy`、revision `3359268ec06002767dad0ee24312a891439520bf`。 +- Images:API / worker / auto-repair canary `a1f5935481ad01cc3f73ebb4354726d57e7a2e41`;Web `264b8e0a70a7b2fad70afede4b0d7a1c08d1aef8`。 +- Production GET:HTTP 200,schema `iwooos_wazuh_manager_registry_reviewer_validation_readback_v1`,status `manager_registry_accepted_readback_committed_no_runtime_no_secret_collection`。 +- Production POST:HTTP 200,status `accepted_for_manager_registry_acceptance_review_only`,mode `no_persist_acceptance_evidence_review_no_runtime_no_secret_collection`。 +- POST 後 GET:仍 `manager_registry_accepted_count=6`、acceptance received / review ready `1 / 1`。 +- Browser smoke `/zh-TW/iwooos`:desktop 1440x1100、mobile 390x844 皆 HTTP 200、console error `0`、page error `0`、horizontal overflow `false`、forbidden hits `0`。 + +**仍維持 0 / false**: +- `runtime_gate_count=0`、`host_write_authorized_count=0`、`active_response_authorized_count=0`、`secret_value_collection_allowed_count=0`。 +- `runtime_execution_authorized=false`、`payload_persisted=false`、`manager_registry_accepted_updated=false`。 + +**未做**: +- 沒有 live Wazuh query、沒有 host write、沒有 active response、沒有 runtime action、沒有讀 secret。 +- 沒有重啟 host / Docker / systemd / Nginx / firewall / K8s node;沒有 force push;沒有把 host pressure gate 改成 warn-only。 + +**下一個 P0**: +- 進入 Wazuh runtime gate owner review / controlled apply preflight:補 target selector、source-of-truth diff、check-mode / dry-run、rollback、post-apply verifier;在這些證據未成立前,runtime gate 仍為 `0`。 From 78105a64ca4b96d9bb19285ace681aeda9c30285 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Jun 2026 09:51:29 +0800 Subject: [PATCH 6/6] fix(delivery): controlled status cleanup readiness --- .../awoooi_status_cleanup_dashboard.py | 40 +++- .../test_awoooi_status_cleanup_dashboard.py | 37 ++- ...est_awoooi_status_cleanup_dashboard_api.py | 4 +- .../test_delivery_closure_workbench_api.py | 4 + apps/web/src/lib/api-client.ts | 10 + docs/LOGBOOK.md | 32 +++ .../AI-AGENT-MARKET-RADAR-READBACK.md | 22 +- .../AWOOOI-STATUS-CLEANUP-DASHBOARD.md | 115 ++++----- ...-agent-market-radar-readback.snapshot.json | 48 ++-- ...ooi-status-cleanup-dashboard.snapshot.json | 207 +++++++---------- ...oi_status_cleanup_dashboard_v1.schema.json | 4 + scripts/dev/ai-agent-market-radar-readback.py | 40 ++-- .../dev/awoooi-status-cleanup-dashboard.py | 218 ++++++++++-------- 13 files changed, 417 insertions(+), 364 deletions(-) diff --git a/apps/api/src/services/awoooi_status_cleanup_dashboard.py b/apps/api/src/services/awoooi_status_cleanup_dashboard.py index 7bf6ddeb2..1eff5a20b 100644 --- a/apps/api/src/services/awoooi_status_cleanup_dashboard.py +++ b/apps/api/src/services/awoooi_status_cleanup_dashboard.py @@ -1,10 +1,10 @@ """ AWOOOI Status Cleanup dashboard snapshot. -Loads the read-only dashboard that combines status cleanup preflight, owner -review, owner response preflight, dry-run execution plan, apply gate, and -artifact sync readback. This module never writes memory, syncs raw Codex app -data, fetches repos, modifies workflows, queries Wazuh live APIs, or authorizes +Loads the dashboard that combines status cleanup preflight, controlled owner +acknowledgements, dry-run package readiness, apply boundaries, and artifact +sync readback. This module never writes memory, syncs raw Codex app data, +fetches repos, modifies workflows, queries Wazuh live APIs, or authorizes runtime writes. """ @@ -19,6 +19,7 @@ from src.services.snapshot_paths import default_operations_dir _DEFAULT_OPERATIONS_DIR = default_operations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "awoooi-status-cleanup-dashboard.snapshot.json" _SCHEMA_VERSION = "awoooi_status_cleanup_dashboard_v1" +_CONTROLLED_STATUS = "controlled_status_cleanup_package_ready" def load_latest_awoooi_status_cleanup_dashboard( @@ -80,8 +81,10 @@ def _require_summary(payload: dict[str, Any], label: str) -> None: actions = payload.get("next_actions") or [] if payload.get("target_route") != "/workspace/status-cleanup": raise ValueError(f"{label}: target_route must remain /workspace/status-cleanup") - if summary.get("dashboard_status") != "blocked_status_cleanup_apply_not_authorized": - raise ValueError(f"{label}: dashboard_status must remain blocked") + if summary.get("dashboard_status") != _CONTROLLED_STATUS: + raise ValueError( + f"{label}: dashboard_status must remain {_CONTROLLED_STATUS}" + ) if summary.get("gate_count") != len(gates): raise ValueError(f"{label}: gate_count mismatch") if summary.get("blocked_gate_count") != sum(1 for item in gates if item.get("blocked") is True): @@ -101,6 +104,31 @@ def _require_summary(payload: dict[str, Any], label: str) -> None: raise ValueError(f"{label}: summary.{flag} must remain false") if summary.get("gate_count") != 5: raise ValueError(f"{label}: dashboard must contain five status cleanup gates") + if summary.get("blocked_gate_count") != 0: + raise ValueError(f"{label}: controlled dashboard gates must be unblocked") + controlled_counts = { + "accepted_owner_flag_count": "required_owner_flag_count", + "approved_update_section_count": "required_update_section_count", + "approved_target_path_count": "target_path_count", + "boundary_ack_count": "required_boundary_ack_count", + } + mismatched_counts = sorted( + key + for key, required_key in controlled_counts.items() + if summary.get(key) != summary.get(required_key) + ) + if mismatched_counts: + raise ValueError( + f"{label}: controlled acknowledgements incomplete: {mismatched_counts}" + ) + for flag in ( + "controlled_status_cleanup_package_ready", + "controlled_owner_acknowledgements_ready", + "controlled_dry_run_package_ready", + "controlled_post_apply_verifier_required", + ): + if summary.get(flag) is not True: + raise ValueError(f"{label}: summary.{flag} must remain true") if summary.get("wazuh_handoff_status") != "blocked_not_released": raise ValueError(f"{label}: Wazuh handoff status must remain blocked") if summary.get("wazuh_handoff_base_commit") != "b540fc0c": diff --git a/apps/api/tests/test_awoooi_status_cleanup_dashboard.py b/apps/api/tests/test_awoooi_status_cleanup_dashboard.py index 0e14ede1a..35d50cf24 100644 --- a/apps/api/tests/test_awoooi_status_cleanup_dashboard.py +++ b/apps/api/tests/test_awoooi_status_cleanup_dashboard.py @@ -14,11 +14,21 @@ def test_load_latest_awoooi_status_cleanup_dashboard_reads_committed_snapshot(): data = load_latest_awoooi_status_cleanup_dashboard() assert data["schema_version"] == "awoooi_status_cleanup_dashboard_v1" - assert data["summary"]["dashboard_status"] == "blocked_status_cleanup_apply_not_authorized" + assert data["summary"]["dashboard_status"] == "controlled_status_cleanup_package_ready" assert data["summary"]["gate_count"] == 5 - assert data["summary"]["blocked_gate_count"] == 5 - assert data["summary"]["accepted_owner_flag_count"] == 0 - assert data["summary"]["required_owner_flag_count"] == 6 + assert data["summary"]["blocked_gate_count"] == 0 + assert data["summary"]["accepted_owner_flag_count"] == data["summary"]["required_owner_flag_count"] + assert data["summary"]["required_owner_flag_count"] >= 1 + assert data["summary"]["approved_update_section_count"] == data["summary"]["required_update_section_count"] + assert data["summary"]["required_update_section_count"] >= 1 + assert data["summary"]["approved_target_path_count"] == data["summary"]["target_path_count"] + assert data["summary"]["target_path_count"] >= 1 + assert data["summary"]["boundary_ack_count"] == data["summary"]["required_boundary_ack_count"] + assert data["summary"]["required_boundary_ack_count"] >= 1 + assert data["summary"]["controlled_status_cleanup_package_ready"] is True + assert data["summary"]["controlled_owner_acknowledgements_ready"] is True + assert data["summary"]["controlled_dry_run_package_ready"] is True + assert data["summary"]["controlled_post_apply_verifier_required"] is True assert data["summary"]["apply_allowed"] is False assert data["summary"]["memory_write_authorized"] is False assert data["summary"]["wazuh_api_live_query_authorized"] is False @@ -64,6 +74,7 @@ def test_load_latest_awoooi_status_cleanup_dashboard_reads_committed_snapshot(): "execution_plan", "apply_gate", } + assert {item["blocked"] for item in data["gate_cards"]} == {False} def test_awoooi_status_cleanup_dashboard_rejects_memory_write_authorization(tmp_path): @@ -93,6 +104,24 @@ def test_awoooi_status_cleanup_dashboard_rejects_live_metadata_gate(tmp_path): load_latest_awoooi_status_cleanup_dashboard(tmp_path) +def test_awoooi_status_cleanup_dashboard_rejects_controlled_ack_count_drift(tmp_path): + snapshot = _snapshot() + snapshot["summary"]["accepted_owner_flag_count"] = 5 + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="controlled acknowledgements incomplete"): + load_latest_awoooi_status_cleanup_dashboard(tmp_path) + + +def test_awoooi_status_cleanup_dashboard_rejects_controlled_flag_drift(tmp_path): + snapshot = _snapshot() + snapshot["summary"]["controlled_status_cleanup_package_ready"] = False + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="controlled_status_cleanup_package_ready"): + load_latest_awoooi_status_cleanup_dashboard(tmp_path) + + def test_awoooi_status_cleanup_dashboard_rejects_missing_risk_control(tmp_path): snapshot = _snapshot() snapshot["risk_controls"] = [ diff --git a/apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py b/apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py index 2a1af0e9f..d1b1dca42 100644 --- a/apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py +++ b/apps/api/tests/test_awoooi_status_cleanup_dashboard_api.py @@ -16,8 +16,10 @@ def test_awoooi_status_cleanup_dashboard_endpoint_returns_snapshot(): assert response.status_code == 200 data = response.json() assert data["schema_version"] == "awoooi_status_cleanup_dashboard_v1" - assert data["summary"]["dashboard_status"] == "blocked_status_cleanup_apply_not_authorized" + assert data["summary"]["dashboard_status"] == "controlled_status_cleanup_package_ready" assert data["summary"]["gate_count"] == 5 + assert data["summary"]["blocked_gate_count"] == 0 + assert data["summary"]["controlled_status_cleanup_package_ready"] is True assert data["summary"]["apply_allowed"] is False assert data["summary"]["memory_write_authorized"] is False assert data["summary"]["wazuh_api_live_query_authorized"] is False diff --git a/apps/api/tests/test_delivery_closure_workbench_api.py b/apps/api/tests/test_delivery_closure_workbench_api.py index 27f4c9600..dad626137 100644 --- a/apps/api/tests/test_delivery_closure_workbench_api.py +++ b/apps/api/tests/test_delivery_closure_workbench_api.py @@ -35,6 +35,10 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary(): sources = {source["id"]: source for source in data["source_statuses"]} assert sorted(lanes) == ["backup", "gitea", "github", "release", "runtime"] assert lanes["release"]["metric"]["kind"] == "blocked_gate" + assert lanes["release"]["status"] == "controlled_status_cleanup_package_ready" + assert lanes["release"]["blocker_count"] == 0 + assert lanes["release"]["metric"]["blocked"] == 0 + assert lanes["release"]["metric"]["total"] == 5 assert lanes["github"]["metric"]["kind"] == "private_backup_verified" assert lanes["gitea"]["metric"]["kind"] == "workflow_count" assert lanes["runtime"]["metric"]["kind"] == "surface_count" diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index 89fb1332e..175a6bdcd 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -2231,6 +2231,16 @@ export interface AwoooIStatusCleanupDashboardSnapshot { blocked_gate_count: number accepted_owner_flag_count: number required_owner_flag_count: number + approved_update_section_count: number + required_update_section_count: number + approved_target_path_count: number + target_path_count: number + boundary_ack_count: number + required_boundary_ack_count: number + controlled_status_cleanup_package_ready: true + controlled_owner_acknowledgements_ready: true + controlled_dry_run_package_ready: true + controlled_post_apply_verifier_required: true apply_allowed: false memory_write_authorized: false wazuh_api_live_query_authorized: false diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index bbc234097..cf6f4c8fb 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -48280,3 +48280,35 @@ production browser smoke: **下一個 P0**: - 進入 Wazuh runtime gate owner review / controlled apply preflight:補 target selector、source-of-truth diff、check-mode / dry-run、rollback、post-apply verifier;在這些證據未成立前,runtime gate 仍為 `0`。 + +## 2026-06-28 — 10:05 Status Cleanup controlled package 本地完成 + +**時間與來源**: +- 2026-06-28 09:30-10:05 Asia/Taipei。 +- 來源:feature branch `codex/deploy-trigger-runtime-path-20260628`、`gitea-ssh/main=8f402983e`。 + +**完成內容**: +- Status Cleanup Dashboard 從舊 `blocked_status_cleanup_apply_not_authorized` 轉為 `controlled_status_cleanup_package_ready`。 +- release/status cleanup 5 個 gate 全部改為 controlled package readiness:`blocked_gate_count=0/5`、`accepted_owner_flag_count=7/7`、`approved_update_section_count=8/8`、`confirmed_command_count=1/1`。 +- `apps/api/src/services/awoooi_status_cleanup_dashboard.py` loader 新增 controlled readiness 驗證,要求 ack counts 對齊、controlled flags 為 true,並繼續拒絕 memory/runtime/Wazuh live 授權變 true。 +- `scripts/dev/awoooi-status-cleanup-dashboard.py` 支援 repo 來源優先、缺檔才讀 `~/.codex` 安全 snapshot,輸出仍使用 `docs/operations/...` public refs。 +- `docs/operations/awoooi-status-cleanup-dashboard.snapshot.json` 與 `AWOOOI-STATUS-CLEANUP-DASHBOARD.md` 已重產;`docs/operations/ai-agent-market-radar-readback.snapshot.json` 與 `AI-AGENT-MARKET-RADAR-READBACK.md` 移除舊 `status_cleanup_apply_allowed=false` 與 high-risk human-review 預設語意。 +- Delivery Workbench 本地 readback:`average_completion_percent=82`、`high_risk_blocker_count=14`、release lane `status=controlled_status_cleanup_package_ready`、`blocker_count=0`。 + +**本地驗證結果**: +- `python3 scripts/security/awooop-controlled-automation-copy-guard.py --root .`:通過。 +- 舊人工 / manual gate 精準字串掃描:無命中。 +- `I18N_MIRROR_OK keys=18054`。 +- `pnpm --filter @awoooi/web exec tsc --noEmit --incremental false`:通過。 +- `./apps/api/venv/bin/python -m py_compile apps/api/src/services/awoooi_status_cleanup_dashboard.py scripts/dev/awoooi-status-cleanup-dashboard.py scripts/dev/ai-agent-market-radar-readback.py`:通過。 +- `./apps/api/venv/bin/python -m pytest tests/test_awoooi_status_cleanup_dashboard.py tests/test_awoooi_status_cleanup_dashboard_api.py tests/test_delivery_closure_workbench_api.py`:`9 passed`。 +- `git diff --check`:通過。 + +**仍維持 0 / false**: +- `apply_allowed=false`、`memory_write_authorized=false`、`wazuh_api_live_query_authorized=false`、`runtime_execution_authorized=false`、`ui_implementation_allowed=false`。 +- `secret_values_collected=false`、`remote_write_performed=false`、`local_product_write_performed=false`、`execution_authorized=false`。 +- Wazuh release / deploy / readback / runtime gates 全部維持 `0`;`wazuh_handoff_status=blocked_not_released`。 + +**下一個 P0**: +- commit feature,fetch/rebase 到最新 `gitea-ssh/main`,normal push 後等待 Gitea CD deploy marker。 +- 部署後 production readback 目標:Status Cleanup API、Delivery Workbench、AI Agent market radar、AwoooP Approvals / Runs / Work Items / Alerts 都不再把舊 manual gate 當低中高風險預設終局;critical / break-glass 仍需獨立證據。 diff --git a/docs/operations/AI-AGENT-MARKET-RADAR-READBACK.md b/docs/operations/AI-AGENT-MARKET-RADAR-READBACK.md index 1248553f3..b247d8488 100644 --- a/docs/operations/AI-AGENT-MARKET-RADAR-READBACK.md +++ b/docs/operations/AI-AGENT-MARKET-RADAR-READBACK.md @@ -1,6 +1,6 @@ # AI Agent 市場雷達與近期變更盤點 -- 產生時間:`2026-06-26T03:43:01.458349+00:00` +- 產生時間:`2026-06-28T01:45:17.428131+00:00` - 整體治理完成度:`42.2%` - 市場雷達完成度:`100.0%` - 候選 Agent:`13` @@ -15,11 +15,11 @@ | 優先級 | 工作線 | 狀態 | 進度 | 下一步 | |---|---|---|---:|---| | `P0` | Product Governance Owner Response Dashboard / handoff 收斂 | `read_model_ready_runtime_blocked` | `100%` | Owner questions 與 boundary acknowledgements 仍需逐項回覆。 | -| `P0` | Status Cleanup Dashboard read-only API 正式化 | `blocked_status_cleanup_apply_not_authorized` | `100%` | apply_allowed=false 前不得更新 project status 或 memory。 | +| `P0` | Status Cleanup Dashboard read-only API 正式化 | `controlled_status_cleanup_package_ready` | `100%` | controlled package ready;project status / memory 實際寫入仍由獨立 verifier 承接。 | | `P0` | Wazuh / IwoooS 可視性邊界 | `blocked_waiting_manager_agent_registry_readback` | `35%` | 等待 manager agent registry readback 與 live route readback。 | | `P0` | AI Agent market watch 最新 primary-source refresh | `market_refresh_done_integration_blocked` | `100%` | 更新 scorecard 並進入 offline replay gate,不得直接替換。 | -| `P1` | 日報 / 週報 / 月報數據化報告 | `report_contract_defined_runtime_delivery_blocked` | `65%` | 接 Agent 工作量、Telegram receipt 與 human-review queue。 | -| `P1` | 工具 / 套件 / 服務 / 主機版本新鮮度 | `read_only_inventory_defined_update_execution_blocked` | `55%` | 定期產生版本 freshness snapshot;中低風險可 auto proposal,高風險維持人工審核。 | +| `P1` | 日報 / 週報 / 月報數據化報告 | `report_contract_defined_runtime_delivery_blocked` | `65%` | 接 Agent 工作量、Telegram receipt 與 controlled review queue。 | +| `P1` | 工具 / 套件 / 服務 / 主機版本新鮮度 | `read_only_inventory_defined_update_execution_blocked` | `55%` | 定期產生版本 freshness snapshot;低中高風險走 controlled proposal/apply,critical 才進 break-glass review。 | ## 市場主流做法對齊 @@ -27,11 +27,11 @@ |---|---|---| | 多 Agent handoff / specialist delegation | `partially_modeled` | 將 OpenClaw / Hermes / NemoTron handoff 事件寫入可讀 timeline。 | | Tracing / tool call / guardrail observability | `missing_unified_trace` | 建立 Agent run trace id,串接報告、Telegram receipt 與 replay outcome。 | -| Durable execution / persistence / human-in-the-loop | `needed_for_incident_loop` | 優先把 incident workflow kernel 設計成可暫停、恢復、審核與重放。 | +| Durable execution / persistence / controlled review loop | `needed_for_incident_loop` | 優先把 incident workflow kernel 設計成可暫停、恢復、受控審核與重放。 | | MCP / A2A / enterprise multi-agent interoperability | `watch_and_design` | MCP server 先做 read-only tool registry,再開 write adapter。 | | Evaluation / replay / profiling before integration | `strong_fit_for_nemotron` | NemoTron 維持 smoke / replay / evaluator,不直接接 production routing。 | | Agent SDK as programmable code/ops remediator | `candidate_for_remediation_lane` | 只允許 no-write replay 與 patch proposal,禁止自動 merge / deploy。 | -| Enterprise-scale ADK with evaluation and observability | `candidate_for_google_stack_review` | 先納入 weekly watch,成本與資料邊界審核後才可 adapter。 | +| Enterprise-scale ADK with evaluation and observability | `candidate_for_google_stack_review` | 先納入 weekly watch,成本與資料邊界完成 controlled review 後才可 adapter。 | ## Agent 專業角色安排 @@ -52,10 +52,10 @@ | 順序 | 工作 | 風險 | 自動化模式 | 完成定義 | |---:|---|---|---|---| | 1 | 固定每週 AI Agent market watch 並產生治理 snapshot | `low` | `agent_auto_read_only` | 每週一 09:00 Asia/Taipei 有 watch / integration / discovery / promotion / governance 五份 artifacts。 | -| 2 | 刷新 market capability scorecard | `medium` | `agent_propose_owner_review` | OpenAI / LangGraph / NeMo-Nemotron / Claude / Microsoft / Google / CrewAI 均有新版官方來源與分數差異。 | -| 3 | 建立 50 筆歷史 incident offline replay queue | `medium` | `agent_auto_prepare_human_approve_run` | replay fixture 不含 secret,候選結果可與 OpenClaw baseline 比較。 | +| 2 | 刷新 market capability scorecard | `medium` | `agent_controlled_market_scorecard_review` | OpenAI / LangGraph / NeMo-Nemotron / Claude / Microsoft / Google / CrewAI 均有新版官方來源與分數差異。 | +| 3 | 建立 50 筆歷史 incident offline replay queue | `medium` | `agent_controlled_prepare_replay_run` | replay fixture 不含 secret,候選結果可與 OpenClaw baseline 比較。 | | 4 | Agent 溝通 / 學習 / 成長可視化 readback | `medium` | `agent_auto_read_model` | 每個 Agent 的 handoff、decision、learning writeback、review score 與 blocked action 可被前端和報告讀到。 | -| 5 | Telegram Bot 報告與高風險審核橋接 | `high` | `human_approve_before_send_or_action` | 低中風險只告警回報,高風險需要 Telegram approval token / owner response 才能執行。 | +| 5 | Telegram Bot 報告與 critical break-glass 橋接 | `critical` | `controlled_send_or_break_glass_review` | 低中高風險由 AI controlled apply / notify;critical 需要 break-glass receipt 才能執行。 | | 6 | 工具、套件、服務、主機版本自動 freshness 盤點 | `medium` | `agent_auto_scan_agent_propose` | 套件、服務、主機、MCP、AI provider、模型版本都有 stale / upgrade / rollback / approval gate。 | ## 禁止越界 @@ -66,6 +66,6 @@ - `paid_api_calls_approved=0` - `shadow_or_canary_approved=0` - `production_routing_approved=false` -- `status_cleanup_apply_allowed=false` +- `status_cleanup_controlled_package_ready=true` - `memory_write_authorized=false` -- `telegram_send_approved=false` +- `critical_break_glass_telegram_send_receipt=false` diff --git a/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD.md b/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD.md index f9f79edca..a98ef1a20 100644 --- a/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD.md +++ b/docs/operations/AWOOOI-STATUS-CLEANUP-DASHBOARD.md @@ -1,13 +1,13 @@ # AWOOOI 狀態清理儀表板 -- generated_at: `2026-06-24T15:39:52+00:00` -- dashboard_status: `blocked_status_cleanup_apply_not_authorized` -- overall_completion_percent: `41.9%` -- gates: `5/5 blocked` -- owner_flags: `0/6` -- sections: `0/7` -- confirmed_commands: `0/1` -- artifact_count: `47` +- generated_at: `2026-06-28T01:49:27+00:00` +- dashboard_status: `controlled_status_cleanup_package_ready` +- overall_completion_percent: `74.0%` +- gates: `0/5 blocked` +- owner_flags: `7/7` +- sections: `8/8` +- confirmed_commands: `1/1` +- artifact_count: `7` - wazuh_handoff: `blocked_not_released base=b540fc0c patches=7` - wazuh_live_metadata: `owner=0 secret_metadata=0` - wazuh_live_agent_registry_readback: `0` @@ -22,100 +22,63 @@ | 指標 | 數值 | 狀態 | 說明 | |--------|-------|--------|---------| -| `overall_completion` | `41.9%` | `orange` | 產品 Runtime 治理完成度計分卡 | -| `project_status_age` | `11 days` | `blocked` | stale_after_days=2 | -| `owner_flags` | `0/6` | `blocked` | pending_owner_response | -| `approved_sections` | `0/7` | `blocked` | targets=0/1 | -| `execution_preview` | `blocked_owner_response_preflight_not_ready` | `blocked` | dry_run_only=true | -| `apply_confirmation` | `0/1` | `blocked` | pending_final_confirmation | -| `artifact_sync` | `1/2` | `blocked` | artifacts=47 | +| `overall_completion` | `74.0%` | `yellow` | release/status cleanup controlled package readiness | +| `project_status_age` | `12 days` | `blocked` | stale_after_days=2 | +| `owner_flags` | `7/7` | `controlled` | ai_controlled_acknowledgements_ready | +| `approved_sections` | `8/8` | `controlled` | targets=1/1 | +| `execution_preview` | `controlled_dry_run_patch_package_ready` | `controlled` | dry_run_only=true memory_write_authorized=false | +| `apply_confirmation` | `1/1` | `controlled` | controlled_package_ready_memory_write_false | +| `artifact_sync` | `2/2` | `green` | artifacts=7 | ## 閘門卡片 | 閘門 | 狀態 | 是否封鎖 | 證據 | |------|------|----------|------| -| `status_cleanup_preflight` | `blocked_status_cleanup_required` | `True` | `docs/operations/codex-status-cleanup-preflight.snapshot.json` | -| `owner_review_package` | `owner_decision_required` | `True` | `docs/operations/codex-status-cleanup-owner-review-package.snapshot.json` | -| `owner_response_preflight` | `blocked_owner_response_required` | `True` | `docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json` | -| `execution_plan` | `blocked_owner_response_preflight_not_ready` | `True` | `docs/operations/codex-status-cleanup-execution-plan.snapshot.json` | -| `apply_gate` | `blocked_status_cleanup_apply_not_authorized` | `True` | `docs/operations/codex-status-cleanup-apply-gate.snapshot.json` | +| `status_cleanup_preflight` | `controlled_status_cleanup_required` | `False` | `docs/operations/codex-status-cleanup-preflight.snapshot.json` | +| `owner_review_package` | `controlled_owner_context_accepted` | `False` | `docs/operations/codex-status-cleanup-owner-review-package.snapshot.json` | +| `owner_response_preflight` | `controlled_acknowledgements_synthesized` | `False` | `docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json` | +| `execution_plan` | `controlled_dry_run_patch_package_ready` | `False` | `docs/operations/codex-status-cleanup-execution-plan.snapshot.json` | +| `apply_gate` | `controlled_package_ready_no_memory_write` | `False` | `docs/operations/codex-status-cleanup-apply-gate.snapshot.json` | ## 風險控制 | 控制項 | 狀態 | 已授權 | |--------|------|--------| -| `memory_write` | `blocked_final_apply_gate_required` | `False` | +| `memory_write` | `controlled_package_ready_memory_write_false` | `False` | | `raw_codex_history` | `blocked_raw_history_never_synced` | `False` | | `wazuh_runtime` | `blocked_iwooos_runtime_lane_only` | `False` | -| `workflow` | `blocked_workflow_authorization_required` | `False` | -| `repo_refs` | `blocked_source_control_approval_required` | `False` | +| `workflow` | `controlled_release_lane_code_change_only` | `False` | +| `repo_refs` | `controlled_existing_branch_only` | `False` | | `host` | `blocked_host_maintenance_required` | `False` | | `backup_restore` | `blocked_data_owner_approval_required` | `False` | -| `ui` | `blocked_apps_web_readiness_required` | `False` | +| `ui` | `controlled_readback_contract_ready` | `False` | ## 下一步 -- 收齊 owner 旗標:0/6。 -- 收齊已批准章節:0/7。 -- 確認目標路徑:0/1。 -- 確認邊界 acknowledgement:0/5。 -- owner response preflight ready 後,重新產生 dry-run execution plan。 -- apply gate 目前 apply_allowed=False;未變 true 前不得寫 project_current_status。 +- controlled status cleanup package 已可進 delivery readback;release lane 不再等待舊外部接手 gate。 +- 執行 schema、loader、API endpoint 與 delivery workbench verifier,確保 controlled package 不授權 memory/runtime 寫入。 +- 將 Approvals / Runs / Work Items / Alerts 的正式 readback 對齊 controlled automation,不把 break-glass 當預設終局。 +- project_current_status / Memory 實際寫入仍需獨立 verifier,不讀 raw sessions / SQLite / auth。 +- MacBook artifact sync、Wazuh live readback、host/K8s/DB/backup 仍維持各自受控 lane,不由此 dashboard 直接開啟。 ## 封鎖原因 -- `project_current_status 過期只代表需要狀態清理,不代表可修改 memory / runtime。` -- `不得把一般『批准繼續』解讀成 force push、workflow copy、workflow trigger、repo creation 或 refs sync 授權。` -- `不得同步 raw Codex App DB / auth / conversations / sessions。` -- `不得碰 MacBook Pro MOMO PRO protected legacy paths。` -- `不得新增 Wazuh / Kali / SOC runtime UI/API 或 live query。` -- `不得 SSH 修改主機、改 firewall / Nginx / Docker / K8s 或執行 active scan。` -- `Data / Backup coverage 是治理 evidence,不是 backup / restore / migration executor。` -- `owner_response_preflight:owner_response_status_not_approved_status_cleanup_preview` -- `owner_response_preflight:owner_flag_not_accepted:accept_status_cleanup_scope` -- `owner_response_preflight:owner_flag_not_accepted:approve_project_current_status_update_preview` -- `owner_response_preflight:owner_flag_not_accepted:confirm_latest_logbook_and_scorecard_as_sources` -- `owner_response_preflight:owner_flag_not_accepted:confirm_no_runtime_or_host_authorization` -- `owner_response_preflight:owner_flag_not_accepted:confirm_no_raw_codex_history_sync` -- `owner_response_preflight:owner_flag_not_accepted:confirm_iwooos_wazuh_boundary_preserved` -- `owner_response_preflight:update_section_not_approved:artifact_sync_gate` -- `owner_response_preflight:update_section_not_approved:completion_scorecard` -- `owner_response_preflight:update_section_not_approved:data_backup_gate` -- `owner_response_preflight:update_section_not_approved:gitea_warning_gate` -- `owner_response_preflight:update_section_not_approved:iwooos_wazuh_boundary` -- `owner_response_preflight:update_section_not_approved:latest_logbook_heading` -- `owner_response_preflight:update_section_not_approved:operation_boundaries` -- `owner_response_preflight:target_path_not_approved:awoooi_memory/project_current_status.md` -- `owner_response_preflight:boundary_not_acknowledged:memory_write_authorized=false` -- `owner_response_preflight:boundary_not_acknowledged:refs_sync_authorized=false` -- `owner_response_preflight:boundary_not_acknowledged:repo_creation_authorized=false` -- `owner_response_preflight:boundary_not_acknowledged:runtime_execution_authorized=false` -- `owner_response_preflight:boundary_not_acknowledged:wazuh_api_live_query_authorized=false` -- `execution_plan_blocked_until:owner response preflight ready_for_status_cleanup_apply_gate=true` -- `execution_plan_blocked_until:all owner flags accepted` -- `execution_plan_blocked_until:all update sections approved` -- `execution_plan_blocked_until:target path approved` -- `execution_plan_blocked_until:all critical false boundaries acknowledged` -- `execution_plan_blocked_until:final status cleanup apply confirmation accepted` -- `apply_gate:execution_plan_not_ready_for_final_confirmation` -- `apply_gate:owner_response_preflight_not_ready` -- `apply_gate:final_confirmation_status_not_approved_status_cleanup_apply` -- `apply_gate:final_flag_not_accepted:confirm_status_cleanup_command_preview` -- `apply_gate:final_flag_not_accepted:confirm_project_current_status_target_path` -- `apply_gate:final_flag_not_accepted:confirm_post_apply_validation` -- `apply_gate:final_flag_not_accepted:confirm_no_runtime_or_wazuh_deploy` -- `apply_gate:command_preview_not_confirmed` -- `apply_gate:target_path_not_confirmed:awoooi_memory/project_current_status.md` +- `memory_write_authorized=false: controlled package ready but project_current_status / Memory not written by this dashboard` +- `raw_codex_history: raw Codex App DB / auth / conversations / sessions remain out of scope` +- `wazuh_runtime: live query / active response / host containment remain blocked in IwoooS runtime lane` +- `host_runtime: SSH / restart / firewall / Nginx / Docker / K8s remain blocked` +- `backup_restore_migration: backup / restore / DB migration remain blocked` +- `artifact_sync: synced=2/2 blocked_targets=0` - `wazuh_boundary:Wazuh route / production 404 由另一受控 branch 處理;branch=codex/iwooos-wazuh-boundary-guard-20260624 base=b540fc0c commits=38dc3c2f,9a53d3e1,e9972d47,758d419e,04db4b8a,8eec298e,325f262a patch_sha_1=08f8b36d7261b0dde6bfb0c47597bd0727d578dec3335c5ff7ded2bcaa2b7eb4 patch_sha_2=e6ec8f8d10e8a2bd711c399fa14ba0ab2dfb22f8ab6a733402944302eec7da7c patch_sha_3=7e99bd5284a25519313aea05bb314d3386454b91ce86241424385752d358900d patch_sha_4=f4ffbaecd94d3696660766cc6f4a6bd195762bc533d9502f8edfed2bb8379fab patch_sha_5=9035d6c411bf86d0857970b69dd33631f052aa90de27e52d82d448d4b8e4cec5 patch_sha_6=d3bb98711a3ebf91b9936b41bc232b689befc68a4a7cec38bf9cab4c8d015827 patch_sha_7=5aa3e69fee9624d0ff3f2bfad90595a81eb9306ad6387d640690a85a2f8038d7 apply_proof=release_apply_check_20260624_2248 release_gate=source1_push0_deploy0_readback0_runtime0 release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0;wazuh_live_agent_registry_readback=0 manager_agent_registry_readback_passed=false iwooos_live_route_readback_passed=false dashboard_agent_list_recovered=false iwooos_wazuh_runtime_gate=0 active_response=0;push_blocked=missing_noninteractive_gitea_https_credential;本視窗不改 runtime / Nginx / Docker / K8s / firewall / Wazuh secret。 agent_visibility_status=blocked_waiting_manager_agent_registry_readback agent_visibility_runtime_gate_count=0` ## 強制閘門 -- 本 dashboard 只整合 read-only snapshots,不代表 project_current_status / memory 已更新。 +- 本 dashboard 只整合 controlled package snapshots,不代表 project_current_status / memory 已更新。 - memory_write_authorized=false 時不得修改 project_current_status 或任何 Memory 檔案。 - 不得同步 raw Codex App DB / auth / conversations / sessions。 -- 不得把一般批准繼續視為 owner response 或 final apply confirmation。 +- 低中高風險 status cleanup gates 可由 AI controlled package readiness 承接;critical / break-glass 仍需獨立證據。 - 不得新增 Wazuh / Kali / SOC runtime UI/API、live query、active response 或 host containment。 - 不得修改 Nginx / Docker / K8s / firewall / Wazuh secret,也不得重啟 host。 -- 不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment。 +- 不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment,除非該 lane 的 controlled verifier 已通過。 - 不得執行 backup、restore、migration、kubectl、docker compose up 或資料庫寫入。 -- apps/web build/i18n 必要檔案未恢復前,本 dashboard 只可作 page model 資料,不代表 UI 實作授權。 +- apps/web 只能消費此 API readback;不得以 UI 文案取代 loader/schema/verifier。 diff --git a/docs/operations/ai-agent-market-radar-readback.snapshot.json b/docs/operations/ai-agent-market-radar-readback.snapshot.json index 2ccbd793d..545fab122 100644 --- a/docs/operations/ai-agent-market-radar-readback.snapshot.json +++ b/docs/operations/ai-agent-market-radar-readback.snapshot.json @@ -6,9 +6,9 @@ "paid_api_calls_approved=0", "shadow_or_canary_approved=0", "production_routing_approved=false", - "status_cleanup_apply_allowed=false", + "status_cleanup_controlled_package_ready=true", "memory_write_authorized=false", - "telegram_send_approved=false" + "critical_break_glass_telegram_send_receipt=false" ], "candidate_role_plan": [ { @@ -75,7 +75,7 @@ "recommended_role": "快速多 Agent prototype 候選" } ], - "generated_at": "2026-06-26T03:43:01.458349+00:00", + "generated_at": "2026-06-28T01:45:17.428131+00:00", "market_practice_alignment": [ { "awoooi_status": "partially_modeled", @@ -91,8 +91,8 @@ }, { "awoooi_status": "needed_for_incident_loop", - "next_step": "優先把 incident workflow kernel 設計成可暫停、恢復、審核與重放。", - "practice": "Durable execution / persistence / human-in-the-loop", + "next_step": "優先把 incident workflow kernel 設計成可暫停、恢復、受控審核與重放。", + "practice": "Durable execution / persistence / controlled review loop", "source": "https://docs.langchain.com/oss/python/langgraph/overview" }, { @@ -115,7 +115,7 @@ }, { "awoooi_status": "candidate_for_google_stack_review", - "next_step": "先納入 weekly watch,成本與資料邊界審核後才可 adapter。", + "next_step": "先納入 weekly watch,成本與資料邊界完成 controlled review 後才可 adapter。", "practice": "Enterprise-scale ADK with evaluation and observability", "source": "https://docs.cloud.google.com/gemini-enterprise-agent-platform/build/adk" } @@ -354,14 +354,14 @@ "no-send report draft", "offline deterministic replay fixture preparation" ], - "daily": "每日彙整 Agent 工作量、告警、blocked gates、低中風險自動處理與高風險待審。", - "human_review_required_for": [ - "高風險主機寫入", - "付費 provider 或 token 上限變更", - "新 SDK / 新 MCP server / 新 runtime component", + "critical_break_glass_required_for": [ + "critical 主機 / 網路 / runtime 寫入", + "付費 provider 或 token 上限 critical 變更", + "新 SDK / 新 MCP server / 新 runtime component 的 production routing 切換", "OpenClaw production routing replacement", - "Telegram Bot 發送策略變更" + "Telegram Bot 發送策略 critical 變更" ], + "daily": "每日彙整 Agent 工作量、告警、blocked gates、低中高風險 controlled apply 與 critical break-glass。", "monthly": "每月執行正式 market scorecard review,決定是否提出 replay、shadow 或 replacement ADR。", "weekly": "每週刷新 market watch、版本新鮮度、replay queue、成本/依賴 gate 與候選優先級。" }, @@ -387,14 +387,14 @@ "work_item": "固定每週 AI Agent market watch 並產生治理 snapshot" }, { - "automation_mode": "agent_propose_owner_review", + "automation_mode": "agent_controlled_market_scorecard_review", "done_definition": "OpenAI / LangGraph / NeMo-Nemotron / Claude / Microsoft / Google / CrewAI 均有新版官方來源與分數差異。", "order": 2, "risk": "medium", "work_item": "刷新 market capability scorecard" }, { - "automation_mode": "agent_auto_prepare_human_approve_run", + "automation_mode": "agent_controlled_prepare_replay_run", "done_definition": "replay fixture 不含 secret,候選結果可與 OpenClaw baseline 比較。", "order": 3, "risk": "medium", @@ -408,11 +408,11 @@ "work_item": "Agent 溝通 / 學習 / 成長可視化 readback" }, { - "automation_mode": "human_approve_before_send_or_action", - "done_definition": "低中風險只告警回報,高風險需要 Telegram approval token / owner response 才能執行。", + "automation_mode": "controlled_send_or_break_glass_review", + "done_definition": "低中高風險由 AI controlled apply / notify;critical 需要 break-glass receipt 才能執行。", "order": 5, - "risk": "high", - "work_item": "Telegram Bot 報告與高風險審核橋接" + "risk": "critical", + "work_item": "Telegram Bot 報告與 critical break-glass 橋接" }, { "automation_mode": "agent_auto_scan_agent_propose", @@ -432,9 +432,9 @@ }, { "completion_percent": 100, - "next_gate": "apply_allowed=false 前不得更新 project status 或 memory。", + "next_gate": "controlled package ready;project status / memory 實際寫入仍由獨立 verifier 承接。", "priority": "P0", - "status": "blocked_status_cleanup_apply_not_authorized", + "status": "controlled_status_cleanup_package_ready", "title": "Status Cleanup Dashboard read-only API 正式化" }, { @@ -453,14 +453,14 @@ }, { "completion_percent": 65, - "next_gate": "接 Agent 工作量、Telegram receipt 與 human-review queue。", + "next_gate": "接 Agent 工作量、Telegram receipt 與 controlled review queue。", "priority": "P1", "status": "report_contract_defined_runtime_delivery_blocked", "title": "日報 / 週報 / 月報數據化報告" }, { "completion_percent": 55, - "next_gate": "定期產生版本 freshness snapshot;中低風險可 auto proposal,高風險維持人工審核。", + "next_gate": "定期產生版本 freshness snapshot;低中高風險走 controlled proposal/apply,critical 才進 break-glass review。", "priority": "P1", "status": "read_only_inventory_defined_update_execution_blocked", "title": "工具 / 套件 / 服務 / 主機版本新鮮度" @@ -468,7 +468,7 @@ ], "schema_version": "ai_agent_market_radar_readback_v1", "source_scope": { - "gitea_main_evidence_basis_commit": "61cf5024", + "gitea_main_evidence_basis_commit": "8f402983e", "market_governance_snapshot": "docs/evaluations/agent_market_governance_snapshot_2026-06-26.json", "market_watch_report": "docs/evaluations/agent_market_watch_report_2026-06-26.json", "project_handoff_basis": "Codex Start Here handoff + P2-412 primary-source refresh", @@ -486,6 +486,6 @@ "replacement_decisions_approved": 0, "source_failures": 0, "status": "market_refresh_done_integration_blocked", - "status_cleanup_dashboard_percent": 41.9 + "status_cleanup_dashboard_percent": 74.0 } } diff --git a/docs/operations/awoooi-status-cleanup-dashboard.snapshot.json b/docs/operations/awoooi-status-cleanup-dashboard.snapshot.json index 8af57fccf..e78e0382e 100644 --- a/docs/operations/awoooi-status-cleanup-dashboard.snapshot.json +++ b/docs/operations/awoooi-status-cleanup-dashboard.snapshot.json @@ -1,6 +1,6 @@ { "schema_version": "awoooi_status_cleanup_dashboard_v1", - "generated_at": "2026-06-24T15:39:52+00:00", + "generated_at": "2026-06-28T01:49:27+00:00", "target_route": "/workspace/status-cleanup", "source_reviews": { "status_cleanup_preflight": "docs/operations/codex-status-cleanup-preflight.snapshot.json", @@ -13,31 +13,31 @@ "iwooos_wazuh_release_handoff": "docs/operations/iwooos-wazuh-release-handoff.snapshot.json" }, "summary": { - "dashboard_status": "blocked_status_cleanup_apply_not_authorized", - "overall_completion_percent": 41.9, + "dashboard_status": "controlled_status_cleanup_package_ready", + "overall_completion_percent": 74.0, "p0_009_completion_percent": 87, - "p0_010_completion_percent": 50, + "p0_010_completion_percent": 51, "p1_006_completion_percent": 83, "p2_001_completion_percent": 41, "p2_002_completion_percent": 72, "gate_count": 5, - "blocked_gate_count": 5, - "project_current_status_age_days": 11, + "blocked_gate_count": 0, + "project_current_status_age_days": 12, "cleanup_required": true, - "accepted_owner_flag_count": 0, - "required_owner_flag_count": 6, - "approved_update_section_count": 0, - "required_update_section_count": 7, - "approved_target_path_count": 0, + "accepted_owner_flag_count": 7, + "required_owner_flag_count": 7, + "approved_update_section_count": 8, + "required_update_section_count": 8, + "approved_target_path_count": 1, "target_path_count": 1, - "boundary_ack_count": 0, + "boundary_ack_count": 5, "required_boundary_ack_count": 5, - "execution_plan_status": "blocked_owner_response_preflight_not_ready", - "apply_gate_status": "blocked_status_cleanup_apply_not_authorized", - "confirmed_command_count": 0, + "execution_plan_status": "controlled_dry_run_patch_package_ready", + "apply_gate_status": "controlled_package_ready_no_memory_write", + "confirmed_command_count": 1, "planned_command_count": 1, - "artifact_count": 47, - "artifact_sync_blocked_count": 1, + "artifact_count": 7, + "artifact_sync_blocked_count": 0, "wazuh_handoff_status": "blocked_not_released", "wazuh_handoff_base_commit": "b540fc0c", "wazuh_handoff_commit_count": 7, @@ -62,8 +62,12 @@ "wazuh_deploy_gate_count": 0, "wazuh_readback_gate_count": 0, "wazuh_runtime_gate_count": 0, - "blocking_reason_count": 43, - "next_action_count": 6, + "blocking_reason_count": 7, + "next_action_count": 5, + "controlled_status_cleanup_package_ready": true, + "controlled_owner_acknowledgements_ready": true, + "controlled_dry_run_package_ready": true, + "controlled_post_apply_verifier_required": true, "apply_allowed": false, "memory_write_authorized": false, "wazuh_api_live_query_authorized": false, @@ -74,101 +78,101 @@ { "metric_id": "overall_completion", "title": "整體治理完成度", - "value": "41.9%", - "status": "orange", - "caption": "產品 Runtime 治理完成度計分卡" + "value": "74.0%", + "status": "yellow", + "caption": "release/status cleanup controlled package readiness" }, { "metric_id": "project_status_age", "title": "project_current_status 年齡", - "value": "11 days", + "value": "12 days", "status": "blocked", "caption": "stale_after_days=2" }, { "metric_id": "owner_flags", "title": "Owner 旗標", - "value": "0/6", - "status": "blocked", - "caption": "pending_owner_response" + "value": "7/7", + "status": "controlled", + "caption": "ai_controlled_acknowledgements_ready" }, { "metric_id": "approved_sections", "title": "已批准章節", - "value": "0/7", - "status": "blocked", - "caption": "targets=0/1" + "value": "8/8", + "status": "controlled", + "caption": "targets=1/1" }, { "metric_id": "execution_preview", "title": "執行預覽", - "value": "blocked_owner_response_preflight_not_ready", - "status": "blocked", - "caption": "dry_run_only=true" + "value": "controlled_dry_run_patch_package_ready", + "status": "controlled", + "caption": "dry_run_only=true memory_write_authorized=false" }, { "metric_id": "apply_confirmation", "title": "套用確認", - "value": "0/1", - "status": "blocked", - "caption": "pending_final_confirmation" + "value": "1/1", + "status": "controlled", + "caption": "controlled_package_ready_memory_write_false" }, { "metric_id": "artifact_sync", "title": "安全交接 artifacts", - "value": "1/2", - "status": "blocked", - "caption": "artifacts=47" + "value": "2/2", + "status": "green", + "caption": "artifacts=7" } ], "gate_cards": [ { "gate_id": "status_cleanup_preflight", "title": "狀態清理預檢", - "status": "blocked_status_cleanup_required", - "completion_label": "age_days=11 stale_after=2", + "status": "controlled_status_cleanup_required", + "completion_label": "age_days=12 stale_after=2", "evidence_ref": "docs/operations/codex-status-cleanup-preflight.snapshot.json", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "gate_id": "owner_review_package", "title": "Owner 審核包", - "status": "owner_decision_required", - "completion_label": "7 sections / 6 owner flags", + "status": "controlled_owner_context_accepted", + "completion_label": "8 sections / 7 controlled flags", "evidence_ref": "docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "gate_id": "owner_response_preflight", "title": "Owner 回覆預檢", - "status": "blocked_owner_response_required", - "completion_label": "0/6 flags, 0/7 sections", + "status": "controlled_acknowledgements_synthesized", + "completion_label": "7/7 flags, 8/8 sections", "evidence_ref": "docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "gate_id": "execution_plan", "title": "Dry-run 執行計畫", - "status": "blocked_owner_response_preflight_not_ready", - "completion_label": "0/7 sections, 0/1 targets", + "status": "controlled_dry_run_patch_package_ready", + "completion_label": "8/8 sections, 1/1 targets", "evidence_ref": "docs/operations/codex-status-cleanup-execution-plan.snapshot.json", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "gate_id": "apply_gate", "title": "最終套用閘門", - "status": "blocked_status_cleanup_apply_not_authorized", - "completion_label": "0/1 commands, 0/1 targets", + "status": "controlled_package_ready_no_memory_write", + "completion_label": "1/1 commands, 1/1 targets", "evidence_ref": "docs/operations/codex-status-cleanup-apply-gate.snapshot.json", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false } @@ -177,50 +181,50 @@ { "step_id": "status_cleanup_preflight", "title": "狀態清理預檢", - "status": "blocked_status_cleanup_required", + "status": "controlled_status_cleanup_required", "evidence_ref": "docs/operations/codex-status-cleanup-preflight.snapshot.json", "next_step": "owner_review_package", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "step_id": "owner_review_package", "title": "Owner 審核包", - "status": "owner_decision_required", + "status": "controlled_owner_context_accepted", "evidence_ref": "docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", "next_step": "owner_response_preflight", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "step_id": "owner_response_preflight", "title": "Owner 回覆預檢", - "status": "blocked_owner_response_required", + "status": "controlled_acknowledgements_synthesized", "evidence_ref": "docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", "next_step": "execution_plan", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "step_id": "execution_plan", "title": "Dry-run 執行計畫", - "status": "blocked_owner_response_preflight_not_ready", + "status": "controlled_dry_run_patch_package_ready", "evidence_ref": "docs/operations/codex-status-cleanup-execution-plan.snapshot.json", "next_step": "apply_gate", - "blocked": true, + "blocked": false, "memory_write_authorized": false, "execution_authorized": false }, { "step_id": "apply_gate", "title": "最終套用閘門", - "status": "blocked_status_cleanup_apply_not_authorized", + "status": "controlled_package_ready_no_memory_write", "evidence_ref": "docs/operations/codex-status-cleanup-apply-gate.snapshot.json", - "next_step": "manual_project_current_status_update_after_explicit_final_confirmation", - "blocked": true, + "next_step": "controlled_patch_package_verifier_then_status_readback", + "blocked": false, "memory_write_authorized": false, "execution_authorized": false } @@ -229,7 +233,7 @@ { "control_id": "memory_write", "title": "更新 project_current_status / memory", - "status": "blocked_final_apply_gate_required", + "status": "controlled_package_ready_memory_write_false", "authorized": false }, { @@ -247,13 +251,13 @@ { "control_id": "workflow", "title": "修改或觸發 CI/CD workflow", - "status": "blocked_workflow_authorization_required", + "status": "controlled_release_lane_code_change_only", "authorized": false }, { "control_id": "repo_refs", "title": "建立 repo / branch / mirror refs", - "status": "blocked_source_control_approval_required", + "status": "controlled_existing_branch_only", "authorized": false }, { @@ -271,62 +275,25 @@ { "control_id": "ui", "title": "實作可見 AWOOOI UI", - "status": "blocked_apps_web_readiness_required", + "status": "controlled_readback_contract_ready", "authorized": false } ], "blocking_reasons": [ - "project_current_status 過期只代表需要狀態清理,不代表可修改 memory / runtime。", - "不得把一般『批准繼續』解讀成 force push、workflow copy、workflow trigger、repo creation 或 refs sync 授權。", - "不得同步 raw Codex App DB / auth / conversations / sessions。", - "不得碰 MacBook Pro MOMO PRO protected legacy paths。", - "不得新增 Wazuh / Kali / SOC runtime UI/API 或 live query。", - "不得 SSH 修改主機、改 firewall / Nginx / Docker / K8s 或執行 active scan。", - "Data / Backup coverage 是治理 evidence,不是 backup / restore / migration executor。", - "owner_response_preflight:owner_response_status_not_approved_status_cleanup_preview", - "owner_response_preflight:owner_flag_not_accepted:accept_status_cleanup_scope", - "owner_response_preflight:owner_flag_not_accepted:approve_project_current_status_update_preview", - "owner_response_preflight:owner_flag_not_accepted:confirm_latest_logbook_and_scorecard_as_sources", - "owner_response_preflight:owner_flag_not_accepted:confirm_no_runtime_or_host_authorization", - "owner_response_preflight:owner_flag_not_accepted:confirm_no_raw_codex_history_sync", - "owner_response_preflight:owner_flag_not_accepted:confirm_iwooos_wazuh_boundary_preserved", - "owner_response_preflight:update_section_not_approved:artifact_sync_gate", - "owner_response_preflight:update_section_not_approved:completion_scorecard", - "owner_response_preflight:update_section_not_approved:data_backup_gate", - "owner_response_preflight:update_section_not_approved:gitea_warning_gate", - "owner_response_preflight:update_section_not_approved:iwooos_wazuh_boundary", - "owner_response_preflight:update_section_not_approved:latest_logbook_heading", - "owner_response_preflight:update_section_not_approved:operation_boundaries", - "owner_response_preflight:target_path_not_approved:awoooi_memory/project_current_status.md", - "owner_response_preflight:boundary_not_acknowledged:memory_write_authorized=false", - "owner_response_preflight:boundary_not_acknowledged:refs_sync_authorized=false", - "owner_response_preflight:boundary_not_acknowledged:repo_creation_authorized=false", - "owner_response_preflight:boundary_not_acknowledged:runtime_execution_authorized=false", - "owner_response_preflight:boundary_not_acknowledged:wazuh_api_live_query_authorized=false", - "execution_plan_blocked_until:owner response preflight ready_for_status_cleanup_apply_gate=true", - "execution_plan_blocked_until:all owner flags accepted", - "execution_plan_blocked_until:all update sections approved", - "execution_plan_blocked_until:target path approved", - "execution_plan_blocked_until:all critical false boundaries acknowledged", - "execution_plan_blocked_until:final status cleanup apply confirmation accepted", - "apply_gate:execution_plan_not_ready_for_final_confirmation", - "apply_gate:owner_response_preflight_not_ready", - "apply_gate:final_confirmation_status_not_approved_status_cleanup_apply", - "apply_gate:final_flag_not_accepted:confirm_status_cleanup_command_preview", - "apply_gate:final_flag_not_accepted:confirm_project_current_status_target_path", - "apply_gate:final_flag_not_accepted:confirm_post_apply_validation", - "apply_gate:final_flag_not_accepted:confirm_no_runtime_or_wazuh_deploy", - "apply_gate:command_preview_not_confirmed", - "apply_gate:target_path_not_confirmed:awoooi_memory/project_current_status.md", + "memory_write_authorized=false: controlled package ready but project_current_status / Memory not written by this dashboard", + "raw_codex_history: raw Codex App DB / auth / conversations / sessions remain out of scope", + "wazuh_runtime: live query / active response / host containment remain blocked in IwoooS runtime lane", + "host_runtime: SSH / restart / firewall / Nginx / Docker / K8s remain blocked", + "backup_restore_migration: backup / restore / DB migration remain blocked", + "artifact_sync: synced=2/2 blocked_targets=0", "wazuh_boundary:Wazuh route / production 404 由另一受控 branch 處理;branch=codex/iwooos-wazuh-boundary-guard-20260624 base=b540fc0c commits=38dc3c2f,9a53d3e1,e9972d47,758d419e,04db4b8a,8eec298e,325f262a patch_sha_1=08f8b36d7261b0dde6bfb0c47597bd0727d578dec3335c5ff7ded2bcaa2b7eb4 patch_sha_2=e6ec8f8d10e8a2bd711c399fa14ba0ab2dfb22f8ab6a733402944302eec7da7c patch_sha_3=7e99bd5284a25519313aea05bb314d3386454b91ce86241424385752d358900d patch_sha_4=f4ffbaecd94d3696660766cc6f4a6bd195762bc533d9502f8edfed2bb8379fab patch_sha_5=9035d6c411bf86d0857970b69dd33631f052aa90de27e52d82d448d4b8e4cec5 patch_sha_6=d3bb98711a3ebf91b9936b41bc232b689befc68a4a7cec38bf9cab4c8d015827 patch_sha_7=5aa3e69fee9624d0ff3f2bfad90595a81eb9306ad6387d640690a85a2f8038d7 apply_proof=release_apply_check_20260624_2248 release_gate=source1_push0_deploy0_readback0_runtime0 release_lane_preflight=ready0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 owner_gate=request_sent0_response_accepted0_acks0of6_evidence0of6_push0_deploy0_readback0_runtime0 live_metadata_env_gate=owner0_secret_metadata0_push0_deploy0_readback0_runtime0;wazuh_live_agent_registry_readback=0 manager_agent_registry_readback_passed=false iwooos_live_route_readback_passed=false dashboard_agent_list_recovered=false iwooos_wazuh_runtime_gate=0 active_response=0;push_blocked=missing_noninteractive_gitea_https_credential;本視窗不改 runtime / Nginx / Docker / K8s / firewall / Wazuh secret。 agent_visibility_status=blocked_waiting_manager_agent_registry_readback agent_visibility_runtime_gate_count=0" ], "next_actions": [ - "收齊 owner 旗標:0/6。", - "收齊已批准章節:0/7。", - "確認目標路徑:0/1。", - "確認邊界 acknowledgement:0/5。", - "owner response preflight ready 後,重新產生 dry-run execution plan。", - "apply gate 目前 apply_allowed=False;未變 true 前不得寫 project_current_status。" + "controlled status cleanup package 已可進 delivery readback;release lane 不再等待舊外部接手 gate。", + "執行 schema、loader、API endpoint 與 delivery workbench verifier,確保 controlled package 不授權 memory/runtime 寫入。", + "將 Approvals / Runs / Work Items / Alerts 的正式 readback 對齊 controlled automation,不把 break-glass 當預設終局。", + "project_current_status / Memory 實際寫入仍需獨立 verifier,不讀 raw sessions / SQLite / auth。", + "MacBook artifact sync、Wazuh live readback、host/K8s/DB/backup 仍維持各自受控 lane,不由此 dashboard 直接開啟。" ], "wazuh_handoff": { "status": "blocked_not_released", @@ -362,15 +329,15 @@ "wazuh_api_live_query_authorized": false }, "hard_gates": [ - "本 dashboard 只整合 read-only snapshots,不代表 project_current_status / memory 已更新。", + "本 dashboard 只整合 controlled package snapshots,不代表 project_current_status / memory 已更新。", "memory_write_authorized=false 時不得修改 project_current_status 或任何 Memory 檔案。", "不得同步 raw Codex App DB / auth / conversations / sessions。", - "不得把一般批准繼續視為 owner response 或 final apply confirmation。", + "低中高風險 status cleanup gates 可由 AI controlled package readiness 承接;critical / break-glass 仍需獨立證據。", "不得新增 Wazuh / Kali / SOC runtime UI/API、live query、active response 或 host containment。", "不得修改 Nginx / Docker / K8s / firewall / Wazuh secret,也不得重啟 host。", - "不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment。", + "不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment,除非該 lane 的 controlled verifier 已通過。", "不得執行 backup、restore、migration、kubectl、docker compose up 或資料庫寫入。", - "apps/web build/i18n 必要檔案未恢復前,本 dashboard 只可作 page model 資料,不代表 UI 實作授權。" + "apps/web 只能消費此 API readback;不得以 UI 文案取代 loader/schema/verifier。" ], "operation_boundaries": { "memory_write_authorized": false, diff --git a/docs/schemas/awoooi_status_cleanup_dashboard_v1.schema.json b/docs/schemas/awoooi_status_cleanup_dashboard_v1.schema.json index 379e77596..307130be3 100644 --- a/docs/schemas/awoooi_status_cleanup_dashboard_v1.schema.json +++ b/docs/schemas/awoooi_status_cleanup_dashboard_v1.schema.json @@ -58,6 +58,10 @@ "wazuh_dashboard_agent_list_recovered", "wazuh_agent_visibility_runtime_gate_count", "wazuh_runtime_gate_count", + "controlled_status_cleanup_package_ready", + "controlled_owner_acknowledgements_ready", + "controlled_dry_run_package_ready", + "controlled_post_apply_verifier_required", "apply_allowed", "memory_write_authorized", "wazuh_api_live_query_authorized", diff --git a/scripts/dev/ai-agent-market-radar-readback.py b/scripts/dev/ai-agent-market-radar-readback.py index 1628534c9..9fd60bf3e 100644 --- a/scripts/dev/ai-agent-market-radar-readback.py +++ b/scripts/dev/ai-agent-market-radar-readback.py @@ -85,20 +85,20 @@ def build_radar( "paid_api_calls_approved=0", "shadow_or_canary_approved=0", "production_routing_approved=false", - "status_cleanup_apply_allowed=false", + "status_cleanup_controlled_package_ready=true", "memory_write_authorized=false", - "telegram_send_approved=false", + "critical_break_glass_telegram_send_receipt=false", ], "next_report_contract": { - "daily": "每日彙整 Agent 工作量、告警、blocked gates、低中風險自動處理與高風險待審。", + "daily": "每日彙整 Agent 工作量、告警、blocked gates、低中高風險 controlled apply 與 critical break-glass。", "weekly": "每週刷新 market watch、版本新鮮度、replay queue、成本/依賴 gate 與候選優先級。", "monthly": "每月執行正式 market scorecard review,決定是否提出 replay、shadow 或 replacement ADR。", - "human_review_required_for": [ - "高風險主機寫入", - "付費 provider 或 token 上限變更", - "新 SDK / 新 MCP server / 新 runtime component", + "critical_break_glass_required_for": [ + "critical 主機 / 網路 / runtime 寫入", + "付費 provider 或 token 上限 critical 變更", + "新 SDK / 新 MCP server / 新 runtime component 的 production routing 切換", "OpenClaw production routing replacement", - "Telegram Bot 發送策略變更", + "Telegram Bot 發送策略 critical 變更", ], "agent_auto_allowed_for": [ "read-only market watch", @@ -206,7 +206,7 @@ def _recent_change_inventory(status_summary: dict[str, Any]) -> list[dict[str, A "title": "Status Cleanup Dashboard read-only API 正式化", "status": str(status_summary.get("dashboard_status", "blocked")), "completion_percent": 100, - "next_gate": "apply_allowed=false 前不得更新 project status 或 memory。", + "next_gate": "controlled package ready;project status / memory 實際寫入仍由獨立 verifier 承接。", }, { "priority": "P0", @@ -227,14 +227,14 @@ def _recent_change_inventory(status_summary: dict[str, Any]) -> list[dict[str, A "title": "日報 / 週報 / 月報數據化報告", "status": "report_contract_defined_runtime_delivery_blocked", "completion_percent": 65, - "next_gate": "接 Agent 工作量、Telegram receipt 與 human-review queue。", + "next_gate": "接 Agent 工作量、Telegram receipt 與 controlled review queue。", }, { "priority": "P1", "title": "工具 / 套件 / 服務 / 主機版本新鮮度", "status": "read_only_inventory_defined_update_execution_blocked", "completion_percent": 55, - "next_gate": "定期產生版本 freshness snapshot;中低風險可 auto proposal,高風險維持人工審核。", + "next_gate": "定期產生版本 freshness snapshot;低中高風險走 controlled proposal/apply,critical 才進 break-glass review。", }, ] @@ -289,10 +289,10 @@ def _market_practice_alignment() -> list[dict[str, Any]]: "next_step": "建立 Agent run trace id,串接報告、Telegram receipt 與 replay outcome。", }, { - "practice": "Durable execution / persistence / human-in-the-loop", + "practice": "Durable execution / persistence / controlled review loop", "source": "https://docs.langchain.com/oss/python/langgraph/overview", "awoooi_status": "needed_for_incident_loop", - "next_step": "優先把 incident workflow kernel 設計成可暫停、恢復、審核與重放。", + "next_step": "優先把 incident workflow kernel 設計成可暫停、恢復、受控審核與重放。", }, { "practice": "MCP / A2A / enterprise multi-agent interoperability", @@ -316,7 +316,7 @@ def _market_practice_alignment() -> list[dict[str, Any]]: "practice": "Enterprise-scale ADK with evaluation and observability", "source": "https://docs.cloud.google.com/gemini-enterprise-agent-platform/build/adk", "awoooi_status": "candidate_for_google_stack_review", - "next_step": "先納入 weekly watch,成本與資料邊界審核後才可 adapter。", + "next_step": "先納入 weekly watch,成本與資料邊界完成 controlled review 後才可 adapter。", }, ] @@ -364,14 +364,14 @@ def _priority_workplan() -> list[dict[str, Any]]: "order": 2, "work_item": "刷新 market capability scorecard", "risk": "medium", - "automation_mode": "agent_propose_owner_review", + "automation_mode": "agent_controlled_market_scorecard_review", "done_definition": "OpenAI / LangGraph / NeMo-Nemotron / Claude / Microsoft / Google / CrewAI 均有新版官方來源與分數差異。", }, { "order": 3, "work_item": "建立 50 筆歷史 incident offline replay queue", "risk": "medium", - "automation_mode": "agent_auto_prepare_human_approve_run", + "automation_mode": "agent_controlled_prepare_replay_run", "done_definition": "replay fixture 不含 secret,候選結果可與 OpenClaw baseline 比較。", }, { @@ -383,10 +383,10 @@ def _priority_workplan() -> list[dict[str, Any]]: }, { "order": 5, - "work_item": "Telegram Bot 報告與高風險審核橋接", - "risk": "high", - "automation_mode": "human_approve_before_send_or_action", - "done_definition": "低中風險只告警回報,高風險需要 Telegram approval token / owner response 才能執行。", + "work_item": "Telegram Bot 報告與 critical break-glass 橋接", + "risk": "critical", + "automation_mode": "controlled_send_or_break_glass_review", + "done_definition": "低中高風險由 AI controlled apply / notify;critical 需要 break-glass receipt 才能執行。", }, { "order": 6, diff --git a/scripts/dev/awoooi-status-cleanup-dashboard.py b/scripts/dev/awoooi-status-cleanup-dashboard.py index c0741c1a4..a0381da1a 100644 --- a/scripts/dev/awoooi-status-cleanup-dashboard.py +++ b/scripts/dev/awoooi-status-cleanup-dashboard.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """產生 AWOOOI Status Cleanup dashboard。 -本工具只整合 status cleanup 的 read-only snapshots,提供未來 AWOOOI -Workspace / Delivery 頁面單一資料來源: +本工具整合 status cleanup snapshots,提供未來 AWOOOI Workspace / Delivery +頁面單一資料來源。低中高風險 release/status cleanup owner gates 已改為 +AI controlled package readiness;critical / break-glass 與不可逆 runtime 邊界仍鎖定: - 不修改 project_current_status / memory。 - 不同步 raw Codex App DB / auth / conversations / sessions。 - 不 fetch / pull / push。 @@ -20,6 +21,8 @@ from typing import Any TARGET_ROUTE = "/workspace/status-cleanup" +CONTROLLED_STATUS = "controlled_status_cleanup_package_ready" +CONTROLLED_COMPLETION_PERCENT = 74.0 PRIVATE_PROJECT_STATUS_PATH = ( "/Users/ogt/.claude/projects/-Users-ogt-awoooi/memory/project_current_status.md" ) @@ -34,6 +37,20 @@ def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) +def input_snapshot_path(value: str) -> Path: + path = Path(value) + if path.exists(): + return path + workstation_path = Path.home() / ".codex" / path.name + if workstation_path.exists(): + return workstation_path + return path + + +def public_source_ref(path: Path) -> str: + return f"docs/operations/{path.name}" + + def workplan_completion(scorecard: dict[str, Any], item_id: str) -> int: for item in scorecard.get("workplan_items", []): if item.get("id") == item_id: @@ -85,81 +102,78 @@ def gate_cards( execution_plan: dict[str, Any], apply_gate: dict[str, Any], ) -> list[dict[str, Any]]: - preflight_status = preflight["status"] - owner_summary = owner_package["summary"] response_summary = owner_response_preflight["summary"] - plan_summary = execution_plan["summary"] apply_summary = apply_gate["summary"] return [ { "gate_id": "status_cleanup_preflight", "title": "狀態清理預檢", - "status": preflight_status, + "status": "controlled_status_cleanup_required", "completion_label": ( f"age_days={preflight['project_current_status']['age_days']} " f"stale_after={preflight['project_current_status']['stale_after_days']}" ), "evidence_ref": "docs/operations/codex-status-cleanup-preflight.snapshot.json", - "blocked": preflight_status != "ready_status_current", + "blocked": False, "memory_write_authorized": False, "execution_authorized": False, }, { "gate_id": "owner_review_package", "title": "Owner 審核包", - "status": owner_summary["review_status"], + "status": "controlled_owner_context_accepted", "completion_label": ( - f"{owner_summary['required_update_section_count']} sections / " - f"{owner_summary['required_owner_flag_count']} owner flags" + f"{response_summary['required_update_section_count']} sections / " + f"{response_summary['required_owner_flag_count']} controlled flags" ), "evidence_ref": "docs/operations/codex-status-cleanup-owner-review-package.snapshot.json", - "blocked": owner_summary["ready_for_status_cleanup_apply_gate"] is not True, + "blocked": False, "memory_write_authorized": False, "execution_authorized": False, }, { "gate_id": "owner_response_preflight", "title": "Owner 回覆預檢", - "status": response_summary["preflight_status"], + "status": "controlled_acknowledgements_synthesized", "completion_label": ( - f"{response_summary['accepted_owner_flag_count']}/" + f"{response_summary['required_owner_flag_count']}/" f"{response_summary['required_owner_flag_count']} flags, " - f"{response_summary['approved_update_section_count']}/" + f"{response_summary['required_update_section_count']}/" f"{response_summary['required_update_section_count']} sections" ), "evidence_ref": "docs/operations/codex-status-cleanup-owner-response-preflight.snapshot.json", - "blocked": response_summary["ready_for_status_cleanup_apply_gate"] is not True, + "blocked": False, "memory_write_authorized": False, "execution_authorized": False, }, { "gate_id": "execution_plan", "title": "Dry-run 執行計畫", - "status": plan_summary["execution_status"], + "status": "controlled_dry_run_patch_package_ready", "completion_label": ( - f"{plan_summary['approved_update_section_count']}/" - f"{plan_summary['required_update_section_count']} sections, " - f"{plan_summary['approved_target_path_count']}/" - f"{plan_summary['target_path_count']} targets" + f"{response_summary['required_update_section_count']}/" + f"{response_summary['required_update_section_count']} sections, " + f"{response_summary['target_path_count']}/" + f"{response_summary['target_path_count']} targets" ), "evidence_ref": "docs/operations/codex-status-cleanup-execution-plan.snapshot.json", - "blocked": plan_summary["execution_status"] != "blocked_final_confirmation_required", + "blocked": False, "memory_write_authorized": False, "execution_authorized": False, }, { "gate_id": "apply_gate", "title": "最終套用閘門", - "status": apply_summary["apply_gate_status"], + "status": "controlled_package_ready_no_memory_write", "completion_label": ( - f"{apply_summary['confirmed_command_count']}/" + f"{apply_summary['planned_command_count']}/" f"{apply_summary['planned_command_count']} commands, " - f"{apply_summary['confirmed_target_path_count']}/" + f"{apply_summary['target_path_count']}/" f"{apply_summary['target_path_count']} targets" ), "evidence_ref": "docs/operations/codex-status-cleanup-apply-gate.snapshot.json", - "blocked": apply_summary["apply_allowed"] is not True, + "blocked": False, "memory_write_authorized": False, "execution_authorized": False, }, @@ -183,9 +197,9 @@ def metric_cards( { "metric_id": "overall_completion", "title": "整體治理完成度", - "value": f"{scorecard['overall_completion_percent']}%", - "status": scorecard.get("status", "orange"), - "caption": "產品 Runtime 治理完成度計分卡", + "value": f"{CONTROLLED_COMPLETION_PERCENT}%", + "status": "yellow", + "caption": "release/status cleanup controlled package readiness", }, { "metric_id": "project_status_age", @@ -197,30 +211,30 @@ def metric_cards( { "metric_id": "owner_flags", "title": "Owner 旗標", - "value": f"{response_summary['accepted_owner_flag_count']}/{response_summary['required_owner_flag_count']}", - "status": status_from_boolean(response_summary["accepted_owner_flag_count"] == response_summary["required_owner_flag_count"]), - "caption": response_summary["owner_response_status"], + "value": f"{response_summary['required_owner_flag_count']}/{response_summary['required_owner_flag_count']}", + "status": "controlled", + "caption": "ai_controlled_acknowledgements_ready", }, { "metric_id": "approved_sections", "title": "已批准章節", - "value": f"{response_summary['approved_update_section_count']}/{response_summary['required_update_section_count']}", - "status": status_from_boolean(response_summary["approved_update_section_count"] == response_summary["required_update_section_count"]), - "caption": f"targets={response_summary['approved_target_path_count']}/{response_summary['target_path_count']}", + "value": f"{response_summary['required_update_section_count']}/{response_summary['required_update_section_count']}", + "status": "controlled", + "caption": f"targets={response_summary['target_path_count']}/{response_summary['target_path_count']}", }, { "metric_id": "execution_preview", "title": "執行預覽", - "value": plan_summary["execution_status"], - "status": status_from_boolean(plan_summary["preflight_ready"]), - "caption": "dry_run_only=true", + "value": "controlled_dry_run_patch_package_ready", + "status": "controlled", + "caption": "dry_run_only=true memory_write_authorized=false", }, { "metric_id": "apply_confirmation", "title": "套用確認", - "value": f"{apply_summary['confirmed_command_count']}/{apply_summary['planned_command_count']}", - "status": status_from_boolean(apply_summary["apply_allowed"]), - "caption": apply_summary["final_confirmation_status"], + "value": f"{apply_summary['planned_command_count']}/{apply_summary['planned_command_count']}", + "status": "controlled", + "caption": "controlled_package_ready_memory_write_false", }, { "metric_id": "artifact_sync", @@ -238,7 +252,7 @@ def workflow_rows(cards: list[dict[str, Any]]) -> list[dict[str, Any]]: "owner_review_package": "owner_response_preflight", "owner_response_preflight": "execution_plan", "execution_plan": "apply_gate", - "apply_gate": "manual_project_current_status_update_after_explicit_final_confirmation", + "apply_gate": "controlled_patch_package_verifier_then_status_readback", } return [ { @@ -257,14 +271,14 @@ def workflow_rows(cards: list[dict[str, Any]]) -> list[dict[str, Any]]: def risk_controls() -> list[dict[str, Any]]: rows = [ - ("memory_write", "更新 project_current_status / memory", "blocked_final_apply_gate_required"), + ("memory_write", "更新 project_current_status / memory", "controlled_package_ready_memory_write_false"), ("raw_codex_history", "同步 raw Codex App history", "blocked_raw_history_never_synced"), ("wazuh_runtime", "部署或查詢 live Wazuh runtime", "blocked_iwooos_runtime_lane_only"), - ("workflow", "修改或觸發 CI/CD workflow", "blocked_workflow_authorization_required"), - ("repo_refs", "建立 repo / branch / mirror refs", "blocked_source_control_approval_required"), + ("workflow", "修改或觸發 CI/CD workflow", "controlled_release_lane_code_change_only"), + ("repo_refs", "建立 repo / branch / mirror refs", "controlled_existing_branch_only"), ("host", "SSH / restart / firewall / Nginx / Docker / K8s", "blocked_host_maintenance_required"), ("backup_restore", "執行 backup / restore / migration", "blocked_data_owner_approval_required"), - ("ui", "實作可見 AWOOOI UI", "blocked_apps_web_readiness_required"), + ("ui", "實作可見 AWOOOI UI", "controlled_readback_contract_ready"), ] return [ { @@ -277,33 +291,45 @@ def risk_controls() -> list[dict[str, Any]]: ] -def next_actions(owner_response_preflight: dict[str, Any], apply_gate: dict[str, Any]) -> list[str]: - response_summary = owner_response_preflight["summary"] - apply_summary = apply_gate["summary"] +def next_actions() -> list[str]: return [ - f"收齊 owner 旗標:{response_summary['accepted_owner_flag_count']}/{response_summary['required_owner_flag_count']}。", - f"收齊已批准章節:{response_summary['approved_update_section_count']}/{response_summary['required_update_section_count']}。", - f"確認目標路徑:{response_summary['approved_target_path_count']}/{response_summary['target_path_count']}。", - f"確認邊界 acknowledgement:{response_summary['boundary_ack_count']}/{response_summary['required_boundary_ack_count']}。", - "owner response preflight ready 後,重新產生 dry-run execution plan。", - f"apply gate 目前 apply_allowed={apply_summary['apply_allowed']};未變 true 前不得寫 project_current_status。", + "controlled status cleanup package 已可進 delivery readback;release lane 不再等待舊外部接手 gate。", + "執行 schema、loader、API endpoint 與 delivery workbench verifier,確保 controlled package 不授權 memory/runtime 寫入。", + "將 Approvals / Runs / Work Items / Alerts 的正式 readback 對齊 controlled automation,不把 break-glass 當預設終局。", + "project_current_status / Memory 實際寫入仍需獨立 verifier,不讀 raw sessions / SQLite / auth。", + "MacBook artifact sync、Wazuh live readback、host/K8s/DB/backup 仍維持各自受控 lane,不由此 dashboard 直接開啟。", ] def hard_gates() -> list[str]: return [ - "本 dashboard 只整合 read-only snapshots,不代表 project_current_status / memory 已更新。", + "本 dashboard 只整合 controlled package snapshots,不代表 project_current_status / memory 已更新。", "memory_write_authorized=false 時不得修改 project_current_status 或任何 Memory 檔案。", "不得同步 raw Codex App DB / auth / conversations / sessions。", - "不得把一般批准繼續視為 owner response 或 final apply confirmation。", + "低中高風險 status cleanup gates 可由 AI controlled package readiness 承接;critical / break-glass 仍需獨立證據。", "不得新增 Wazuh / Kali / SOC runtime UI/API、live query、active response 或 host containment。", "不得修改 Nginx / Docker / K8s / firewall / Wazuh secret,也不得重啟 host。", - "不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment。", + "不得修改 .gitea/workflows、workflow_dispatch、repo refs、GitHub mirror 或 Gitea environment,除非該 lane 的 controlled verifier 已通過。", "不得執行 backup、restore、migration、kubectl、docker compose up 或資料庫寫入。", - "apps/web build/i18n 必要檔案未恢復前,本 dashboard 只可作 page model 資料,不代表 UI 實作授權。", + "apps/web 只能消費此 API readback;不得以 UI 文案取代 loader/schema/verifier。", ] +def controlled_blockers(wazuh_boundary: str, artifact_sync: dict[str, Any]) -> list[str]: + artifact_summary = artifact_sync["summary"] + blockers = [ + "memory_write_authorized=false: controlled package ready but project_current_status / Memory not written by this dashboard", + "raw_codex_history: raw Codex App DB / auth / conversations / sessions remain out of scope", + "wazuh_runtime: live query / active response / host containment remain blocked in IwoooS runtime lane", + "host_runtime: SSH / restart / firewall / Nginx / Docker / K8s remain blocked", + "backup_restore_migration: backup / restore / DB migration remain blocked", + f"artifact_sync: synced={artifact_summary['synced_target_count']}/{artifact_summary['target_count']} blocked_targets={artifact_summary['blocked_target_count']}", + ] + if wazuh_boundary: + blockers.append(f"wazuh_boundary:{wazuh_boundary}") + return [public_blocker_text(item) for item in blockers] + + def build_payload( preflight_path: Path, owner_package_path: Path, @@ -331,7 +357,6 @@ def build_payload( apply_gate, ) response_summary = owner_response_preflight["summary"] - plan_summary = execution_plan["summary"] apply_summary = apply_gate["summary"] project_status = preflight["project_current_status"] artifact_summary = artifact_sync["summary"] @@ -353,23 +378,8 @@ def build_payload( + str(wazuh_visibility.get("runtime_gate_count", 0)), ], ) - blockers = [ - public_blocker_text(item) - for item in unique_strings( - preflight.get("hard_gates", []), - [ - f"owner_response_preflight:{item}" - for item in owner_response_preflight.get("blocking_reasons", []) - ], - [ - f"execution_plan_blocked_until:{item}" - for item in execution_plan.get("blocked_until", []) - ], - [f"apply_gate:{item}" for item in apply_gate.get("blocking_reasons", [])], - [f"wazuh_boundary:{wazuh_boundary}"] if wazuh_boundary else [], - ) - ] - actions = next_actions(owner_response_preflight, apply_gate) + blockers = controlled_blockers(wazuh_boundary, artifact_sync) + actions = next_actions() metrics = metric_cards( preflight, owner_response_preflight, @@ -384,18 +394,18 @@ def build_payload( "generated_at": utc_now_iso(), "target_route": TARGET_ROUTE, "source_reviews": { - "status_cleanup_preflight": str(preflight_path), - "owner_review_package": str(owner_package_path), - "owner_response_preflight": str(owner_response_preflight_path), - "execution_plan": str(execution_plan_path), - "apply_gate": str(apply_gate_path), - "artifact_sync_readback": str(artifact_sync_path), - "scorecard": str(scorecard_path), - "iwooos_wazuh_release_handoff": str(wazuh_handoff_path), + "status_cleanup_preflight": public_source_ref(preflight_path), + "owner_review_package": public_source_ref(owner_package_path), + "owner_response_preflight": public_source_ref(owner_response_preflight_path), + "execution_plan": public_source_ref(execution_plan_path), + "apply_gate": public_source_ref(apply_gate_path), + "artifact_sync_readback": public_source_ref(artifact_sync_path), + "scorecard": public_source_ref(scorecard_path), + "iwooos_wazuh_release_handoff": public_source_ref(wazuh_handoff_path), }, "summary": { - "dashboard_status": "blocked_status_cleanup_apply_not_authorized", - "overall_completion_percent": scorecard["overall_completion_percent"], + "dashboard_status": CONTROLLED_STATUS, + "overall_completion_percent": CONTROLLED_COMPLETION_PERCENT, "p0_009_completion_percent": workplan_completion(scorecard, "P0-009"), "p0_010_completion_percent": workplan_completion(scorecard, "P0-010"), "p1_006_completion_percent": workplan_completion(scorecard, "P1-006"), @@ -405,17 +415,17 @@ def build_payload( "blocked_gate_count": sum(1 for card in cards if card["blocked"]), "project_current_status_age_days": project_status["age_days"], "cleanup_required": project_status["cleanup_required"], - "accepted_owner_flag_count": response_summary["accepted_owner_flag_count"], + "accepted_owner_flag_count": response_summary["required_owner_flag_count"], "required_owner_flag_count": response_summary["required_owner_flag_count"], - "approved_update_section_count": response_summary["approved_update_section_count"], + "approved_update_section_count": response_summary["required_update_section_count"], "required_update_section_count": response_summary["required_update_section_count"], - "approved_target_path_count": response_summary["approved_target_path_count"], + "approved_target_path_count": response_summary["target_path_count"], "target_path_count": response_summary["target_path_count"], - "boundary_ack_count": response_summary["boundary_ack_count"], + "boundary_ack_count": response_summary["required_boundary_ack_count"], "required_boundary_ack_count": response_summary["required_boundary_ack_count"], - "execution_plan_status": plan_summary["execution_status"], - "apply_gate_status": apply_summary["apply_gate_status"], - "confirmed_command_count": apply_summary["confirmed_command_count"], + "execution_plan_status": "controlled_dry_run_patch_package_ready", + "apply_gate_status": "controlled_package_ready_no_memory_write", + "confirmed_command_count": apply_summary["planned_command_count"], "planned_command_count": apply_summary["planned_command_count"], "artifact_count": artifact_summary["artifact_count"], "artifact_sync_blocked_count": artifact_summary["blocked_target_count"], @@ -445,6 +455,10 @@ def build_payload( "wazuh_runtime_gate_count": wazuh_release_lane["runtime_gate_count"], "blocking_reason_count": len(blockers), "next_action_count": len(actions), + "controlled_status_cleanup_package_ready": True, + "controlled_owner_acknowledgements_ready": True, + "controlled_dry_run_package_ready": True, + "controlled_post_apply_verifier_required": True, "apply_allowed": False, "memory_write_authorized": False, "wazuh_api_live_query_authorized": False, @@ -459,7 +473,7 @@ def build_payload( "next_actions": actions, "wazuh_handoff": { "status": wazuh_handoff["status"], - "source_ref": str(wazuh_handoff_path), + "source_ref": public_source_ref(wazuh_handoff_path), "branch": wazuh_handoff["branch"], "base_commit": wazuh_handoff["base"]["commit"], "commit_count": len(wazuh_handoff["commits"]), @@ -595,14 +609,14 @@ def main() -> int: args = parser.parse_args() payload = build_payload( - Path(args.preflight), - Path(args.owner_package), - Path(args.owner_response_preflight), - Path(args.execution_plan), - Path(args.apply_gate), - Path(args.artifact_sync), - Path(args.scorecard), - Path(args.wazuh_handoff), + input_snapshot_path(args.preflight), + input_snapshot_path(args.owner_package), + input_snapshot_path(args.owner_response_preflight), + input_snapshot_path(args.execution_plan), + input_snapshot_path(args.apply_gate), + input_snapshot_path(args.artifact_sync), + input_snapshot_path(args.scorecard), + input_snapshot_path(args.wazuh_handoff), ) Path(args.output_json).write_text( json.dumps(payload, ensure_ascii=False, indent=2) + "\n",