---
title: FR-069 模組化抽取 — 全案收官 SUMMARY
brand: Guidant AI · **FR-069**
eyebrow: FR-069 模組化抽取 · 全案收官 · 2026-09-01
h1: 四階段、26 支套件、一個月——FR-069 全案收官報告
lede: 本檔是 FR-069 整個 task arc 的收口彙整，**逐 block 濃縮自 [`fr069-LOG.md`](fr069-LOG.md)**（append-only 歷程檔）而非憑記憶重建。內容含四階段帳、26 支套件終態、18 條教訓精選、未結 follow-up 與部署 handover。
chips: [{text: 五階段全收, kind: ok}, {text: 現役 25 支套件, kind: accent}, {text: 待 user 令收母卡, kind: warn}]
---

# FR-069 模組化抽取 — 全案收官 SUMMARY

- **期間**：2026-08-29 ～ 2026-09-01（四天）
- **母卡**：CM-1435（**本檔產出時尚未收 Done**——收母卡屬結論宣告，等 user 令）
- **範圍**：BE 主專案 ＋ jedi monorepo ＋（少量）FE
- **材料來源**：`fr069-LOG.md` 七個 block 逐一濃縮；數字全部實跑指令核對，不沿用文中舊值

---

## 0. 一句話

把後端 40 個模組裡「別的產品也用得到」的能力，一支支抽成可獨立安裝的 jedi-* 套件——
**現役 25 支、退役 10 支、對外契約零漂移、主專案淨刪約 16,500 行**。

---

## 1. 帳面總表

| 項目 | 數字 | 怎麼得到的 |
|---|---|---|
| BE commits | **155**（`fe56ab6a..HEAD`） | `git log --oneline \| wc -l` |
| BE 檔案變動 | **1,456 檔、+57,776 / −74,308**（淨刪 ~16,500 行） | `git diff --shortstat` |
| monorepo commits | **64**（`bc0f2b86..HEAD`） | 同上 |
| monorepo 檔案變動 | **2,137 檔** | 同上 |
| 現役套件 | **25 支** | 磁碟目錄數 ＝ `pyproject.toml` pin 數（差集為空） |
| 完全體插件（自帶 route） | **16 支** | 逐支數 `api.add_resource` 活行 |
| 退役刪除 | **10 支／塊** | 見 §3 |
| 本 arc 發版 | **18 支**（CM-1473 十一支＋CM-1499 七支） | 對照 `release-plan-p4.md` |
| Notion 卡 | **CM-1435 母卡＋約 65 張子卡** | CM-1436～CM-1502 連號區間 |

**對外契約凍結面（D5）**：231 個 error code 值／424 條 URL／藍圖名，
arc-review 機器 diff 逐字比對**零漂移**。

---

## 2. 四階段做了什麼（＋2.5＋發版＋收官）

### 第一階段（P0–P8，8 棒）— 抽五支新套件＋地基擴充

- **P0** 前置清理，含 D5 追加的 **grc → flow_control 全面改名**（error code 字串值與 API path 刻意不動）。
- **P1** jedi-ai-bot——**插件契約首例**，同時產出 `extraction-sop.md` 七階段 SOP。
- **P2** jedi-integrity（FR-064 防竄改）／**P4** jedi-remote-agent（定 **migration 隨包五規則**）。
- **P3** jedi-log-forwarding（含「迷你接收器真的收到 log」端到端實測）／**P5** jedi-license-runtime
  （驗簽引擎逐行比對零改動，**執法留宿主**）。
- **P7** jedi-common 擴充兩批——全程**先加後刪**，主專案舊位置改 re-export shim，
  **124 處消費端零改動、零檔案被碰**。
- **P8** authz 主體域併入身分套件——拆兩半精準（主體域五檔進套件／資源域四檔＋license 軸留主專案），
  **126 處消費點零改動**；四個守門 error code 進套件但**字串值凍結 `GRC_*`**（FE i18n key，實地驗過 8 筆翻譯靠它）。

