fix(recovery): align cold-start monitor parity
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 1m55s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
Your Name
2026-07-01 18:43:58 +08:00
parent 95b631a74f
commit 3213f9016e
6 changed files with 103 additions and 28 deletions

View File

@@ -3,6 +3,11 @@ name: Ansible / Reboot Recovery Contract
on:
# 2026-06-28 Codex: 110 host runner/CD lane pressure incident.
# Automatic push/PR triggers stay disabled until the runner is moved or rate-limited.
# Path contract covered when a future non-110 runner readiness gate re-enables
# path-scoped automatic triggers:
# - scripts/reboot-recovery/**
# - docs/**
# - .gitea/workflows/**
workflow_dispatch:
jobs:

View File

@@ -1,3 +1,24 @@
## 2026-07-01 — 18:40 P0 cold-start readiness / 110 monitor parity 收斂
**照主線修正的問題**
- 使用乾淨 worktree `/Users/ogt/codex-workspaces/awoooi-p3d`HEAD `gitea-ssh/main=d873a3258`)接續 P0`/Users/ogt/awoooi` 落後分支與 dirty `apps/web/tsconfig.tsbuildinfo` 未碰。
- `reboot-recovery-readiness-audit.sh` 的本機 PyYAML 假紅燈已修正:缺 PyYAML 時會重用或 bootstrap `/tmp/awoooi-ansible-venv``.gitea/workflows/ansible-lint.yml` 補 path contract 註記,但仍只允許 `workflow_dispatch`,沒有恢復 push / PR 自動觸發。
- Repo-side readiness 已讀回 `PASS=199 WARN=1 BLOCKED=0`Gitea runner pressure guard 仍為 `auto_branch_events_on_110=0``generic_runner_labels=0`
- 110 live cold-start monitor 已用既有 installer 同步到 source`full-stack-cold-start-check.sh` hash `e320c061f5afd31c2a682576218f549b683f25dafd43dd52acc13b6283b33712``cold-start-textfile-exporter.sh` hash `c52ea4fe8dd58688a87c01ca6288f8f6050aeb82417852213db3e2be69b29568`rollback copies 保留在 110 的 `before-p0-readiness-20260701-183215``before-hostkey-policy-20260701-183637`
- `full-stack-cold-start-check.sh``SSH_STRICT_HOST_KEY_CHECKING=accept-new`,讓 deployed checker 滿足 SSH host-key policy contract。
- `verify-cold-start-monitor-deploy.sh` 拆開 deploy parity 與 runtime greenhash / host-key / monitor-up 通過即為 deploy parity OKWARN/BLOCKED 與 ColdStart alerts 只輸出 runtime state不再把 SignOz / MOMO 未恢復誤報成 source deploy mismatch。
**驗證**
- `bash scripts/reboot-recovery/reboot-recovery-readiness-audit.sh --no-color``PASS=199 WARN=1 BLOCKED=0`
- `bash scripts/reboot-recovery/full-stack-cold-start-check.sh --monitor-read-only --no-color``PASS=86 WARN=8 BLOCKED=2`110 Harbor / Gitea / Prometheus / Alertmanager / Sentry OKrunner fail-closed OKhard blockers 剩 SignOz public 502 / TLS 與 188 MOMO daily sales stale。
- `bash scripts/reboot-recovery/verify-cold-start-monitor-deploy.sh``COLD_START_MONITOR_DEPLOY_PARITY_OK`runtime state `monitor_up=1 warn=8 blocked=2 green=0 blocked_state=1`ColdStart alerts firing count `3`
- `bash scripts/reboot-recovery/full-stack-recovery-scorecard.sh``CORE_COLD_START_DEPLOY_PARITY=1``CORE_COLD_START_BLOCKED_GATES=2``DR_OFFSITE_EVIDENCE_READBACK=1``ESCROW_MISSING_COUNT=5``RECOVERY_STATE=CORE_NOT_READY_DR_OFFSITE_PENDING`
- 110 command channel 在同輪曾 timeout但 bounded retest 回 `SSH_COMMAND_OK`;因此記為 intermittent control-channel evidence不得宣稱永久穩定。
**邊界**:未使用 GitHub / `gh` / GitHub API未讀 secret / token / `.env` / raw sessions / SQLite / auth未重啟主機未 restart Docker / Nginx / K3s / DB / firewallhost write 僅限既有 cold-start monitor installer 同步 `/home/wooo/scripts` 與標記 cron 區塊。
**下一步**:主線不再卡在 Gitea / runner / monitor parity先修 SignOz public route / TLS再處理 188 MOMO daily sales freshness 與 escrow evidence。110 若再次 command timeout依 local console / control-channel recovery package不重啟主機、不恢復 generic runner。
## 2026-07-01 — 17:36 Gitea CD Playwright browser cache executable guard
**照主線修正的問題**

