# FR-069 主專案功能模組化——jedi-* 套件抽取計畫 — 設計文件

> 狀態：第一階段執行中（P0 進行中）｜建立日期：2026-08-29
> 討論脈絡：本案無獨立討論稿——盤點（三棒平行探勘）與拍板均在對話中完成，決策脈絡完整收錄於本文件 §3 決策定案表。

## 變更紀錄

| 日期 | 變更 | 對應 |
|------|------|------|
| 2026-08-31 | **D17／D18 拍板入決策表（第四階段地基棒 CM-1475）**——D17 資料關聯三律（想建外鍵＝該合併疆界／真跨疆界＝軟參照存 id 不建約束／流程耦合＝事件；外鍵塞不進 port，故疆界切分在資料層另立判準）、D18 設定 schema 申報制（schema 進套件、值與存放留宿主、模組開關表永遠留宿主；含作用域必填／預設活在 code DB 只存差異／fallback 鏈由套件申報三補充）。§7 第四階段節改寫為定序棒次表（4.A→4.B→P12→flow_control→P13→P9／P10→P11→P6，每棒附排序理由）；架構手冊新增 `package-taxonomy.md` 套件分類地圖（十族） | 母案 / CM-1475 |
| 2026-08-30 | **改名棒 CM-1464 執行完畢**——monorepo `jedi-identity/` → `jedi-iam/`（import 名 `jedi_iam`）、三支轉發殼轉發目標同步、主專案 96 處 import 改名；`jedi_identity` **不留轉發殼**（未上 Nexus、無外部消費者），守衛測試加擋舊名復活；§3 D9 與 §7 2.5 節同步改名 | CM-1464 |
| 2026-08-30 | flow_control 拆出由終局選項升格為第四階段議程（user 指示；與 P13 同席設計，D12 單向依賴耦合）；終局選項縮為 project 平台化一項 | 母案 |
| 2026-08-30 | D9 命名修訂：PM 拍板 jedi-identity→jedi-iam（CM-1464 執行）；D12 前置查證完成（CM-1462，改寫方向待裁）；§8 圖表同步改名 | 母案 / CM-1462 / CM-1464 |
| 2026-08-30 | 新增 §8「套件階層依賴圖」（living）——三層架構圖＋port 接線圖＋逐套件狀態表（維護主體）；原端到端驗收改列 §9。此節隨每次拍板同步更新 | 母案 |
| 2026-08-30 | D13 追加：jedi-department／jedi-resource-store 即刻退役執行（user 拍板、首腦四面查證零使用）——主專案拔 pin 清殘行、monorepo 兩包標 deprecated；§8.3 兩列改「已退役」 | 母案 / CM-1435 |
| 2026-08-29 | 初版定案：P0–P8 第一階段收案（P6 ai-dashboard 降觀望）、P9–P13 凍結進路線圖、authz 拆兩半併 jedi-auth、抽取配方沿用 jedi-information-system 範本 | 母案 |
| 2026-08-29 | D5 拍板——grc 模組改名 flow_control，納入 P0 範圍（P0-⑤） | 母案 / CM-1436 |
| 2026-08-29 | P0 實作已由平行 session 開工（working tree 現況：uploadfile 合併＋error code 歸位進行中）。P0-⑤ 改名（D5）為後拍板追加，該棒需讀 CM-1436 卡的範圍追加補做 | CM-1436 |
| 2026-08-29 | D6 拍板——目標升級為「插件（plugin）模式」：套件自帶 api 層 / DI container / migration，隨插即用；驗收核心＝拔掉測試。P1 負責定義插件契約首例，P2–P5 照抄 | 母案 / CM-1437 |
| 2026-08-29 | D7 拍板——複用保障三件套：standalone harness＋接入 README 為每支套件標配（P1 首做入 SOP）；插件 vs 服務化形態分流準則入凍結路線圖 | 母案 / CM-1437 |
| 2026-08-30 | D8 拍板——全路線解凍（廢除「有第二個消費者才抽」原則，P6＋P9–P13 全解凍）＋新增第二階段（ext 表收斂＋JSONB 擴充容器）與第三階段（既有 20 支老套件升級插件模式）＋插件契約補「身分脈絡標準件」（user/tenant/org_unit 名冊 port 一族）。§7 由「凍結路線圖」改寫為「第二～四階段與終局選項」 | 母案 / CM-1435 |
| 2026-08-30 | D9 拍板——身分族合併為 **jedi-identity**（auth＋login＋mfa＋captcha 四合一＋主專案身分性質中介層收進套件），插隊為**第 2.5 階段**（第二階段表收斂後、第三階段補殼前）；notification 維持獨立、agent 機器身分不併；順手清理四項（jedi-department 下架等）。另立 **RLS 覆蓋缺口獨立 case**（user_roles/user_tenants/user_org_units 三表無 RLS），不混入本案 | 母案 / CM-1435 |
| 2026-08-30 | 立**疆界盤點案**（FR-069.9）——第四階段開工前置：照 jedi-identity 案的方法對全套件庫（26 支＋主專案候選模組）做疆界地圖，定出合併/獨立補殼/退役/下架的完整去向，供拍板後改寫第三、四階段棒次表。與第二階段平行（純分析不動碼）。順手：D9 前置條件改「已確認」（user 確認 jedi-* 僅 Guidant AI 消費） | 母案 / CM-1435 |
| 2026-08-30 | **D10–D16 批次拍板**——疆界地圖（boundary-map.md）與 ext 盤點（phase2-ext-table-inventory.md）裁決落地：問卷疆界合併成立（P11 改寫）、device 不併檢測（P9 改寫）、OSCAL 三角第三邊改 flow_control（P13 開工前先查證）、三支退役先標 deprecated、bulletin 維持獨立套件、CM-1449 首批改打 project_extensions、基礎依賴 port 化通則入插件契約。⚠️ 編號註記：boundary-map §6 自用的 D15（jedi-project 凍結）/ D16（jedi-issue 死碼去留）**尚未拍板**，本表 D15/D16 依決策表 canonical 重新編定，地圖那兩項改列「待另議」 | 母案 / CM-1446 / CM-1447 |

---

## 1. 需求背景與目標

主專案（compliance-manager-be）歷經多輪功能開發，累積約 **40 個註冊模組、1500+ 個 py 檔**，多數功能未模組化——所有能力都長在主專案內，未來其他產品要複用相同功能（遠端 agent、授權驗證、防篡改、log 轉發…）時無法直接轉移套用。本案目標：**把可通用的功能抽成 jedi-* 外部套件**，放進既有 monorepo（`~/Projects/Jedicogy/module/jedi-python-package/`，已有 20 個套件），未來新產品以 poetry 依賴直接安裝複用。

### 盤點關鍵事實（2026-08-29 三棒平行探勘結論）

- **核心不可分割環**：`participant ↔ flow_engine ↔ grc ↔ associations ↔ oscal ↔ module_frame` 六模組互相雙向引用、直接穿透 ORM model，合計約 **760 檔**（超過全專案一半）——這一團是產品本體，第一階段不切片。
- **外圍有一圈乾淨的葉節點**：約 8 個模組零跨模組依賴、只吃 `common/` + `jedi_common`，是低風險抽取候選。
- **`common/` 不能整包搬**：需三分——真橫切的進 jedi-common、授權語意的進 jedi-auth、業務語意的歸位回各模組。且 `common/` 現有 **9 條反向 import 上層**（違反分層方向），是搬遷前的硬前置。
- **抽取先例已存在**：jedi-information-system（FR-032 G）是從主專案抽出的成功範本——import 名不變、`IUserLookup` 窄介面解耦、套件不含 api 層、守衛測試防回歸。本案全程沿用該配方。
- **依賴方向的關鍵洞見**：模組能不能抽，看的是它「往外依賴多少」（out-degree），不是「被多少人依賴」（in-degree）——import 名不變，消費端不用改任何一行。此洞見決定了凍結路線圖中 participant 的定位（最大 hub 卻最先能抽）。

---

## 2. 分工概述（30 秒版）

整案一句話：**先清理地基（P0），再把六支零耦合的基礎設施模組抽成新 jedi 套件（P1–P5），最後把 common/ 的橫切能力上移進 jedi-common 與 jedi-auth（P7–P8）；核心業務模組的抽取凍結成路線圖，等這一輪做完拿實際成本數據再議。**

| 階段 | 做什麼 | 產出 | 完成怎麼判定（決策者檢查法） |
|------|--------|------|------------------------------|
| P0 前置清理 | 合併重複的上傳模組、斷 common/ 的 9 條反向引用、錯放的錯誤碼歸位 | 乾淨的主專案地基（不產新套件） | 請 Claude 跑 `grep -rE "from (domain|infra|di_containers)" common/` 應為 0 筆；BE 重啟後手測檔案上傳功能正常 |
| P1 jedi-ai-bot | 把 AI 聊天機器人抽成套件（最小、練手走通整條流程） | 新套件上 Nexus + 主專案 pin | `poetry show jedi-ai-bot` 看得到版號；系統內 AI 聊天功能手測正常 |
| P2 jedi-integrity | 防篡改偵測（FR-064 成果）抽成套件 | 新套件 + pin | 同上檢查法；落地版啟動防篡改 gate 手測正常 |
| P3 jedi-log-forwarding | Log 轉發（FR-068 成果）抽成套件 | 新套件 + pin | 同上；UI 設定轉發到測試 syslog 收得到 log |
| P4 jedi-remote-agent | 遠端 agent 註冊/心跳/派工（mTLS+JWT）抽成套件 | 新套件 + pin | 同上；agent 心跳與檢測派工手測正常 |
| P5 jedi-license-runtime | 授權驗證/機器指紋/到期狀態機抽成套件 | 新套件 + pin | 同上；換一顆 license 檔啟用流程手測正常 |
| P7 jedi-common 擴充 | 全專案最熱的通用工具（回應包裝等）上移進 jedi-common | jedi-common 新版 | `poetry show jedi-common` 版號有進；請 Claude 跑既有測試全綠 + 抽測三個常用頁面 API 回應格式不變 |
| P8 authz 併 jedi-auth | 授權守門「主體域」半包併入 jedi-auth | jedi-auth 新版 | 同上；用 admin / 一般帳號各登一次，權限行為與改前一致 |

