Skip to content

Latest commit

 

History

History
142 lines (98 loc) · 23.6 KB

File metadata and controls

142 lines (98 loc) · 23.6 KB

Program — Quality Matrix

2026-04-07(WORK-QC-A)起,這份文檔是 PathKeep 在進入 M4 前的 quality gate source of truth。
原則很簡單:文檔怎麼寫,repo 就怎麼擋。凡是會被宣稱成 blocking、release 或 deep check 的驗收,都必須能在 scripts 與 workflow 裡兌現。


Mainline Blocking Path

Gate Command / Workflow 保護範圍 備註
Strict checker bun run check / GitHub CI workflow base checks、100% JS/Rust coverage、browser build、browser-preview e2e、desktop-bridge truth gate、desktop-contract JS mutation、Codecov upload 這是 signed-off per-commit checker;main push / PR / manual CI 與本地使用同一條 effective gate。
Base triage bun run check:base Prettier、ESLint、i18n parity/raw-English guard、TypeScript、Vitest、desktop contract slice、Rust fmt / clippy / workspace tests、supply-chain audit、host-matched platform-native checks、release-config drift guard 只作 fast triage helper;不能替代 bun run check
JS coverage bun run coverage:js active src/**/*.{ts,tsx} runtime source 100% statement / branch / function / line coverage。
Rust coverage bun run coverage:rust full src-tauri/**/src/*.rs workspace source surface 100% line + function coverage;舊 quality slice 只保留作 triage helper。
Browser build bun run build TypeScript compile + Vite bundle 已由 bun run check 觸發;可單獨跑作 build triage。
Browser smoke bun run test:e2e browser preview 的 shell / onboarding / dashboard / trust / intelligence smoke 已由 bun run check 觸發;這是 preview surface smoke,不等於完整 desktop / Tauri signoff。
Desktop bridge bun run test:e2e:desktop-bridge:truth Chrome + Playwright 透過 feature-gated bridge 驗證真實 Rust desktop command façade 已由 bun run check 觸發;仍不是完整 Tauri WebView/plugin signoff。
JS mutation bun run mutation:js / check:mutation src/main.tsxsrc/lib/ipc/bridge.ts desktop-contract slice Stryker high / low / break thresholds 都是 100;這是 per-commit mutation gate。

bun run check 內含的 targeted sub-gate

bun run check:base 目前固定包含 bun run check:desktop-contractbun run check:platform,保護:

  • src/main.tsx
  • src/lib/ipc/bridge.ts
  • host-matched platform-native keyring / scheduler / launcher / discovery / biometric smoke
  • desktop updater / launcher command surface 與 debug desktop build smoke
  • release workflow / Tauri bundle / support-link drift,包含 unsigned Windows installer、WebView2 download bootstrapper、PathKeep updater URL、GitHub Actions runtime major 與不得重新加入 Windows signing gate

這些 sub-gate 的責任是保護 desktop entry、typed IPC contract 與 platform-specific host truth;它們不是替 shell / route / sidebar / trust-critical flows 做全站背書。GitHub CI workflow 現在會在 main push、PR 與 manual dispatch 時安裝 Linux desktop/native dependencies 後直接跑 bun run check,再把 coverage/js/lcov.info 與用同一 Rust verifier 口徑產出的 coverage/rust-codecov.lcov.info 上傳到 Codecov;所以 hosted runner 也要承擔同一條 per-commit checker;manual Platform Native workflow 只保留作 host-sensitive parity / triage。


Current Quality Surfaces

2026-04-27 gate-cost note:bun run check 本身就是 per-commit gate。check:full 只是 check alias;verify 在 strict checker 之後額外跑 debug desktop build。全量 JS/Rust mutation 因實測成本與 current cargo-mutants sandbox fragility,不再是 per-commit hard gate,改由 check:deep / scheduled Mutation workflow 承接。

JS coverage / mutation quality surface

bun run coverage:js 目前對齊 active frontend runtime surface:

  • include:src/**/*.{ts,tsx}
  • allowed excludes:tests、fixtures、assets、generated declarations、type-only contract files、以及已證明不是 runtime surface 的 reference-only files。
  • required thresholds:coverage lines / functions / branches / statements = 100。