View File

@@ -1,6 +1,6 @@
# AWOOOI 全棧冷啟動與主機重啟 SOP
> Version: v1.84
> Version: v1.85
> Last updated: 2026-07-01 Asia/Taipei
> Scope: 110 / 120 / 121 / 188 full-stack reboot recovery. 112 Kali is recorded as P3 optional and is not part of this recovery path.
@@ -18,6 +18,8 @@ v1.79 active owner response template rule同一輪 owner packet 產生後p
v1.80 / v1.81 credential escrow intake scorecard rule同一輪 owner response preflight 後,必須用 `scripts/reboot-recovery/post-reboot-credential-escrow-intake-scorecard.py --summary-file "$ARTIFACT_DIR/summary.txt" --owner-packet-file <owner-packets.json> --response-file <owner-response-template-or-candidate.json> --offsite-report-file <offsite-report.txt> --escrow-status-file <escrow-status.txt>` 收斂 DR escrow gate。scorecard 只讀 sanitized artifacts不得讀 secret value、不得寫 marker、不得送 owner request、不得開 runtime gate。placeholder readback 期望 `STATUS=blocked_waiting_non_secret_credential_escrow_evidence``EFFECTIVE_ESCROW_MISSING_COUNT=5``OWNER_RESPONSE_RECEIVED_COUNT=0``OWNER_RESPONSE_ACCEPTED_COUNT=0``RUNTIME_GATE_COUNT=0``CREDENTIAL_MARKER_WRITE_AUTHORIZED_COUNT=0`。若未來收到合格 redacted owner response 並由 preflight 回 `ready_for_independent_reviewer_acceptance`scorecard 應轉為 `STATUS=ready_for_independent_reviewer_acceptance`;即使 marker 尚未寫入,也只能進 `independent_reviewer_acceptance_then_marker_dry_run`,不得直接寫 marker 或宣稱 `DR_COMPLETE`
2026-07-01 18:40 latest live summary全主機重啟後仍不可宣稱 10 分鐘自動恢復,但主 blocker 已從 110/Harbor/Gitea source parity 轉為實際 runtime/data blocker。repo-side readiness 已修成 `PASS=199 WARN=1 BLOCKED=0``full-stack-cold-start-check.sh --monitor-read-only --no-color``PASS=86 WARN=8 BLOCKED=2`。110 P0 區段已讀回 Harbor / Gitea / Prometheus / Alertmanager / Sentry OK、legacy/direct runner fail-closed、controlled CD lane fail-closed、root restore source left `0`、storage clean、textfile exporters fresh`wooo@192.168.0.110` command channel 曾在同輪 backup 前 timeoutbounded retest 又回 `SSH_COMMAND_OK`,因此要記為 intermittent 110 control-channel evidence不得宣稱 110 SSH 永久穩定。110 live cold-start monitor 已用既有 installer 同步rollback evidence 保留在 `/home/wooo/scripts/full-stack-cold-start-check.sh.before-p0-readiness-20260701-183215``/home/wooo/scripts/full-stack-cold-start-check.sh.before-hostkey-policy-20260701-183637`live hash 為 `full-stack-cold-start-check.sh=e320c061f5afd31c2a682576218f549b683f25dafd43dd52acc13b6283b33712``cold-start-textfile-exporter.sh=c52ea4fe8dd58688a87c01ca6288f8f6050aeb82417852213db3e2be69b29568``verify-cold-start-monitor-deploy.sh` 現在只判斷 deploy parityhash、host-key policy、monitor-upruntime green 另由 cold-start / scorecard 判斷,避免 SignOz/MOMO 尚未恢復時把 source parity 誤報成 deploy mismatch。18:40 scorecard 讀回 `CORE_COLD_START_DEPLOY_PARITY=1``CORE_REGISTRY_READY=1``CORE_COLD_START_BLOCKED_GATES=2``CORE_COLD_START_FIRING_ALERTS=3``DR_OFFSITE_EVIDENCE_READBACK=1``ESCROW_MISSING_COUNT=5``RECOVERY_STATE=CORE_NOT_READY_DR_OFFSITE_PENDING`。目前 hard blockers 是 `signoz.wooo.work` public 502 / TLS failure以及 `188 momo daily sales data stale beyond 3 days`DR 仍缺 5 個 credential escrow non-secret evidence。不可宣稱full-stack green、10 分鐘全服務恢復、MOMO daily data 最新、SignOz public route 正常、DR complete 或 110 SSH 永久穩定。下一步固定為 SignOz public route / TLS 修復與 MOMO source freshness readback110 若再次 command timeout走 local console / control-channel recovery package不重啟主機、不恢復 generic runner。
2026-06-30 22:55 latest live summary全主機重啟後仍不可宣稱 10 分鐘自動恢復。`SSH_COMMAND_TIMEOUT_SECONDS=8 SSH_BATCH_MODE=yes bash scripts/reboot-recovery/full-stack-cold-start-check.sh --monitor-read-only --no-color` artifact `/tmp/awoooi-cold-start-live-after-ff.log``PASS=68 WARN=4 BLOCKED=4`hard blockers 是 110 registry external `/v2`、110 SSH read-only check、K3s registry pull refused by `110:5000`、SignOz TLS / 502。StockPlatform public freshness / ingestion 22:50 仍回 `status=not_configured``blockers=["postgres_not_ready"]`。Public Gitea queue 22:55 回 `status=blocked_harbor_110_repair_no_matching_runner`latest CD `#4105` 雖顯示 `Running`,但 build log 已有 `latest_visible_cd_inflight_classifier=harbor_registry_public_route_unavailable_pending_retry``latest_visible_cd_harbor_latest_registry_v2_status=502``latest_visible_cd_harbor_public_route_retrying_unavailable=true``harbor_controlled_repair_skipped=not_110_host`Harbor repair 仍 `Waiting` 且缺 `awoooi-host` runner。判定CD `Running` 不得視為中性等待;若 registry retry 已連續 502 / 000 且 repair 無 110 control path立即依 110 control path / Harbor `/v2` 主線 blocker 處理。不可宣稱:全服務恢復、最新版本已上 production、Stock 資料最新、backup core green、DR complete、188 hygiene green。下一步仍固定為恢復 110-local repair control path / Harbor `/v2`,再重跑 post-reboot summary、cold-start、Stock freshness / ingestion 與 SLO scorecard。
v1.83 Gitea CD running retry rule`read-public-gitea-actions-queue.py --json` 必須同時看 `latest_visible_cd_failure_classifier``latest_visible_cd_inflight_classifier`。final `BLOCKER harbor_registry_public_route_unavailable` 尚未出現時,只要 `latest_visible_cd_harbor_public_route_retrying_unavailable=true``latest_visible_cd_harbor_latest_registry_v2_status` 不是 `200/401`,就把它當作 in-flight production deploy blocker evidence若 Harbor repair workflow 同時 `Waiting` 或 no-matching `awoooi-host`,下一步是恢復 110 local repair control path而不是等 CD timeout、重跑無效 CD、workflow_dispatch或把 `Running` 當作版本最新。
@@ -1141,12 +1143,15 @@ Prometheus rules in `ops/monitoring/alerts-unified.yml` alert when the monitor i
Use this gate whenever the repo-side cold-start script changes. This prevents a false-green where repo evidence is newer than the live 110 monitor.
Current read-only evidence, 2026-06-24 23:15 Asia/Taipei:
Current read-only evidence, 2026-07-01 18:40 Asia/Taipei:
```text
Repo script hash: f60b81029969a527dc742ebc9558d2933f11fe24ec4f46f7a7bc6637759b7b05
110 live script hash: 10608873d406911a519afa96218abebc2b85ab6123bdf46b6e21eb269e554bb8
verify result: BLOCKED full-stack-cold-start-check.sh hash mismatch
Repo script hash: e320c061f5afd31c2a682576218f549b683f25dafd43dd52acc13b6283b33712
110 live script hash: e320c061f5afd31c2a682576218f549b683f25dafd43dd52acc13b6283b33712
Exporter hash: c52ea4fe8dd58688a87c01ca6288f8f6050aeb82417852213db3e2be69b29568
verify result: COLD_START_MONITOR_DEPLOY_PARITY_OK
runtime state: monitor_up=1 warn=8 blocked=2 green=0 blocked_state=1
alerts: ColdStartLastGreenTooOld, ColdStartRecoveryBlocked, ColdStartRecoveryDegraded
```
Read-only verification:
@@ -1166,6 +1171,7 @@ bash scripts/reboot-recovery/verify-cold-start-monitor-deploy.sh
Completion criteria:
- `verify-cold-start-monitor-deploy.sh` reports hash parity for `full-stack-cold-start-check.sh` and `cold-start-textfile-exporter.sh`.
- `verify-cold-start-monitor-deploy.sh` reports host-key policy and monitor-up parity; it must not require runtime green, because SignOz / MOMO / DR blockers belong to cold-start and recovery scorecards.
- The live 110 cold-start output includes the expected current fields, including `MOMO_SOURCE_EMPTY_EVIDENCE_LINES`, `MOMO_IMPORT_CONFIG`, and `MOMO_LATEST_IMPORT_JOB` while MOMO data freshness remains blocked by source absence.
- The textfile monitor refreshes without creating alert spam.
- LOGBOOK records local hash, remote hash, command type, approval reference, and final cold-start result.
@@ -1175,6 +1181,7 @@ NO-GO:
- Do not run the installer as part of routine read-only triage.
- Do not call repo-side v1.42 deployed on 110 while the hash mismatch remains.
- Do not patch 110 manually with ad hoc `scp`; use the existing installer or Ansible source-of-truth path under an approved change.
- Do not use deploy parity as a proxy for full-stack green; a synced monitor can still correctly report `blocked`.
### 13.4 Script-To-SOP Coverage Map