依賴一句：**P0 是 P7/P8 的硬前置；P1–P5 彼此獨立可平行派工；每支套件抽完都要等發版上 Nexus 才能 commit 主專案的目錄移除**。決策者功課：每階段收口時做上表「檢查法」欄的手測。

---

## 3. 決策定案（D1–D18）

| # | 決策 | 定案 |
|---|------|------|
| **D1** | 第一階段範圍 | **收 P0–P5 + P7–P8 共 8 個 arc**。P6 jedi-ai-dashboard **降到觀望梯隊**——AI 儀表板生成引擎概念上通用，但資料源 registry 深綁本產品 data API，port 化成本不小，且目前無第二個產品確定要此能力；按「沒消費者不抽」原則不過線。P5 jedi-license-runtime 收案——License Center 已是獨立系統、落地版產品線是既定方向，消費者可預期，複用價值最明確。 |
| **D2** | 核心模組（P9–P13）處置 | **凍結進路線圖（§7），不排時程**。理由：①抽取收益只在「有第二個消費者」時兌現，目前無具體產品在等；②P0–P8 做完會拿到「抽一支套件實際花幾棒、踩什麼坑」的可信成本數據，屆時評估 5–10 倍工程量的核心抽取才有依據；③凍結不是丟掉——P0 清理與守衛測試會讓核心環邊界持續變乾淨，前置成本已被順手支付一部分。**例外：若近期出現要用檢測能力的新產品，P9（jedi-detection）可單獨解凍提前。** |
| **D3** | authz 去向 | **拆兩半**：主體域（`platform.py` / `admin.py` / `capability.py` / `decorators.py` / `signed_token.py`——只看「你是誰」）**併入 jedi-auth**；資源域（`project.py` / `ssp.py` / `workflow.py` / `menu_license_filter.py`——要先 resolve 資源才知道判誰，帶產品語意）**留主專案**。被排除方案＝獨立 jedi-authz：會立刻新增一條 jedi-auth 依賴邊（monorepo 第 5 條非 common 邊）、強迫消費者永遠裝兩包對版本，且不存在「只要 authz 不要 auth」的消費場景。附帶收益：9 條反向 import 中指向 `domain.oscal` / `domain.participant` 的 5 條全在資源域那半，拆半後 P8 的搬遷面自動避開它們。 |
| **D4** | 抽取標準配方 | **全程沿用 jedi-information-system（FR-032 G）範本**：①dist 名連字號、import 名底線，**消費端 import 句一字不改**；②跨套件耦合一律 dependency inversion——套件內定義窄 ABC port（如 `IUserLookup` / `INotifier`），主專案寫 adapter + DI 注入；③套件只到 app service 層——**route、DI container、DB migration 留主專案**（套件只 ship ORM model）；④**extract → publish → pin 是綁定順序**——主專案「移除目錄 + pin」的 commit 必須等套件推上 Nexus 之後；⑤每支套件寫一支守衛測試（grep 套件源碼 0 處主專案 import），比照 `test/test_fr032g_information_system_decouple.py`。 |
| **D5** | grc 模組改名 | **`grc` 改名 `flow_control`（流程控管），納入 P0 範圍（P0-⑤）**。改的範圍＝python 目錄與類名：`app/grc/`→`app/flow_control/`、`domain/grc/`、`infra/grc/`、`api/grc/` 同步，DI 註冊（containers.py / app_modules.py）跟改，`GrcErrorCode`→`FlowControlErrorCode` 等類名跟改。**不改**＝error code 字串值（`GRC_404001` 等——wire 契約，FE `error-code.json` i18n 靠字串比對）、對外 API URL path、DB 內容。理由：GRC（治理/風險/法遵）名大於實——模組實際是「流程節點掛業務（頁面/功能/狀態）」的流程控管層，稽核只是第一個掛進來的業務，未來問卷填寫審核等流程都會套此模式；與 `flow_engine` 成對命名（引擎推流程、控管掛業務）；業界對應概念為 case management / process orchestration 的應用層。被排除方案：`audit`（綁死稽核語意，與通用化願景衝突）、`engagement`（稽核/顧問業行話，對通用流程不直覺）、`process_control`（撞工業控制語感）、`case_management`（case 一詞內部已被 Notion 任務卡佔用）。 |
| **D6** | 插件契約（plugin contract） | **抽取目標從 library 升級為 plugin——隨插即用**。每支新套件自帶完整垂直切片：①api 層（Flask blueprint / resources）②預設 DI container ③migration 腳本（隨包 package data，安裝時套用）④標準註冊入口（`register(app, adapters)` 形式，**具體簽名由 P1 定案**；簽名需預留四類插槽——`config`（參數值差異）/ `adapters`（行為差異）/ `schema_extensions`（欄位差異）/ `mount_api`（端點卸載逃生門），詳細指引見 CM-1437 卡「API 差異化四道防線」段；此為 D6 實作細節，不另立決策）。主專案接觸面收斂為：`config/app_modules.py` 一行註冊 + port adapter 實作。**驗收核心＝拔掉測試**：註解掉註冊行後 BE 正常啟動、該模組選單/路由消失、其餘功能不受影響。**邊界誠實聲明**：做到 install-time pluggable（安裝時可插拔），非 runtime 熱插拔——DB schema / RLS 不可能執行中掛載，對齊業界（GitLab / Odoo 模組）等級。**與現行慣例的關係**：刻意偏離既有 jedi-* 慣例（套件無 api 層、route 留主專案，即 D4-③）——新契約自 P1 起適用於 FR-069 新抽套件，**既有 20 支套件不回頭改**；D4 其餘各項（import 名不變、port 解耦、extract→publish→pin、守衛測試）照舊適用。**地基盤點**：app_modules.py 本就是模組註冊表（fail-fast 載入）、選單走 DB ui_routes、license gating 機制已在——半成品地基可承接。 |
| **D7** | 複用保障三件套 | 防「開發完一包就沒了」的三條標配。**① standalone harness（每支新套件標配）**：套件自帶最小獨立宿主——`dev_app.py`（最小 Flask app）＋ docker-compose（起獨立 DB）＋套件內測試可脫離主專案獨立跑，並由 CI 獨立執行。動機：「模組能離開宿主活著」是業界判斷真解耦的黃金標準——harness 跑得起來就是零隱藏耦合的鐵證（比 grep 守衛強一級）；且 CI 天天跑讓套件有自己的生命跡象，主產品改動導致套件壞掉會當天被抓到，而不是等下個產品要用時才發現屍體。**② 接入 README（每支新套件標配）**：「新產品如何接入」文件——要實作的 port 清單、註冊行寫法、migration 套法、10 分鐘 quickstart。動機：第二個產品團隊評估「讀懂套件 vs 自己重寫」時，沒這份必選重寫（業界 library 複用最常見死因）。**③ 形態分流準則（入凍結路線圖 §7，非本階段執行項）**：P9 之後每支候選啟動前先判——有獨立生命週期的能力（如未來檢測平台）評估走**服務化**（License Center 模式：獨立部署、網路 API、自有 DB，邊界是網路協定想耦合都耦合不了）；與宿主同進退的能力走**插件**（D6 契約）。理由背景：業界 rule of three——沒有兩三個真實消費者前，抽出的東西只是搬了位置的產品程式碼；①②就是在消費者出現前維持套件可信度的機制。 |
| **D8** | 全案解凍與階段重整（2026-08-30） | **廢除「有第二個消費者才抽」原則**——動機重新定調為「把歷史發散、耦合過高的能力一次整併成可抽換式，安裝即有 API 可用」，此目標本身即成立，不等客戶。P6（AI 儀表板）與 P9–P13 **全部解凍**。全案階段結構重整（詳見 §7）：**第一階段**＝P0–P8（原範圍，收尾中）；**第二階段**＝ext 表收斂＋JSONB 擴充容器（純展示型客製欄位收回主表＋容器、帶業務邏輯欄位轉正式欄位、auth/user 優先、ext 表模式收斂後退役）＋ jedi-common 查詢層補能力（JSONB 擴充欄位查詢/過濾＋「身分脈絡包」共通定義）；**第三階段**＝既有 20 支老套件升級插件模式——每支補：自帶 api 層、register 四插槽、harness、接入 README、拔掉測試、（有表的）migration 隨包、（有吐稽核欄位的）身分脈絡名冊。分級處理：完整升級（auth / file-upload / notification / survey / flow-engine 等多消費者）／輕量升級（captcha / bulletin / system-menu 等小而穩）／評估退役或合併（盤點時現形者）。jedi-auth 排首棒示範——P8 動完邊界、第二階段動完表、此階段補殼＝新標準完全體；**第四階段**＝原凍結區全解凍（P6、P9、P10、P11、P12〔人員指派，解耦鑰匙，本階段先做〕、P13〔OSCAL 三角，P12 之後〕），內部順序於第一階段總結時定；**終局選項**＝flow_control / 專案本體平台化，唯一保留「等商業決策」。**三案交錯原則**：jedi-auth 被 P8（邊界）→ 第二階段（表）→ 第三階段（殼）各動一個面向，依此順序不互踩。 |
| **D9** | jedi-iam 身分套件合併案（2026-08-30） | **jedi-auth＋jedi-login＋jedi-mfa＋jedi-captcha（降級為 turnstile 小模組）合併為 `jedi-iam`**；jedi-notification **維持獨立**（12 個消費點多與身分無關——flow_control / task_survey / flow_engine / notify_config 都在用；mfa→notification 依賴改 `INotifier` port 由宿主注入，範式照抄 jedi-auth `UserChangePasswordService` 現有做法）；**agent 機器身分（mTLS / X-Agent-Uid）明確不併**——身分套件管「人」、jedi-remote-agent 管「機器」，兩套認證體系平行。**合併範圍除四支套件外，主專案下列身分性質程式一併收進套件**：`common/middleware/jwt_mw.py`（170 行，JWT 驗證的唯一 HTTP 入口）、`socketio_auth.py` 的驗簽/blocklist 段（socket 身分入口）、`user_context_builder.py`（57 行，`allowed_tenant_paths` 的唯一產生點——RLS 鑰匙）、安全政策**定義**（security_policy 約 300 行的政策 schema/DEFAULTS——登入鎖定/密碼週期/JWT 效期/強制 MFA；**值的存放留宿主**）、`tenant_provisioning_service.py`（214 行，租戶開通；販售模組清單常數拆出留宿主）、`root_admin_reader.py`／`tenant_admin_role_flag.py`（合併後改走 domain service，消滅 raw SQL 繞道）、使用者批次匯入編排（寄信 port 化）。**UserContextDTO 留 jedi-common**（它是 `session_scope()` 的直接輸入，搬走會讓 jedi-common 反向依賴身分套件）——DTO 當防腐邊界、**組裝邏輯進 jedi-iam**。X-Tenant-ID 作用租戶覆寫收進套件**並補授權檢查**（現況只信 header、無任何檢查）。**數據支撐**：①login→auth 21 處 import 且穿透到 infra 層（`password_adapter` 直接 import `UserRepoImpl`）＝一個套件被切成兩個目錄；②近三次發版五支全部同批 bump＝「獨立演化」從未兌現；③B 類通用膠水 1,300 行（登入編排/MFA 分派/JWT 接線——任何產品都要重抄）＋C 類純重複 320 行（LoginConfig 組裝 ×4、OTP 信組裝 ×2 逐字元相同、DI 重複宣告 ×5 container）＝第二個產品的重工稅；④死鏈三條（mfa `send_otp_mail`＋11 個測死代碼的測試、圖形驗證碼全鏈 FE 零呼叫、login_logs 五層 8 檔 DB 0 列）。**與 D3 的關係**：不衝突，是 D3 判準的延伸——D3 排除獨立 jedi-authz 的三理由（新增依賴邊/強迫裝兩包對版/無單獨消費場景）逐字適用於 jedi-login。**排序＝第 2.5 階段**（第二階段表收斂之後、第三階段補殼之前）；理由：補殼成本與套件數成正比——先合併再補殼＝補 1 次，先補殼再合併＝補 5 次然後扔掉 4 次。**名稱定案 `jedi-iam`**（2026-08-30 PM 拍板改定，取代開發期暫名 jedi-identity）：IAM＝Identity and Access Management，業界類別正名（Keycloak/Auth0/Okta 皆屬 IAM 解決方案）；identity 一詞僅涵蓋「你是誰」半邊，本套件實質是身分＋存取管理（authz 主體域/capability/安全政策/租戶覆寫檢查）全疆界，用 identity 太狹隘。原 2026-08-30 首輪定名 jedi-identity 的推導（「Identity Platform 是業界類別詞」）經 PM 指正不精確——那是廠商行銷用語，類別正名是 IAM。被排除：沿用 jedi-auth（auth 慣例偏認證，承載不了合併後三倍內容物）、jedi-authz（只指授權小角，方向相反）。改名執行＝CM-1464（.16 之後、.18 publish 之前一次改；2026-08-30 執行完畢）。**`jedi_identity` 不留轉發殼**——它從未推上 Nexus、無任何外部消費者，留殼只會養出第二條 import 路徑；守衛測試已焊死舊名不得復活（`test_module_boundaries.py`）。被合併的四支舊名（jedi-auth / jedi-login / jedi-mfa / jedi-captcha）**維持轉發殼**——它們已發版在外，第三階段才下架。**前置確認**：已確認（2026-08-30）——user 確認 jedi-* 目前僅 Guidant AI 產品線消費，無 breaking change 風險。**順手清理四項＋依賴大掃除**：①`jedi-department` 下架（與 org_units 同概念兩份實作、無 tenant_id 無 path 不支援多租戶、主專案 pyproject 已註解未安裝——留著日後有人裝回來就是兩張部門表）；②「判 root tenant」三份複製收斂為一份（jedi-common `_session_paths_have_root()` 為 canonical，`jedi_auth/authz/platform.py` 與 `capability_service.py` 兩份改為呼叫——三份漂移＝授權事故）；③`roles.is_admin` 殭屍旗標連呼叫端刪除（`tenant_admin_role_flag.py` 檔頭已自承）；④jedi-login `infra/test_local/` 手測腳本（265 行）清出正式目錄。加碼：合併時 pyproject 依賴大掃除——4 個虛掛依賴（captcha 的 Flask-JWT-Extended/bcrypt、notification 的 bcrypt、auth 的 babel 疑似）＋5 支套件的 testcontainers/pytest 錯放主依賴，一併修正。**反悔條件**：若未來出現「只要登入不要帳號管理」的真實消費者（如純 SSO gateway 產品、user 存外部 IdP），login 獨立才成立。 |
| **D10** | 問卷疆界合併（2026-08-30） | **成立——task_survey（作答層）與 jedi-survey（問卷設計）合併為問卷疆界套件；P11 從「抽新套件」改寫為「合併」**。鐵證：資料庫有**兩條外鍵直接跨過套件與主專案的邊界**——套件邊界不可能靠 DB 外鍵縫合，原「把作答層抽成獨立新套件」技術上不可行；2026-03 schema 搬遷腳本刻意把作答表與問卷表放同一 schema，設計意圖即「問卷＝設計＋作答」。反悔條件：出現「只要問卷編輯器、不要作答」的真實消費者（如純表單設計器產品）。詳 boundary-map.md。 |
| **D11** | 檢測疆界——device 不併（2026-08-30） | **device 不屬於檢測疆界；P9 改為「detection_tools＋detection_execution＋common/ 檢測碼」，不含 device**。證據：檢測程式對 jedi-device 引用 0；綁 device 的任務 3,012 筆中檢測型 **0 筆**；商業模型不同（device 是 25/25 租戶標配、檢測是 6/25 加購模組）。device 真正歸屬＝資產盤點語境，形態＝獨立插件補殼。反悔條件：未來檢測改為「掃描目標必須來自資產主檔」的產品設計。 |
| **D12** | OSCAL 疆界改寫（2026-08-30） | **方向同意：真三角的第三邊是 flow_control（62 條）而非 module_frame（18 條），且依賴方向與原 §7 P13 所寫相反——oscal 應用層通用件併入 v2、module_frame 另立、依賴方向定為 oscal → flow_control 單向**。**附加開工門檻**：P13 開工前先派一棒查證「v2 app service 為何被 77 條 RepoImpl 繞過」——答案決定是「搬應用層進套件」還是「在 v2 內補編排層」，查證通過才改寫。**查證已完成（2026-08-30，CM-1462）**：77 條中 97% 為服務層缺能力（讀取面未開放 41＋寫入面缺 21）、僅 2 條便宜行事——原設兩選項皆不成立（編排層已存在且在用；應用層含 GRC 產品知識不宜進套件），查證棒建議中間路線「套件補讀取面＋缺口寫入、應用層留主專案」，**改寫方向待決策者拍板**；報告見 docs/analysis/2026-08-30-oscal-v2-repoimpl-bypass-attribution.md。 |
| **D13** | 三支退役（2026-08-30） | **jedi-oscal v1（可退役，七項證據齊備，順帶消 AGPL 依賴）、jedi-resource-store（殭屍——程式碼零引用、資料表四環境從未建立、路由 2026-02 已刪，併入既有 CM-1143 追蹤）、jedi_system_log（一載入即炸——與 jedi-common 宣告同一張表，import 即錯）三支退役**。執行方式：**先標 deprecated 觀察一版再刪**（防 Nexus 上有未查證的四 repo 以外下載者）；jedi_system_log 屬「會炸的碼」優先處理。jedi-log 不整支退役——拆兩半：API 存取紀錄活著（DB 10 萬筆）留、炸的那半（jedi_system_log）刪。**2026-08-30 user 拍板追加：jedi-department／jedi-resource-store 即刻退役執行**（首腦四面查證零使用——department 主專案零引用/venv 未裝/DB 表不存在/其他套件零依賴；resource-store 程式與 e2e 零引用/表從未建立，惟主專案仍 pin 0.0.11）。已落地：主專案拔 resource-store pin＋清 department 註解殘行；monorepo 兩包 README 頂部退役標記＋pyproject description 前綴 [DEPRECATED]（不動版號不 publish，目錄保留觀察期）。 |
| **D14** | jedi-bulletin 去留（2026-08-30） | **維持獨立套件**（user 定案：公告是可拔去其他產品使用的插件，有複用前景）；被排除方案＝下架收回主專案（boundary-map 原建議，基於「套件 408 行 vs 主專案重寫 406 行」的形態稅論——user 以複用前景否決）。其對身分名冊/通知的依賴照 **D16 基礎依賴 port 化通則**處理，第三階段照補插件殼。 |
| **D15** | ext 盤點裁決三件（2026-08-30） | ①**CM-1449 首批對象改為 `compliance.project_extensions`**——盤點證實 auth/user 域無可收斂延伸表（totp_secrets 是刻意安全邊界、user_tenants/user_org_units 根本是關聯表非延伸表），project_extensions 是全庫 190 張表中唯一明確收斂對象（4 欄全帶業務邏輯、與主表 1:1 零缺列），也是「跨套件邊界長出側掛」的典型示範；主要風險＝14 處手寫 SQL JOIN 散在 9 檔、主表屬 jedi-project 套件（加欄位走套件異動規範）。②**`assessment_plan_extensions` 孤兒問題（193 列全對不上主表）另開獨立 case 查證**——刪表 vs 修斷鏈是相反動作，查清前 CM-1449 跳過此表。③**JSONB 容器定位調整**：由「消化存量的手段」改為「**預防性建設——今後新客製一律走容器、不再開側表**」；理由＝盤點證實本系統的側掛表不是客製欄位長出來的、是跨套件邊界長出來的，9 張中純展示型 0 張。 |
| **D16** | 基礎依賴 port 化通則（2026-08-30，入插件契約） | **套件對「基礎能力」的依賴，一律判「疆界內知識 vs 疆界外服務」**：疆界外服務（誰是使用者/怎麼發通知/檔案存哪/設定值放哪）→ **一律 port 化**，套件只宣告需要的服務形狀、宿主接線時注入；疆界內知識留套件。**標準 port 四張**：身分脈絡包三名冊（IUserNameResolver/ITenantResolver/IOrgUnitResolver，已落 jedi-common）、INotifier（通知）、檔案存取介面（file-upload）、設定讀取。**唯一例外＝jedi-common**——允許直接依賴的地基（session/查詢基底/例外類），不 port 化。第三階段每支補殼**必辦**：把該套件直接 import 其他套件之處換成標準 port。終局圖景：一層地基（jedi-common）＋一排彼此不直接相依的疆界插件，插件間協作全經宿主接線盤——拔走任一支時把接線盤上的線重接到新產品實作即完成移植。 |
| **D17** | 資料關聯三律（2026-08-31，入插件契約） | **表與表的關聯不靠外鍵縫合疆界，改用三條判斷律**——外鍵是資料庫自己執行的規則，**塞不進 port**（程式碼可以「不直接認識」，外鍵不行），因此疆界切分必須在資料層另有一套判準。**① 想建外鍵＝兩張表該住一起**：天天連表查、要同生共死的資料，代表**同一個能力被切錯了**，正解是合併疆界而非硬拆——D10 問卷疆界合併（設計表與作答表跨界外鍵）即此律的鐵證案例，本條把該個案通則化。**② 真跨疆界＝軟參照**：A 表只存 B 的識別碼（id / uid / 帳號），**不建外鍵約束**；要顯示名稱走名冊 port（D16 三名冊）批次問，**查不到必降級顯示識別碼、絕不炸**——jedi-log-forwarding 已是實作樣板，「禁止套件 JOIN 宿主 user / tenant / org 表」（D8 身分脈絡標準件既有禁令）是本律的既有特例。**③ 流程耦合＝事件／port，連識別碼都不存**：「B 發生事、A 要知道」是流程通知不是資料關聯，走 port 呼叫或事件，事過境遷不留關聯——`INotifier` 即此類。**業界對應**：律①＝DDD「兩個領域老要互摸對方的表＝邊界劃錯」；律②＝GitLab loose foreign keys、微服務 database-per-service、DDD reference-by-ID、Shopify 禁跨模組 JOIN（CI 強制掃）；律③＝事件驅動架構。**已知代價與收拾方式**：軟參照後資料庫不再擋「指到已刪除的資料」，由兩件事收拾——「查不到必降級」（律②內建）＋**定期清理孤兒資料**（背景作業，比照 GitLab loose FK＋清理器；本案尚未建置，隨第四階段各疆界落地時補）。**適用範圍**：第四階段每支候選（P12 participant hub／問卷合併／flow_control 拆出／P13 OSCAL）判斷疆界切分時一律依此三律；既有跨界外鍵不回頭全面拆除，隨各疆界抽取時逐案處理（拆到誰才判誰）。**與 D10／D11 的關係**：D10（問卷合併）是律①的先例、D11（device 不併檢測）是「無外鍵亦無共生關係→本就不同疆界」的反面印證，兩者皆不因本條改判。**view 條款（2026-08-31 補）**：DB view＝存在資料庫裡的跨疆界查詢，同受本三律管轄；但守衛測試掃 Python 掃不到 view（CM-1474 實證——.12 雙寫守衛漏掉 vw_user_job_queue），故 view 歸屬**必須人工列冊**（現存 4 張的歸屬表見 CM-1478 卡）。 **反悔條件**：若未來出現「跨疆界一致性必須由 DB 保證」的硬需求（如金額結算類），該處回到單一疆界內處理，而非把外鍵加回跨界。 |
| **D18** | 設定 schema 申報制（2026-08-31，D6 config 插槽升格正式規格） | **設定的 schema 進套件、值與存放留宿主、模組開關表永遠留宿主**——套件管規格書、主產品管實際的值。**① schema 進套件**：套件於 `register()` 時申報自己的設定 schema（鍵名／型別／預設值／驗證規則／**作用域**），隨包出貨；**② 值與存放留宿主**：宿主照舊集中存 `system_configs`，並拿套件申報的 schema 驗證寫入——套件不自建設定表、不決定值存哪；**③ 模組開關表永遠留宿主**：「哪些模組對這個租戶開啟」是宿主的組裝決策，不隨任何套件走（套件不該知道自己有沒有被啟用）。**三項補充（2026-08-31 討論定案）**：**(a) 作用域為 schema 的必填欄位**——分系統級（這個部署：LDAP 連線、儲存後端，平台管理員改）／租戶級（這個客戶：通知偏好，租戶管理員改）／租戶可覆寫系統級（平台給預設、客戶可換自己的：SMTP 信箱伺服器）三種；儲存機制現成（`system_configs` 已有 `tenant_id` 與 `(tenant_id, group, key)` 唯一鍵），**不需新表**。**(b) 預設活在 code、DB 只存差異**——讀取時查無列即回 schema 內建預設，免除 seed 時序問題（套件裝上就有預設值可用，不必等 migration 跑完）；若確實需要 seed，**只補缺鍵、絕不覆蓋既有值**（冪等鐵則——覆蓋會把客戶調過的設定打回原廠）。**(c) 讀取 fallback 鏈由套件申報、宿主執行**：租戶值→系統值→schema 預設，**全鏈皆無則 fail loudly**（不可靜默回 None 讓呼叫端當沒設定）；客戶只能改自己作用域內的值、寫入須過 schema 驗證、跨作用域寫入須有授權守門。**業界同構**：GitLab／Odoo／WordPress `register_setting()`／K8s CRD——皆為「插件宣告形狀、平台管存放」。**first case**：2.5 階段 `security_policy.py`（七個設定鍵 schema 進套件、值留 `system_configs`）已是本制的實例，本條把它升格為正式規格。**加分項（非必辦）**：設定頁由申報 schema 自動生成表單——不必每加一個設定就手刻一次畫面。**適用範圍**：第四階段新抽套件一律照辦；既有 20 支套件於第三階段補殼時回頭套用（有設定者才做，無設定者免）。 |

