Repository navigation
fix(health): report what could not be observed as unknown (#537) - #629
Conversation
The health CLI (saccade_ctl.sh health; printed once by the orchestrator at start) reported an NVML failure as OK, an unreachable Redis as a queue depth of 0 and a user unit that does not exist as inactive/failed, and showed its own loop latency and allocator as stress metrics without saying so. REQ-535-07-03 asks that a caller can tell unknown from healthy and see where each reading comes from. Each check is now ok / degraded / unknown with its source: systemd reads LoadState/ActiveState of the user unit (not-found or a systemctl that cannot answer is unknown), NVML GPU 0 that cannot be read is unknown with its error, Redis that cannot be reached is degraded with no queue depth. The status is DEGRADED when any check is degraded, else UNKNOWN when any is unknown, else OK; overall_ok only for OK. The Redis URL is shown without credentials. The exit code stays 0: no readiness policy is chosen. Docs: failure_degradation_537.md §24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex r1 (2b120bf) NOT-READY: - MAJOR: redacted_url() read parts.port, which raises ValueError on an invalid or out-of-range port, so render() failed -- and with it the orchestrator's startup report. Credentials and the query are now cut from the URL text and host:port is shown as written; a URL that cannot be split shows as <unparseable URL>. - MINOR: IPv6 hosts lost their brackets; they are kept now. - MINOR: the threshold tests read VRAM_WARN_PCT from the environment; they pin it. Tests: 11 redaction cases and a render with an invalid URL. Mutants 10 (the first redaction) and 11 (keeping the query) are caught; §24 counts updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex r2 (1368913) NOT-READY, two MAJORs: - A REDIS_URL without scheme:// (user:pw@host:6379/0) was split with the userinfo in the path and printed. Such a URL is now shown only when it has no '@', '?' or '#'; otherwise <unparseable URL>. - redis-py's URL parse error quotes the whole netloc, and the report printed it as the Redis detail. When REDIS_URL may carry secrets ('@', '?' or '#'), the detail is the error type only. Tests: six more redaction cases, the real redis-py parser refusing a fullwidth-slash URL (no connection), and the hidden/kept message rule. Mutants 12-14 are caught; §24 counts updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex r3 (3486a7b) NOT-READY: - MAJOR: userinfo the split leaves in place (redis://u:pw/x@h, redis:///u:pw@h) was printed. - MAJOR: a fullwidth '@' passed the '@?#' check, and redis-py's parse error then printed the password as the Redis detail. - MINOR: two tests read REDIS_URL from the environment. Three rounds of denylist patches still leaked, so the rule is now an allowlist: after userinfo, query and fragment are cut, the URL is shown only when it is plain ASCII scheme/host/port/path characters (alphanumerics and "+-._~:/[]"); otherwise <unparseable URL>. The Redis error message is shown only when REDIS_URL itself is plain; otherwise the error type. Both test modules pin REDIS_URL. Tests: a whole-report regression over 12 password-bearing URLs whose error quotes the URL, the real parser refusing a fullwidth '@', five more redaction cases. 16 mutants caught; §24 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex r4 (bc8e378) NOT-READY, one MAJOR: an unescaped '?' or '#' in a password (redis://user:pw?x@host) moves '@host' into the discarded query or fragment, and the remaining user:pw passed the allowlist. A URL with an '@' anywhere outside its netloc is now <unparseable URL>. Tests: three redaction cases and two whole-report URLs; mutant 17 is caught; §24 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex r5 (60c2ddb) NOT-READY, one MAJOR: redis://user:pw?x@host (fullwidth '@') passed both the '@' count and the allowlist, because the query or fragment holding the look-alike was dropped before the check. redacted_url() now cuts only the netloc's userinfo and keeps the rest as written; the whole result must be plain, so nothing is discarded before the check. A URL with a query or fragment is therefore never shown. The '@' count check is redundant under this rule and is removed. Tests: the '@' + '?'/'#' combinations in the redaction and whole-report lists, an uppercase scheme; two expectations change (a URL with a query or fragment is now <unparseable URL>). 17 mutants caught, among them the r4 version; §24 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex r6 (d9e949d) READY-WITH-CHANGES, one MINOR: urlsplit drops tabs and newlines, so slicing the input by its netloc length misaligned and redis://u:pw\t@cache.local:6379/0 showed port 63799 (no leak). The netloc is now cut from the input up to the first '/', '?' or '#'. Tests: tab/newline in userinfo and host, a URL without a path. 18 mutants caught, among them the r5 version; §24 updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fresh capture on the implementation head 2d0b283 (clean tree, own build/runtime_identity, --run-probe --require-complete under the gpu0 lease): - implementation: ab1bc4ea82d70131 -> 4ad5ae2c7f2a7d2c; 317 -> 317 files, only src/saccade/pipeline/health.py changed (the health CLI's ok/degraded/unknown report). - decision_surface, environment, identity_semantics, runtime_inputs: recomputed, unchanged. - probe: rerun, not reused; digest 2dabed0b... unchanged. Probe equality is not an equivalence claim; equivalence stays unproven. The outgoing canonical is archived as runtime_identity.2026-10-23.ab1bc4ea82d70131.json (byte-identical to origin/main's). check_runtime_identity_staleness.py --mode attested exits 0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-identity docs(runtime): republish the runtime coordinate for #537 health CLI
Self-check(#550 §0A 合併條件 1–5)head:
剩餘 OPEN:
Rollback/拒絕路徑:revert 本 PR 的 merge commit,再依 runbook 重新 capture 並出版座標,不直接恢復舊 canonical 冒充 current。Owner 若否決方案 A,依此路徑回退。 合併指令(需 owner 執行): |
#537 health CLI:取不到的不算健康(REQ-535-07-03)
方案紀錄:#537 comment 6367964691(選 A)。內容寫在
failure_degradation_537.md§24。src/saccade/pipeline/health.py是saccade_ctl.sh health執行的 health CLI,orchestrator 啟動時也會在 process 內跑一次並印出(SE-48)。修復前在main@5aa63ee5上重現到以下問題:Saccade Status — OK。0 items。inactive/failed。改了什麼
ok、degraded或unknown,並附來源。systemctl --user show -p LoadState,ActiveState。not-found、systemctl 不存在、逾時或非零 exit 都是 unknown。None。overall_ok只在 OK 時為真。REDIS_URL只在確認安全時顯示:<unparseable URL>。REDIS_URL本身不是純字元時,Redis 錯誤訊息只印例外類型。VRAM_WARN_PCT、orchestrator、ResourceManager。審查(Codex,綁定 head)
2b120bf6render()丟例外(MAJOR);IPv6 方括號被拿掉、門檻測試依賴環境變數(MINOR)。三項都已修正1368913bscheme://的 URL 印出密碼;redis-py 的解析錯誤經 detail 印出密碼(MAJOR×2)。已修正3486a7be@繞過 denylist(MAJOR×2);測試讀環境中的REDIS_URL(MINOR)。改用 allowlistbc8e3780?/#把@host推出 netloc(MAJOR)。已修正60c2ddb2@加?/#的組合(MAJOR)。改成不丟棄任何部分就檢查整段d9e949d1urlsplit刪 tab 造成顯示錯位(MINOR,沒有外洩)。改從原始字串切 netloc2d0b2834測試
tests/unit/test_pipeline_health_contract_537.py(新增,88 個案例)與tests/unit/test_pipeline_health.py(3 個):91 passed。另外在VRAM_WARN_PCT=10、REDIS_URL=redis://u:s3cret@x:1/0兩種環境下都是 91 passed。全程 host-only,不碰真實的 systemctl、NVML、Redis 或 CUDA(唯一例外是用真實 redis-py 解析器在連線前拒絕的兩個 URL)。redacted_url;共享的health.pySHA-256 前後相同。2d0b2834:Redis 未啟動、沒有安裝 user units,報告為 DEGRADED;三個 unit 顯示? unknown: no such user unit,queue depth 為 unknown,Fragmentation 為 n/a,exit 0。pre_push@3486a7be:6010 passed,4 failed,都是 coordinate 落後時預期會失敗的 identity 測試(test_research_lock×2、test_runtime_identity_staleness×2)。Runtime identity
health.py是decision_relevant。座標由 stacked PR 重新發佈(base = 本分支),審完後先 merge 進本分支,再由本 PR 原子落地。只有 implementation 軸改變。未驗證
Refs #537
🤖 Generated with Claude Code