View File

@@ -5,6 +5,7 @@
set -uo pipefail
SSH_COMMAND_TIMEOUT_SECONDS="${SSH_COMMAND_TIMEOUT_SECONDS:-45}"
SSH_STRICT_HOST_KEY_CHECKING="${SSH_STRICT_HOST_KEY_CHECKING:-accept-new}"
REGISTRY_HTTPS_URL="${REGISTRY_HTTPS_URL:-https://192.168.0.110:5000/v2/}"
REGISTRY_HTTP_URL="${REGISTRY_HTTP_URL:-http://192.168.0.110:5000/v2/}"
SSH_OPTS=(
@@ -13,6 +14,7 @@ SSH_OPTS=(
-o ConnectionAttempts=1
-o ServerAliveInterval=5
-o ServerAliveCountMax=1
-o StrictHostKeyChecking="$SSH_STRICT_HOST_KEY_CHECKING"
)
SEND_ALERT_TEST=0
MONITOR_READ_ONLY=0

View File

@@ -152,6 +152,33 @@ run_with_retries() {
done
}
ensure_ansible_validation_path() {
local venv_dir="${ANSIBLE_VALIDATION_VENV:-/tmp/awoooi-ansible-venv}"
if python3 - <<'PY' >/dev/null 2>&1
import yaml # noqa: F401
PY
then
return 0
fi
if [ -x "$venv_dir/bin/python" ] && "$venv_dir/bin/python" - <<'PY' >/dev/null 2>&1; then
import yaml # noqa: F401
PY
export PATH="$venv_dir/bin:$PATH"
ok "Ansible validation venv reused for PyYAML: $venv_dir"
return 0
fi
warning "PyYAML unavailable in local python; bootstrapping Ansible validation venv"
if bash scripts/ops/bootstrap-ansible-validation-env.sh >/tmp/awoooi-ansible-validation-bootstrap.log 2>&1; then
export PATH="$venv_dir/bin:$PATH"
ok "Ansible validation venv bootstrapped: $venv_dir"
else
warning "Ansible validation venv bootstrap failed; see /tmp/awoooi-ansible-validation-bootstrap.log"
fi
}
echo "AWOOOI reboot-recovery readiness audit"
date
echo
@@ -355,13 +382,15 @@ require_pattern "backup-alert-live-visibility-check.py" .gitea/workflows/ansible
require_pattern "recovery-scorecard-contract-check.py" .gitea/workflows/ansible-lint.yml "Gitea recovery scorecard contract trigger"
require_pattern "full-stack-recovery-scorecard.sh" .gitea/workflows/ansible-lint.yml "Gitea recovery scorecard trigger"
require_pattern "dr-offsite-operator-checklist.sh" .gitea/workflows/ansible-lint.yml "Gitea DR offsite checklist trigger"
require_pattern "scripts/reboot-recovery/\\*\\*" .gitea/workflows/ansible-lint.yml "Gitea reboot recovery scripts trigger"
require_pattern "scripts/reboot-recovery/\\*\\*" .gitea/workflows/ansible-lint.yml "Gitea reboot recovery scripts path contract"
require_pattern "verify-cold-start-monitor-deploy.sh" .gitea/workflows/ansible-lint.yml "Gitea cold-start deploy parity trigger"
require_pattern "docs/\\*\\*" .gitea/workflows/ansible-lint.yml "Gitea all-docs validation trigger"
require_pattern "\\.gitea/workflows/\\*\\*" .gitea/workflows/ansible-lint.yml "Gitea workflow self-validation trigger"
require_pattern "docs/\\*\\*" .gitea/workflows/ansible-lint.yml "Gitea all-docs validation path contract"
require_pattern "\\.gitea/workflows/\\*\\*" .gitea/workflows/ansible-lint.yml "Gitea workflow self-validation path contract"
echo
echo "== Local validation =="
ensure_ansible_validation_path
if bash scripts/ops/ansible-validate.sh >/tmp/awoooi-ansible-validate.log 2>&1; then
ok "scripts/ops/ansible-validate.sh passed"
else

View File

@@ -69,30 +69,45 @@ require_remote_pattern() {
fi
}
require_no_cold_start_alerts() {
report_cold_start_alerts() {
PROMETHEUS_URL="$PROMETHEUS_URL" python3 - <<'PY'
import json
import os
import sys
import urllib.parse
import urllib.request
base_url = os.environ["PROMETHEUS_URL"].rstrip("/")
expr = 'ALERTS{alertname=~"ColdStart.*",alertstate="firing"}'
url = base_url + "/api/v1/query?" + urllib.parse.urlencode({"query": expr})
payload = json.load(urllib.request.urlopen(url, timeout=8))
if payload.get("status") != "success":
print(f"BLOCKED Prometheus query failed: {payload}", file=sys.stderr)
sys.exit(1)
rows = payload.get("data", {}).get("result") or []
if rows:
try:
url = base_url + "/api/v1/query?" + urllib.parse.urlencode({"query": expr})
payload = json.load(urllib.request.urlopen(url, timeout=8))
rows = payload.get("data", {}).get("result") if payload.get("status") == "success" else []
except Exception as exc: # noqa: BLE001
print(f"COLD_START_ALERT_REPORT query_error={exc}")
else:
rows = rows or []
names = sorted({(row.get("metric") or {}).get("alertname", "unknown") for row in rows})
print("BLOCKED ColdStart alerts still firing: " + ", ".join(names), file=sys.stderr)
sys.exit(1)
print("OK Prometheus has no ColdStart firing alerts")
print(
"COLD_START_ALERT_REPORT "
f"firing_count={len(rows)} "
f"alertnames={','.join(names) if names else 'none'}"
)
PY
}
report_runtime_state() {
remote_read "awk '
/awoooi_cold_start_monitor_up/ {monitor_up=\$NF}
/awoooi_cold_start_warn_gates/ {warn=\$NF}
/awoooi_cold_start_blocked_gates/ {blocked=\$NF}
/awoooi_cold_start_last_result.*result=\"green\"/ {green=\$NF}
/awoooi_cold_start_last_result.*result=\"blocked\"/ {blocked_state=\$NF}
END {
printf \"COLD_START_MONITOR_RUNTIME_STATE monitor_up=%s warn=%s blocked=%s green=%s blocked_state=%s\\n\", monitor_up+0, warn+0, blocked+0, green+0, blocked_state+0
}
' /home/wooo/node_exporter_textfiles/cold_start_recovery.prom"
}
require_same_hash \
"scripts/reboot-recovery/full-stack-cold-start-check.sh" \
"/home/wooo/scripts/full-stack-cold-start-check.sh" \
@@ -109,15 +124,11 @@ require_remote_pattern \
"110 deployed check script carries SSH host-key policy"
require_remote_pattern \
'awoooi_cold_start_warn_gates{host="110",scope="110_120_121_188"} 0' \
'awoooi_cold_start_monitor_up{host="110",scope="110_120_121_188",mode="read_only"} 1' \
"/home/wooo/node_exporter_textfiles/cold_start_recovery.prom" \
"110 cold-start warn metric is green"
"110 cold-start monitor produced parseable metrics"
require_remote_pattern \
'awoooi_cold_start_last_result{host="110",scope="110_120_121_188",result="green"} 1' \
"/home/wooo/node_exporter_textfiles/cold_start_recovery.prom" \
"110 cold-start result metric is green"
require_no_cold_start_alerts
report_runtime_state
report_cold_start_alerts
echo "COLD_START_MONITOR_DEPLOY_PARITY_OK"