### 插件契約補充：身分脈絡標準件（D8 併入 D6 契約）

- **規則**：凡套件 API 會吐 `created_user` / `updated_user` / `tenant_id` / `org_unit_id` 或業務性 user 參照（如 `matched_user_id`）者，套件**必須內建對應名冊 port 並在 app 層批次 enrich**（`*_user_name`、租戶名、單位名）；純引擎型無此輸出者免做——標準是「有吐才要補」，不是每支硬裝。
- **名冊 port 一族**（皆同構：批次 id→名稱、選填、不注入優雅降級不炸）：
  - `IUserNameResolver`——帳號批→顯示名稱。**P3 jedi-log-forwarding 已有樣板**（批次防 N+1、選填、降級顯示帳號），為本標準件的參考實作。
  - `ITenantResolver`——tenant_id 批→租戶名稱。
  - `IOrgUnitResolver`——org_unit_id 批→單位名稱。
- **收斂設計**：第二階段在 jedi-common 定義共通「身分脈絡包」（identity context）——套件宣告需要哪幾張名冊、宿主接線時一次注入一組，避免 20 支套件各自定義微妙不同的簽名再度發散。
- **多租戶欄位規則**（引用 P4 先例）：tenant / org_unit 欄位在 001 腳本建 nullable、002（需宿主 RLS 生態）才收緊 NOT NULL——套件必須在**多租戶與單租戶宿主都能活**。
- **禁止**：套件 JOIN 宿主 user / tenant / org 表、認得宿主身分模型——一律透過名冊 port 問。
- **現況註記**：P1 / P2 / P4 / P5 未內建名冊（其管理面不吐稽核欄位，當時無需求）——**不回補**，第三階段統一過一輪時順手做；主專案現有 28 處 app 層自行補名屬歷史重工，隨第三階段各套件升級逐步刪除。

