Skip to content

architecture(C): define capability failure, fallback and runtime degradation contracts #537

Open

Description

@raylei50653

Parent: #534 — C(失敗、fallback、執行時降級)
Depends on: #535 的支持 profiles;引用 #536 的相關邊界。靜態盤點可先做,改行為前須凍結契約。
Feeds into: F/G;與 D 協調跨進程狀態/錯誤傳遞。
Status: backlog。

目標

系統必須能區分『真的沒有 tracks』『能力不可用』『受控降級』『监控未知』『功能已退役』。本單擁有執行時狀態,不擁有功能退休決策。

已觀察到的實作與待重現疑點

座標 aca28d1645b03ce0efc01032d3bbe3456044c62c。以下為 source inspection,尚未執行 repository 測試。

C1 — native 能力缺失與空輸出

tracker_gpu.py 在 native extension 無法 import 時建立 stub,update() 回 []、部分方法 no-op。README 已公開說明此 stub 及 is_cuda == False。

先追到 public wrapper 與真正 consumer,判定哪些入口已 preflight、哪些只允許 import/測試替身。不能僅憑 stub 存在就宣稱 shipping 有 silent failure。 需要決定受支持的真實 tracking 入口在能力缺失時何時拒絕、如何診斷;保留無 GPU import 的需求也要明列。

C2 — degradation state machine

resource_manager.py 宣告 85/92/96% 門檻與 5% hysteresis,但升階區塊直接依 usage 重新指定較低 level。

待用 CPU fixture 重現的靜態反例:

  • current=EMERGENCY, usage=94 → 程式路徑設為 FAST_PATH,即使尚未低於註解所對應的 91% 解除門檻;
  • current=FAST_PATH, usage=90 → 設為 REDUCED,即使尚未低於 87%。

先固定真正預期:升級是否立即/可跨級、恢復是否逐級、等號邊界、deadband、重啟初值、非法輸入。若註解不是正確需求,需 owner 決策而非照現有碼合理化。

C3 — unknown 不應未經裁決當 NORMAL

同檔中 NVML init failure 後 get_stats() 回全零;SHM reader 找不到 writer 時回 NORMAL。reader 持有的 1-byte segment 沒有 generation/heartbeat/freshness 欄位。需核對 writer 崩潰/重建後 reader 的 freshness 與 reconnect 行為,不預先指定必須採哪種 IPC 實作。

dispatcher.py 的 WorkbenchPool get_stats() 回固定 current_level: NORMAL;需標明此 profile 是否實際支持 resource degradation,不能把『未接此能力』當已測健康。

最小工作順序

  1. 分 profile 建立 capability/failure matrix:required→error、optional→explicit disable、approved fallback→具名降級;記錄錯誤碼/例外、health/report 可觀測性、允許副作用與恢復条件。
  2. 先盤點既有測試,再以 no-GPU fixtures 固定上述反例與控制組;monkeypatch resource probe,不依賴實際耗盡 VRAM。
  3. 一類行為一個小 PR:native preflight、hysteresis、monitor unknown/stale 分開,不混成全域『fail-closed 重寫』。實作期望必須先核准。
  4. Optional fallback 需有名字、觸發理由、輸出/精度/狀態差異;不假設 CPU/GPU 或 geometry/ReID 等價,也不默默把失敗轉空資料。涉及品質差異時依既有契約重驗。
  5. 檢查 recovery、重复故障、告警節流與 state reset;不只测 happy path。

完成條件

  • Native stub 的合法範圍、consumer 檢查及真實運行失敗語義已裁決;正常空幀與能力缺失可分辨。
  • Degradation 的完整 transition table 已固定;門檻附近與上述反例有可重現測試,證明舊行為是否違約。
  • NVML unavailable、SHM missing/stale/restarted、非法 level 有明確策略與測試;unknown 不被無註記包裝成 healthy。
  • 每項 fallback/disable 有可觀察狀態及恢復條件,並說明 profile 差異。
  • 至少一個已核准的高風險缺口以小 PR 關閉且有負控制;其他已確認問題有獨立 owner/後續 issue,不能只留『待改善』。
  • 既有 shipping 行為、數值、runtime identity 與其他 profiles 未被無意改動;G 可引用 evidence/具名限制。

非目標

不宣稱本次已發現/重現 GPU race;不調 tracker 參數、不刪 ReID、不統一所有 fallback,不改 #465 已接受的支持矩陣。若獨立修復已先落地,引用並驗證其證據,不重做。