> **插曲（值得記）**：首腦把 jedi-common 頂層 7 行的 `base_repository.py` 誤判為「舊代待汰換」發了清理棒，
> 子棒查繼承鏈發現它是被完整版繼承的 session 底座——**按紀律煞車、未改任何檔**。結論改為「不汰換、正名 SessionMixin」。

### 第二階段（.10–.12，3 棒）— 延伸表收斂＋查詢層擴充

- **.10 ext 全庫盤點**：190 張表掃出 9 張延伸表形態。**推翻原假設**——全庫沒有任何
  「純展示型客製欄位」，側掛表是**跨套件邊界長出來的**，不是客製需求。
  JSONB 容器定位因此改為「預防性建設」（D15）。
- **.11 jedi-common 查詢層**：JSONB 查詢前綴（`_ext_`/`_extlike_`/…）＋身分脈絡包＋SessionMixin 正名，全「用加的」。
- **.12 project_extensions 收斂**：四欄收回主表（皆帶業務邏輯，**轉正式欄位而非塞 JSONB**）；
  14 處 raw SQL JOIN 改完；DEV 實查回填 211 列零不一致；**STG 實查未被套**（環境紅線守住）。

### 2.5 階段（.13–.18＋.H＋改名，8 棒）— 身分四套件合併成 jedi-iam

- jedi-auth＋jedi-login＋jedi-mfa＋jedi-captcha **四支合一**，＋主專案身分中介層／登入編排。
- **.16 抓到真提權洞**：`X-Tenant-ID: 0` 觸發 `is_super_admin` 繞過全 RLS（含 socket 整數 0 falsy 變體），皆修。
- **命名定案 jedi-iam**：PM 推翻 `identity`（「只涵蓋你是誰半邊，太狹隘」）；`jedi_identity` **不留轉發殼**直接死名。
- **驗證瘦身規則七條**拍板（實測驗證吃 6 成工時；判準「壞了會不會靜默」）。

### 第三階段（CM-1467–1472，6 棒一天全收）— 老套件升級插件

- **3.1 jedi-iam 完全體**（範本棒）：39 條 route 進套件、主專案 `api/auth/` 26 檔刪除、四殼退役、D16 port 化。
  **三個照抄要點**成為後續 11 支的範本：
  ① route 取 service 走 `ctx().service(name)` 而非 `@inject`（**存 provider 不存實例**——存實例是併發才爆的壞）；
  ② **缺認證接線拒絕掛載**（跳過的後果是身分端點全變公開而健康檢查照樣綠）；
  ③ 切分判準「**換一個產品，這段還會一樣嗎**」。
- 3.2–3.5 補殼十支、3.6 退役四支（monorepo 26→24）。
- **發版裁決反轉**：user 質疑四殼為何要發，重盤後**裁不發改退役**——.18 清單的前提已被 3.1 拔 pin 推翻。

### 第四階段（CM-1475–1483 九棒＋補洞，全收）— 核心能力抽取

- **4.A** D17 資料關聯三律／D18 設定申報制入決策表；**4.B** AI registry 申報制。
- **P12 jedi-participant**（核心環鑰匙）：readmodel 櫃檯建置＋40 組參數 md5 比對＋
  A/B 對打抓到 Containers 類別層真回歸。
- **4.2 flow_control 分家四步**（本階段最重）：設計稿 → CM-1486 退役 4,351 行 →
  CM-1487 搬遷 190 檔 → CM-1488 分家（平台層零稽核詞的機械判準）→ CM-1490 第 3 步
  （D-10 償債／申報制上線／問卷審核開關「設定不是客製」實證）→ CM-1492 打包定名。
- **P9 detection**（147 支，基準庫 5.4MB 隨包）／**P10 evidence-classification**／**P6 ai-dashboard**／
  **P11 問卷合併**。
- **三包定名**：jedi-flow-engine 不動／**jedi-task-platform**／**jedi-compliance-audit**（不佔 audit 泛稱）。
- **平台化方向拍板**：**project ＝插座不是插頭**；flow_control 分家＝**分家是複用的前提**。