這代表前端 shell / route / sidebar / primitives / page-scoped providers 都回到 checker 裡;不能再用 desktop-contract slice 或舊 living M0-M3 helper list 代替全站 runtime coverage surface。

bun run mutation:js 是 per-commit desktop-contract mutation gate,範圍固定為 src/main.tsxsrc/lib/ipc/bridge.tsbun run mutation:js:full 仍保留 active frontend runtime surface 的 full Stryker sweep,供 check:deep、scheduled workflow 或高風險 release 候選使用;surviving mutant 仍必須用補測、修產品碼、或 narrow equivalent/inapplicable annotation 處理。

Rust coverage quality surface

bun run coverage:rust 現在以 full scope 驗證 src-tauri/**/src/*.rs 的 100% line + function coverage。舊的 desktop command / bridge contract slice 保留為 bun run coverage:rust:quality,只用於縮小 regression 追查範圍,不再是 coverage:rust 的預設語義:

  • src-tauri/src/file_manager.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/main.rs
  • src-tauri/src/session.rs
  • src-tauri/src/worker_bridge/

如果 full coverage gate 失敗,不能用 quality slice 代替 release signoff;必須把 uncovered path 補測、降出正式 surface,或在 source docs 中明確記錄不能達標的原因與後續 work block。

Rust mutation quality surface

bun run mutation:rust 現在指向 bun run mutation:rust:full,也就是 whole-workspace cargo-mutants sweep,但它是 manual / deep gate,不再是 per-commit bun run check 的一部分。舊的 focused parser + AI helper contract 保留為 bun run mutation:rust:quality,只用於縮小失敗 triage 範圍:

  • browser-history-parser crate
  • src-tauri/crates/vault-core/src/ai.rs 的 status/helper slice:
    • ai_index_status
    • ai_queue_status
    • reconcile_ai_queue_controls
    • provider_capabilities
    • provider_connection_failure_report
    • test_provider_connection

whole-workspace mutation 是 deep/release investigation gate;若成本或 surviving mutants 無法在當前 closeout 修乾淨,必須把 surviving mutant 清單與原因寫成明確缺陷,而不是把 focused contract 說成全後端驗收。2026-04-27 實測顯示 full Rust mutation 有 5869 個 candidate mutants,且 current copy-sandbox baseline 會因 repo-root reference/.../safari.sqlite fixture path 缺失而失敗;在修復 fixture/copy contract 前,Rust mutation 不可作 per-commit hard gate。


Focused / Release Helpers

Gate Command / Workflow 用途 備註
Platform Rust native sweep bun run test:platform:rust / GitHub Platform Native workflow 直接驗證 host keyring / scheduler / launcher / discovery / biometric capability hosted runner 成本高且 host-sensitive,所以移到 manual workflow;Linux job 仍會在隔離 dbus-run-session 內啟動 gnome-keyring-daemon
Platform desktop slice bun run test:platform:desktop / GitHub Platform Native workflow debug desktop build + updater / launcher desktop command slice 這是 desktop command truth,不等於 browser preview e2e。
Release config guard bun run release:check release workflow / updater URL / Windows bundle config drift 已納入 check:base;保護 unsigned Windows release path、WebView2 download bootstrapper、PathKeep support/updater URLs、GitHub Actions runtime major,且不得把 Windows signing gate 加回來。
Project-scoped native dependency proof bun run native-deps:doctor + OpenCC vcpkg install / GitHub Native Dependencies workflow 驗證 repo-local vcpkg native dependency contract 只在 manual dispatch、PR、或 main push 觸及 native-deps/vcpkg contract path 時跑;macOS proof 用 macos-15-intel + x64-osx,不屬於每次 commit 的 strict checker。
Chrome desktop bridge smoke bun run test:e2e:desktop-bridge:truth 啟動 feature-gated desktop bridge,讓 Chrome / Playwright 驗證真實 Rust command 已納入 bun run check;單獨跑只作 bridge triage。
Desktop-contract JS mutation bun run mutation:js / bun run check:mutation 對 desktop entry + typed IPC contract 做 lightweight mutation gate 已納入 bun run check;2026-04-27 current-host wall time 約 50 秒,break threshold 是 100。
Full JS mutation sweep bun run mutation:js:full / GitHub Mutation workflow javascript-mutation 對 active frontend runtime surface 做 repo-level mutation investigation Manual / scheduled deep gate;2026-04-27 dry-run 約 2m20s,full sweep 21769 mutants,按 44m/32% 實測估算約 2-3 小時。
Rust mutation sweep bun run mutation:rust:full / GitHub Mutation workflow rust-mutation whole-workspace cargo-mutants sweep Manual / scheduled deep gate;focused mutation:rust:quality 只作 triage helper。
Full local sweep bun run check:full bun run check alias 保留給舊 muscle memory;不再是比 check 更嚴的 gate。
Release-style local verification bun run verify check + desktop:build:debug 作為 release / milestone closeout 的本地預演;會先透過 check 自動觸發 coverage、e2e 與 mutation。
Deep local verification bun run check:deep check + full JS/Rust mutation sweep 只用於 release candidate / long-running manual pass;不作每次 commit 要求。

2026-04-27 gate-cost decision:舊的 2026-04-08 signed-off parser / AI helper mutation contract 仍是 focused triage helper;不能被誤報成 Rust mutation gate。WORK-QA-GATE-A 的當前 truth 以 STATUS.md 為準,100% JS/Rust coverage 仍是 stop-ship,full JS/Rust mutation 改為 deep/manual evidence 而不是 per-commit blocker。


Honest Boundaries

  • bun run test:e2e 是 browser preview smoke,不是 Tauri desktop、worker process、scheduler artifact、keyring 或 filesystem side effect 的最終驗收。
  • bun run test:e2e:desktop-bridge 證明 Chrome / Playwright 能透過 dev-only localhost bridge 打到真實 desktop command façade,現在也能覆蓋 updater install / relaunch 的 mirrored command transport;但它仍不是完整的 Tauri WebView / plugin guest API signoff,progress events 等 event-driven plugin surface 仍需 Tauri 實機驗證。
  • bun run check:platform 才是目前對 macOS / Linux host-native scheduler、keyring、launcher 與 updater desktop slice 的 blocking signoff;preview e2e 不能拿來替這些能力背書。
  • schedule / security / import / intelligence 這些高風險 surface 的 desktop truth,仍要靠 Rust tests、worker bridge tests、Tauri command tests 與對應的 PME / product docs 對齊。
  • Scheduled-backup blocking contract:每次 native wake 必須在 keyring/config/archive 之前建立 durable attempt,所有普通錯誤、due skip、lock defer 與 crash-recovered stale run 都有 typed terminal outcome;App Lock locked 不阻擋 trusted scheduled worker;manual success 不能滿足 schedule health;attempt ledger corruption/read failure 必須顯式報錯,不能降級成空歷史。macOS launcher 的 0 不可用來替代 worker outcome。
  • Background queue blocking contract:任何 claimed/selected deterministic、enrichment 或 AI job 每次 drain 都必須持久化 success/cancel/fail/retry outcome;malformed payload 或 executor/preflight error 不得 tight-loop、等 lease 才模糊重試、或丟失原始錯誤。Recovery snapshot inventory 只有在完整掃描成功時才可驅動 retention;任何非 NotFound I/O 錯誤一律 fail closed、零刪除。
  • coverage:js 現在覆蓋 active frontend runtime source;若某個 runtime owner 尚未被測試保護,這是 checker failure,不是文檔例外。
  • coverage:rust 已恢復 full src-tauri/**/src/*.rs 100% gate;如果實際命令失敗,失敗本身就是 release blocker,不能再降回 quality slice 後宣稱全後端達標。

覆蓋率 ≠ 行為:behavioral-assertion 鐵律(2026-06-28)

教訓:兩個一碰就炸、明顯影響 UX 的功能在「100% coverage」下溜過——(1) 關鍵字搜尋找不到 note 內容(note 從未進 FTS;有測試覆蓋 note 的寫入/讀取,卻沒有任何測試斷言「存了 note → 關鍵字搜得到」),(2) 語義索引寫 0 向量且中斷一次就無法恢復(真正的 embedding I/O 引擎被 #[cfg(not(any(test, coverage)))] 編譯掉、換成永遠成功的 stub,連 provider-error 分支都是 cfg(coverage) 假造的)。根因不是運氣,是方法論漏洞:coverage 量的是「行有沒有跑」,不是「行為對不對」

鐵律(文檔怎麼寫,review 就怎麼擋):

  1. 覆蓋率必要但不充分。 100% line/function coverage 是地板不是天花板。一個 call 了函數卻把結果丟掉(let _ = …)的測試,和一個斷言輸出的測試,在 gate 眼裡一樣綠——但只有後者算數。
  2. 每個使用者可見功能至少要一條 end-to-end 行為斷言:「使用者在真實使用的介面/路徑做 X → 觀察到 Y」。不能用「在另一個 plane 寫得進/讀得出」代替「在使用者打字的搜尋框找得到」。要負→正斷言:斷言「東西被找到/被寫入」,不是只斷言「沒丟錯」。
  3. 任何 production I/O 路徑都不得被 cfg 編譯出 coverage binary。 要 seam 就 seam 在 transport endpoint(可注入 client / 本地 fake server),把真正的 compute/decode/timeout/empty-response 留在被量測的 build 裡。整個引擎換 stub=把真正的失敗模式藏起來(embedding 0-byte 與 note 搜尋兩個 bug 都是這樣溜的)。
  4. 避免 coverage-theaterlet _ = 吞結果、dispatch_for_coverage 式「跑過但不斷言」、render-the-string(只斷言錯誤字串非空)。這些讓 gate 變綠卻不證明任何契約。
  5. 新功能 review 必問:「使用者實際操作有沒有一條測試從真實入口斷言預期結果?真實 I/O 有沒有被編譯掉?」答不出=不算 ship-ready,無論 coverage 幾趴。

待辦(見 BACKLOG,2026-06-28 立項):external embedding transport 改可注入 seam + fake-HTTP /v1/embeddings 整合測試(mid-batch 500 / 空 data[] / timeout);dispatch_for_coverage 的 fire-and-forget walk 改成 per-command 契約斷言。


資料完整性:「為什麼沒測到」與此後的 blocking 鐵律(2026-06-30)

教訓:一個會 整個 app 變磚 的缺陷在「100%-GREEN gate」下溜給了使用者——磁碟上兩個 canonical DB 都被 SQLCipher 加密,config.json 卻寫 Plaintext,salt 被歸零、又沒有 journal,下一次開檔命中 SQLITE_NOTADB 直接死在啟動。根因和 2026-06-28 同源、但更嚴重:gate 量的是 EXECUTION(coverage),不是 BEHAVIOR / INVARIANT。具體四個漏洞:(a) 沒有任何測試斷言跨檔不變式「config.json 記的 at-rest mode == canonical DB 磁碟上的真實 at-rest mode」;(b) 測試手搓 AppConfig struct,不走 load_config,真正的 config-load/drift 路徑從沒被跑過;(c) 部分真實 I/O 引擎被 #[cfg(not(any(test, coverage)))] 從 coverage binary 編譯掉、換成永遠成功的 stub;(d) 完全沒有 crash-window / 併發 / 交錯測試。Phase A–D 補了真正的 crash-window 測試(fault_inject seam + 真 SQLCipher round-trip),Phase E 把它系統化成可複用方法論並補齊剩餘缺口。

鐵律(文檔怎麼寫,repo 就怎麼擋;每條都要能被 check 兌現):

  1. Durability(原子 + F_FULLFSYNC + dir-fsync)。 所有 archive 寫入路徑必須走原子持久化路徑——durable_io::atomic_durable_write / install_file_durably(temp → F_FULLFSYNC → rename → parent-dir fsync)與 save_config / remove_file_durably;archive 寫入路徑內 不得 出現裸 fs::write / 裸 fs::rename 去落地一個 canonical 檔或 config.json。crash 中斷只能留下「完整舊值」或「完整新值」,絕不是把 mode 改動悄悄吞掉的截斷/空檔。
  2. Config↔disk consistency(跨檔不變式)。 每一個 archive-mutation 測試(rekey / reconcile / import / restore / backup)的 post-condition 都要斷言 archive::at_rest::check_config_disk_consistency(經 真正的 load_configconfig.json,用 detect_disk_encryption_mode 讀兩個 canonical DB 的真實磁碟 at-rest mode,header-only、key-free)。config 記的 at-rest mode 永遠不得 和已安裝的 DB 分歧——這就是能擋下本次事故的那條 check。專屬的事故重現測試(加密檔 + Plaintext config)斷言 checker FAIL;crash-window 測試(rekey.after_swap_before_config)斷言 checker + launch recovery 一起 抓到「config 落後於已安裝的檔案」。
  3. Crash-window coverage。 每個破壞性 archive op 都要有一條 kill-at-checkpoint regression(fault_inject 具名 checkpoint + FaultGuard::error_at_must_fire),且該測試在 未硬化的舊碼 上會 FAIL(舊碼沒有 crash seam、在 swap 前就寫 config)。測試必須斷言 被注入的 錯誤有沿 format!("{err:#}") / err.chain() 傳播,不能只 is_err() / 只驗「可恢復」。每個 op 還要有 per-checkpoint 的 窮舉 torture:對每個 checkpoint 注入 crash → launch recovery → 斷言收斂到 consistent-or-fail-closed(config 對得上檔案、canonical rows 還在,或 marker 留著 fail-closed),永不 empty/mixed。torture 必須 DETERMINISTIC:以固定 checkpoint 列舉或固定 seed 驅動,禁止 wall-clock / Math.random / thread-timing 造成的 flakiness(gate 會跑這些)。
  4. 不得把 production I/O 用 cfg 編譯出 coverage binary。 真正的 compute / I/O 路徑要留在 被量測 的 build 裡;只有最外層 endpoint(socket / syscall fd)可以 seam,且 seam 用的 thread-local FIFO 在 production 恆為空(見 fault_injectwrite_locknext_flock_faultdurable_ioinject_fsync_faults)。Coverage 量的是執行,不是行為:每個 user-facing invariant 都要一條明確的 behavioral 斷言(負→正:斷言「被 heal / 被 restore / rows 還在」,不是只斷言「沒丟錯」)。

新功能 / 硬化 review 必問:「破壞性 op 有沒有 kill-at-checkpoint 測試、且在舊碼上會 FAIL?成功路徑有沒有斷言 check_config_disk_consistency?測試走的是 load_config 還是手搓 config?真實 I/O 有沒有被編譯掉?」答不出=不算 ship-ready,無論 coverage 幾趴。

對應實作(Phase E 交付,vault-core):archive::at_rest::check_config_disk_consistency(+ 事故重現 / crash-window / 窮舉 torture / 同進程雙 op 併發序列化測試);migration::fault_tests 的 import 成功 post-condition + per-checkpoint torture;archive::maintenance restore 成功 + interrupted-restore recovery post-condition;archive::tests backup 成功 post-condition + crash-window suite banner;flaky diagnostics::tests::rust_panic_payloads_keep_owned_strings_and_fallback_text 以 thread-local opt-in 隔離 process-global panic hook(外來 thread 的 panic forward 給 previous hook,不再污染擷取)。