Activity

  1. raylei50653 commented on Oct 13, 2026

    @raylei50653
    OwnerAuthor

    C0 起步:無 GPU 重現證據與 Owner 裁決(2026-10-13)

    座標 main@8350f6f7bc591f313d9a37996b33d7e77f66a61f(#594 merge;main CI SUCCESS)。以下是 read-only 重現:用 mock 取代 NVML,SHM 用私有名稱,native 擴充以 sys.modules 封鎖。沒有修改 runtime。 raw 腳本與輸出放在本機 gitignored 保留目錄 results/537_c0_repro_20261013/(未進 git),下一個 C0 PR 會把同樣的案例寫成 repo 內測試。

    重現結果(observed as-is)

    項目 觀察
    C2 issue 反例 EMERGENCY@94 → FAST_PATH;FAST_PATH@90 → REDUCED。兩者都重現
    C2 完整單步表 ① FAST_PATH 實際上沒有遲滯:usage ≤ 92 就回到 REDUCED。② EMERGENCY 的轉移不單調:@≤90.9 → FAST_PATH,但 @91~92 → REDUCED(一次跳兩級)。③ 只有 REDUCED→NORMAL 的 −5%(<80)有作用。升級門檻為嚴格大於(85/92/96 本身不升級)
    C3 NVML init 失敗 get_stats() 全部回 0;從 EMERGENCY 開始,連續 3 次決策會依序變成 FAST_PATH → REDUCED → NORMAL。「量不到」被當成「健康」
    C3 SHM 缺失/非法值 沒有 writer 時 read() 回 NORMAL;writer 晚於 reader 建立時能附掛上。byte=7 時 read() 拋 ValueError
    B0-F07 stale reader 重現成功。 舊 segment(EMERGENCY)未 unlink → reader 附掛 → writer unlink 並重建,寫入 NORMAL/REDUCED → 舊 reader 一直讀到 EMERGENCY;新建的 reader 則讀到 REDUCED。writer close() 之後,舊 reader 仍讀到最後的值。main.py --mode full 中 PipelineOrchestrator() 是 asyncio.gather 的參數,會比 dispatcher.start() 先建構(source 確認)。依 orchestrator.py 的分支,非 anomaly 事件會一直被丟棄(此影響是 source 判讀,沒有跑完整 service)
    B0-F02 沒有擴充時,AsyncDispatcher.get_tracker() 拋 TypeError: Any cannot be instantiated
    C1 facade stub 沒有擴充但有 CUDA 時,GPUByteTracker.update() 對 3 個和 0 個 detection 都回 [],is_cuda=False,和合法空幀無法區分。沒有 CUDA 時,update() 在進到 stub 之前就拋 RuntimeError: No CUDA GPUs are available
    既有測試 ResourceManager/VRAMLevelReader/VRAMLevelWriter 沒有直接測試,只有 tests/extended/test_dispatcher.py 以 mock 使用

    Owner 裁決(本次工作 session,轉錄)

    • D-537-1 C0 範圍:一條 Draft PR,只包含文件矩陣+無 GPU 特徵測試,負責固定現況與失敗證據,不混入修復;不改 runtime 行為,沒有 merge 授權。
    • D-537-2 C2 目標語義(採納 docs/modules/resource/architecture_degradation.md §5):升級立即生效,可以跨級;恢復時每次決策只降一級,而且 usage 必須嚴格低於目前級別的升級門檻 −5 才降(EMERGENCY <91、FAST_PATH <87、REDUCED <80)。重啟初值、非法 usage(NaN/負值/>100)與決策頻率尚未裁決。目標測試及實作屬於後續修復 PR。
    • D-537-3 第一個修復目標:B0-F07 stale reader,另開小 PR。依 REQ-535-07-01 的分工,architecture(C): define capability failure, fallback and runtime degradation contracts #537 選擇 consumer policy,freshness/restart 判準歸 architecture(D): audit async ownership, backpressure, error propagation and shutdown #538;修復前要先提出具體方案給 owner 核准。

    C1/F02 的拒絕點(REQ-535-02-01/02-04)、NVML 失敗與 SHM 缺失的 consumer policy 尚未裁決,都列入 C0 矩陣的待決清單。

  2. raylei50653 commented on Oct 13, 2026

    @raylei50653
    OwnerAuthor

    C0 Draft PR:#595,head 604306b79b6763f5ccf1ee891ef3f314009192c3,PR CI+C++ 8/8 SUCCESS,Codex r2 沒有 actionable findings;完整 checkpoint 見 #550。沒有 merge 授權;C2 與 B0-F07 修復尚未開始。

  3. raylei50653 commented on Oct 17, 2026

    @raylei50653
    OwnerAuthor

    狀態回寫:B0-F07、C2、R-1(C0-11)已合併(2026-10-16~17)

    本則補上 C0(#595)之後還沒回寫到本 issue 的三個修復。詳細內容在 failure_degradation_537.md §7~§10,跨 issue 進度在 #550。

    1. B0-F07(C0-06/07/08)— #596 MERGED 978e5538

    2. C2 遲滯(C0-04)— #598 MERGED 82126e7c(2026-10-17)

    • 依 D-537-2 採方案 A(單行 clamp)。owner 另裁決 R0/O0/F0,並接受 EMERGENCY 的保留區間。
    • 測試:86 個 contract 案例;修復前有 17 個失敗;6 個 mutation 都被抓到。
    • 座標由 #599 co-land(implementation 30bce8b0…→e6d423b4…)。
    • CI:PR CI+C++、main CI+C++ 都是 SUCCESS(§8)。

    3. R-1/C0-11 dispatcher↔tracker 契約 — 經 #609 原子合併 68c482c0(2026-10-17)

    • C0-11 發現(#600,§9):問題不只是缺少 degradation API。AsyncDispatcher 對 tracker 的兩種呼叫,native binding 和 Python facade 都不支援。結果是任何 level 下,第一個有 detection 的 batch 就會讓 worker 結束。
    • 修復(#601,§10,owner 2026-10-17 裁決):
      • 新增內部的 OnlineTrackerAdapter,只做 geometry-only。
      • native 或 CUDA 不可用時,丟出 OnlineTrackerUnavailable 明確拒絕,start() 會先做 preflight;傳入 extractor(ReID)也會被拒絕。
      • FAST_PATH/EMERGENCY 只把 batch 限制為 min(max_batch, 2),不呼叫 set_degradation_params(),也不用 hasattr 略過;get_stats()["tracker_degradation"]=="none"。
    • C0-03 一併收斂:native 缺失時,start() 立即拒絕,不再拖到第一個有 detection 的 batch 才拋 TypeError。
    • 同一次落地的相關切片:
    • 驗證:
      • R-1 修復前的版本有 21 個行為斷言失敗;7 個 mutation 都被抓到。
      • native adapter/dispatcher 的 smoke 測試只在本機 CUDA 跑過,其餘都是 host-only。
      • 整合樹 f7f46b8f:PR CI+C++ 8/8;本機 pre_push 6017 passed/0 failed;attested exit 0。
      • main 68c482c0:main CI+C++ SUCCESS(pytest 5729 passed/0 failed)。在 main 上跑 attested 也是 exit 0。
    • main.py --mode full 改為明確的 geometry-only,--reid 會 fail-fast(caller lifecycle,見 architecture(D): audit async ownership, backpressure, error propagation and shutdown #538)。

    仍為 OPEN(本 issue)

    • C0-01/02 的拒絕點與回報方式(REQ-535-02-01/03/04;packaging: define the public tracker-core API surface and choose the distribution channel #441)。
    • C0-05:NVML 失敗時仍發佈 FRESH NORMAL(REQ-535-07-01)。
    • C0-09:WorkbenchPool 是否支援 resource degradation(REQ-535-07-02)。目前 current_level 固定為 NORMAL。
    • C2 的非法 usage(O0)只維持現況,不構成契約。
    • 本次修復不是 REQ-535-05-* 的支持批准,也不是 online-service qualification。on_detection_finished 仍是 no-op,所以 --mode full 沒有接通 detection→cognition。

    本 issue 保持 OPEN。

  4. added a commit that references this issue on Oct 23, 2026
  5. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    C0-05(D-537-4A)已合併(2026-10-23)

    #622 MERGED 5c38b486(head b5ca6834,merge tree 等於 head)。座標由 stacked #623 一起原子落地。

    • 方案:4A。owner 2026-10-20 核准;方案紀錄在 failure_degradation_537.md §19,實作與證據在 §20。4B 未批准。
    • 行為:NVML init、probe 失敗或 sample 無效時,拋 ResourceMonitorUnavailable,不再回四個 0、不再發佈 FRESH NORMAL。AsyncDispatcher.start() 先做 preflight;執行中失敗沿 D2-AD 進入 FAILED。stats 顯示 UNKNOWN 加上 monitor status/error。
    • 證據:PR CI 8/8 SUCCESS(第一次執行就通過);main CI +C++ 8/8 SUCCESS(pytest 5849 passed/0 failed);main 上 --mode attested exit 0;本機 pre_push 6006 passed、0 failed;GPU 測試 15 passed;Codex r2 READY-TO-MERGE。
    • Runtime identity:implementation 583ede47 → d04e0183,其餘四軸不變,probe 2dabed0b 不變,equivalence=unproven。
    • 界線:沒有做真實 NVML 故障或 VRAM 耗盡注入;不提升 online-service/WorkbenchPool 的資格。

    完成條件進度:「至少一個已核准的高風險缺口以小 PR 關閉且有負控制」這一項,加上 F07(#596)、C2(#598),由本 PR 再補一項。「NVML unavailable …有明確策略與測試」這一項,NVML 部分已完成;SHM 部分已由 F07 完成。仍 OPEN:C0-09(WorkbenchPool 沒有接上監控)、C0-01/02(public facade/eval 拒絕點)、C0-10、O0(非法 usage)、health CLI/telemetry(REQ-535-07-03)。#537 保持 OPEN。

  6. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    C0-09 方案紀錄(§0A 開工條件 3)

    問題:WorkbenchPool.get_stats() 固定回 current_level: "NORMAL",但這條路徑沒有 resource manager/VRAM writer(failure_degradation_537.md §2 C0-09、§19.3)。stats 看起來健康,其實沒有在監控。

    候選方案

    • A:current_level 改為新的哨兵值 "UNMONITORED"。缺點是 level 詞彙多出第三種非 level 值,consumer 要多處理一種字串。
    • B(選擇):current_level 改為 "UNKNOWN",並新增 resource_monitor_status: "not_wired" 與 resource_monitor_error: "",和 C0-05 後 AsyncDispatcher 的 stats 欄位結構一致。run_8stream_perception.py --workbench 的報告同步印出 monitor=not_wired。
    • C:移除 current_level 鍵。這會讓既有 caller 的報告 KeyError,相容性最差。

    理由:B 沿用 C0-05 已定義的 unknown 語義與 monitor 欄位,consumer 只要讀 resource_monitor_status 就能分辨「監控失敗」和「根本沒接」,不必另外學一種 level 字串。

    不做的事:不在 WorkbenchPool 接上降級 controller 或 writer(§19.3);--workbench 仍然 unsupported(#538);不改 AsyncDispatcher、hysteresis、快照 v1。

    受影響路徑:src/saccade/perception/dispatcher.py(只改 WorkbenchPool.get_stats)、scripts/ops/run_8stream_perception.py(只改 workbench 報告)、測試與文件。兩個檔案都是 decision_relevant,風險:高,需要 Codex 獨立審查,runtime identity 重新發佈後原子落地。

    測試與負控制

    • host 單元測試:stats 在 CREATED/RUNNING/FAILED/STOPPED 各狀態都不回 "NORMAL",resource_monitor_status == "not_wired";caller 報告含 monitor=not_wired。
    • mutation:把 current_level 改回 "NORMAL"、或移除 resource_monitor_status,測試必須失敗。
    • GPU:在既有 WorkbenchPool GPU contract 測試中,以真實 engine 確認 stats 欄位。

    回退:revert 實作 commit,並依 runbook 重新發佈座標(不直接恢復舊 publication 冒充 current)。

    未驗證:不跑完整 online service;不提供 WorkbenchPool 的任何資源降級能力;不取得 online-service qualification。

  7. added 2 commits that reference this issue on Oct 23, 2026
  8. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    C0-09 已合併(2026-10-23)

    #624 MERGED 43432942(head f0ceeeec,merge tree 等於 head)。座標由 stacked #625 一起原子落地。

    • 方案:B(方案紀錄),依 [Hub] SACCADE 工作總入口:進度、優先序、依賴與 checkpoint #550 §0A 自行啟動並合併。實作與證據在 failure_degradation_537.md §21。
    • 行為:WorkbenchPool.get_stats() 不再固定回 current_level="NORMAL",改為 "UNKNOWN",並加上 resource_monitor_status="not_wired"、resource_monitor_error="";--workbench 報告印出 monitor=not_wired。沒有接上降級 controller。
    • 證據:main CI+C++ 8/8 SUCCESS(pytest 5851 passed/0 failed);PR CI 8/8 第一次執行就通過;main 上 --mode attested exit 0;本機 pre_push 6009 passed、0 failed;GPU 測試 16 passed;五個 mutation 都被攔住;Codex r1 的 MINOR(缺 STOPPING)已修正,r2/r3 READY-TO-MERGE;self-check。
    • Runtime identity:implementation d04e0183 → 46d37ec5(只改 dispatcher.py、run_8stream_perception.py),其餘四軸不變,probe 2dabed0b 不變,equivalence=unproven。
    • 界線:not_wired 只說明沒有監控,不是健康判定。WorkbenchPool 仍沒有資源降級能力,--workbench 仍 unsupported(architecture(D): audit async ownership, backpressure, error propagation and shutdown #538),不提升任何 qualification。

    仍 OPEN:C0-01/02(public facade/eval 拒絕點)、C0-10(shipping run-state 轉換重查)、O0(非法 usage 的決策語義)、health CLI/telemetry(REQ-535-07-03);WorkbenchPool 是否要支援資源降級仍未裁決(§6)。#537 保持 OPEN。

  9. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    O0 方案紀錄(§0A 開工條件 3)

    問題:ResourceManager.decide_degradation_level() 不檢查 VRAMStats.usage_percent。直接注入非法值時(failure_degradation_537.md §8 O0、§19.3),NaN 維持原級、負值降一級、>100 升到 EMERGENCY,三者都會被當成一次有效決策並推進遲滯狀態。4A 之後 production get_stats() 只會回傳 0~100 的有限值,所以這條路徑只在 get_stats() 被替換(測試替身、子類別、未來的其他 telemetry 來源)時可達;目前沒有 production 觸發點。

    候選方案

    • A(選擇):決策前驗證 usage 必須是 0~100(含端點)的有限實數;不符合就丟 ResourceMonitorUnavailable(cause 為 ValueError,訊息含 GPU 與原值),current_level 不變、不做這次決策。這和 4A「無效 sample 也屬 monitor unavailable」同一條規則,dispatcher 走既有錯誤通道:monitor 回報 unavailable、level 為 UNKNOWN、worker FAILED、不寫 FRESH 快照。
    • B:夾到 [0, 100],NaN 維持原級。負值會被當成 0% 而降級,等於把壞資料當成「資源充足」,和 4A 拒絕無效 sample 的方向相反。
    • C:維持現況,只在文件標為 unsupported。非法值仍會推進遲滯狀態(負值可以讓 EMERGENCY 降級),O0 留著不收斂。

    理由:A 把已核准的 4A 規則套到決策輸入上,不新增新的失敗語義或錯誤通道;B 會讓壞資料造成降級(fail-open)。

    不做的事:不改 get_stats() 的 sample 驗證、門檻、D-537-2 遲滯、R0/F0、快照 v1 或 dispatcher。dispatcher 的啟動 probe 直接呼叫 get_stats(),所以被替換的 get_stats() 回傳非法 usage 時,啟動仍會成功,第一次決策才失敗。production get_stats() 不會產生這種值,這一點只寫進文件,不在本 PR 處理。

    受影響路徑:src/saccade/resource/resource_manager.py(只改 decide_degradation_level)、測試、failure_degradation_537.md、architecture_degradation.md §5/§7。實作檔屬 decision_relevant,風險:高:需要 Codex 獨立審查,runtime identity 重新發佈後原子落地。

    測試與負控制

    • host 單元測試:NaN、±inf、負值、>100(含 100.0001)、非數字、None 都丟 ResourceMonitorUnavailable,cause 為 ValueError,current_level 不變;之後送入有效值時,從保留的 level 正常逐級恢復;0 與 100 仍有效(既有 T1 表)。原本的 T7 O0「觀察」列改成契約。
    • dispatcher(host fake):執行中 usage 變成非法值時,worker FAILED、monitor 回報 unavailable、level 為 UNKNOWN、不寫新快照,原 level 保留。
    • mutation:移除驗證、上界改成 < 100、改丟 ValueError、驗證移到改寫 current_level 之後,測試都必須失敗。

    回退:revert 實作 commit,並依 runbook 重新發佈座標。

    未驗證:不跑完整 online service,也沒有真實 GPU 的非法 sample(production 無觸發點);不取得 online-service qualification。Owner 可以事後否決,依回退路徑處理。

  10. added 2 commits that reference this issue on Oct 23, 2026
  11. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    O0 已合併(2026-10-23)

    #626 已合併為 1f18cf76(head c625ce1b,merge tree 與 head 相同)。座標由 stacked #627 一起原子落地。

    • 方案:A(方案紀錄),依 [Hub] SACCADE 工作總入口:進度、優先序、依賴與 checkpoint #550 §0A 自行啟動並合併。實作與證據在 failure_degradation_537.md §22。
    • 行為:decide_degradation_level() 只接受 0~100(含端點)的實數 usage。NaN、±inf、超出範圍或非實數型別(字串、None、Decimal)一律拋 ResourceMonitorUnavailable(cause 為 ValueError),level 不變,也不算一次決策;dispatcher 沿用 4A 的通道處理。修復前的行為是:NaN 維持原級、負值降一級、>100 升到 EMERGENCY。production get_stats() 不會產生這類值,只有被替換的 get_stats() 會走到這條路徑。
    • 證據:
      • CI:main CI+C++ 8/8 SUCCESS(pytest 5900 passed/0 failed);PR CI 第一次失敗只有 4 個座標落後的預期測試,合入 docs(runtime): republish the runtime coordinate for #537 O0 #627 後轉為 8/8。
      • 本機:main 上 --mode attested exit 0;pre_push @ 座標 head 6058 passed、0 failed。
      • 測試強度:新測試在修復前的實作下有 47 個失敗;六個 mutation 都被攔住;真實 NVML smoke(resctl gpu0)通過。
      • 審查:Codex r1 NOT-READY(Decimal("NaN") 的 InvalidOperation 漏接),改為先檢查 numbers.Real 後 r2/r3 READY-TO-MERGE;self-check。
    • Runtime identity:implementation 46d37ec5 → ab1bc4ea(只改 resource_manager.py),其餘四軸不變,probe 2dabed0b 不變,equivalence=unproven。
    • 界線:dispatcher 的啟動 probe 直接呼叫 get_stats(),所以被替換的 get_stats() 回非法值時,要到第一次 batch 決策才失敗(§22 已記錄;production 不可達)。不跑完整 online service,不提升任何 qualification。

    仍 OPEN:C0-01/02(public facade/eval 拒絕點,需 #441/#536 協調)、C0-10(shipping run-state 轉換重查)、health CLI/telemetry(REQ-535-07-03);WorkbenchPool 是否要支援資源降級仍未裁決(§6)。#537 保持 OPEN。

  12. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    C0-10 方案紀錄(§0A 開工條件 3)

    問題:shipping saccade_track 的 run journal(CC-536-01-01,#562 起實作)有 running/failed/complete 三個 state,但「各 state 怎麼轉換、失敗怎麼分類」在卡上明寫歸 #537,C0 也沒有重查(failure_degradation_537.md §2 C0-10、§19.3)。重新讀 source(main@1f18cf76:run_completion.cpp、track_driver.hpp、saccade_track.cpp、preflight.cpp)後,有三件事需要定下來:

    1. exit code 與 journal 可能互相矛盾。complete 寫在 completion.run() 的最後;run() 回傳後才解構 runtime(CUDA stream、nvJPEG、TRT、torch),process 結束時還有 static/driver teardown。這段如果 abort 或被 kill,結果就是 journal complete、exit 非零。卡的第 5 項說「非零代表本輪未完成」,caller 判讀規則卻只看 journal;reader(native_track_parity)實際上兩者都要求。(source-inspected)
    2. 失敗分類沒有欄位,只能從 tuple 推。failure 只有 {sequence, message}。由 state、identity.level/required、load_verification、failure.sequence、已寫出的 sequence 數,可以推出:Gate A 前或 Gate A 拒絕、--require-identity 拒絕、Gate B 載入失敗(DetectorLoadError)、Gate B 其他錯誤(GPU 初始化等,load_verification 為 null)、第 k 個 sequence、report 發布或 commit 寫入失敗。這套推導規則沒有寫下來,也沒有逐條測試。
    3. 邊界情形:被捕捉的錯誤加上 journal 寫不進去時,會是 running+exit 2(fail() 是 best effort)。sequence i 的 txt 已 rename、journal 更新失敗,而之後 fail() 寫成功時,同一份 journal 會同時有「i 是 written」和「failure.sequence=i」(source-inspected)。

    候選方案

    • A(選擇):只做重查與釘住,不改 shipping/**。在 failure_degradation_537.md 新增 §23:(a) 轉換表,每一列是 事件 → journal state、failure.sequence、identity.level、load_verification、已提交 sequence、exit code,各自綁一個檢查(既有或本 PR 新增),註明 reproduced 或 source-inspected;(b) 失敗分類的推導規則(不加新欄位);(c) 判讀規則採合取:只有 exit 0 而且這次 run_id 的 journal 是 complete,才算完整成功。complete 搭配非零 exit 的意思是「輸出已提交,但 process 沒有正常結束」,保守的 caller 判為未完成。這和 reader 現行行為以及卡的第 5 項一致,比只看 journal 更嚴,不放寬任何 fail-closed 規則。(d) 「部分完成」定義為:failed 或 running,且至少有一個 sequence 依 journal sha 確認提交。CC-536-01-01 卡只加一個指向 §23 的連結;轉換語義的唯一寫入者是 §23。
    • B:journal 加明確的 failure.stage/class 欄位(journal v4、report 新版)。會改 shipping/**、reader 與 format,runtime identity 要重新發佈,package pin 也會過期(re-pin 屬 §0A 例外)。tuple 已經推得出分類,目前也沒有 consumer 需要,所以不做。若之後有 consumer 需要 tuple 推不出的分類,再提。
    • C:把 commit point 移到 runtime 解構之後。要改 shipping/**,而且 process 結束時的 driver/static teardown 還是會在 commit 之後發生,矛盾不會消失,所以不做。

    理由:A 把現有行為的語義寫清楚並用測試釘住,判讀規則採更嚴的那一邊,不需要動 shipping binary,也不需要 re-pin;B、C 的成本(identity/package re-pin)換不到新的可觀測性。

    不做的事:不改 shipping/**、journal/report format、exit code、lock、作廢範圍、reader 邏輯;不碰 C0-01/02、health CLI、#538;不新增 run 存活探測(lock 被持有 = 仍在跑,lock 空出且 running = 已中斷;這點只寫成 caller 診斷提示)。

    受影響路徑:docs/architecture/failure_degradation_537.md(§2 C0-10、§6、§19.3、新 §23)、docs/architecture/ship_export_contracts_536.md(CC-536-01-01 只加連結)、tests/native/test_shipping_run_completion.cpp、視需要 tests/unit/test_native_track_parity.py。依 h2_path_partition,以上都是 non_execution,不移動 runtime coordinate。風險:一般(測試+文件,不改 production 程式)。

    測試與負控制(CPU,shipping-config-loader ctest,CI 會跑)

    • 新增 protocol 案例:Gate A 拒絕(level null、load_verification null、failure.sequence null、沒有 sequence 提交);--require-identity 拒絕(level 已封存但低於 required);被捕捉的錯誤加上 <out> 不可寫 → running+exit 2,journal 停在前一版;complete 之後 SIGABRT → journal complete、exit 為 signal,用來釘住「commit 在 teardown 之前」。既有案例(rerun 失敗、載入失敗、第 k 個 sequence throw/kill、rename 前後 kill、report 失敗、lock 衝突)在 §23 逐列對應。
    • reader:complete journal 加上 log 不是 exit=0 → 不得判 EXACT(若既有測試沒有涵蓋就補)。
    • mutation(在獨立副本上,不改共用 source):fail() 不寫 failure.sequence、complete 提前到 report 之前、Gate A 拒絕時保留 level、reader 忽略 exit code,新案例必須攔住。

    回退:revert 該 PR(只有文件與測試)。

    未驗證:真實 binary 在 teardown 時 abort 的頻率與時點(只釘 protocol 層的順序);斷電 durability;網路 FS/WSL 掛 Windows 磁碟上的 flock/rename;F3 的 127 落點。不取得 package/installed 驗證,不改 V5/installed/equivalence 狀態。證據交 #541。Owner 可以事後否決,依回退路徑處理。

  13. added a commit that references this issue on Oct 23, 2026
  14. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    C0-10 已合併(2026-10-23)

    #628 已合併為 5aa63ee5(head 8a9b47e3,merge tree 與 head 相同)。

    • 方案:A,只重查與釘住(方案紀錄),依 [Hub] SACCADE 工作總入口:進度、優先序、依賴與 checkpoint #550 §0A 自行啟動並合併。內容在 failure_degradation_537.md §23。CC-536-01-01 卡只加了一個連結。
    • 寫定的語義:
      • 轉換表:每一種結束方式對應的 journal 欄位與 exit code,每列都綁一個測試。
      • 失敗分類:從既有欄位推導,不新增欄位。
      • 成功判讀採合取:exit 0,而且這次 run_id 的 journal 是 complete。journal 在 runtime teardown 之前就 commit,所以「complete 但 exit 非零」是可能的;reader 原本就兩者都要求。
      • 部分完成:failed 或 running,且至少一個 sequence 已提交。
    • 不改:shipping/**、journal/report format、exit code、reader 邏輯。四個檔案都是 non_execution,runtime coordinate 與 package pin 都沒有動。
    • 證據:
      • CI:PR CI+C++ 8/8,第一次就通過;main CI 7/7 SUCCESS(pytest 5901 passed/0 failed)。main 的 C++ build 依 path filter 不觸發。
      • 本機:協定測試 329 → 407 checks、0 failures,新增 4 個案例;pre_push 5958 passed、0 failed。
      • mutation:五個隔離 mutation 都被攔住。
      • self-check。風險層級為一般(docs+tests),沒有做獨立審查。
    • 未驗證:

    仍 OPEN:C0-01/02(public facade 與 eval 拒絕點,需和 #441、#536 協調)、health CLI/telemetry(REQ-535-07-03);WorkbenchPool 是否要支援資源降級仍未裁決。#537 保持 OPEN。

  15. raylei50653 commented on Oct 23, 2026

    @raylei50653
    OwnerAuthor

    health CLI 方案紀錄(§0A 開工條件 3;REQ-535-07-03)

    問題:src/saccade/pipeline/health.py(saccade_ctl.sh health;orchestrator 啟動時也在 process 內執行一次並印出,即 SE-48)會把「取不到」報成健康或報成 0。REQ-535-07-03 要求維運 caller 能從 CLI 報告判讀 unknown/unavailable、service scope 與報告來源。在 main@5aa63ee5 本機執行與 mock 重現了以下幾點:

    1. NVML 失敗被當成健康:check_vram() 吞掉例外回 None,overall_ok 對 None 視為通過。mock nvmlInit 失敗、其餘檢查通過時,報告是 Saccade Status — OK。(reproduced)
    2. Redis 連不上時 queue depth 報 0:連線失敗時 check_redis() 回 depth 0,報告印 L3 Queue Depth: 0 items,和真的空 queue 分不出來。(本機 Redis 未啟動時 reproduced)
    3. unit 不存在被報成 inactive:systemctl --user is-active 對不存在的 unit 也回 inactive(本機 rc 4、LoadState=not-found),報告印 inactive/failed。setup_services 安裝的是 system units,ctl/health 查的卻是 --user(ledger 部署段已記);systemctl 不存在或逾時時,則把例外字串當成 failed。(reproduced)
    4. stress metrics 的 scope 錯置:Loop Latency 量的是 health process 自己的 event loop;VRAM Fragmentation 讀的是 health process 自己的 torch allocator(沒有 CUDA 或沒有配置時固定 0.0%),兩者都不是服務的數值,報告也沒有標示。(reproduced:CLI 印 0.01 ms/0.0%)
    5. exit code 不反映結果:DEGRADED 時 exit 仍是 0。(reproduced)

    候選方案

    • A(選擇):只改報告語義,不選 readiness policy。
      • 每個檢查改成三態 ok/degraded/unknown,並附來源(systemctl --user、NVML GPU 0、REDIS_URL)。
      • 整體狀態:任一項已知異常為 DEGRADED;否則任一項 unknown 為 UNKNOWN;全部 ok 才是 OK。overall_ok 只在 OK 時為真,unknown 不再算通過。
      • 各檢查的判定:
        • VRAM:取不到時為 unknown(並保留錯誤類型)。
        • Redis:失敗時為 degraded,queue depth 為 unknown(None),不是 0。
        • systemd:改用 systemctl --user show -p LoadState,ActiveState 判讀:loaded+active 為 ok;loaded+其他狀態為 degraded;not-found 為 unknown(「此 user scope 沒有該 unit」);systemctl 不存在或逾時為 unknown。
      • stress metrics:Loop Latency 與 Fragmentation 標明「health process 自身」;沒有 CUDA 時 Fragmentation 為 unknown,不是 0。
      • exit code 維持 0。
    • B:A 再加上 exit code(例如 0 OK/1 DEGRADED/2 UNKNOWN)。這等於選了 readiness policy,而 REQ-535-07-03 明寫 readiness 政策未選;saccade_ctl.sh 有 set -e,health 子命令會因此改變行為。不做,留給 owner 裁決。
    • C:只修第 1 點(VRAM None 不算通過)。第 2~4 點的「unknown 被報成 0 或 inactive」仍然存在,REQ 的 scope/來源可判讀也沒有收斂。

    理由:A 只把報告改成 fail-closed 且可判讀,不新增 readiness、SLO 或 API 承諾;B 的 exit code 屬於未裁決的政策;C 不完整。

    不做的事:

    • 不改 exit code、systemd unit 清單、--user 與 system scope 的部署落差(只在報告標明 scope)、VRAM_WARN_PCT 門檻或 orchestrator(SE-48 仍只印報告、不擋啟動)。
    • 不新增 JSON 輸出。
    • 不碰 ResourceManager/dispatcher 的監控通道(§20),也不把 health 報告當成 online-service qualification。
    • REQ-535-07-03 仍是 candidate;本切片不批准它。

    受影響路徑:

    • src/saccade/pipeline/health.py:decision_relevant,風險:高。需要 Codex 獨立審查,runtime identity 重新捕捉後以 stacked publication 原子落地。
    • tests/unit/test_pipeline_health.py:既有「失敗時 depth == 0」改為 None。
    • docs/architecture/failure_degradation_537.md(新 §24、§6、§19.3)。
    • system_overview_536.md SN-ON8 列只加連結。

    測試與負控制(host,mock systemctl/NVML/Redis):

    • 三態矩陣:各檢查 ok/degraded/unknown,整體依優先序判定;NVML 失敗而其餘 ok 時為 UNKNOWN、overall_ok 為假。
    • systemctl 的 active/inactive/failed/not-found/缺少 binary/逾時。
    • Redis 失敗時 depth 為 None、報告印 unknown。
    • 沒有 CUDA 時 fragmentation 為 unknown。
    • render 必含各來源標示、不含 0 items;NVML 失敗時要 nvmlShutdown。
    • 真實 smoke:本機實際執行一次,結果記在 PR。
    • mutation:VRAM None 又算通過、Redis 失敗回 0、not-found 當成 degraded 或 ok、UNKNOWN 優先於 DEGRADED,測試都必須失敗。

    回退:revert 實作並依 runbook 重新發佈座標。

    未驗證:沒有在真實部署(system 或 user units 實際執行中)上驗證;沒有 orchestrator 端到端驗證;多 GPU 只看 device 0。不取得 online-service qualification。Owner 可以事後否決,依回退路徑處理。

  16. added 2 commits that reference this issue on Oct 25, 2026
  17. raylei50653 commented on Oct 25, 2026

    @raylei50653
    OwnerAuthor

    health CLI 已合併(2026-10-25;REQ-535-07-03)

    #629 加上 stacked 的 #630 合併為 main 3375e7d4(head c91d665f,merge tree f2e47f32 與審過的 head 相同)。

    • 方案:A,只改報告語義(方案紀錄),依 [Hub] SACCADE 工作總入口:進度、優先序、依賴與 checkpoint #550 §0A 自行啟動並合併。內容在 failure_degradation_537.md §24。
    • 行為變化:
      • 每項檢查回報 ok、degraded 或 unknown,並附來源(systemctl --user、NVML GPU 0、REDIS_URL)。
      • 整體狀態:DEGRADED 優先於 UNKNOWN,UNKNOWN 優先於 OK;overall_ok 只在 OK 時為真。
      • NVML 失敗不再報成 OK;Redis 連不上時 queue depth 為 unknown,不再報 0;不存在的 user unit 報成 unknown,不再報成 inactive/failed。
      • stress metrics 標明是 health process 自身的數值,不計入狀態。
      • REDIS_URL 只在整段通過 allowlist 時才顯示,並去掉 userinfo。
    • 不改:exit code(維持 0,不選 readiness policy)、unit 清單、VRAM_WARN_PCT、orchestrator、ResourceManager。
    • Runtime identity:implementation ab1bc4ea → 4ad5ae2c,只有 health.py 不同;其餘四軸與 probe digest 不變,equivalence=unproven。在 main 上 attested exit 0。
    • 證據:
      • Codex:r1–r6 的 findings 都已處置,r7 @ 2d0b2834 與 publication 重跑 @ 30dbef82 都是 READY-TO-MERGE,findings: none。第一次 publication 審查中斷,沒有 verdict,不採用。
      • CI:PR 的 merged head 8/8,都是第一次就通過;第一次失敗(只有實作時 4 個 identity 測試失敗)已分類為座標落後的預期中間態。self-check。
      • main:CI+C++ 8/8 SUCCESS,attempt 1(pytest 5989 passed/0 failed)。
      • 測試:91 個案例;修復前 89 failed/2 passed;18 個隔離 mutation 都被攔住。
      • 真實 smoke @ 2d0b2834:報告為 DEGRADED,三個 unit 為 unknown,queue depth 為 unknown,exit 0。
    • 未驗證:
      • 真實部署(units 執行中、Redis 有資料)。
      • orchestrator 端到端。
      • 多 GPU(只看 device 0)。
      • 不取得 online-service qualification。

    仍 OPEN:

    #537 保持 OPEN。

  18. added 6 commits that reference this issue on Oct 25, 2026
  19. raylei50653 commented on Oct 25, 2026

    @raylei50653
    OwnerAuthor

    F01/F06 已落地(2026-10-25)

    項目 PR main 契約 證據(repo 外)
    F01 mot17.py --processes 子程序失敗傳播 #632(+ stacked #633) 07fdaf58,main CI 8/8,attested exit 0 failure_degradation_537.md §25 ~/.local/state/saccade/evidence/537-f01/20261025T145150/
    F06 評估 sequence 不再靜默略過 #634(+ stacked #635) aa69c3c1,main CI 8/8,attested exit 0 failure_degradation_537.md §26 ~/.local/state/saccade/evidence/537-f06/20261025T155523/

    F01:fail-fast。第一個失敗(非零 exit、signal、無法啟動、launcher 自己出錯)讓 launch 失敗:待啟動的不啟動,執行中的 SIGTERM → grace → SIGKILL,全部 reap。parent 先決定 exit code(原 code/128+N/127/70/130),再盡力回報,不算 metrics。

    • 修復前:GPU probe exit 0。修復後:exit 1,兩個子程序各自保留原始 traceback;成功路徑的結果檔逐位元組相同。
    • 44 個 CPU 案例(真實子程序),40+10 個 mutant 全部攔住。
    • Codex r1–r10 的發現都已修正,只有一項刻意不改(子程序處理 SIGTERM 後重新送出 SIGTERM 時記為 terminated;不影響 exit code,也不遮住原始失敗)。
    • 限制:parent 自己收到 SIGTERM 不處理;子程序的後代程序不在範圍。

    F06:明確指定但缺少 seqinfo.ini → SequenceSelectionError(exit 1,在 run claim 與 detector 建構之前);自動發現時記錄原因後略過;零個有效 → exit 1;C++ 路徑不再以 1920×1080 預設值建構。

    • GPU:normal/partial/allmissing × 6 種模式;20 組結果檔與修復前逐位元組相同。
    • 38 個 CPU 案例,source 與 wiring mutant 全部攔住;Codex r3 READY-TO-MERGE。
    • 刻意的改變:自動發現時被略過的 sequence 不再出現在 EvalConfig.seqs 與 metrics 清單。
    • 邊界:concurrent_mot17.py 自己的失敗傳播。

    Runtime coordinate:implementation 4ad5ae2c… → 11204b8a…(F01)→ 11263f7c…(F06);其他軸不動,probe 重跑 digest 相同,equivalence=unproven。

    未擴大 online qualification,tracking 數值不變,未帶入 #538 的修復。下一步:#538 F02 計數契約。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions