diff --git a/docs/opc-zip-surgery.md b/docs/opc-zip-surgery.md
new file mode 100644
index 0000000..f6f507a
--- /dev/null
+++ b/docs/opc-zip-surgery.md
@@ -0,0 +1,340 @@
+# OPC ZIP 手術:VBA 注入與保真契約
+
+> 狀態:#135 `che-excel-mcp` 離線 `inject_vba` 能力的設計依據
+>
+> 適用範圍:以受信任的 VBA carrier 將一般 `.xlsx` 轉為 `.xlsm`
+>
+> 來源:PsychQuant/macdoc#136;document-module 綁定陷阱見
+> PsychQuant/macdoc#138
+
+## 1. 核心原則
+
+`.xlsx` 與 `.xlsm` 都是 OPC ZIP package。巨集注入不需要先開啟 Excel,也不應把
+整份活頁簿交給另一套文件模型重寫;它只應修改明確列入契約的 package parts,並
+保留其餘 ZIP entry 的**解壓後 payload bytes**。
+
+這裡有兩種不可混稱為「保真」的模式:
+
+| 模式 | 負責的事 | 可主張的保證 |
+|---|---|---|
+| 離線 OPC 手術 | 精準修改 package、加入 VBA binary、補宿主綁定 | 未觸碰 entry payload byte-identical;預期 XML 可逆 |
+| 活 Excel 開啟/另存 | 重算、執行巨集、讓 Excel 驗證與重寫文件 | 功能與 Excel 相容;不保證 ZIP entry 或 XML bytes 不變 |
+
+ZIP central directory、壓縮層級、時間戳或 entry 順序可能因封裝工具重建而改變;因此
+「整個 `.xlsm` 檔案的 bytes 等於某個預期 ZIP」不是本契約。保真比較的單位是 entry
+名稱集合與每個 entry 的解壓後 payload。
+
+## 2. `.xlsx → .xlsm` 的 package delta
+
+### 2.1 固定三項
+
+一般 `.xlsx` 轉成含 VBA project 的 `.xlsm`,固定需要以下三項變更:
+
+| Part | 變更 |
+|---|---|
+| `[Content_Types].xml` | 將 `/xl/workbook.xml` 的 main content type 改成 `application/vnd.ms-excel.sheet.macroEnabled.main+xml`,並宣告 `bin` 為 `application/vnd.ms-office.vbaProject` |
+| `xl/_rels/workbook.xml.rels` | 新增 type 為 `http://schemas.microsoft.com/office/2006/relationships/vbaProject`、target 為 `vbaProject.bin` 的 internal relationship |
+| `xl/vbaProject.bin` | 加入與受信任 carrier asset 完全相同的 binary payload |
+
+這三個 URI 是第一版刻意採用的 **canonical Excel layout profile**。preflight 必須從
+package root relationship 解析 office document、再由 workbook relationships 解析每個
+sheet,並確認實際 URI 正是這個 profile;合法但使用其他 part URI 的 OPC package
+目前應回報 `unsupportedPackageLayout`,不得仍向寫死路徑寫入。實作者也不得假設固定
+`rId`;relationship ID 必須在現有集合中唯一。
+
+來源 workbook content type 必須是一般 `.xlsx` 的
+`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml`。若 package
+已是 macro-enabled、已有 VBA relationship/part、已有 VBA content-type Override,或
+既有 `.bin` Default 使用不同 content type,第一版應 fail-loud,不得靜默覆寫或重新
+分類其他 binary parts。同樣要拒絕重複/衝突的 workbook Override,以及 TargetMode、
+Target 或 type 不符與懸空的疑似 VBA relationship。替換既有 VBA project 必須是另一個
+有明確驗收的操作模式。
+
+### 2.2 條件式宿主綁定
+
+「固定三項」只描述 package 最小差異,不代表每個來源活頁簿都只需改三個 parts。
+只要 carrier 的 VBA project 含 document modules,注入器還必須讓宿主 XML 的
+`codeName` 與 carrier module inventory 及本次 target binding 一致:
+
+| VBA project 宣告 | 宿主綁定 |
+|---|---|
+| workbook document module,通常為 `ThisWorkbook` | `xl/workbook.xml` 的 `` |
+| worksheet document module | 對應 `xl/worksheets/sheetN.xml` 的 `` |
+
+這些是**條件式 delta**。如果來源 XML 已有相同綁定,就不應改寫;若缺少,才做
+窄幅插入;若現值衝突,或 inventory/target binding 無法完整對映,必須停止並回報衝突。
+
+`codeName` 不是使用者在頁籤看到的 sheet name。工作表必須透過
+`xl/workbook.xml` 的 sheet relationship ID 與 workbook relationships 對映到實際
+worksheet part;不得用頁籤順序或顯示名稱猜測。這需要兩份不能混用的資料:
+
+1. **Carrier module inventory** 跟 `vbaProject.bin` digest 綁定,記錄受控 asset ID/
+ 版本、workbook document-module code name,以及每個 module 的 kind、code name 與
+ 文字來源版本;另列出完整 callable/event procedure inventory,以及 prohibited
+ auto/event entrypoint scan 的工具版本、source digest 與結果。不得保存使用者本機
+ 路徑。carrier 自己的 `sheetN.xml` URI 不能拿來決定另一份目標活頁簿的對映。
+2. **Target binding** 由本次 caller 明示,將 inventory 中每個 worksheet module 一一
+ 對映到目標 workbook 裡的 sheet relationship ID/解析後 part URI。preflight 必須
+ 驗證 module 集合完整、沒有多餘項目、每個 target 是 workbook 實際列出的 worksheet,
+ 且 module 與 target 都是一對一。
+
+第一版 typed host kind 只支援 `workbook` 與 `worksheet`。carrier 若含 chart sheet、
+dialog sheet、macro sheet 或無法對映的 document module,資產註冊與注入都要 fail-loud;
+不可把它猜成 worksheet。所有 code names 必須符合 Excel 的長度/identifier 限制;
+每個宿主 code name 要與 inventory 中對應 document-module identifier 不分大小寫相等,
+再對不同 logical modules 的 identifier 做不分大小寫的唯一性檢查,且不得與不相干的
+standard/class module 撞名。正確的 `ThisWorkbook ↔ ThisWorkbook` 對應不是衝突。
+
+寫入 XML 時有三項結構要求:
+
+1. 既有 `workbookPr` 要保留其他屬性與 namespace 宣告;它在 schema 中是 leaf,若含
+ 無法解釋的子節點應拒絕輸入。既有 `sheetPr` 則要保留其他屬性與合法子節點,兩者
+ 都只更新 `codeName`。
+2. 元素不存在時要依 SpreadsheetML schema 順序插入。
+3. 每個容器最多只能有一個 `workbookPr` 或 `sheetPr`;不得用 regex 直接附加而產生
+ duplicate elements。
+
+## 3. document-module 綁定為何會靜默失敗
+
+carrier 的 `vbaProject.bin` 內含 VBA project stream。實戰 carrier 的 `PROJECT`
+stream 宣告了 `ThisWorkbook` 與 worksheet document modules;如果宿主 XML 沒有
+對應 `codeName`,Excel 雖然仍可能列出一般巨集,document object 卻無法正確解析。
+
+這個錯誤特別危險,因為三個現象會疊在一起:
+
+1. VBA 的 `On Error` handler 可能吃掉 runtime error 429,讓使用者只看到 no-op。
+2. Mac Excel 的 AppleScript `run VB macro` 對無法解析或不存在的巨集可能回傳成功,
+ 所以 process exit status 不能證明巨集執行過。
+3. 若測試資料原本就等於預期輸出,「結果正確」也無法區分成功與完全沒執行。
+
+因此 `inject_vba` 的完成條件不能只有「Excel 可開啟」、「巨集出現在清單」或「命令
+沒有報錯」。宿主綁定必須先由離線結構檢查證明,功能驗收還必須觀察狀態改變。
+
+## 4. `vbaProject.bin` 的來源與供應鏈
+
+`vbaProject.bin` 是含 OLE streams、壓縮來源與編譯產物的專有容器。本專案不把它
+視為可由任意文字即時可靠生成的檔案,也不從不受信任的活頁簿抽出後直接重用。
+
+建議的受控產製流程是:
+
+1. 以版本控制中的文字模組作 source of truth;標準 module 使用 `.bas`,需要時另存
+ `.cls`/`.frm` 與資源。
+2. 文字來源採可預測的 ASCII policy;非 ASCII 顯示文字用 `ChrW` 等明確組合,避免
+ VBE legacy code page 把多位元組尾碼與引號誤判在一起。
+3. 由明確版本的真實 Excel 將來源匯入受控 carrier 活頁簿,編譯後另存 `.xlsm`。
+4. 從 carrier 抽出 `xl/vbaProject.bin`,同時產生 carrier module inventory。
+5. 將文字來源版本、Excel 版本、opaque carrier asset ID、carrier digest、binary
+ digest、module inventory 與產製日期一起保存;binary 是 build artifact,不取代
+ 文字來源。
+
+來源模組與 carrier 進入資產庫前要做 secrets、個人資料、硬編碼路徑與不必要外部
+連線的掃描及人工審查。binary 與可解出的 VBA streams 都視為敏感 build artifact,
+使用最小權限存放;一般執行日誌只能記錄 asset ID、版本與 digest,不得複製 VBA
+source、binary、工作表內容或本機來源路徑。
+
+任何 VBA 邏輯或 document-module 組成變更,都必須重走產製與驗證流程。已簽章的
+VBA project、含密碼或不明保護的 carrier 不屬於第一版支援範圍。
+
+## 5. 注入演算法的交易邊界
+
+### 5.1 Preflight
+
+在寫入任何資料前:
+
+1. 將 caller 明確授權的來源與目的 URL canonicalize;以 no-follow 語意逐段檢查父目錄
+ 及 leaf identity,拒絕 symlink/alias 跳轉與授權範圍外的目的地。開啟並持有來源、
+ staging 與目的 parent directory file descriptors,後續 I/O 只用 descriptor + relative
+ leaf 錨定,不能在 commit 時重新解析整條 pathname。計算來源活頁簿與 VBA asset
+ digest,核對 caller 提供的 expected digest。
+2. 驗證副檔名、OPC root parts、workbook main part 與 relationships 存在且唯一,並
+ 確認上一節的 canonical layout、一般 `.xlsx` content type 與無既存 VBA 狀態。
+3. 拒絕 traversal、absolute/non-canonical path、duplicate entry、symlink entry,
+ 以及超過 entry 數量、解壓總量、壓縮比、XML 深度或節點數上限的 package。
+ 每個 internal relationship Target 都要相對 owning part 做 URI 解析與 canonical
+ resolution,且結果必須仍在 package 內;不得以 ZIP root 為所有 Target 的共同基準。
+ 既有 external relationship 只可作 opaque metadata 保留,offline validator 永不對
+ `file:`、網路或其他 external Target 解參照;新增 external relationship 一律拒絕,
+ 要交給活 Excel 前則依第 8 節另做 deny/consent gate。
+4. XML parser 停用 DTD 與 external entities;未知 parts 保留但不執行。
+5. 拒絕已加密、密碼保護、數位簽章或帶未知保護語意的輸入。
+6. 驗證 carrier provenance、binary hash、carrier module inventory、target binding
+ 的完整雙射,以及允許的 macro 清單。
+7. 第一版只接受 **distinct new destination**:來源與目的經 canonical/no-follow
+ identity 判定後必須不同,目的在 preflight 必須不存在。任何 existing destination、
+ `overwrite = true` 或 source = destination 都要拒絕;支援既有目的檔的 conditional
+ replace 必須另案設計。
+
+### 5.2 Staging
+
+1. 透過目的 parent dirfd 以 `mkdirat` + 隨機名稱原子建立同檔案系統的**私有 staging
+ directory**;owner 必須是目前使用者、mode 為 `0700`,且不得有 ACL、group/other
+ write 或其他 writer。以 `O_DIRECTORY | O_NOFOLLOW` 開啟並持有 staging dirfd。
+ 候選檔再以 `openat(O_CREAT | O_EXCL | O_NOFOLLOW, 0600)` 建立,從不重用既有 leaf,
+ 也不把 staging 路徑暴露給 log、callback 或其他程序。
+2. 逐 entry 讀取來源;只有 allowed touched parts 進入專用 XML patcher,其餘 payload
+ 原樣複製。
+3. 加入 `xl/vbaProject.bin`;before/after bytes 只存在受限的暫存記憶體或 staging
+ 驗證範圍,工作完成即清除。持久日誌只記 part name、mutation kind、before/after
+ digest 與 semantic assertion,不得記錄 raw XML、cell value 或 VBA bytes。
+4. 完成 ZIP 後 fsync 持有的 staging file descriptor;透過 staging dirfd + relative
+ leaf 重新開啟,跑結構與 digest 驗證。commit 前再次以 `fstat`/`fstatat` 核對持有
+ file descriptor 與 leaf 的 device、inode、type、owner、mode、link count、size、
+ digest 完全相同,也重驗私有 staging directory identity/權限;不符就停止。
+5. 同時透過持有的 descriptors 重新檢查來源 identity/digest 與目的 parent identity。
+ 最後提交不可使用「先檢查 absent、再一般 rename」;必須以 relative leaf
+ 呼叫 macOS `renameatx_np(fromDirFD, ..., toDirFD, ...,
+ RENAME_EXCL | RENAME_NOFOLLOW_ANY)`,或語意等價、descriptor-anchored 的單一 atomic
+ no-replace primitive。不支援這種 primitive 就 fail-loud。若 race 中目的 leaf 出現,
+ commit 必須失敗並保留外部 bytes;失敗處理只清除 staging,不得用 backup 回滾或
+ 覆蓋目的檔。若原 pathname 的 ancestor 被換成 symlink,descriptor-anchored commit
+ 仍可安全寫入原先已授權的 directory object,但絕不可寫入 redirect target;回傳值
+ 要包含透過 `toDirFD` 重新開啟驗證的 output identity/handle,原 pathname 只作提示,
+ 不宣稱仍指向產物。成功後透過 `toDirFD` 以 `O_NOFOLLOW` 重開輸出,確認 inode 與
+ 已驗證候選相同並重算 digest,再透過持有的 directory descriptor 同步 metadata。
+
+staging 驗證失敗不得改到來源或目的檔。第一版一律輸出到新的 `.xlsm` URL,不提供
+覆寫模式。
+
+上述保證的威脅模型是互動式單一使用者,且私有 staging directory 在交易期間只有
+本程序能修改。能以同一使用者權限無視 `0700`、注入程序或修改其記憶體的惡意程序
+不在第一版邊界內;若 staging identity/post-commit digest 仍不一致,必須回報完整性
+事件、不得把產物交給 Excel,也不得自動用任何 backup 覆寫現場證據。
+
+## 6. Byte-level 保真驗收
+
+對來源 snapshot 記錄 `entry name → SHA-256(uncompressed payload)`。注入後的契約如下:
+
+### 6.1 Entry 集合
+
+在不支援既有 VBA project 的第一版:
+
+```text
+output entries = input entries ∪ {xl/vbaProject.bin}
+```
+
+不應多出暫存檔、重複 entry 或未宣告 part。
+
+### 6.2 Allowed touched parts
+
+固定可改:
+
+- `[Content_Types].xml`
+- `xl/_rels/workbook.xml.rels`
+- 新增的 `xl/vbaProject.bin`
+
+只有 target binding 要求且來源缺少相同綁定時,才可再改:
+
+- `xl/workbook.xml`
+- target binding 列出的 `xl/worksheets/sheetN.xml`
+
+所有其他 entry 的 payload bytes 與 digest 必須逐筆相同。
+
+### 6.3 被修改 XML 的可逆性
+
+每個 XML patch 都要有交易內的 typed patch ledger。每筆 mutation 記錄 part、唯一
+typed locator、原始 byte range、expected-before digest/bytes、after digest/bytes 與
+patch order;後續 patch 的定位以當下版本重新驗證,任何 ambiguity 或 expected-before
+mismatch 都要停止,不能猜測。raw bytes 只存在交易內受限暫存,不進一般日誌。
+
+驗收先確認輸出語意,再依 ledger 反向順序只撤回列出的變更;撤回後的 XML bytes 必須
+與來源完全相等。這能抓到 formatter 順手改了空白、attribute order、namespace、XML
+declaration 或其他無關節點的問題。
+
+只比較 parse tree 不夠,因為它看不出非必要的 byte drift;只比較 bytes 也不夠,
+因為它不能證明 relationship、content type 與 `codeName` 語意正確。兩種檢查都要做。
+
+### 6.4 Binary 與結構
+
+- 輸出的 `xl/vbaProject.bin` bytes 必須與已核准 asset 完全相同。
+- package 重新開啟後必須只有一個 VBA part 與一條對應 internal relationship。
+- workbook main content type、binary content type、relationship target 與所有 target
+ binding 都必須精確吻合。
+- offline 驗證完成後才可把副本交給 Excel 作功能驗收;Excel 另存後的 package 不再
+ 使用上述 byte-preserving 主張。
+
+## 7. 巨集真的有執行:狀態改變驗收
+
+功能驗收必須使用受控 fixture、受控 macro allow-list 與真實 Excel for Mac:
+
+1. **第一次交給 Excel 前**,重新核對受信任 carrier module inventory 的 procedure
+ 清單與 prohibited-entrypoint scan attestation,確認其 source/binary digest 仍吻合,
+ 且沒有 `Auto_Open`、`Workbook_Open` 或其他 auto/event entrypoint;external content
+ policy 也必須通過。以事件巨集停用、外部更新拒絕的方式開啟;無法保證時就不執行。
+2. 使用隔離的受控 Excel session,不同時開啟可能暴露同名 macro 的其他 workbook 或
+ add-in;驗證目標活頁簿 identity、檔案 digest、巨集清單、VBE 非 break mode,且
+ 沒有 modal dialog。先切為 manual calculation、停用 refresh;無法控制這兩者時就
+ 不執行該驗收。
+3. 受控 fixture 的 allow-listed macro 必須另外更新專用 execution witness(caller
+ nonce 或單調 counter);witness 不能由公式、recalculation、refresh 或一般輸入寫入。
+4. 讀取挑戰輸入、輸出、checksum 與 witness,確認測試前置條件成立。
+5. 將至少一個輸入改成會讓獨立 oracle 產生不同輸出的值;呼叫巨集前立即重讀,斷言
+ 輸出與 witness 都尚未改變,排除 automatic calculation 或 data refresh 的假陽性。
+6. 以 Excel bridge 驗證過的 **workbook/project-qualified + module-qualified** 名稱執行
+ allow-list 內的巨集;執行前後都重驗 target identity。設定 timeout,任何 dialog、
+ 作用中活頁簿漂移或失去目標 workbook 都要 fail-loud。
+7. 重新讀取輸出,斷言結果與 witness **同時**按本輪 nonce/counter 改變,並逐格與
+ 獨立實作的預期結果比對。
+8. 還原輸入後再執行一次,要求新的 witness,再斷言輸出也復原。
+
+以下都不是充分證據:AppleScript exit 0、巨集名稱可列出、Excel 沒跳錯誤、輸出
+剛好等於測試前的預填值。
+
+#138 記錄的歷史案例曾在 42,112 格中觀察到 310 格隨挑戰輸入改變,並與獨立計算
+結果一致。這是失敗模式的實戰證據;在對應 fixture、oracle 與命令正式進入 repo 前,
+不能把該數字當成可重現的自動驗收結果。
+
+## 8. 巨集與應用程式安全
+
+- **注入與執行分離**:離線 `inject_vba` 不啟動 Excel、不執行事件巨集。執行巨集是
+ 另一個需要使用者明示同意的操作。
+- **三重身分核對**:執行前核對 workbook identity、當下檔案 digest 與 macro
+ allow-list;任一項不同就停止。
+- **不降低安全設定**:不得自動修改 Excel Trust Center、macOS TCC、VBE trust 或
+ 系統權限,也不得把安全提示當成要繞過的錯誤。
+- **外部內容**:external links、DDE、OLE/ActiveX、XLM macro、事件巨集與網路存取
+ 預設拒絕;若未來要支援,必須有獨立威脅模型與逐次同意。
+- **輸出語意**:離線手術產生結構標準的 `.xlsm`,不代表檔案可信。Excel 開啟時仍
+ 應套用原本的 Protected View、macro policy 與平台安全機制。
+
+## 9. 平台與家族邊界
+
+- 離線 OPC patcher 的設計可使用 ZIPFoundation 與 Foundation XML,不依賴 Python、
+ LibreOffice 或 Excel runtime;第一個產品實作仍由 #135 定義與驗收。
+- 活 Excel 驗收只承諾**互動式、單一使用者的 macOS 工作階段 + Excel for Mac**。
+ 它不是無頭、多租戶或遠端服務的安全邊界。
+- `.docm` 與 `.pptm` 也屬 OPC,能借用「窄幅 patch + 未觸碰 payload byte equality」
+ 的方法;但它們的 main part、content type、relationships 與 document-module 綁定
+ 不同,沒有各自 fixture 與驗收前不得宣稱可直接平移。
+
+## 10. #135 實作交接清單
+
+`che-excel-mcp` 的 `inject_vba` 在進入 verify 前至少要有:
+
+- [ ] typed request:經使用者授權且 identity 不同的來源/新目的 URL、expected input
+ identity/digest、`expectedDestination = absent`、asset ID/digest、carrier module
+ inventory、target binding;
+- [ ] hostile OPC preflight 與明確的 typed errors;
+- [ ] canonical Excel layout、一般 `.xlsx` content type、無既有 VBA 與只支援
+ workbook/worksheet document modules 的 fail-loud gate;
+- [ ] owning-part-relative relationship confinement,以及 external Target 永不解參照;
+- [ ] fixed delta 與 conditional binding delta 的窄幅 XML patcher;
+- [ ] entry-set、untouched payload、reverse-patch、binary 與結構驗收;
+- [ ] same-filesystem staging、fsync、fresh reopen、source generation check 與 atomic
+ no-replace;pre-rename race 建立目的檔時必須失敗並保留外部 bytes;
+- [ ] 私有 `0700` staging directory、`O_EXCL | O_NOFOLLOW` candidate、pre-rename
+ fd/leaf inode+digest equality 與 post-commit inode+digest equality;同名 leaf 取代
+ 的 deterministic hook 必須在最後 identity gate 被拒絕;
+- [ ] pre-rename 把 pathname ancestor 換成 symlink 時,產物仍只落在原授權 directory
+ object,redirect target bytes 不變,且回傳的是由持有 dirfd 重開驗證的 identity;
+- [ ] raw workbook/VBA bytes 不進日誌、telemetry 或一般 artifact 的隱私測試;
+- [ ] 受控 fixture 的 macro-alive 狀態改變測試;
+- [ ] 另一 workbook 暴露同名 module/macro 時仍不會誤呼叫的 integration regression;
+- [ ] 真 Excel integration gate 與缺少 Excel 時的明確 skip/fail policy;
+- [ ] 文件化的平台、巨集授權與不支援封裝邊界。
+
+## 非目標
+
+- 不把 `vbaProject.bin` 當成文字來源,也不宣稱任意 VBA project 可由純 Swift 生成。
+- 不以 Excel 另存取代 byte-preserving OPC 手術。
+- 不以注入成功推論巨集可信或已執行。
+- 不在本文件新增 `macdoc convert` 的 xlsx 路徑。