### 發版與收官串

- **CM-1473**（第三階段）11 支上 Nexus、四殼刪除 24→20、守衛換軌。
- **CM-1499**（第四階段）7 支上 Nexus、pin 入版收三批託付改動、path override 全清。
  ✅ **拉分支不再需要 `pip install -e`**，`poetry install` 即可。
- **CM-1500** 問卷共編修復（jedi-survey 0.1.1）——見 §5 教訓 ⑥。
- **CM-1497/1498** 套件債清理＋associations 殭屍退役。
- **arc-review**（5 平行 agent）→ **CM-1501 修復棒**（13 條逐條，每條守衛都做突變驗證）
  → **CM-1502 文件收官**（本檔所屬棒）。

---

## 3. 26 支套件終態

**完整終態表在** [`architecture-handbook/modularization-roadmap.md` §套件族譜](../../architecture-handbook/modularization-roadmap.md#genealogy)
（25 支逐支列版號／來源／形態＋10 支退役去向）。此處只放摘要：

| 分類 | 支數 | 內容 |
|---|---|---|
| **本 arc 新抽／合併** | 8 | iam（四合一）／task-platform／compliance-audit／participant／detection／evidence-classification／ai-dashboard／survey（吸收作答層） |
| 第一階段新生 | 5 | ai-bot／integrity／license-runtime／log-forwarding／remote-agent |
| 老套件升級（完全體） | 5 | file-upload／system-menu／log／issue／notification |
| 老套件補殼（半體） | 4 | bulletin／system-config／device／information-system |
| 引擎／領域／地基 | 3 | flow-engine／oscal-v2／common |
| **退役刪除** | **10** | auth/login/mfa/captcha（→iam）／project（→task-platform）／flow-control（→compliance-audit）／identity（改名）／oscal v1（→v2）／department／resource-store／jedi_system_log |

> 「半體」不是沒做完——是第三階段逐支實查後**誠實聲明不搬的理由**
> （route 的 service 實際住在主專案，硬搬會變成套件反向 import）。

**零套件反向 import**：arc-review AST 全掃 25 支（含 lazy／函式層）確認。

---

## 4. 產品本體最後剩什麼

四類，完整版（含 15 條實例路徑）在
[`modularization-roadmap.md` §產品本體](../../architecture-handbook/modularization-roadmap.md#host-four)：

① **報表櫃檯**（`infra/readmodel/`，八支跨疆界純讀聚合）
② **接線盤**（ports + adapters）
③ **組裝根**（`core/app_factory.py`／各 `*_wiring.py`／`config/app_modules.py`）
④ **業務加工**（`app/oscal/`／`app/module_frame/`／`app/cloud_integration/`／`app/project_summary_report/`）

---

## 5. 教訓精選（LOG 共 18 條＋收官 3 條，這裡挑最可複用的）

### 關於「前提」

1. **卡片／裁決的事實斷言，查證責任在開卡人**——本 arc 首腦裁決被 runner 推翻 **2 次**、
   卡片斷言被推翻 **4 次**，全數靠「前提被推翻就停」的煞車紀律救回。
   代表案例：`base_repository` 誤判（憑檔名大小推斷新舊兩代）、
   detection 綁定表歸屬（只憑語意沒跑 grep／`pg_constraint`）、
   CM-1487「226 檔比照 P12 機械搬」（P12 能機械搬因 out-degree 8，flow_control 纏五疆界）。
2. **「比照先例」前必驗前提等價**——類比壓掉的正是前提。
3. **上游線索是待驗假說不是結論**——CM-1485 兩條線索各推翻一半
   （camunda 程式側確實死透，但「只剩 BPMN 解析」被推翻：執行引擎完全活著）。

### 關於「守衛」（arc-review 主題）

4. **綠著的守衛不代表在守**——四類「守衛空轉」：掃舊名（改名後永遠綠）、掃描根縮水、
   雙份碼表無相等斷言、引用不存在的測試檔。
5. **守衛要做突變驗證**——故意違規 → 確認變紅 → 還原。CM-1501 每條修復都做了，
   其中一發突變**在舊守衛下是雙重隱形**。
6. **契約要看實作不看簽名**（CM-1500）——`mount_api=False` 簽名有、行為缺一半，
   於是「不掛路由」與「拿得到 context」在套件裡綁死成二選一，宿主端補不出來。
   runner 照卡片邊界停手回報，是這次沒被繞成「在 socket 模式掛 REST」（會倒退 FR-063.1c）的原因。
7. **契約測試要同時焊「行為成立」與「語意一致」**——只驗「拿得到 ctx()」的話，
   把寫入改成 skip-if-exists 仍會綠。

### 關於「歸因」

8. **套件全量測試的 baseline 要先量再歸因**——用 `git stash` 抽掉改動跑同一份指令對照，
   不先量就會把存量債記在自己頭上（3.2 的 16 ERROR、jedi-survey 的 48 failed 皆是）。
9. **驗收歸因要三證才下結論**（參數來源 commit／本棒未觸該檔／父節點同紅）。
10. **手測報錯先看 log 的 Host 與連的庫**——.12 的「無法顯示專案」實為 `.env` 連錯庫。

### 關於「協作」

11. **平行棒共用 git index 是真事故**——顯式 `git add` 防不了「別人已 staged 的混進來」；
    **commit 前必 `git diff --cached --stat` 核清單**（已入所有後續 prompt）。
12. **harness 只證明「掛得上」不夠，要實打一支端點走通**——3.1 讓 harness 真打 `POST /login`，
    挖出四道宿主前提。其中 `PROPAGATE_EXCEPTIONS` 最陰：flask-restful 的 `Api` 包掉
    `handle_user_exception`，於是 `register_error_handlers` **註冊成功卻完全不生效**，401/403/404 一律變 500。
13. **改名要掃「寫死字串前綴」**——它們壞掉的方式是**靜默匹配到 0 條**，測試變成「掃了個寂寞卻顯示通過」。
14. **改名前先分辨「舊套件名」與「領域概念」**——`JWT_IDENTITY_CLAIM`／`get_jwt_identity()`／
    `Accept-Encoding: identity` 都不能改（改 JWT claim 名會讓所有既有 token 當場失效）。
15. **view 與 SQL 字串是守衛盲區**（兩案實證）——退役／搬遷必查 `pg_views` ＋表名字串，人工列冊。
16. **守衛須 `python -m pytest`**，直呼 `pytest` 會 `ModuleNotFoundError: common` 假紅。

### 關於「揭露」

17. **揭露的缺口比沉默安全**——task-platform pyproject 自記 jedi-iam 債、兩套件 README 聲明
    無 harness 與條件、CM-1490 誠實寫「孤兒定期清理未建置」。arc-review 特別點名這兩處值得記。
18. **驗收打折要當場講明**——本 arc 多棒因 MFA／無 token／AI 費用而無法端到端，
    一律在卡上寫明「驗的是什麼、沒驗的是什麼」＋手測清單，不報成「通過」。

---

## 6. 未結 follow-up

### 已開卡待辦（Notion 狀態 `Not started`）

| 卡 | 內容 |
|---|---|
| **CM-1484** | jedi-participant 六支 ORM 直連身分表改走 `IUserDirectory` port（＋主專案 10 支同病 repo 評估） |
| **CM-1495** | 任務綁定表**孤兒定期清理 job**——CM-1490 D-10 償債的配套，尚未建置（現有防線＝顯式刪＋讀取降級） |
| **CM-1445** | RLS 覆蓋缺口：`user_roles`／`user_tenants`／`user_org_units` 三張授權關聯表未啟用 RLS（獨立安全債，不掛 FR-069） |
| **CM-1461** | 裸路徑死端點追蹤——剩 bulletin／device／system-config 9 支未拔（route 未上移） |
| **CM-1463** | `roles.is_admin` 舊管理員旗標收斂（消費端改道 capability 後拆除） |

### 待 user 裁決

- **jedi-issue GL/GH 整合碼**留／刪（CM-1497 已備 A/B 材料，367 行）。
- **`test_audit_event_instrumentation` 併跑汙染**——CM-1498 查明是既有債，建議開卡。
- **S4 恆偽分支退役**原併 e2e 站，隨 e2e 取消**暫無落點**。
- **jedi-survey I-8「問卷審核開關端到端接通」**——CM-1501 已把契約與失敗可見性準備好，
  但實查發現缺口不只「adapters 沒填」而是**平台側消費端尚未建造**（見 arc-review §⓪ 停下回報段），
  建議另開卡。

### 🔴 待發版帶出（CM-1501 改到已發版套件的內容）

| 套件 | 改了什麼 | 不補發的後果 |
|---|---|---|
| **jedi-detection** | C-1 撞號（碼表＋contract pin） | 已發版 0.0.1 仍帶撞號——**唯一有實際使用者影響**（檢測工具 uid 報錯在 FE 顯示 DOCX 文案） |
| **jedi-compliance-audit** | C-1 鏡像碼表同步 | 同上；`PENDING_REPUBLISH` 例外表目前有一列 |
| jedi-survey | I-8 port 收斂＋接線錯誤 log | 純防禦性，無 live 影響 |
| jedi-task-platform | 守衛＋URL 契約＋註解 | 測試與註解，不影響 runtime |

補發後：① 主專案 bump pin；② 刪 `PENDING_REPUBLISH` 那列（`test_no_stale_pending_entries` 會主動變紅提醒）。

### 綠色記帳（時機未到不開卡）

四張 `hi_*` 歷史表 0 筆留另議／`flow_template` API 欄位併 FE 拿掉／兩套件 harness 待型別償還後補／
存量紅燈一批（8 紅 guard 未接線＋AI dashboard 舊測試 `metadata_builder` 不存在）／
readmodel 按語境分子資料夾（規格已寫，搬檔未做）。

---

## 7. 部署 handover

### 現況

- **本機／DEV**：`poetry install` 即可起 BE（path override 已全清，venv 內無 editable jedi 套件）。
  守衛 **77 綠**（`python -m pytest`）。
- **STG／POC**：**本 arc 一律未動**（環境異動鐵律）。兩台跑的是 installer 出的 docker stack，
  升級走 image，與本分支無關。
- **migration 水位**：CM-1490 的軟參照 migration **只套 DEV**，STG/POC 等放行。

### dev server 驗證（收官串最後一項，未做）

user 2026-09-01 裁：e2e 暫不做，改「dev server 直跑功能驗證」。

- **候選機＝190**（e2e 機）：安裝包 stack（guidant-ai-1.16.0 六容器）已 `docker stop`
  （**資料卷保留可復原**）；兩組 agent 容器**刻意留跑**（檢測派工可能用到）；
  機上**無 BE/FE git clone**（歷來跑 image）；資源充裕（RAM 31G／碟 142G）。
- **部署形狀待裁**：A＝190 上 clone 直跑 dev 模式／B＝本機當 dev server。
- MFA 維持關閉（測試便利；DB 值不入版控，不影響出貨）。

### 未 push / 未 merge

- 兩 repo 的 `feature/FR-069` **分支 push 已委任**（memory `feedback_fr069_push_delegated_branch_only`）。
- 🔴 **合併回 main 仍是 user 本人**；arc 收口後委任失效。

---

## 8. 本檔的邊界

本 SUMMARY 屬**材料性**收官（CM-1502 第一段）。下列**結論性**動作**尚未做，等 user 令**：

- SPEC 頁面更新（本 arc 對使用者可見的行為變更幾乎為零，預判只有檢測錯誤文案一條）
- 使用手冊（預判免——純內部重構）
- **母卡 CM-1435 收 `Done`**
- memory `feedback_*` 條目落檔

