From 215bd9b73c178a2e378c4be28b266749372aa503 Mon Sep 17 00:00:00 2001 From: OoO Date: Tue, 5 May 2026 23:40:45 +0800 Subject: [PATCH] ci(observability): verify CSS mirror instead of mutating runner --- .gitea/workflows/cd.yaml | 2 +- docs/guides/deployment_sop.md | 3 ++- docs/guides/observability_ui_governance.md | 2 ++ ...observability_ui_qa_guardrails_20260505.md | 4 +++- scripts/quick_review.sh | 11 +++++++-- scripts/sync_observability_css.py | 23 +++++++++++++++++-- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 6b62b55..5ed4a6e 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -160,7 +160,7 @@ jobs: - name: AI 觀測台 Pre-deploy 靜態 QA if: steps.observability_qa.outputs.needed == 'true' run: | - bash ./scripts/quick_review.sh --sync-observability-css + bash ./scripts/quick_review.sh --check-observability-css bash ./scripts/quick_review.sh --observability-qa --skip-production # ── 模式 A:僅同步 Python 檔案(最常見,~10s) ──────────────────────── diff --git a/docs/guides/deployment_sop.md b/docs/guides/deployment_sop.md index 338c195..2292f79 100644 --- a/docs/guides/deployment_sop.md +++ b/docs/guides/deployment_sop.md @@ -48,6 +48,7 @@ ssh -J wooo@192.168.0.110 ollama@192.168.0.188 \ 本地先跑: ```bash ./scripts/quick_review.sh --sync-observability-css +./scripts/quick_review.sh --check-observability-css ./scripts/quick_review.sh --observability-qa ``` @@ -72,7 +73,7 @@ QA 套件會檢查: CD 也會自動判斷觀測台相關變更: -- Deploy 前跑 `./scripts/quick_review.sh --sync-observability-css`。 +- Deploy 前跑 `./scripts/quick_review.sh --check-observability-css`,確認 CSS mirror 已提交一致,不在 runner 內偷偷修。 - Deploy 前跑 `./scripts/quick_review.sh --observability-qa --skip-production`。 - Deploy 後跑 `./scripts/quick_review.sh --observability-smoke --base-url https://mo.wooo.work --timeout 12`。 - 若變更與觀測台無關,CD 會跳過這組額外 QA,避免拖慢一般後端部署。 diff --git a/docs/guides/observability_ui_governance.md b/docs/guides/observability_ui_governance.md index 16bac45..db0caeb 100644 --- a/docs/guides/observability_ui_governance.md +++ b/docs/guides/observability_ui_governance.md @@ -54,6 +54,7 @@ python3 scripts/check_observability_ui.py ```bash python3 scripts/sync_observability_css.py +python3 scripts/sync_observability_css.py --check ``` 或透過既有 quick review 選單執行第 8 項完整 QA 套件、第 6 項靜態 UI guard、第 9 項 CSS mirror sync: @@ -66,6 +67,7 @@ python3 scripts/sync_observability_css.py ```bash ./scripts/quick_review.sh --sync-observability-css +./scripts/quick_review.sh --check-observability-css ./scripts/quick_review.sh --observability-qa ``` diff --git a/docs/memory/observability_ui_qa_guardrails_20260505.md b/docs/memory/observability_ui_qa_guardrails_20260505.md index 06498e3..6d7a7bb 100644 --- a/docs/memory/observability_ui_qa_guardrails_20260505.md +++ b/docs/memory/observability_ui_qa_guardrails_20260505.md @@ -26,6 +26,7 @@ Claude Code Phase 38→55 完成 AI 觀測台 10 頁,但前端曾出現以下 ```bash ./scripts/quick_review.sh --sync-observability-css +./scripts/quick_review.sh --check-observability-css ./scripts/quick_review.sh --observability-qa ``` @@ -42,6 +43,7 @@ Claude Code Phase 38→55 完成 AI 觀測台 10 頁,但前端曾出現以下 ```bash python3 scripts/sync_observability_css.py +python3 scripts/sync_observability_css.py --check bash scripts/check_observability_suite.sh ``` @@ -53,7 +55,7 @@ bash scripts/check_observability_suite.sh - Production smoke 必須看到 `觀測台 CSS: HTTP 200, markers=ok`。 - 觀測台頁面清單、URL、`active_page`、內容 marker 不要分散維護,先改 `scripts/observability_contract.py`。 - `quick_review.sh --observability-qa` 預設打 production `https://mo.wooo.work`;測 staging/localhost 時要明確帶 `--base-url`。 -- Gitea CD 會偵測觀測台 template/CSS/route/QA script/guide 變更:deploy 前跑 CSS sync + static QA,deploy 後跑 production smoke。 +- Gitea CD 會偵測觀測台 template/CSS/route/QA script/guide 變更:deploy 前跑 CSS mirror check + static QA,deploy 後跑 production smoke。CD 不會偷偷修 mirror;若 check fail,先本地跑 sync 後提交。 ## 已鎖住的回歸 diff --git a/scripts/quick_review.sh b/scripts/quick_review.sh index d53601e..76d5ee5 100755 --- a/scripts/quick_review.sh +++ b/scripts/quick_review.sh @@ -83,7 +83,7 @@ run_observability_css_sync() { fi echo -e "${GREEN}🎨 同步 AI觀測台 CSS 到 Flask static path...${NC}" - python3 "$OBSERVABILITY_CSS_SYNC" + python3 "$OBSERVABILITY_CSS_SYNC" "$@" } # 非互動入口:給部署腳本、CI、或 Codex session 直接呼叫。 @@ -106,7 +106,12 @@ if [ $# -gt 0 ]; then ;; --sync-observability-css) shift - run_observability_css_sync + run_observability_css_sync "$@" + exit $? + ;; + --check-observability-css) + shift + run_observability_css_sync --check exit $? ;; --observability-help) @@ -114,6 +119,8 @@ if [ $# -gt 0 ]; then AI observability quick-review flags: --sync-observability-css Sync static/css/observability-system.css to web/static/css mirror. + --check-observability-css + Verify CSS mirror is already committed and in sync. --observability-ui Run static UI/template/navigation guard only. --observability-smoke [--base-url URL] [--timeout SEC] diff --git a/scripts/sync_observability_css.py b/scripts/sync_observability_css.py index 3447242..38a198b 100644 --- a/scripts/sync_observability_css.py +++ b/scripts/sync_observability_css.py @@ -13,6 +13,7 @@ This helper keeps the two files byte-identical so deploys do not regress into from __future__ import annotations +import argparse import shutil from pathlib import Path @@ -23,20 +24,38 @@ TARGET = ROOT / "web/static/css/observability-system.css" def main() -> int: + parser = argparse.ArgumentParser(description="Sync or check observability CSS mirror") + parser.add_argument( + "--check", + action="store_true", + help="Only verify source and Flask static mirror are byte-identical.", + ) + args = parser.parse_args() + if not SOURCE.exists(): print(f"ERROR: missing source CSS: {SOURCE.relative_to(ROOT)}") return 1 - TARGET.parent.mkdir(parents=True, exist_ok=True) + if not args.check: + TARGET.parent.mkdir(parents=True, exist_ok=True) + source_bytes = SOURCE.read_bytes() target_bytes = TARGET.read_bytes() if TARGET.exists() else b"" if source_bytes == target_bytes: - print("Observability CSS sync: already in sync") + action = "check" if args.check else "sync" + print(f"Observability CSS {action}: already in sync") print(f"- source: {SOURCE.relative_to(ROOT)}") print(f"- target: {TARGET.relative_to(ROOT)}") return 0 + if args.check: + print("Observability CSS check: FAIL") + print(f"- source: {SOURCE.relative_to(ROOT)}") + print(f"- target: {TARGET.relative_to(ROOT)}") + print("- fix: python3 scripts/sync_observability_css.py") + return 1 + shutil.copyfile(SOURCE, TARGET) print("Observability CSS sync: synced") print(f"- source: {SOURCE.relative_to(ROOT)}")