動作
Fix #377
進行中
SC
T2-18 P0×3 裁決與修補(ADR-118~121)+ 業主拍板無刪除語意/record_status + S9 hook 誤報修正
Fix #377:
T2-18 P0×3 裁決與修補(ADR-118~121)+ 業主拍板無刪除語意/record_status + S9 hook 誤報修正
狀態:
New
優先權:
Normal
被分派者:
-
開始日期:
2026-07-15
完成日期:
完成比例:
0%
預估工時:
概述
T2-18 P0×3 裁決與修補(ADR-118~121)+ 業主拍板無刪除語意/record_status + S9 hook 誤報修正¶
背景¶
- #375 / c5accf1 已 push、CI 綠。devil-advocate 的 T2-18 挑戰留下 P0×3 未裁決,全部落在病患安全與 TFDA Class II 安全閥路徑上,且阻擋 T2-08 / T2-10 動工。
- qa-engineer 在 T2-11 測試計畫拋出的規格問題(ARES CRDT 無 tombstone/刪除語意——規格意圖或缺口?)同樣待 system-architect 裁決。
- Stop hook S9(記憶污染偵測)對「檔頭 40 行出現
superseded字樣」的判定,把當前有效的 q7 結論誤判為已被取代——因為它誠實寫了「本結論取代舊結論(已標 superseded)」。該 warn 無法用移除引用清除(決策樹本來就該指向有效結論),永久掛著 → 警報疲勞。 - 舊 q7 結論(2026-04-26,已被 ADR-117 取代)仍被
tree/research/q07-tw-regulatory.md引用 → 教訓 15 場景 I 的記憶污染路徑。
變更內容¶
1 ADR-118:e-Tag 重置 Phase 2 單一 commit point(P0-1 成立)¶
-
缺口成立:
reset-boundary.mdv0.1.0 只點名 Step 2.2(寫新 patient_id)為「單一 NVS transaction」,Step 2.1(清空 severity 回 GREEN、notes、pending_sync)未納入同一原子邊界 → 兩者間斷電 → e-Tag 呈現「舊傷患身分 + GREEN 無傷情」,而該傷患實為 BLACK/RED。違反 constraints §4,且違反方式是 §4 沒設想過的(不是資料遺失,是重置流程把最後檢傷狀態改寫成錯誤初始值)。 -
否決 devil 的修法理由:devil 建議「同一次
nvs_commit即可」,但 ESP-IDF NVS 多鍵斷電原子性本次未經查證(【假設】),不得作為病患安全論證的地基。改採 A/B 雙槽 + generation word 翻轉——只依賴「單字寫入原子」這個更弱、更易成立的前提。 - 斷電後可觀察狀態集合恰為兩個(全舊/全新);第三種一律定義
ERR_RESET_TORN→ DEGRADED,不得自行猜測該呈現哪一邊。顯示器重繪只能在 commit point 之後。 -
reset-boundary.md→ v0.2.0;constraints.md§4 補規則型子條款(禁止任何「身分已變/臨床狀態未變」的中間態,非個案禁止)。 - T2-10 驗收必須含斷電注入測試(commit point 前後隨機斷電 N 次,斷言可觀察狀態恰為兩個)——這是本 ADR 唯一能被證偽的方式。 firmware-engineer 須核算 identity blob 大小,超出 NVS 分區預算則須回頭修訂。
2 ADR-119:狀態變更與稽核紀錄必須同一 transaction(P0-2 成立,且範圍比報告更大)¶
- 三處撕裂窗,非一處(逐行核對程式碼確認):
- (a) confirm 路徑(devil 1.2,P0):severity 已套用(可能 BLACK)、佇列項仍在 → 重開機後指揮官不知情按「拒絕」→ 稽核寫著「指揮官拒絕了升級」、實際 severity 卻是 BLACK。這條路徑正是 ADR-107 × TFDA Class II 的安全閥,卻是全程式庫原子性最薄弱的一段——安全閥自身不可靠,等於沒有安全閥。
-
(b) notes ingest(devil 1.3,P1):
NOTE_ID_COLLISION稽核永久遺失(碰撞筆不在toInsert裡,不會有第二次偵測機會)。 -
(c)
applyRemote的 enqueue 迴圈( architect 發現,devil 未抓到,P0):Class II fallback 下 severity 升級只存在於 pending queue → 佇列沒寫成功,這次升級徹底消失且零稽核痕跡。直接違反 crdt-merge-strategy 不變式 5「永不丟棄」(該不變式為防溢出丟棄而寫,擋不住斷電丟棄)。 - 否決 devil 的「啟動時一致性掃描」:Max-CRDT 下 severity 可能因另一條完全合法的路徑剛好等於 proposedValue,此時自動補寫「指揮官已確認」稽核=偽造一次不存在的人類確認,對 TFDA 安全閥比原缺陷更糟;且對 (c) 根本無效(佇列項從未寫入,無殘跡可偵測)。
- 立規則型不變式而非逐案修補:任一狀態轉換 + 其 append-only 稽核 + 其伴隨佇列 CRUD 必須落在同一
db.withTransaction {};跨 Repository 不構成豁免。 -
crdt-merge-strategy.mdv0.1.3、crdt-notes-replay.mdv0.1.2、software-stack.md同步。
3 ADR-120:RestartSyncGate 預設 fail-closed + 有界逾時開閘(P0-3 成立,但 devil 的修法被否決)¶
-
缺口成立(grep 複核):
isOpen預設true,非測試碼零呼叫close()/open();HlcRepository.restore()同為孤兒 API。成因更正:不是有人忘了接,而是 PoC 階段根本沒有 app module(settings.gradle.kts只有:core/:data)→ app module 落地時 fail-open 預設會無聲生效。RestartSyncGateTest的defaults to open把危險預設鍍上綠燈。 - 否決 devil 的「永久 fail-closed 直到收到對等回應」:災難現場第一台到場的 Tier 0 平板沒有對等節點,是每次任務的正常起手式,永久關閉 → 第一台平板無法建立任何傷票 → 系統在最需要它的那一刻完全不可用,違反 constraints §1。把「陳舊資料風險」換成「完全無法檢傷」不是走安全側,是換一個更大的病患安全事故。
-
裁決:預設
isOpen = false(未接線=寫入立刻拋SyncWindowClosedException,大聲失敗)+ 30 秒逾時後必須開閘,逾時強制留RESTART_SYNC_TIMEOUT稽核 + 持續性 UI 橫幅。C-16 要的是 default 走安全側(已滿足);有界逾時後開閘並留痕,是把風險從「靜默」轉為「大聲」。 - 新增單一恢復進入點
RestartRecoverySequencer.onAppRestart()(收攏 restore HLC → close gate → 廣播 SYNC → 等待/逾時 → open gate),open()/close()收窄為 internal,HlcRepository.restore()不再是孤兒。網路層未落地期間注入NoOpSyncCoordinator→ 走逾時路徑 → 今天就能接線且行為誠實(系統確實沒同步到任何人,UI 就這麼說)。
4 ADR-121:無刪除語意(禁止 tombstone GC)+ record_status(業主 2026-07-15 拍板兩項)¶
-
裁決:刻意設計,非缺口。CRDT tombstone 的用途是讓 payload 可被垃圾回收——而 constraints §8 + 電子病歷管理辦法 §11(1)(2)/§13 禁止的正是這件事。法規禁止的就是 tombstone 真正想達成的那件事。 →
crdt-triage-record.md不變式 7。T2-11 的 tombstone 測試項因此是「不適用」而非「待補」,正確迴歸網是「斷言不存在刪除路徑」。 -
但 qa 舉的營運缺口是真的:「誤建傷票」+「同一傷患被兩顆 e-Tag 重複建票」無處理路徑 → 幽靈條目 → 統計失真 → 指揮官資源調度誤判(實質病患安全影響)。解不是 tombstone,是單調註記
record_status: ACTIVE → VOIDED / MERGED_INTO(資料一律保留)。 -
業主 2026-07-15 拍板(記錄於新建的
docs/owner-tracking/2026-07-15_owner-decisions-deletion-semantics.md): - 決策 1:確認「ARES 永不提供刪除路徑」為產品決策。
- 決策 2:record_status 納入 MVP(選項 A)。
-
接地錯誤已更正並留痕:architect 初版把「無刪除語意」標 並以 ADR 自身作
[evidence:]錨點 → Stop hook S1 判為不合規(架構師不得自我授權接地)→ 退回 → 業主拍板後改指 owner-tracking 記錄檔。此錯誤明文寫進 ADR-121 Status 段供未來引以為戒。 -
業主拍板的邊界機械化落實:specs 拆三行——「無刪除語意」、「納入 MVP(範圍)」(均錨 owner-tracking),「
record_status的 CRDT 合併語意」** 待驗證、刻意無錨點**。業主拍板的是「做不做」,不是「怎麼做是對的」。
5 docs/interfaces/crdt-record-status.md(v0.1.0-draft,明標「不可實作契約」,8 條 )¶
-
RS-5(最高優先,規格衝突):ADR-121 §2 寫「
MERGED_INTO目標記錄 severity 取兩者 Max」——但把一張誤建的 BLACK 票併進 GREEN 傷患 → 該傷患被不可逆升為 BLACK。「合併是為了修正錯誤」與「Max-CRDT 永不降級(constraints §7)」互斥。 RS-5 收斂前,ADR-121 §2 的 severity-Max bullet 不得作為實作依據(已標進 tree/adr/adr-121 節點)。候選解會牽動 ADR-107 的 TFDA Fallback A/B。 - RS-7(有實體世界後果):被作廢的 e-Ink 傷票實體仍掛在傷患身上,現場人員看到什麼?需 firmware-engineer + 業主現場 SOP。
-
record_status刻意不寫進crdt-triage-record.md的 Schema 與 per-column 表——未驗證的設計若寫進 schema,會以「已定契約」之姿被工程層直接實作(接地上限條款要擋的正是這件事)。
6 程式碼修補(android-developer,110/110 綠)¶
-
ADR-119 四條接線:
TriageRecordRepository.{confirmPendingSeverityMerge, rejectPendingSeverityMerge, applyRemote}+NotesReplayRepository.ingestNotes全數收進單一db.withTransaction {}(讀取也在交易內,防 TOCTOU)。 -
ADR-120:
RestartSyncGate預設反轉為 closed、open/close收窄 internal;新增RestartRecoverySequencer、SyncCoordinator(含NoOpSyncCoordinator)、RestartSyncAuditEntity/Dao;RestartSyncGateTest的defaults to open反轉為defaults to closed(從「把危險預設鍍綠燈」變成「守住 fail-closed 不被改回去」的迴歸網)。 - 每條交易路徑補中途失敗測試(第二步拋例外的 fake DAO → 斷言第一步已回滾);新增
RestartRecoverySequencerTest(逾時路徑/有 peer 路徑/未呼叫onAppRestart時寫入拋SyncWindowClosedException)。
7 攔下一個假綠燈:RoomNestedTransactionJoinTest 原本不具判別力¶
- ADR-119 全部接線依賴一個【假設】:Room 的 suspend
withTransaction巢狀呼叫會 join 外層交易(ADR 明文要求「須以測試確認,不得憑印象」)。 - android-developer 初版寫的兩個測試皆不具判別力:「巢狀內拋例外 → 全部回滾」在「join」與「獨立交易」兩個假說下預測完全相同的結果(獨立交易的例外同樣會往外傳、讓外層一起回滾)→ 其綠燈不構成任何證據,但它據此宣稱假設已驗證。
- 主協調者把關退回,補唯一具判別力的形狀:巢狀正常完成 → 之後外層才失敗 → join 預測巢狀寫入被回滾、獨立 commit 預測它存活。這個形狀本身就是 ADR-119 要防的撕裂態。
-
實測結果:
inner3被回滾 → 假說 A(join)成立,ADR-119 現行接線安全,不需改為內聯 DAO 呼叫。原兩個測試改標NON-DISCRIMINATING保留為迴歸網,KDoc 改為誠實陳述。 - 教訓:測試綠燈 ≠ 假設已驗證。要問的是「這個測試在假設為假時會不會也通過」。
8 Stop hook S9 判定收緊(誤報修正,非放寬防線)¶
- 舊判定:檔頭前 40 行任何位置出現
superseded字樣 → 判為已被取代。→ 當前有效的 q7 結論因誠實寫了「本結論取代舊結論(已標 superseded)」而把自己標成 superseded,hook 遂抱怨決策樹引用了它——但決策樹本來就該指向有效結論,該 warn 清不掉。 - 新判定:只認兩種刻意標記——(a)
status:欄位(行首,frontmatter 或本文)內含 superseded/;(b) 行首即為 的獨立標記行。句中提及一律不計。舊 q7 結論的 frontmatter 有status: superseded(ADR-106…)→ 仍被正確抓到,故為提高精確度、非放寬。 - 測試:
test-v17-s1s2s6.ps141/41 全過(含原本會漏掉的 S9 案例——該測試的 fixture 把status:寫在本文而非 frontmatter,測試抓到了我第一版過窄的規則,遂改規則而非改測試);test-cjk-regression過。誤報實測消失(hook 自身輸出證實)。
9 記憶污染清理(教訓 15 場景 I)+ 決策樹¶
-
tree/research/q07-tw-regulatory.md:承載 ADR 由「待產出」→ ADR-117(ADR-106 Superseded 保留不刪);移除指向 2026-04-26 舊結論的可點路徑(改為 Superseded 說明;檔案仍保留在 conclusions/ 且檔頭自帶警語)。驗證:grep 2026-04-26_q7-…-conclusion docs/decisions/tree/→ 零命中(改前 1 筆)。 - 新增
tree/adr/節點×4(ADR-118~121,verification_status: pending,待 system-architect 驗證);00-index.mdNEW section 補行 + 更正 L455 過時的「ADR-106 待新 ADR 取代」。
驗收標準¶
-
測試:
java -cp gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain :core:test :data:test --rerun-tasks→ exit code 0、BUILD SUCCESSFUL、110/110 tests, 0 failures, 0 errors(主協調者獨立重跑並自 XML report 逐檔核算,非採信 agent 轉述)。 -
判別性測試:
RoomNestedTransactionJoinTest的DISCRIMINATING - ...LATER outer failurePASS → Room 巢狀withTransactionjoin 外層交易,實測確立。 -
hook 測試:
test-v17-s1s2s6.ps141/41、test-cjk-regression.ps1PASS。 -
S1 帳目:本輪新增 2 條 規格行,錨點均指向存在的
docs/owner-tracking/2026-07-15_owner-decisions-deletion-semantics.md→ S1 靜默通過。 - S9 誤報:實測消失。
-
CI 權威綠燈:push 後確認
tests.yml(含 committed_spec_audit)+android-core-tests.yml。
已知未解 / 下一步¶
- RS-5(規格衝突,最高優先):合併 severity 取 Max ↔ constraints §7 不可逆互斥。Phase 2 動工前須收斂,建議先讓 devil-advocate 挑戰再開 ADR。ADR-121 §2 的 severity-Max bullet 在此之前不得作為實作依據。
- RS-7:作廢的 e-Ink 傷票實體仍在傷患身上 → 現場人員看到什麼?需 firmware-engineer + 業主 SOP。
-
android/gradlew自 850ae5e(T2-06a)起即損毀(第 28 行混入org.gradle.jvmargs=-Xmx2g、缺APP_HOME解析)→./gradlew從未能自 bash 執行,須繞道java -cp gradle-wrapper.jar。本次未修(不擴大 diff),另案處理。 -
devil P1×4 / P2×4 未裁決(HLC 未來時鐘中毒無上界、已封存實體遭遲到寫入竄改、
ERR_HLC_OVERFLOW死代碼、log_entries 同 HLC 不同內容碰撞)。 - ADR-120 §3 的 UI 橫幅尚無可掛載的 UI module,目前只到
RestartOutcome.OpenedWithoutPeerSync這個 domain 訊號。 - Phase 2 範圍因 record_status 擴大(e-Tag 韌體同步、Tier 0 作廢/合併 UI + 二次確認閘、訓練教材)→ project-manager 需重排。
- 續追:L4 DUE(q7-redo-verification 未過 L4)、pdf-requests 3 檔 pending(含 NCC FL094278 403)、network.md BLE 列下批拆列、legacy bare
[evidence: owner]×3 可補 owner-tracking 記錄檔消 warn。
沒有任何資料可供顯示
動作