---

## 4. 現況盤點

### 4.1 全模組四分類

| 分類 | 模組 | 處置 |
|------|------|------|
| **① 產品核心（不抽）** | grc(236檔)、oscal(193)、module_frame(79)、associations(67)、participant(90)、project(25)、project_summary_report(36)、task_survey(65)、detection_tools(99)、detection_execution(28)、evidence_classification(42)、device、subtask_status_history、information_system 殼、flow_engine 殼 | 第一階段不動；抽取路徑見 §7 凍結路線圖 |
| **② 既有 jedi 套件的殼（免抽）** | auth、survey、bulletin、notify_config、system_config、system_menu、captcha、log、issue/feedback、upload_file | 主專案剩 route + DI wiring + 專案特有 enrich，正是設計上「該留主專案」的部分 |
| **③ 抽取候選（本案主體）** | ai(6檔)、integrity(+common/integrity ≈23)、log_forwarding(+common/log_forwarding ≈32)、remote_agent+agent_task(+agent_auth ≈87)、license(+common/license ≈44) | P1–P5 逐支抽出 |
| **④ common/ 分類搬遷** | 通用 util → jedi-common；authz 主體域 → jedi-auth；業務 error code/enum → 歸位回各模組 | P0 歸位、P7/P8 上移 |

### 4.2 耦合分析重點

- **P1–P5 候選全部是葉節點**：零跨模組 outgoing（license 僅 1 條對 notification 的細線），且除 DI 註冊行外無任何模組 import 它們。
- **hub 排行**：participant（in-degree 10、被 import 60+ 次，但 out-degree 僅 8——凍結路線圖的鑰匙）、flow_engine（in 9）、notification（in 8 但廣而淺）、system_config（in 7）、grc（in 7 + out 11，雙向樞紐）。
- **`di_containers/containers.py` 是隱形 hub**（401 行、43 個 import）：每次抽取都要同步改註冊，是每支套件的固定成本，各 arc 驗收條件都含這一項。

### 4.3 必須先修的架構債

