文件寫入守衛
狀態:P3 設計已通過第 2 輪審閱(受審 revision
r2-a2b1504b61f97ef6,ada@leandev.io);實作亦於 2026-10-21 通過第 2 輪審閱(受審 revisionr2-38282cca1103fcfa,ada@leandev.io)。參考工作區因 57 份既有未登記檔,仍未安裝/啟用 hook。 日期:2026-10-21 依據:ADR-023 D1/D3/D4、Registry、文件解析與索引、證據保留;追蹤 FU-142。
1. 目的與範圍
P2-1~P2-6 的設計與實作均已通過。P3 在支援的檔案工具寫入前,以相同 registry 分類器拒絕未登記或分類歧義的位置。文件內容仍由 skill 透過原有工具編輯,不新增內容代理、文件生成器或批准流程。
| 納入 | 保留原契約/本期未涵蓋 |
|---|---|
唯讀 check-write、Claude Code PreToolUse adapter | 需求品質、正文語意、人工確認、UAT/report 原生 verifier |
| registry 掃描根的路徑、分類、符號連結檢查 | writer 身分、欄位所有權、append-only 與受管投影的內容保護 |
| 獨立且可重跑的 hook 設定安裝/檢查 | NotebookEdit、任意 Bash/PowerShell/MCP 寫檔、外部編輯器及人手寫檔 |
| 原投影、專案擴充及既有 legacy 的相容性 | 全量資料修復、retention 清理、FU-143/144/145、fleet 發布或部署 |
本期提供 Claude Code adapter。其他執行工具可呼叫共用 CLI,但未接上同等寫前攔截者須列為未覆蓋,不將規則指示或 adapter 文件等同機械攔截。
2. 上游契約與能力界線
三層防線維持:skill 寫前 resolve、已安裝 hook 的支援工具寫入當下檢查、事後 audit。分類成功只證明位置已登記,不證明文件命名能被 resolver 解析、內容正確、原生證據有效或可以發布。
classify 可查不存在的路徑;沿用其共同層/專案層合併、priority、container 繼承與 legacy 判定。不得在 hook 複製一份 regex/類型清單,不以 Markdown 副檔名猜文件用途,也不把 other 變成 catch-all。
D1 覆蓋澄清(第 1 輪 R1-04 採納,第 2 輪修訂已通過):.codex/、/tmp 及其他掃描根外的位置,不能單憑路徑判定這次寫的是給人閱讀的文件還是合法程式/設定/暫存。P3 不封鎖這些位置的所有寫入,也不宣稱已機械保證所有 D1 義務;D1 的語意義務仍由 rules/skills 與人審執行。Registry §5 原先「由 P3 hook 執行」的概括承諾與 ADR-023 D4 已同批補正為實際覆蓋,不能以其為全檔案系統攔截的驗收依據;此為設計契約修訂,不代表 hook 已實作或啟用。
框架 multi-instance 文檔站沿用原生維護方式。未登記為該 workspace type: docs 的框架站,不自動冒充已套用 bundle 的專案站;本期不擴張其 registry 或改 project.json。
3. 寫入檢查的共同入口
以下為共用介面(實作待審):
python3 {skillRoot}/scripts/docs.py check-write --workspace {workspaceRoot}
[--docs-module {moduleKey}] --path {workspaceRelativePath} [--path …]
- 每個
--path是單一目標檔案,不解釋 glob,不讀取目標正文;新檔案不必存在。相對路徑以 workspace 為基準,絕對路徑先轉換,範圍外路徑回outside-scope。 - 輸出單一 JSON,含
status、readOnly: true、writes: 0、decision: pass|deny、各路徑的scope/分類/原因,以及實際使用的 project.json、投影與專案層 SHA-256。 - exit 0:本次集合可繼續正常工具權限流程;exit 1:至少一個受管目標被拒;exit 2:輸入或設定無法可信判定。不存在
--force、--allow-unregistered、waiver 略過或自動登記。 - 無法正規化的結果仍保留原輸入
path與scope: unresolved/原因;無效 Unicode 以反斜線表示,避免 JSON 輸出中斷。不含工具正文。session 身分缺失另回WG-CONFIG與CLAUDE_PROJECT_DIR修復訊息,不冒充 file_path 錯誤。 - 多路徑是整組判定:任一拒絕即整組 deny,不先寫入通過者。CLI 本身永遠零寫入、零 stage,不建立快取或執行 audit/build/retention。
hook 與 CLI 呼叫同一核心函式;adapter 只負責協定轉換、cwd 與路徑來源,不能把 CLI 的 exit 2 當作通過。
4. Workspace 與管轄範圍
4.1 定位
CLI 以明示 workspace 定位最近的 project.json;hook 以事件 cwd 向上定位 workspace,再核對 session 的 CLAUDE_PROJECT_DIR 與工具所在工作樹。模組子目錄啟動與 workspace 根啟動必須等價,不以 Git root 代替 project.json,也不以 docs 模組名稱字尾猜測。目標若位於另一個可識別 workspace,回 WG-CONFIG,改在該 workspace 依其原設定操作,不套用本專案 registry 放行。
從 project.json 列舉全部 type: docs 模組;單站沿現有自動選擇,多站由目標路徑判定 owner。CLI 的 --docs-module 只供明示單站查詢;正式 workspace hook 不可只選一站而漏掉其他站。
對各站以其投影 scan.roots 展開 {docsRoot}/{workspace}/{module},採完整路徑 segment 判斷根及後代。不存在的根也納入,以便檢查新檔。各站共享的 workspace 根有重疊時,受影響各站均須可判定且通過;不任選第一站。
4.2 決策表
| 情況 | 結果 |
|---|---|
| 受管根內,唯一分類且路徑安全 | pass,回傳類型與生效特性 |
| 受管根內,沒有類型(含既有檔、新檔、已有 audit waiver) | WG-UNREGISTERED,deny;不依已追蹤、ignored 或存在與否豁免 |
| 同分分類歧義 | WG-AMBIGUOUS,deny,列出候選 |
| 已有/新建 legacy 位置,唯一分類 | pass 並標記 legacy;既有 resolve/audit 的修復義務保留 |
| 命中 scan.excludeDirs/excludeFiles | excluded,pass;不宣稱檢查其文件位置 |
| 已可信定位、目標在全部受管根之外 | outside-scope,pass;不讀正文猜用途 |
| 根判定需要的 project.json/投影缺失、非法或 registry lint 失敗 | WG-CONFIG,exit 2;hook deny,列修復入口 |
| 其他 docs 站與目標明確無關,但該站投影不可讀 | 不能可靠得知該站的額外 scan roots,仍回 WG-CONFIG;不得宣稱整個 workspace 已覆蓋 |
scan.exclude* 僅使用 registry 的現行定義,不另建 ignored 目錄白名單;.gitignore 不能作為繞過理由。未受管的原始碼改動在設定可讀時直接 outside-scope,不執行 Git 或全站掃描。設定損壞可能暫時阻止支援檔案工具的呼叫,修復流程見 §8。
legacy 位置目前仍可由 resolver 回傳;若 hook 一律禁止,會造成已通過 P2 的既有文件無法維護。因此本期只限制「未登記」,不擅自把 legacy 警告升級為強制搬檔;新文件仍依 resolve 的正式 template 產生。
4.3 既有未登記檔與啟用前對帳
未登記不等於可刪除;也不能因檔案已存在或被 audit 豁免,就把位置視為已登記。Registry §6 的 waiver 仍只影響 audit 彙整與 --fail-on,不成為 hook 寫入許可。本設計不加入 grandfather 清單,也不因既有欠帳自動改名、搬移、分類或刪檔。
安裝前置:install --check 與 install 的 preflight 依相同 scan.roots/exclude 掃描既有一般檔案,列出完整的 existingUnregistered 清單(workspace 相對路徑、docs owner、Git 狀態、audit waiver 與 wouldDeny: true),不截斷;另列總數、waived/unwaived 與 tracked/untracked/ignored 數量。Git 狀態與 waiver 只供對帳,不影響是否被拒。多站重疊路徑依 owner 分列,檔數與 owner 筆數分開。
清單非空時回 WG-EXISTING-UNREGISTERED、exit 1、ready: false,不新增或更新 hook 片段。先依用途補正式/專案類型或經授權修正位置,再重跑清單為空才可 install;只新增 waiver 或 gitignore 不解除此安裝前置。不得為通過而清除歷史資料或加無用途的 catch-all。這只約束新安裝/升級,不新增產品交付、retention、UAT 或 commit 閘門。
對已安裝的 hook,--check 分開回報 installed 與 ready;之後出現未登記檔時列出新影響,既有檔案呼叫仍按 §4.2 拒絕,不自動移除 hook 或回復設定。安裝器在寫設定前重核對 registry 與影響清單,變動時回 WG-CHANGED、零設定寫入。
2026-10-20 唯讀現況盤點:參考實作有 57 個受管範圍內未登記檔,其中 54 個已追蹤、3 個 ignored、0 個 waived;包括設計對齊附件、reference-audits、個別 checklist 與 UAT 維護 audit。這些資料本輪原位保留;依上述前置,參考實作目前為 未就緒,不能直接安裝。完整逐檔清單是本輪審閱證據,不寫死為框架例外;實際 install 必須重新盤點,不能沿用此數字。
5. 路徑安全與一致性
- 空值、非字串、NUL、無效編碼或目標為目錄時回
WG-INPUT;不展開~、shell 變數、URL 或 glob。 - 先定位實體 workspace 根:從明示 workspace/事件 cwd 的 lexical 祖先定位 project.json,將該根對應為實體根,以實體路徑與 filesystem 身分核對 session、工具與 registry。workspace 根以上的 symlink 別名可接受;絕對目標的前綴若指向同一實體 workspace 根,移除該前綴後,改接實體根與原本尾段再分類,不將祖先別名誤報為越界。逐段走訪時取第一次抵達實體 workspace 根的邊界,不把根以下繞回根的 symlink 當成另一個新邊界。找不到可信的同根對應時明示拒絕,不沿用其他 workspace 的 registry。
- symlink 檢查界線為實體 workspace 根以下:根本身完成實體定位後,檢查其後每個路徑 segment,含目標檔;任一 symlink(含斷鏈/迴圈)即
WG-SYMLINK。保留 lexical 尾段與既有祖先,不能先對整個目標resolve()或normpath而掩蓋根以下的 symlink/..。根以下的..若越出 workspace 即拒絕,即使後續再進入同根。lexical 與實體目標任一落入受管根便須檢查,不能以另一端 outside-scope 略過;直接指向 workspace 內部子目錄、卻無同根邊界的外部 symlink 不在祖先別名例外,仍拒絕。解析失敗回明確錯誤,不視為不相關。 - 以實際 filesystem 大小寫語意處理既有祖先及新檔尾段;macOS 大小寫別名、空白、Unicode、同前綴 sibling、巢狀 workspace 均有對抗 fixture。不同 OS 未驗證者不得宣稱支援。
- 一次操作讀取 project.json 與受影響 registry bytes 的固定快照,使用前後核對 SHA;不一致回
WG-CHANGED,重跑本次查詢,不沿用上一操作快取。不得因 audit 的歷史 RT 問題拒絕寫入。
hook 在工具前執行,無法跨越檢查與真正寫入之間鎖住其他程式的 rename/symlink/設定變更。這是協作防誤寫,不是作業系統 sandbox;後驗 audit 與既有原生守衛照常,不宣稱消除競爭窗口。
例如(路徑為示意):/shortcut/team 指向 /storage/team,兩者的 modules 為同一 workspace;/shortcut/team/modules/{docsModule}/docs/... 應與實體路徑判定相同。若實體 workspace 內又有 {docsModule}/linked 指向其他目錄,該段仍拒絕;不能靠第一層祖先別名把它合法化。現行 classify 對別名絕對路徑仍可能報越界,P3 會在共同 check-write 核心完成邊界轉換後才呼叫同一 Matcher,並非宣稱現行 classify 已修復。
6. Claude Code adapter
6.1 平台協定
實作依官方 Hooks reference 的 PreToolUse input/decision control/failure 章節;查核日為 2026-10-20。本機 CLI 為 2.1.303。支援工具先限定 Write|Edit,從 tool_input.file_path 取目標,事件 cwd 僅作定位核對。NotebookEdit(使用 notebook_path)、Bash/PowerShell/MCP 等工具不在 matcher;不讀其參數或宣稱已攔截 NotebookEdit 的 notebook 寫入。工具種類另見官方 Tools reference。
成功時 exit 0 且 stdout 空白,讓既有權限流程繼續;拒絕時回 hookSpecificOutput 的 PreToolUse deny 與英文原因,exit 0。adapter 捕捉到無法輸出合法 JSON 的錯誤時,以英文 stderr 及 exit 2 拒絕。不得回 allow、ask、defer、updatedInput 或自行執行寫入。
同步 command hook 使用 5 秒 timeout、onFailure: block;最低 Claude Code 2.1.295,以涵蓋啟動失敗與逾時。這是本案對官方能力的採用方案,安裝時核對版本;版本較舊時零寫入並列為未啟用。Windows launcher 本期不自動安裝,待獨立驗證後擴充。
6.2 啟動器與輸入
canonical adapter 放在 .claude/skills/docs-site/scripts/write_guard.py,不放在 fleet 不傳播的 .claude/hooks/。設定使用 Python exec-form command/args,避免把目標路徑、事件內容或環境值拼入 shell。
安裝器產生固定的 python3 -c bootstrap:先有界讀取事件,從事件 cwd 向上查找最近 project.json,依 §5 取得實體 workspace,再載入同 workspace 的 canonical adapter,將已解析事件直接傳入,不重讀 stdin。CLAUDE_PROJECT_DIR 可為該 workspace、模組或其 repo 祖先,祖先別名先對應為實體路徑後核對同一工作樹關係;無法核對時拒絕。bootstrap 自己的程式 bytes 為受管片段,不含本機絕對路徑;安裝檢查驗證其完整內容、祖先別名與移動 workspace 後的行為。找不到 workspace、檔案缺失或身分不符時拒絕,不搜尋 sibling clone 或從其他 clone 載入工具。
adapter 一次讀取至多 8 MiB 的 stdin JSON(Write 正文包含在事件內但不保存、不分析);超限或缺少 hook_event_name/tool_name/cwd/tool_input.file_path 時回 WG-INPUT。只接受本節兩個工具,其他名稱在直接呼叫測試時回 unsupported 並拒絕;正常 matcher 不會把它們送進來。禁止記錄 content、old_string、new_string、transcript 或憑證;錯誤只含代碼、必要路徑及修復建議。
7. 設定安裝與下行
.claude/settings.json 是專案自有設定,不在 /methodology publish 的 rules/skills 命名空間;不可複製框架整份 settings 覆蓋下游。本期新增獨立工具(實作待審):
python3 {skillRoot}/scripts/write_guard.py install --workspace {workspaceRoot} --check
python3 {skillRoot}/scripts/write_guard.py install --workspace {workspaceRoot}
--check唯讀,列版本、bootstrap、matcher、timeout、onFailure 與生效設定的可核對狀態,另依 §4.3 輸出全部既有未登記檔與 waiver 影響,分開回報 installed/ready。片段缺失、不同或存在受影響檔時 exit 1;非法設定或不支援環境 exit 2。- install 前須已有可讀且 lint 通過的全部 docs 投影,完成
/docs-sitealigned 檢查,且 §4.3 的 existingUnregistered 為空;不以 audit warning 被 waiver 或忽略解除此條件。未就緒時不寫 settings,先交原用途/類型對帳,本期不由 hook 偷跑 plan/apply。安裝器讀嚴格 JSON,不改個人/managed 設定。 - 只維護一個完整相等可辨識的 PreToolUse group,保留 statusLine、permissions、其他 events 與其他 hooks;初次追加,重跑零變更。已有疑似同職責但內容不同、重複或無法辨識的 group,列差異停止,不猜測所有權、不刪除。
- 改版時只替換已知先前版本的完整片段;未知版本停止。新增前後都核對 settings bytes,原子寫入;已觀察到並行改動時零覆蓋並保留現況。不合作編輯器在最後核對/replace 之間仍有 TOCTOU 窗口,不保證 OS CAS(第 1 輪 R1-05 已接受此界線)。不新增自定義 settings schema 欄位作 owner 標記。
- 檢查 project/local 的 disableAllHooks 與可讀的有效設定;managed 政策或使用者層無法確認時,明示「已安裝,生效待核對」,不得僅以片段存在宣稱啟用。原生 session 載入及真實攔截演練是啟用驗收的一部分。
- 方法論 publish 只送 canonical 腳本與安裝指引;下游完成 docs-site plan/apply 與位置問題處置後,再明示安裝 hook。scaffold 流程在 docs 投影就緒後呼叫同安裝器,不複製來源 hook 的專案狀態。本次設計不執行 fleet 或 scaffold。
模組子目錄啟動須以 claude --settings {workspaceRoot}/.claude/settings.json 明示載入同片段並核對原生 hook;未明示載入且未核對的模組 session 視為未覆蓋,與 NotebookEdit/Bash 等未覆蓋項目並列。此條件也寫入 always-on 的 m-skill-execution.md,不只留在安裝指引;安裝不在各模組複製 hook。
--check/install 均回報 lock.path/lock.exists;發現 .claude/.docs-write-guard.lock(包含搶鎖時才出現)回 WG-LOCKED、exit 2、ready: false、零 settings 寫入。保留原鎖,不推測過期或自動移除;操作者確認沒有其他安裝器運作後,才人工移除該檔並重跑。
P2 發布與 P3 hook 啟用可分開進行。ADR-023 保持 Proposed,P3 設計與實作另行通過後才評估 Accepted;不能以本機有安裝片段取代逐期審閱。
8. 失敗與修復出口
| 代碼/情況 | 修復方式 |
|---|---|
WG-UNREGISTERED | 先查 resolve 的正式位置;確為新用途時,由專案層新增帶 purpose 的類型,再 lint/classify;不自動補一條寬鬆 glob |
WG-EXISTING-UNREGISTERED | 安裝前列出完整的受影響既有檔(含 waived/ignored);依 §4.3 逐份核對用途並補類型或修正位置,清單為空才安裝。保留原檔與 audit waiver 紀錄,不以只加 waiver、ignore 或刪除資料過關 |
WG-AMBIGUOUS | 修正明確的 priority/樣式衝突;不以候選順序決勝 |
WG-CONFIG | 修復 project.json/registry,投影缺失則 docs-site plan/apply;既有設定修復授權照常,原生工具不受檔案 adapter 攔截 |
WG-SYMLINK/WG-INPUT | 使用正確實體文件位置或修正工具參數,不靠換工具繞過同一政策拒絕 |
WG-LOCKED | 檢查 .claude/.docs-write-guard.lock;先確認沒有其他安裝器正在運作,再由操作者人工移除該鎖並重跑。check/install 都回報鎖位置與存在狀態,ready=false、零 settings 寫入;不自動刪鎖,不導向 registry lint/plan/apply |
WG-CHANGED | 重讀設定並重跑,持續變動時列出來源,保留工作樹 |
| launcher/Python 缺失或 timeout | 由操作者修復安裝環境;不自動移除 hook、不新增 force |
| 既有 legacy/RT 欠帳 | 明示保留;不改 UAT 接受、不清理資料,也不擴張成新增交付閘門 |
安裝設定無法使用支援檔案工具修復時,可依原授權用本機編輯器或原生 plan/apply 修復設定本身;這是恢復可判定狀態,不授權把已被判定未登記的文件寫入原位置。停用/移除只處理本工具可辨識片段且需明示維護授權,不代替問題修復。
9. 效能與零寫入
每次 check-write/hook 檢查只讀設定與路徑 metadata,一次載入各站 registry、一次處理全部路徑;不啟動 Node、不執行 Git、不列舉全文、不掃快照或計算 retention、不讀其他文件內容。避免每個路徑啟動一個子程序;hook 在同一 Python 程序呼叫核心。install --check/install 的既有影響盤點是獨立安裝流程,允許依 scan 契約列舉與批次 Git 狀態查詢;不放進逐次寫入熱路徑,不讀檔案正文、不執行 retention。安裝盤點與單次 hook 的耗時分開記錄。
實作驗收以代表性 workspace 的 30 次冷程序呼叫記錄 p50/p95、Python/OS/CLI 版本及路徑數,目標單路徑 p95 小於 500 ms,5 秒 timeout 只作最後出口;不同機器另記實測,不在一般單元測試硬編秒數。無磁碟快取與跨呼叫快取;記錄 stdin 上限與大 Write 的拒絕,不能藉效能略過分類。
判定與 hook 演練前後核對工作樹、index、原生帳本、設定及目標不存在狀態。install 是唯一明示會寫設定的動作,與零寫入判定分開驗收。
10. 實作驗收矩陣
以下為設計核定的驗收矩陣;本次實作結果及原生演練修訂見 §14,不以設計走查冒稱攔截已通過。
| 組別 | 必測命題 |
|---|---|
| G1 分類 | 已有/新建正式路徑;既有/新建未登記檔同樣拒絕(含已追蹤、ignored、audit waived);waiver 仍僅影響 audit;同分歧義、container 索引繼承、專案 extension/extendCommon、legacy、新 legacy、沒有 template 的合法類型 |
| G2 範圍 | 每個 scan root、排除目錄/檔案、ignored 但受管路徑、根目錄同名 sibling、根本身、新建根、多個 docs 站及重疊 workspace roots |
| G3 安全 | 經 workspace 祖先別名定位/開啟與實體根相同判定;同時包含根以下 symlink 的別名路徑仍拒絕;第一次同根邊界、lexical/physical 雙向 symlink、斷鏈/迴圈、symlink 加 ..、大小寫別名、Unicode/空白、目錄目標、NUL、巢狀 workspace、外部絕對路徑 |
| G4 設定 | 投影缺失/非法、project 層 lint/合併錯誤、project.json 損壞/並行改動、多站一站無投影、不同 clone、workspace 搬移、未套用 framework multi-instance |
| G5 協定 | Write/Edit 真實 payload、缺欄位、8 MiB 邊界、合法 deny JSON、unexpected exception exit 2;pass 不出 allow/ask/defer/updatedInput;NotebookEdit 不在 matcher 且其寫入未受檢查;stdout 無非協定文字 |
| G6 安裝 | 既有未登記完整清單與 waived/unwaived、Git 狀態統計;清單非空時零設定寫入、只加 waiver 不解鎖;安裝前清單並行變動、installed/ready 分離;保留所有其他設定、重跑零變更、已知版更新、未知片段/重複拒絕、並行設定改動不覆蓋、disableAllHooks/managed 限制、Python 缺失及不支援版本 |
| G7 原生演練 | 隔離 fixture 啟動真實 Claude Code session:正常 Write/Edit、拒絕新未登記檔且檔案不存在;launcher 缺失、timeout、畸形 JSON 在 onFailure 下均阻止工具;實體根/模組 cwd/經祖先別名開啟 workspace 結果一致,祖先別名加內部 symlink 仍被拒 |
| G8 保留與成本 | 不讀正文/不呼叫 audit、Git、Node 或 retention;30 次冷呼叫成本;未涉及檔案及 index SHA 不變;原生 report/UAT 接受證據保留 |
G7 不在產品資料或現行工作樹製造誤寫;fixture 限定模型工具、費用與輸出,只有本機可用能力才列為通過,無法執行時如實列缺口並留待審閱裁決。真實 Bash 寫入不在本期攔截範圍,須以反例演練與說明呈現,不能以測試工具被禁用製造全覆蓋印象。
11. 預定修改範圍
| 落點 | 實作責任 |
|---|---|
| docs-site scripts/docs.py、write_guard.py 與 focused tests | 共用判定、CLI、adapter、安裝器與對抗驗收 |
| docs-site SKILL/doc-locations、m-docs-site | 安裝順序、拒絕修復、真實覆蓋與原生證據邊界 |
| scaffold-project/scaffold-module、methodology SKILL | 在投影就緒後使用安裝器;publish 不覆蓋 settings |
workspace .claude/settings.json | 僅安裝器維護的單一 hook 片段,保留 statusLine 等原設定 |
| 相容層 | canonical rule/skill 有變時依 migrate-agent-config audit/sync/validate;不宣稱自動啟用另一執行工具的攔截 |
| Registry §5/§6、ADR-023、FU-142 | 更正過度承諾、區分 audit waiver 與寫入/安裝、記錄審閱與啟用狀態;P3 實作通過前不改 Accepted |
本輪新增此設計、側欄/索引與 FU-142 的設計進度,並依 R1-04 同步 Registry §5/§6、ADR-023 D4 的設計契約與限制;未修改上表工具、rules、skills、registry 投影或 settings。設計修訂已通過第 2 輪審閱;後續依本規格實作及接線運作規則/skill,再另行送審。
12. 第 1 輪裁決與修訂對照
審閱方於第 1 輪 R1-04 採納以下三項作者建議,條件為處置 R1-01/R1-02 並同步正式契約;第 2 輪已由審閱方核對四條意見均完成;本節保留第 1 輪裁決及修訂對照。
- 覆蓋邊界:採 Write/Edit 與 registry 掃描根;NotebookEdit、shell/外部工具及根外語意義務未機械覆蓋,Registry §5 與 ADR-023 D4 同批補正。若要求更廣覆蓋,另定工具 adapter 與驗收,不用 shell 字串啟發式充數。
- legacy 相容:唯一分類的 legacy 可維護,新建仍由 resolver 正式 template 導向;不在 P3 強制搬檔。
- 安裝與錯誤:採 2.1.295+ 的 onFailure、失敗拒絕與獨立設定安裝;多 docs 站設定缺失時拒絕,透過原生設定修復恢復,不默默漏一站。
| 意見 | 設計修訂與落點 | 驗收 |
|---|---|---|
| R1-01 major | §5 以實體 workspace 為根,接受根以上的祖先別名;第一次同根邊界以下的 symlink 仍拒絕,§6.2 啟動器同樣正規化 | G3/G7 加祖先別名及別名+內部 symlink |
| R1-02 major | §4.3/§7/§8 安裝前完整列出所有未登記檔(含 waived/ignored),清單非空不寫設定;Registry §6 明訂 waiver 不是寫入許可 | G1/G6 加既有資料及 waiver、清單變動、安裝零寫入;參考實作 57 檔待對帳,本輪不處理 |
| R1-03 minor | §1/§6.1 補列 NotebookEdit 未覆蓋,明訂不得回 defer | G5 加 matcher 與輸出限制 |
| R1-04 minor | 本節保存三項裁決;Registry §5/§6、ADR-023 D4、FU-142 同批同步,不修改工具或啟用狀態 | 本輪核對正式契約與網站;G1~G8 仍待實作 |
以上設計修訂已通過第 2 輪審閱;ADR-023 維持 Proposed,實作與驗收另行完成。
13. 設計審閱結論(2026-10-20)
P3 設計通過第 2 輪審閱,受審 revision r2-a2b1504b61f97ef6,審閱者 ada@leandev.io。R1-01/R1-02 major 與 R1-03/R1-04 minor 均已處置,無 open 或 declined;審閱方重跑第 2 輪驗證並獨立吻合 57 份既有未登記檔的盤點。
定案採用共用唯讀 check-write、限 Write/Edit 的否決 adapter、唯一分類 legacy 的相容維護,以及獨立 settings 安裝器。實體 workspace 根以上的祖先別名可對應回同根,根以下 symlink 仍拒絕;既有未登記檔(含 waived/ignored)須先完整列出並完成用途/位置對帳,清單非空不新增/更新 hook 片段。Audit waiver 不構成寫入特權。NotebookEdit、shell/MCP、外部編輯器及掃描根外語意義務不宣稱機械覆蓋;adapter 不回 allow/ask/defer/updatedInput。
Registry §5/§6 與 ADR-023 D4 已同步設計邊界,FU-142 分開追蹤已通過設計與待完成實作。兩站 docs-only 建置、框架索引 lint 及四頁內容/來源 SHA 核對支持本次設計可審閱;G1~G8 的新工具、安裝、原生 session 與效能驗收仍待實作,不以文件驗證代替攔截驗證。
參考實作的 54 個 tracked+3 個 ignored 未登記檔、52 份原生帳本及原生接受保留原狀,目前尚不符合 hook 安裝前置;後續沿 FU-142 對帳,不為通過而刪除資料或加無用途的 catch-all。FU-145 的帳本路徑修復獨立追蹤。此結論僅為設計通過:工具、rules/skills 接線、registry 投影與 settings 未改,hook 尚未啟用;P3 實作另行送審,ADR 保持 Proposed,未下行或部署。
14. P3 實作與驗收(2026-10-20,待審)
共用入口 docs.py check-write、write_guard.py 核心/Claude adapter/安裝器,以及 focused tests 與明示執行的 verify_write_guard_native.py 已完成。核心使用相同 registry 載入器、Matcher、expand 與 lint;新增可注入 reader 以固定 bytes 快照,不建立第二份類型清單。case-sensitive/insensitive 由同卷宗既有別名唯讀探測;新尾段在已證實不分大小寫的卷宗沿同一 Matcher 補大小寫旗標。根下 symlink 一律拒絕;安裝器目前沒有已發布舊版片段,未知片段停止,未假造升級歷史。
兩項原生驗證揭露的接線修訂,已由實作第 1 輪 R1-05 採納;模組條件依 R1-01 補進 always-on 規則,實作仍待第 2 輪確認:
- 驗證 adapter 輸出:Claude Code 2.1.303 對 exit 0 的不完整 JSON stdout 可能當一般文字而繼續寫入。固定 bootstrap 因此先捕捉 adapter stdout,僅允許空白或完整且只有 PreToolUse deny 的協定;畸形、allow/ask/defer/updatedInput 或例外均以英文 stderr、exit 2 拒絕。G7 以故障注入 adapter 的破損 JSON 驗證這條路徑;不宣稱平台可封鎖所有自行替換的任意 hook 程式。
- 模組啟動須明示設定:本機原生 CLI 從模組子目錄啟動,沒有自動載入上層 workspace settings;即使 fixture 為 Git repo 也一樣。須用
claude --settings {workspaceRoot}/.claude/settings.json載入同一份片段,核心才可做 workspace/模組/祖先別名等價判定。安裝器回報 moduleLaunch 與 needs-native-verification;不複製片段到每個模組、不以 installed 宣稱自動生效。G7 保留未載入設定的失敗證據,最終模組演練採此明示啟動方式。
G1~G6/G8 由 focused tests 覆蓋分類、所有展開 roots、excludes、多 owner、祖先別名、根下連結、大小寫、輸入、設定快照、協定及安裝拒絕/保留/競爭。零寫入熱路徑的對抗測試禁止 subprocess、os.walk、audit、retention 及目標正文讀取。安裝的影響清單只做 metadata/批次 Git 查詢;獨立的既有 docs-site aligned 檢查沿 update.plan 的原生 protected/bundle bytes 指紋,不將其成本或讀取承諾混入熱路徑。settings 的合作安裝器鎖、寫前 bytes 比對與原子 replace 不構成作業系統 CAS;不合作編輯器在最後核對與 replace 之間仍有競爭窗口,與檢查後實際工具寫入的 TOCTOU 限制一併揭露。
原生 G7 在隔離 fixture 中啟動九個有費用/turn 上限的真實 sessions,驗證正常 Write/Edit、新未登記檔不存在、workspace/明示設定的模組/祖先別名、祖先別名加內部 symlink、launcher/adapter 缺失、5 秒 timeout 及畸形 adapter stdout。Bash 反例實際寫入未登記位置,證明它未在 matcher 中;未以禁用 Bash 製造全覆蓋印象。本輪驗證平台為 macOS、Python 3.14、Claude Code 2.1.303,其他 OS 未驗證;逐項最終命令、exit、原始輸出、成本與 SHA 以實作審閱附件為準。
參考工作區重新盤點仍為 57 份未登記檔(54 tracked、3 ignored、0 waived),install --check/install 必須拒絕且不改 settings。現行檔案、52 份原生帳本與接受紀錄保留;未修 FU-145、未下行/部署、未 commit/push。docs-site、doc-locations、m-docs-site、scaffold-project/scaffold-module 與 methodology 指引已接線;Codex 相容層不宣稱新增機械 hook。ADR-023 維持 Proposed,實作通過及結案前不視為 P3 完成。
15. 實作第 1 輪裁決與第 2 輪修訂(2026-10-21)
審閱方 R1-05 已接受四項實作界線:①模組啟動明示 --settings、不複製到各模組,條件為 R1-01 補入 always-on 規則;②固定 bootstrap 先驗證 adapter stdout;③aligned 沿既有 protected/bundle 指紋,安裝盤點與熱路徑分開;④合作鎖+最後 bytes 核對+atomic replace 對不合作編輯器仍有 TOCTOU,接受此揭露而不要求 OS CAS。這是界線裁決,不是整份實作已通過。
| 意見 | 本輪處置 |
|---|---|
| R1-01 | m-skill-execution 的常駐規則與本頁 §7 明訂模組啟動及未覆蓋條件,與 NotebookEdit/Bash 等並列 |
| R1-02 | 專用 WG-LOCKED、lock.path/exists、ready=false、人工確認/移除出口;check/install 與搶鎖競爭均補測試,保留原鎖與 settings |
| R1-03 | scaffold-project 明示逐欄盤點與重建標的非 hook 設定;帶入 statusline-command.sh 就須建立 statusLine 並從兩個 cwd 驗證,未採用則明列理由且不帶入腳本;來源信任/環境設定不移植的理由寫明,兩處舊 cp-r 引用改 rsync |
| R1-04 | unresolved 結果保留輸入 path(無效編碼以可輸出的反斜線表示),缺 CLAUDE_PROJECT_DIR 給 session 身分專用訊息及修復出口;兩項補測試 |
| R1-05 | 四項裁決記於本節,並同步 §7/§14;本輪沒有新增待決方向 |
新的測試、建置及保留核對以第 2 輪審閱附件為準;第 1 輪原始證據不改寫成新版通過。參考工作區仍因 57 份既有未登記檔而不安裝 hook,52 份原生帳本、原生接受、settings 與 index 保留;ADR 維持 Proposed,沒有下行、部署、commit 或 push。
16. 實作審閱結論(2026-10-21)
P3 實作通過第 2 輪審閱,受審 revision r2-38282cca1103fcfa,審閱者 ada@leandev.io,確認時間 2026-10-20T22:58:22Z。R1-01~R1-05 五條 minor 均已處置並由審閱方重現,無 open、declined、blocking 或 major。§14/§15 為實作及修訂期間的紀錄,本節為目前結論。
通過範圍包含共用唯讀 check-write、Claude Write/Edit 否決 adapter、固定 bootstrap 輸出驗證、獨立 settings 安裝器、完整既有未登記清單及 rules/skills 接線。第一次同實體根邊界接受根上別名、拒絕根下 symlink;多 owner 共同判定、缺一投影即拒絕。唯一分類 legacy 可維護,waiver/ignored/既有檔不提供未登記寫入特權;非空清單不新增或更新 hook 設定。
第 1 輪四項裁決及第 2 輪處置定案:模組啟動須明示 --settings 並核對,未載入且未核對視為未覆蓋,條件已放進 always-on 規則;bootstrap 僅接受空 stdout 或合法 deny;aligned 沿既有 protected/bundle 指紋、與安裝盤點及熱路徑分開;合作鎖+最後 bytes 比對+atomic replace 的 TOCTOU 界線獲接受,不宣稱 OS CAS。WG-LOCKED、原輸入 path/session 訊息與 scaffold 非 hook 設定建立責任均已補齊。
作者第 2 輪重跑 focused 29+registry/CLI 48 項、九個真實原生 sessions、兩站 docs-only 建置及相容層驗證;30 次單路徑冷程序 p50 229.9 ms/p95 260.8 ms,符合小於 500 ms 目標。審閱方重現確認處置及 r2 驗證通過。第 1 輪其他回歸保留原始證據,不冒稱第 2 輪全量重跑;本機實測限 macOS/Python 3.14.2/Claude Code 2.1.303,其他 OS 與有效政策另行驗證。完整含 server 同步 build 的 FU-143 未由本期解決。
P0~P3 逐期設計及實作均已通過,ADR-023 改為 Accepted。這是決策及實作的接受,不等於 fleet 已更新或 hook 已啟用。參考工作區仍有 57 份未登記檔(54 tracked+3 ignored),安裝器拒絕且零 settings 寫入;52 份原生帳本、人工接受、settings 與 index 語意保留。FU-142 繼續承接用途/位置對帳及下行/逐站安裝,FU-145 獨立處理原生帳本舊路徑;不為啟用刪資料、加 waiver/ignore/無用途 catch-all。NotebookEdit、shell/MCP/外部編輯器與根外語意義務仍未機械覆蓋。未 commit、push、下行或部署。