| 債 | 內容 | 影響 |
|----|------|------|
| **uploadfile / upload_file 雙模組** | api 層用 `uploadfile`（舊 route）、app/infra/di 用 `upload_file`（managed file upload），同功能兩套命名並存 | 抽取地雷；P0-① 合併 |
| **common/ 的 9 條反向 import** | `common/authz` → `domain.oscal`(3)、`domain.participant`(2)；1 條 → `infra.integrity`；3 條 → `di_containers.containers` | 違反分層方向；不斷掉則 P7/P8 上移後套件對主專案循環依賴。其中 5 條在 authz 資源域（D3 拆半後不隨包搬，但仍該斷）；P0-② 處理 |
| **模組專屬 error code / enum 錯放 common/** | `grc_error_code`(94 處)、`module_frame_error_code` 等 8 支 + `participant_enum`(16) 等業務 enum 放在 `common/code`、`common/enum` | 讓 common/ 看起來比實際「通用」，混淆 P7 搬遷面；P0-③ 歸位 |

### 4.4 common/ 三分法（P7/P8 的搬遷清單基礎）

| 去向 | 內容 | 熱度 |
|------|------|------|
| → **jedi-common**（P7） | `util/response_util.py`（**124 處 import、123 處來自 api 層，全 repo 最熱單檔**）、`common_util`、`audit_log`、`audit_nickname`、`serialization_util`、`redis_client_util`、`excel_util`、`file_util`、`pdf_util`、`savepoint`、`scheduled_time`、`code/error_code.py`(45)、`enum/schema_code.py`(60)、`http_code_enum`、`dao_error_enum` | util 267 處 / 43 模組 |
| → **jedi-auth**（P8） | `authz/` 主體域半包：`platform.py`(9)、`admin.py`(3)、`capability.py`(2)、`decorators.py`、`signed_token.py` | authz 全包 140 處 / 32 模組 |
| **留主專案** | authz 資源域（project/ssp/workflow/menu_license_filter）、各模組 error code/enum（P0 歸位）、OSCAL/CMMC 業務工具（control_id_matcher、profile_extractor、cmmc yaml…）、guard 群（participant/round/workflow_project） | — |
| **隨模組帶走** | `common/integrity/`（→P2）、`common/log_forwarding/`（→P3）、`common/util/agent_auth/`（→P4）、`common/license/`（→P5） | 各自僅該模組使用 |

---

## 5. 詳細設計（P0–P8 各 arc）

### P0 前置清理（不抽東西，先還債）

| 子項 | 內容 | 要點 |
|------|------|------|
| P0-① | 合併 `uploadfile` / `upload_file` | 收斂為 `upload_file` 單一命名；api route 檔搬併、`config/app_modules.py` 與 containers.py 註冊同步；歷史 URL 路徑不變（只動 python 模組名不動 endpoint path） |
| P0-② | 斷 common/ 9 條反向 import | authz→domain 的 5 條：resolve 邏輯改由呼叫端傳入 entity 或改 port 注入；→infra.integrity 1 條：改 DI 注入；→di_containers 3 條：改 lazy import 或注入。完成判準：`grep -rE "^from (domain|infra|di_containers)\.|^import (domain|infra|di_containers)\." common/` = 0 |
| P0-③ | error code / enum 歸位 | 8 支模組專屬 error code + 業務 enum 從 `common/code`、`common/enum` 搬回各模組目錄（如 `app/grc/code/`）；im­port 路徑變動用 IDE 級全域替換 + 全量 import 檢查 |
| P0-④ | 守衛測試基礎 | 建 `test/test_module_boundaries.py` 骨架：斷言 common/ 無反向 import、後續各套件抽出後逐支加「不得 import 主專案」斷言 |
| P0-⑤ | grc → flow_control 改名（D5） | 與 P0-③ error code 歸位**同批 import 替換執行**（省一輪全域替換與全量驗證）。目錄/類名/DI 註冊改，error code 字串值、API URL path、DB 內容不動。完成判準：grep 全 codebase 無 `app.grc` / `domain.grc` / `infra.grc` import 殘留；`grep -rn "GRC_" `（error code 字串值）確認未被改動；BE 重啟 + 稽核主流程手測正常 |

**驗收**：上述 grep 為 0；BE 啟動正常；檔案上傳、任一稽核頁面手測正常；flow_control 改名三判準（import 殘留 0 / 字串值未動 / 主流程手測）全過。

### P1 jedi-ai-bot（SOP 練手＋插件契約首例）

- **內容物**（D6 升級後）：`api/ai/`（blueprint / route）與 `app/ai/`（AiBotService，Anthropic Claude + Redis 對話）**一併進套件**——套件自帶 api 層 + 預設 DI container + `register(app, adapters)` 註冊入口（本模組無 migration，migration 隨包機制的首例落在 P2–P5 中第一支有表的套件）。6 檔，最小案例。
- **契約定義責任**：P1 是 D6 插件契約的第一個實作——`register()` 簽名、blueprint 掛載方式、container wiring 慣例、拔掉測試步驟，全部在本 arc 定案並寫進 SOP checklist，P2–P5 照抄。
- **port**：無——只依賴 jedi_common 與 Redis client（隨包或注入均可，傾向注入 redis client）。
- **D7 責任**：harness ＋接入 README 由 P1 首做並寫進 SOP checklist——`dev_app.py` ＋ compose（本模組吃 Redis，compose 起獨立 Redis）＋套件測試獨立跑；接入 README 含 port 清單 / 註冊行 / quickstart。
- **驗收**：套件上 Nexus、主專案 pin、AI 聊天手測正常、守衛測試綠、**拔掉測試通過**（註解 app_modules.py 註冊行 → BE 正常啟動、AI 聊天路由消失、其餘功能不受影響）、**harness 起得來且套件測試在 harness 內全綠（D7）**、**接入 README 完成且照它 10 分鐘能在 harness 重現 quickstart（D7）**。**本 arc 同時產出「抽取 SOP checklist」文件**（供 P2–P5 復用，必含拔插驗證與 harness/README 步驟）。

### P2 jedi-integrity

- **內容物**：`app/infra/di` 的 integrity 模組 + `common/integrity/` 全包（lockdown / startup_gate / runtime_check / tamper_marker / forensic / lc_report / unlock）。
- **注意**：startup gate 在 main.py 啟動鏈上被呼叫——套件需提供清楚的公開入口函式；與 FR-064 的 manifest/簽章產線（scripts/build/）的檔案座標約定要在套件 README 寫明。
- **port**：無跨模組線。`config.config` 的讀取改為初始化參數傳入。
- **驗收追加（D6）**：拔掉測試通過；插件契約形式照 P1 定案的 SOP（自帶 api 層如適用 / container / register 入口 / migration 隨包）。
- **驗收追加（D7）**：standalone harness ＋接入 README 齊備，形式照 P1 SOP。

### P3 jedi-log-forwarding

- **內容物**：log_forwarding 模組全層 + `common/log_forwarding/`（forwarder / gelf_handler / settings_reader）。
- **要斷的線**：1 條 jedi_auth 細線（設定 CRUD 的權限判斷）——本來就該走主專案 route 層守門，套件內移除。
- **注意**：QueueHandler 掛載點在 logger 初始化鏈，同 P2 提供公開入口函式；「轉發失敗不拖垮主服務」的靜默降級行為要有套件內測試。
- **驗收追加（D6）**：拔掉測試通過；插件契約形式照 P1 定案的 SOP。
- **驗收追加（D7）**：standalone harness ＋接入 README 齊備，形式照 P1 SOP。

### P4 jedi-remote-agent

- **內容物**：`remote_agent`（註冊/enroll token/心跳）+ `agent_task`（派工/查詢）+ `common/util/agent_auth/`（ca/tls/jwt/heartbeat mTLS）。約 87 檔，第一階段最大宗。
- **要斷的線**：remote_agent → jedi_file_upload（agent 上傳結果檔）保留——jedi 套件依賴 jedi 套件是合法邊（比照 jedi-issue → jedi-file-upload 先例）。
- **port**：`IAgentTaskPayloadProvider` 之類的派工內容組裝介面——檢測派工的 params 組裝屬產品知識，留主專案實作注入；套件只管「派工信封」的生命週期（pending/領取/回報/逾時）。
- **注意**：憑證與 CA 檔案路徑全部參數化；detection_tools 對 agent_task 的消費照舊（import 套件）。
- **驗收追加（D6）**：拔掉測試通過；插件契約形式照 P1 定案的 SOP。
- **驗收追加（D7）**：standalone harness ＋接入 README 齊備，形式照 P1 SOP。

### P5 jedi-license-runtime

- **內容物**：license 模組全層 + `common/license/`（engine / machine_fingerprint / public_keys / schema）。
- **要斷的線**：1 條 → notification（到期通知）。套件內定義 `INotifier`（`notify(recipients, subject, body)` 級別的窄介面），主專案 adapter 包 jedi_notification。
- **注意**：PUBLIC_KEYS 常數的更新流程（多環境簽發鑰四步一組）要寫進套件 README；`common/integrity` 若有交叉引用，依 P2 先行的套件邊界處理（license-runtime 依賴 jedi-integrity 是合法 jedi 間邊）。
- **驗收追加（D6）**：拔掉測試通過；插件契約形式照 P1 定案的 SOP。
- **驗收追加（D7）**：standalone harness ＋接入 README 齊備，形式照 P1 SOP。

### P7 jedi-common 擴充

- **內容物**：§4.4 第一列清單上移。分兩批 commit：①response_util（影響 124 檔的解析位置，單獨一批全量回歸）②其餘 util + error code 基底 + 通用 enum。
- **硬要求**：**全量回歸**——既有 pytest 全跑 + 主要頁面手測清單（因 jedi-common 被 42 個模組、20 個套件依賴，此 arc 是全案風險最高點之一）。
- **注意**：jedi-common 版本 bump 後，monorepo 其他套件的 `>=` 約束不需動；主專案 `poetry update jedi-common` 一次收攏。

### P8 authz 主體域併 jedi-auth

- **前置**：P0-② 完成（反向邊已斷）、P7 完成（decorators 若引用通用 util，先等它們就位）。
- **內容物**：`common/authz/` 的 platform / admin / capability / decorators / signed_token 進 jedi-auth 新子包 `jedi_auth/authz/`；資源域四支留原地。
- **相容策略**：`common/authz/__init__.py` 保留 re-export shim（主體域符號轉 import jedi_auth.authz），消費端 32 個模組零改動；shim 標 deprecated，後續新 code 直接 import jedi_auth。
- **驗收**：admin / manager / auditor / 一般使用者四種角色的權限行為全數與改前一致（比對主要守門點清單逐項手測）。

---

## 6. 拆分表（8 arc × 依賴 × 驗收）

| Arc | 依賴 | 可平行 | 驗收條件 |
|-----|------|--------|----------|
| **P0 前置清理**（含 grc→flow_control 改名）｜**進行中（平行 session）** | — | 起點 | 反向 import grep=0；上傳功能手測正常；守衛測試骨架進 test/；grc 舊 import 路徑殘留 0、error code 字串值未動、稽核主流程手測正常 |
| **P1 jedi-ai-bot**｜與 P0 檔案零重疊，可平行派工 | 無（P0 非硬前置） | 與 P2–P5 平行 | 套件上 Nexus + pin；AI 聊天手測；**插件契約定案（register 簽名等）＋拔掉測試通過**；**harness＋接入 README 首例（D7）**；SOP checklist 產出 |
| **P2 jedi-integrity** | 建議在 P1 SOP 之後 | ∥ | 套件 + pin；落地版 startup gate 手測；守衛測試；拔掉測試；harness＋接入 README |
| **P3 jedi-log-forwarding** | 同上 | ∥ | 套件 + pin；轉發到測試 syslog 收到 log；守衛測試；拔掉測試；harness＋接入 README |
| **P4 jedi-remote-agent** | 同上 | ∥ | 套件 + pin；agent 心跳 + 檢測派工端到端手測；守衛測試；拔掉測試；harness＋接入 README |
| **P5 jedi-license-runtime** | 同上 | ∥ | 套件 + pin；license 啟用/到期流程手測；INotifier adapter 生效；守衛測試；拔掉測試；harness＋接入 README |
| **P7 jedi-common 擴充** | P1–P4 完成後（讓新套件先穩定） | 否 | jedi-common 新版；pytest 全綠 + 頁面手測清單；response_util 批單獨回歸 |
| **P8 authz 併 jedi-auth** | P0-② + P7 | 否 | jedi-auth 新版；四角色權限行為一致；shim 生效、消費端零改動 |

每個 arc 共同的固定項目：`di_containers/containers.py` 註冊同步、`config/app_modules.py` 同步（如適用）、jedi-packages.md 補該套件一段、Notion 子卡回寫、extract→publish→pin 順序紀律。

---

## 7. 第二～四階段與終局選項（D8 全解凍後的全案路線）

> 2026-08-30 D8 拍板：原「凍結路線圖」全解凍，「等第二消費者」原則廢除——整併本身即目標。各階段先後與交錯於第一階段（P0–P8）總結時排定；下表順序為建議序。

### 第二階段：ext 表收斂＋JSONB 擴充容器

> **2026-08-30 盤點定案（D15，phase2-ext-table-inventory.md）**：掃 190 張實體表，真正的延伸表 9 張、**純展示型 0 張**——本系統的側掛表不是「客製欄位」長出來的，是「**跨套件邊界**」長出來的（主表在套件、產品欄位沒地方放才側掛）。故本階段實作重點由「JSONB 容器消化存量」調整為「**跨套件側掛欄位收回主表正式欄位**」；JSONB 容器定位改為**預防性建設**——今後新客製一律走容器、不再開側表（查詢能力已由 CM-1448 落地 jedi-common）。

- **盤點**：✅ 已完成（CM-1447，2026-08-30）——9 張延伸表去向：收回主表 1（`project_extensions`）、留置 6、待查證 1（`assessment_plan_extensions` 孤兒表，另開 case）、結構異常另記 1（`ssp_system_characteristics` 缺唯一性約束）。
- **收斂實作（CM-1449）**：**首批對象＝`compliance.project_extensions`**（D15；原「auth/user 優先」經盤點證實無收斂對象，已改）。「只加不破」五步搬遷（加欄位→回填→雙寫期→逐處切讀取→觀察後 drop）＋帶舊資料升級驗證；主要風險＝14 處手寫 SQL JOIN（散 9 檔，改漏不報錯只靜默少過濾軟刪除）＋主表屬 jedi-project 套件（加欄位走套件異動規範）。
- **jedi-common 查詢層補能力**：✅ 已完成（CM-1448，2026-08-30）——JSONB 擴充欄位查詢/排序（`_ext_*` 前綴族）、身分脈絡包三名冊、SessionMixin 正名皆落地，未發版。
- 判斷口訣：只是「多記幾個值」→ JSONB 容器；要「拿來運算/關聯/約束」→ 正式欄位（或客戶側開表）。

#### jedi-common 基底類正名案（2026-08-30 調查定案）

- **調查結論**：`session/database/` 頂層 `base_repository.py`（7 行 session property）**不是舊代殘留**——它是 lazy session 提供者（mixin），`repository/base_repository_impl.py` 的完整查詢基底自己就繼承它（`class BaseRepositoryImpl(IBaseRepo[T, Q], BaseRepository, ...)`）。兩者是「連線底座 vs 完整基底」的**零件關係**，不是新舊兩代。
- **合法用法認定**：主專案 5 檔（cloud_integration 3 支 raw-SQL repo／bulletin link 表／upload_file HTTP adapter）與老套件 27 處直接繼承底座，多屬「特殊 repo 不適用完整基底、只需 session」的合法情境（同 CLAUDE.md「不能繼承 BaseRepositoryImpl 時自己寫 `@property session`」條）——**不做汰換**。
- **真問題＝命名混淆**：底座與 `repository/base_repository.py`（查詢介面）**同名不同角色**，會誤導讀者當成待淘汰舊版——本案調查即因此差點誤發一輪假清理。
- **第二階段動作三條**：
  1. 底座**正名**（如 `SessionMixin`；挪位與否屆時定），舊名留轉發（32+ 處引用零即刻改動）。
  2. 頂層 `base_model.py` 為**真重複**（與 `model/base_model.py` 內容漂移）且全生態 0 引用——直接刪。
  3. 第三階段各套件升級棒「順手看一眼」該套件對底座的繼承是否真屬特殊 repo（預期多數合法），**不再列為汰換欠帳**。
- 教訓註記：同名不同角色的基底類是「假清理」誘因——收斂靠**正名**，不靠強制遷移。

### 第 2.5 階段：jedi-iam 身分套件合併（D9）

> 插在第二階段（表收斂）之後、第三階段（補殼）之前——先合併再補殼＝補 1 次；先補殼再合併＝補 5 次扔 4 次。完整決策內容與數據支撐見 §3 D9。

| 動作 | 內容 |
|------|------|
| 四支合一 | jedi-auth＋jedi-login＋jedi-mfa＋jedi-captcha（降級為 turnstile 小模組）→ **jedi-iam**（暫名 jedi-identity，PM 2026-08-30 定調改名）；舊套件名留轉發過渡 |
| 主專案身分程式收進套件 | jwt_mw（JWT 入口）、socketio_auth 驗簽段（socket 入口）、user_context_builder（RLS 鑰匙產生點）、安全政策定義（值存放留宿主）、tenant_provisioning（販售模組常數留宿主）、root_admin_reader／tenant_admin_role_flag（改走 domain service）、使用者批次匯入編排（寄信 port 化）、X-Tenant-ID 覆寫（**並補授權檢查**） |
| 明確不併 | jedi-notification（基礎設施，12 消費點多與身分無關；mfa 發信改 `INotifier` port）；agent 機器身分（mTLS／X-Agent-Uid，歸 jedi-remote-agent）；UserContextDTO 留 jedi-common 當防腐邊界（組裝進套件） |
| 順手清理 | ①jedi-department 下架（與 org_units 重複且無多租戶）②「判 root tenant」三份複製收斂為一份（jedi-common canonical）③roles.is_admin 殭屍旗標連呼叫端刪④jedi-login infra/test_local 手測腳本清出；加碼 pyproject 依賴大掃除（4 虛掛＋5 支測試依賴錯放） |
| 前置確認 | ✅ 已確認（2026-08-30）：jedi-* 僅 Guidant AI 產品線消費 |

### 疆界盤點案（FR-069.9，第四階段前置，與第二階段平行）

> 純分析案不動碼。**第四階段的開工門檻**——P6／P9–P13 各棒開工前必須先有疆界地圖定案。動機：以前按技術功能切太碎，26 支裡不少是「同一個能力被切成幾半」（jedi-iam 案已證實一組）；先畫清疆界再動工，避免抽完又要合。

**方法**（照 jedi-iam 案的同一套判準與分析深度）：①實際耦合數據（跨套件 import 具體到符號、穿透層級）②發版歷史（獨立生命有無兌現）③主專案膠水量化（A 產品特定／B 通用重寫／C 純重複三分類）④死碼盤點⑤pyproject 依賴健檢。

**待驗證的疆界候選五組**（初步假設，以數據為準）：

| # | 候選疆界 | 內容物 | 對既有計畫的影響 |
|---|---------|--------|----------------|
| 1 | 檔案疆界 | jedi-file-upload＋jedi-resource-store | — |
| 2 | 問卷疆界 | jedi-survey＋主專案 task_survey | 改寫 P11——從「抽新套件」變「合併成疆界套件」 |
| 3 | 檢測疆界 | jedi-device＋主專案 detection_tools/detection_execution | 改寫 P9 |
| 4 | OSCAL 疆界 | jedi-oscal（退役查證）＋jedi-oscal-v2＋module_frame＋oscal 應用層 | 改寫 P13 |
| 5 | 追蹤疆界（弱候選） | jedi-issue＋jedi-bulletin＋主專案 feedback | 可能只是「都很小」而非同疆界，待數據 |

**另兩項退役查證**：jedi-log（vs 新日誌體系的重疊度）、jedi-oscal v1（vs v2 的取代狀態）。

**產出物**：「最終疆界地圖」——26 支現有套件＋主專案候選模組的完整去向表（合併／獨立補殼／退役／下架），供 user 拍板後**改寫第三、四階段的棒次表**。

**願景**：套件庫從 26 支細粒度零件收斂為約 12–15 支輪廓清楚的疆界套件。

### 第三階段：既有老套件升級插件模式

> 身分族已於第 2.5 階段合併為 jedi-iam——本階段補殼時身分族僅 1 支。**補殼對象清單已按疆界盤點結果（2026-08-30 拍板 D10–D14）更新如下**——同疆界者先合併再補殼，避免補完又扔。

每支補齊 D6/D7/身分脈絡標準件（老套件缺的是「插件的殼」，最難的解耦已完成，工程量比新抽輕），**並執行 D16 基礎依賴 port 化**（把直接 import 其他套件之處換成標準 port 四張——這是每支補殼的必辦項）。主專案側同步把每支的接線收斂成「一個接線檔」，並隨升級逐步刪除 28 處 app 層自行補名的歷史重工。

**隨棒必做（追蹤卡 CM-1461）**：2.5 階段與本階段各棒動到某模組的 route 註冊時，順手拔除該模組的裸路徑死端點（62 支總帳見卡——同 Resource 雙路徑註冊缺 handler、打了必 500 但 FE 零裸打；CM-1454 Postman 掃描發現），拔前確認 e2e/agent 無呼叫，拔後回卡勾銷。

| 分級 | 套件 | 處理 |
|------|-----|------|
| 完整升級 | jedi-iam（2.5 階段產物，首棒示範）、jedi-file-upload（唯一有第二使用者的套件——jedi-issue 在用）、jedi-notification、jedi-flow-engine、問卷疆界套件（D10 合併產物） | 完整五件套（api 層/register/harness/README/拔掉測試＋適用者補 migration 隨包與名冊）＋D16 port 化 |
| 輕量升級 | jedi-bulletin（D14 定案維持獨立，名冊/通知依賴 port 化）、jedi-system-menu、jedi-system-config、jedi-device（D11 定案不併檢測、歸資產盤點語境）、jedi-information-system、jedi-issue、jedi-log（拆兩半後留下的 API 存取紀錄半） | 補 register 入口＋README，harness 從簡＋D16 port 化 |
| 退役/下架（D13，先標 deprecated 觀察一版） | jedi-oscal v1（七項證據、消 AGPL）、jedi-resource-store（併 CM-1143）、jedi_system_log（會炸的碼，優先）；另 jedi-department（D9 已定下架） | 不補殼，不花力氣升級殭屍套件 |
| 待另議 | jedi-project（boundary-map 建議短期凍結不補殼、長期併「專案疆界」——涉終局選項，未拍板）、jedi-issue 內 58% 死碼去留（gitlab/github 整合 1,191 行——「已廢棄 vs 留給未來客戶」屬產品決策，未拍板） | 決策前不動 |

### 第四階段：核心抽取（原 P9–P13＋P6，全解凍）

### 形態分流準則（D7-③，每支候選啟動前先過這關）

每支候選開工前，先判「插件 vs 服務化」：

- **服務化**（License Center 模式：獨立部署、網路 API、自有 DB）——適用於有**獨立生命週期**的能力：自己的發版節奏、自己的擴縮容需求、可能服務多個產品實例（如檢測平台）。邊界是網路協定，想耦合都耦合不了，是最強的解耦形態。
- **插件**（D6 契約：隨包安裝、進程內掛載）——適用於與宿主**同進退**的能力：跟宿主同版本演進、共享宿主的 DB/auth/tenancy（如 authz、integrity、log-forwarding 這類）。
- 判準問句：「這個能力壞了，是宿主整個停，還是可以降級繼續跑？」「它需要自己的資料主權嗎？」「同一套實例會服務多個產品嗎？」——三題任一為是，傾向服務化。

棒次順序（2026-08-31 user 拍板定序，卡號 CM-1475–1483 九張連號）——每棒一句排序理由：

| 棒次 | 卡 | 項目 | 內容 | 為什麼排這個位置 | 關鍵事實 |
|------|----|------|------|------------------|----------|
| **4.A** | CM-1475 | **地基棒** | D17／D18 通則入 §3 決策表＋套件分類地圖（架構手冊 `package-taxonomy.md`）＋跨疆界聚合查詢正名搬 `infra/readmodel/` | **排第一因為後面每棒都讀它**——runner 讀不到對話只讀得到文件，通則不落地就每棒重新發明判準；聚合查詢正名是 P12 的硬前置（「我的任務」聚合正躺在即將抽出的人員指派模組裡，SQL 字串裡的跨表依賴 grep 守衛與 harness 都抓不到，誤搬進套件＝靜默炸） | 8 支聚合 query 約 1,500–2,000 行、每支跨 3–4 schema（盤點見 `docs/analysis/2026-08-31-cross-boundary-aggregation-api-inventory.md`） |
| **4.B** | CM-1476 | AI registry 自註冊 | `api_registry.py` 由硬編碼表改為各套件自註冊，宿主只組裝目錄；順修 2 個指向不存在 provider 的壞條目 | **排在核心抽取之前、但可與 P12 平行**——不改的話每抽一支套件就要回頭改 525 行硬編碼表＋14 個 container resolver，抽越多改越多次；與 P12 無共用檔案故可平行 | `api_registry.py` 28 條 API 覆蓋 14 模組；`:244`／`:254` 兩條 provider 不存在（lambda 延遲求值故起服務不炸，AI 選到才 500） |
| **4.1** | CM-1477 | **jedi-participant**（P12） | 多層級任務指派（~90 檔） | **核心抽取首棒——解核心環的鑰匙**：抽完最粗兩條邊自動消失，後續各支才好動；且 out-degree 僅 8，是全部候選裡改動面最小的 | **解核心環的鑰匙**：全專案最大 hub（被 60+ 處依賴）但 out-degree 僅 8——import 名不變原則下，消費端（flow_engine 52 條、grc 43 條）零改動，只需斷自己的 8 條線。抽完核心環最粗兩條邊自動消失，後續各支才好動。**TaskAssigneeService 切法已裁（2026-08-31）**：純 CRUD 隨套件走、跨疆界聚合讀取留主專案 |
| **4.2** | CM-1478 | flow_control 拆出＋流程疆界三方設計 | 稽核執行業務層拆出（拆法「先單純拆出、切分後議」，見下方終局選項段）；flow-engine／flow_control／project 三方同席定 route 與 app 層歸屬 | **排 P12 之後、P13 之前**——P12 先解掉最粗的 participant 邊，搬家包袱最小；又必須在 P13 之前，因 D12 已定 oscal → flow_control 單向依賴，OSCAL 疆界設計必須知道 flow_control 自己也會成為套件，否則拆完返工 | 稽核執行業務層 37 支 service／236 檔；3.4 實查證實 flow-engine 19 條 route 有 17 條吃稽核業務、6,375 行 `app/flow_engine` 疆界未定（CM-1470 裁併入本案） |
| **4.3** | CM-1479 | OSCAL 疆界（**D12 改寫**） | oscal 應用層通用件併入 jedi-oscal-v2；module_frame 另立；三角第三邊＝**flow_control**（62 條，非原寫的 module_frame 18 條）；依賴方向定為 **oscal → flow_control 單向**（與原文件所寫相反） | **緊接 flow_control 之後**——三角第三邊是 flow_control，它定形了 OSCAL 才有邊界可劃 | **開工門檻（D12）**：先派一棒查證「v2 app service 為何被 77 條 RepoImpl 繞過」——答案決定「搬應用層進套件」還是「在 v2 內補編排層」。P12 之後做。**中間路線已裁採納（2026-08-31）**：套件補讀取面＋缺口寫入、應用層留主專案（CM-1462 查證結論，推翻原兩選項） |
| **4.4** | CM-1480 | jedi-detection | detection_tools + detection_execution + common/ 檢測碼（**不含 device——D11 定案**，device 歸資產盤點、獨立補殼） | **核心環解完才動邊緣**——它往外只有 participant／flow_engine／notification 各 1–2 條，那些線在 4.1／4.2 已先斷乾淨 | 核心邊緣：往外僅 participant/flow_engine/notification 各 1–2 條，全 port 化即可；grc 對它的 7 條反向依賴不需處理（届時 import 套件）。形態分流：檢測平台有獨立生命週期特徵，開工前先過服務化評估 |
| **4.5** | CM-1481 | jedi-evidence-classification | AI 佐證分類引擎（~42 檔） | **可與 4.4 平行**——零 incoming、與檢測無共用檔案 | 零 incoming；6 條 outgoing 用 `IEvidenceSource` / `IControlCatalog` 兩 port 斷 |
| **4.6** | CM-1482 | 問卷疆界合併（**D10 改寫：不再抽新套件**） | task_survey 作答層**併入 jedi-survey** 成問卷疆界套件 | **排後段因為它是合併不是抽取**——不解核心環、也不擋別人，且問卷作答層與 flow_control 有牽連（任務帶問卷），等 4.2 定形後再併最省事 | 鐵證：兩條 DB 外鍵跨過套件邊界，原「抽獨立新套件」技術上不可行；2026-03 schema 搬遷已把作答表與問卷表放同 schema。合併後照 D6/D7 補殼 |
| **4.7** | CM-1483 | jedi-ai-dashboard | AI 儀表板生成引擎（~30 檔） | **收尾棒**——資料源 registry 是全系統疆界的鏡子，等所有疆界都定形了才知道要對哪些套件宣告介面（4.B 已先把註冊機制換好） | 零跨模組依賴；資料源 registry 深綁本產品 data API，抽引擎、資料源以 port 注入（原 D1 降觀望，D8 解凍收回） |
| 待定序 | 未開卡 | cloud_integration / feedback / setup | Drive 整合（~72 檔）／意見回饋（~25 檔）／開通精靈（~12 檔） | 三支併入第四階段排程時一起定順位（原文保留） | cloud_integration：7 條 flow_engine/participant 線；feedback：斷 2 條 system_config 線即 A 級；setup：與安裝流程綁定深——三支併入第四階段排程時一起定順位 |
| 不抽 | — | associations | 純關聯表膠水（67 檔） | — | 永不獨立成套件——每張關聯表跟著「擁有語意的那端」走，在第四階段過程中逐步溶解 |

### 終局選項（範圍縮減：2026-08-30 user 指示 flow_control 升格第四階段議程）

**flow_control 拆出——移入第四階段議程**（user 2026-08-30 指示；已確認指的是稽核執行業務層那 37 支 service／236 檔，非 flow_engine 的範本管理）：不再等商業決策，Phase 4 規劃時與 P13 同席討論。**拆法已定調（user 同日拍板）：「先單純拆出、切分後議」**——照 jedi-iam 驗證過的「先搬家、後裝修」路徑：第一步整包搬成套件（import 名不變、稽核業務邏輯原樣帶走、機械比對證明零行為改動），第二步「通用流程骨架 vs 稽核專屬業務」的切分等真實第二種流程（問卷審核等）要掛入時拿真需求當設計輸入再做——無第二消費者硬切必畫錯線。技術耦合：D12 已定 oscal → flow_control 單向依賴，OSCAL 疆界設計必須知道 flow_control 自己也會成為套件，否則拆完返工；排序（P12/P13 之前或之後）屆時拿依賴數據定，初步傾向 P12 之後（先解最粗的 participant 邊、搬家包袱最小）。

**終局選項（仍等商業決策）**：project 平台化——產品本體抽套件、主專案變組裝殼。等第二個合規產品線出現再議。

> **另案追蹤（不屬本案範圍）**：RLS 覆蓋缺口——`user_roles` / `user_tenants` / `user_org_units` 三張授權關聯表未啟用 RLS（live DEV 實查確認），現靠「讀取都經過受 RLS 的 users/roles」間接保護；任何直查這三張表的新程式即為跨租戶洩漏風險。已另開獨立 Notion case 追蹤（D9 身分族盤點的附帶發現），不混入 FR-069。

---

## 8. 套件階層依賴圖（living，隨拍板更新）

> 🔴 **本圖描述 D1–D16 拍板後的「目標架構」，不是現況**——現況到目標的路徑見 §7 各階段章節。每次新決策改變套件版圖（合併定形、退役期滿刪除、查證翻案），**本圖與下方狀態表同步更新，並在變更紀錄註記**。決策編號引用以 §3 決策表為 canonical。
>
> 維護慣例：**狀態表是維護主體**（好 diff、好逐列更新），mermaid 圖是給人看全貌的輔助——改表必改圖，改圖必改表。

### 8.1 三層架構（實線＝允許的直接依賴）

架構只有一條實線規則：**疆界插件只准直接依賴 jedi-common（層 0）；插件彼此絕不直接相依**，插件間的協作一律經宿主（層 2）的接線盤走 port 注入（見 8.2）。

```{.mermaid cap="圖 1 — 套件三層架構（目標態）：層 1 插件彼此零直接依賴，全部只踩層 0 地基"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart TB
    subgraph L2["層 2　宿主／產品（接線盤）"]
        HOST["Guidant AI 主專案<br/>核心業務（flow_control／OSCAL 應用層）<br/>＋各插件 adapter 接線＋資源域授權"]
    end

    subgraph L1["層 1　疆界插件（彼此不直接相依）"]
        direction TB
        subgraph L1A["身分與基礎服務"]
            IDENTITY["jedi-iam<br/>（2.5 階段成形：auth＋login<br/>＋mfa＋captcha＋身分中介層）"]
            NOTIF["jedi-notification"]
            FILEUP["jedi-file-upload"]
        end
        subgraph L1B["第一階段五支（已發版）"]
            AIBOT["jedi-ai-bot"]
            INTEG["jedi-integrity"]
            LOGFWD["jedi-log-forwarding"]
            RAGENT["jedi-remote-agent"]
            LICRT["jedi-license-runtime"]
        end
        subgraph L1C["疆界重劃中（第四階段定形）"]
            SURVEY["jedi-survey-suite（暫名）<br/>survey＋作答層（D10）"]
            DETECT["jedi-detection<br/>不含 device（D11）"]
            OSCALV2["jedi-oscal-v2 系<br/>（D12 查證後定形）"]
        end
        subgraph L1D["獨立補殼者（第三階段）"]
            OTHERS["jedi-device／jedi-bulletin／jedi-issue<br/>jedi-system-config／jedi-system-menu<br/>jedi-information-system／jedi-project<br/>jedi-flow-engine／jedi-log（存活半）"]
        end
    end

    subgraph L0["層 0　地基（唯一允許被直接依賴）"]
        COMMON["jedi-common<br/>session／查詢基底（含 JSONB）／例外<br/>身分脈絡包介面／SessionMixin"]
    end

    HOST --> L1
    L1A --> COMMON
    L1B --> COMMON
    L1C --> COMMON
    L1D --> COMMON
```

退役中（D13，標 deprecated 觀察一版後刪）：`jedi-oscal`（v1）、`jedi-resource-store`、`jedi_system_log`（jedi-log 的壞半）。已下架：`jedi-department`（org_units 重複概念）。這四支不入圖。

### 8.2 Port 接線（虛線＝介面宣告，宿主注入實作）

D16 通則的四張標準 port：插件**宣告**需要什麼形狀的服務，**宿主**接線時把提供方的實作（包成 adapter）注入——提供方與消費方互不知道對方存在。

```{.mermaid cap="圖 2 — 四張標準 port 的接線方向：插件宣告（虛線出）、宿主注入、服務方提供（虛線入）"}
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
flowchart LR
    PLUGINS["任一疆界插件<br/>（bulletin／license-runtime／<br/>log-forwarding／survey-suite …）"]
    HOST["宿主接線盤<br/>（register 時注入 adapters）"]
    IDENTITY["jedi-iam"]
    NOTIF["jedi-notification"]
    FILEUP["jedi-file-upload"]
    SYSCONF["jedi-system-config"]

    PLUGINS -. "①身分名冊 port<br/>（user／tenant／org 三名冊）" .-> HOST
    PLUGINS -. "②INotifier port" .-> HOST
    PLUGINS -. "③檔案存取 port" .-> HOST
    PLUGINS -. "④設定讀取 port" .-> HOST
    HOST -. 注入 adapter .-> IDENTITY
    HOST -. 注入 adapter .-> NOTIF
    HOST -. 注入 adapter .-> FILEUP
    HOST -. 注入 adapter .-> SYSCONF
```

### 8.3 逐套件狀態表（維護主體）

| 套件 | 層級 | 狀態 | 決策編號 |
|------|------|------|---------|
| jedi-common | 0 地基 | 已發版 0.0.32（P7 擴充：JSONB 查詢／身分脈絡包／SessionMixin 正名） | D4、D16、正名案 |
| jedi-iam（原暫名 jedi-identity） | 1 | 合併中（2.5 階段：.13/.14/.15/.16/.17 已收；改名棒 CM-1464 已執行完畢，餘 .18 收口發版） | D9 |
| jedi-notification | 1 | 待補殼（維持獨立——12 消費點多與身分無關） | D9、D16 |
| jedi-file-upload | 1 | 待補殼（維持獨立——jedi-issue 為第二消費者） | 疆界地圖① |
| jedi-ai-bot | 1 | 已發版 0.0.1（插件契約首例） | D6、D7 |
| jedi-integrity | 1 | 已發版 0.0.1 | D6、D7 |
| jedi-log-forwarding | 1 | 已發版 0.0.1 | D6、D7 |
| jedi-remote-agent | 1 | 已發版 0.0.1（migration 隨包首例；機器身分不併 identity） | D6、D7、D9 |
| jedi-license-runtime | 1 | 已發版 0.0.1（驗證核心；執法留宿主） | D6、D7 |
| jedi-survey-suite（暫名） | 1 | 待合併（第四階段：jedi-survey＋主專案作答層，跨邊界 FK 鐵證） | D10 |
| jedi-detection | 1 | 待抽取（第四階段 P9，不含 device） | D11 |
| jedi-oscal-v2 系 | 1 | 待定形（第四階段 P13——真三角第三邊為 flow_control，開工前先派查證棒） | D12 |
| jedi-device | 1 | 待補殼（獨立——歸資產盤點語境，非檢測） | D11 |
| jedi-bulletin | 1 | 待補殼（維持獨立套件——user 定案可拔往其他產品；名冊／通知依賴走 port 化） | D14、D16 |
| jedi-issue | 1 | 待補殼（死碼去留「待另議」——boundary-map 原 D16 項） | 疆界地圖⑤ |
| jedi-system-config | 1 | 待補殼 | — |
| jedi-system-menu | 1 | 待補殼（輕量——與 ui_routes 無重複，已查證） | — |
| jedi-information-system | 1 | 待補殼（輕量；FR-032 G 抽取先例） | D4 |
| jedi-project | 1 | 待補殼（凍結與否「待另議」——boundary-map 原 D15 項；project_extensions 收斂會動它，見 CM-1449） | D15-① |
| jedi-flow-engine | 1 | 待補殼（P12 participant 抽出後評估殼內邏輯併回） | 凍結路線圖 |
| jedi-log（存活半） | 1 | 待拆兩半（API 存取紀錄 10 萬筆活資料留；jedi_system_log 壞半退役） | D13 |
| jedi-oscal（v1） | — | **退役中**（deprecated 觀察一版；順帶消 AGPL 依賴） | D13 |
| jedi-resource-store | — | **已退役**（2026-08-30 user 拍板即刻執行：主專案 pin 已拔、deprecated 標記完成；實體下架待觀察期） | D13 |
| jedi_system_log | — | **退役中**（一載入即炸——與 jedi-common 表衝突） | D13 |
| jedi-department | — | **已退役**（2026-08-30 user 拍板即刻執行：主專案註解殘行已清、deprecated 標記完成；實體下架待觀察期） | D9 清理項／D13 追加 |
| jedi-login／jedi-mfa／jedi-captcha | — | 併入 jedi-iam 後消失（舊名留轉發過渡） | D9 |

---

## 9. 端到端驗收（第一階段全案收口判準)

1. 六支新套件（ai-bot / integrity / log-forwarding / remote-agent / license-runtime）上 Nexus，主專案 `pyproject.toml` 以 `==` pin；jedi-common、jedi-auth 各完成一波擴充發版。
2. 主專案淨減約 220 檔；`common/` 只剩資源域 authz、業務工具與真正的主專案膠水。
3. 守衛測試全綠：每支套件源碼 `grep` 0 處主專案 import；common/ 反向 import 0 條。
4. BE 全功能手測清單過（AI 聊天、防篡改 gate、log 轉發、agent 心跳派工、license 啟用、四角色權限、檔案上傳）。
5. **六支新套件全部通過拔掉測試（D6）**：逐支註解 app_modules.py 註冊行 → BE 正常啟動、該模組路由/選單消失、其餘功能不受影響。
5b. **六支新套件 harness CI 全綠＋接入 README 齊備（D7）**：每支套件 standalone harness 可獨立起、套件測試在 harness 內全綠並掛上 CI；接入 README（port 清單/註冊行/migration 套法/quickstart）齊備且 quickstart 實走過。
6. `docs/claude/jedi-packages.md` 補齊六支新套件段落（並順帶補上既有未登記的 13 支）。
7. monorepo 各套件 repo 側 commit 完成（push 與發版時點依鐵則等 user 明示）。
