歷程:
7e87c2ca)、Notion 開卡 9 張(CM-1435 母+CM-1436~1443 子,連號)。6a0aa72c+6f3d02cd);P1 定插件契約首例+extraction-sop.md 七階段(43bd2ec1),發版 cbcff277。d8cca74c)+P4(jedi-remote-agent c9590b3f)平行;P4 定 migration 隨包五規則(SOP §4.2 首例)。合併發版 327a5963。3de631da,含端到端「迷你接收器真的收到 log」實測)+P5(jedi-license-runtime 7d07d62b,驗簽引擎逐行比對零改動、執法留宿主)平行。合併發版 6daa0f67。cb85f193)、批二通用工具與 enum(0d63684d)——先加後刪、主專案留 re-export shim、124+ 處消費端零改動。jedi-common 未發版(與 jedi-auth 併 P8 後一起,等令)。base_repository.py 誤判為「舊代待汰換」,發了清理棒;子棒查證發現它是 lazy session mixin、BaseRepositoryImpl 自己就繼承它、5 個目標檔全屬合法特殊 repo 用法——按紀律煞車回報,未改任何檔。結論改為「不汰換,第二階段正名(SessionMixin)+刪 0 引用的頂層 base_model.py」,入 design.md(ac9e93f3)。途中一支基於錯誤前提的記帳文件棒被叫停、半成品編輯已還原。b497e5c7)→ D6 插件契約(隨 6f3d02cd 入庫)+四插槽指引(5e8f711e)→ D7 harness+README(dbaf4bdc)→ D8 全路線解凍+四階段+身分脈絡標準件(e2a394a4)。教訓(後棒引以為戒):
status,CLAUDE.md 寫 code;比對搬家前後證明兩版一字不差,是文件債不是搬壞。差點誤判 P7。P7/P8 收口與驗收要點:
cb85f193 response_util 批一/0d63684d 通用工具與 enum 批二),全程「先加後刪」——主專案舊位置全改 re-export shim,124 處消費端零改動、零檔案被碰。jedi-common 內主專案 import 0、自帶測試 153 passed。順帶第一刀 base_repository 清理案被執行棒查證推翻(見上方教訓 2),收斂改走「第二階段正名案」(commit ac9e93f3)。jedi_auth.authz、資源域四檔+license 軸留主專案;126 處消費點零改動(shim)。四個守門 error code 進套件 AuthzErrorCode 但字串值凍結 GRC_*(FE i18n key,實地驗過 8 筆翻譯都靠它)。權限行為實測:admin 200/無 token 401;主專案守衛擴到 47 passed。CLAUDE.md authz 段由 P8 棒同步更新(「改主體域改套件,勿在 shim 補邏輯」)。發版第四輪(收官輪):jedi-common 0.0.32+jedi-auth 0.1.35 上 Nexus(monorepo 版號 commit a206cf7),主專案 pin commit ac696b02,poetry.lock 無連帶升版;全域煙測綠(守衛 47/auth/信封/authz decorator 三鏈 200/無 token 401)。至此七支套件發版全清。
身分族兩棒深度分析 → D9 立案:耦合盤點(login→auth 21 處穿透 infra、五支發版全同批、B 類通用膠水 1,300 行+C 類重複 320 行、死鏈三條)+全站漏網掃描(jwt_mw/socketio 驗簽/user_context_builder/安全政策定義等約 930 行主專案側身分程式;四髒:jedi-department 殭屍、root 判定三份、roles.is_admin 殭屍旗標、login 手測腳本)→ D9:合併為 jedi-identity,插隊 2.5 階段(commit ba44d274)。notification 獨立、agent 機器身分不併。前置確認:jedi-* 僅 Guidant AI 消費。
疆界盤點案立案(FR-069.9/CM-1446,commit 06266329):第四階段開工門檻——照 D9 方法對 26 支套件+主專案候選做疆界地圖;五組候選(檔案/問卷/檢測/OSCAL/追蹤)+兩項退役查證(jedi-log、jedi-oscal v1);願景 26→12–15 支疆界套件。
第二階段開卡:CM-1447(.10 ext 表全庫盤點)/CM-1448(.11 jedi-common 查詢層:JSONB+身分脈絡包+基底正名)/CM-1449(.12 收斂實作 auth/user 首批,等前兩張)。RLS 覆蓋缺口另案 CM-1445(三張授權關聯表無 RLS,獨立安全債不掛 FR-069)。
教訓補一條:
第二階段三棒收口與驗收:
b459da74):190 張表掃出 9 張延伸表形態——收回主表 1(project_extensions)/留置 6/待決策 2;推翻原假設:全庫沒有任何「純展示型客製欄位」,側掛表是「跨套件邊界」長出來的不是客製需求 → JSONB 容器定位改「預防性建設」(D15)。auth/user 域無可收斂表,.12 首批改打 project_extensions。順手發現三則(users.topt_secret 死欄位/projects RLS 停用但 policy 殘留/孤兒表)。涵蓋性自證:四偵測面 SQL 全附可重跑;subagent 初報 raw SQL JOIN 12 處、自己復驗實為 14 並註明。2394200):三件套全「用加的」——JSONB 查詢(_ext_/_extlike_/_extin_/_exthas_/_extcontains 前綴+排序分頁;袋內一律 AND 不混 OR 群組;缺 key 不炸;未宣告袋子的 repo 靜默忽略);identity/ 身分脈絡包(三名冊 port 同構);SessionMixin 正名(舊名 base_repository 留轉發)。jedi-common 測試 215 passed。4c6b90a8+review 修 6f1608e5、jedi-project 0d3ea71):四欄收回主表(皆帶業務邏輯轉正式欄位非 JSONB);五步搬遷走到第 4 步「切讀取」,drop 舊表等令;14 處 raw SQL JOIN 改完 grep 殘留 0;DEV 實查回填 211 列零不一致;STG 實查未被套(環境紅線守住)。讀取端「一手切完」(覆寫 ext repo 的 get_one_by_fields 由主表供值);雙寫收在 repo 層單一入口;審計欄不互蓋(實查兩表本記不同事)。user 手測一度誤連未套 migration 的庫報 UndefinedColumn——教訓見下。Postman 全站 API 設定檔(CM-1454,38e8734a):交付「產生器即真相」——scripts/gen_postman_collection.py 產 collection(637 支/21 資料夾)+environment+端點對照表;驗收時首腦重跑產生器 git diff 空=與路由表零落差;憑證掃描 0。掃出 62 支裸路徑死端點(23 個 Resource 雙路徑註冊缺 handler;runner 逐支查 FE 零裸打、線上零影響;初判 84 支、改讀實際 signature 收斂 62——自我修正值得記)→ 裁決「開追蹤卡隨棒順手拔」= CM-1461(705d8401),design.md 第三階段隨棒必做補一行。
發版第五輪:jedi-common 0.0.33+jedi-project 0.0.9(monorepo 593334e、主專案 pin 0f8c1b93)——九支全正式版、主專案零 path 依賴。push 委任:user 明示「push 到分支授權給你做」=兩 repo feature/FR-069 收口順手推(memory feedback_fr069_push_delegated_branch_only;main 仍禁)。
兩包退役(user 拍板追加 D13):jedi-department(四面查證零使用——程式/venv/DB 表不存在/套件依賴全零)+jedi-resource-store(程式 e2e DB 全零但主專案仍 pin)。退役棒:monorepo 標記 d765191、docs 29e124bd;pin 拆不開處置——working tree 的 pyproject 混有 1455 的 path override 同檔拆不開,pin 移除留工作區、docs 先行記錄、託付 .18 收口棒一併入版。
2.5 階段拆卡六張(CM-1455–1460):.13 地基(骨架+遷入策略 A/B 案查證定案)→ .14 login/.15 mfa+captcha/.17 清理三棒平行 → .16 中介層 → .18 收口。地基棒已發(user 側 session),working tree 已見 jedi-identity 0.1.0+jedi-auth 轉發殼 0.1.36(疑似 A 案)。
教訓補兩條:
⚠️ 本 block 只記 3.1 這一棒(runner 視角)。2.5 階段 .13~.18 與 .H、改名 .G、 第三階段拆卡的歷程由首腦 session 記錄,本檔上方 2026-08-30 之後若有空缺以 STATE 現況清單與各 Notion 卡為準。
交付:身分模組從「套件出 service、route 留宿主」升格為真正隨插即用的插件。 commit:monorepo e614c34(本體)+d6b0dfa(blueprint 改名)/主專案 e483ba17 (本體)+d1a141c2(檔名改名),兩 repo 已 push feature/FR-069。
六件工作:①route 上移(39 條進 jedi_iam/api/,主專案 api/auth/ 26 檔刪除); ②內部結構重整(頂層 common/code 與 common/enum 兩套合一、mfa/common 併回頂層、 mfa/domain/entity→entities);③四殼退役(主專案 80 檔改名+拔 pin+死名守衛); ④D16 port 化(三支認證 adapter 的 10 處自建 RepoImpl 收進 AuthRepositories); ⑤GoogleAuthAdapter 缺 __init__ 的 TypeError 修掉;⑥README 架構節+README.html。
設計上的關鍵取捨(後 11 支照抄,全文見 STATE「照抄要點」):
ctx().service(name) 而非 @inject——Provide[Containers.x.y] 要在 import 期寫死具體 container 路徑,那是宿主的組裝知識,寫進套件=套件反依賴主專案。 改由 register() 把 provider 名冊放進 app.extensions。存 provider 不存實例: 主專案 service 多為每 request 建新的(內含 session-bound repo),存實例是那種平常 看不出、併發才爆的壞。_assert_api_wiring)——不是「有就用沒有就跳過」。跳過的 後果是身分端點全變公開,而服務照常起得來、健康檢查照樣綠燈。三個插槽刻意無預設值。驗證:拔掉測試(mount_api=False → 16 支端點全 404、其餘模組無感);harness POST /login 走通拿到真 JWT(不用 DI 框架、不碰主專案一行);行為表 8 列; URL/能力點/error code 三組集合機械比對相等;突變測試兩發(關掉守門→4 紅、 拼錯一條 URL→1 紅);套件 357 綠、守衛 53 綠;8000 重啟煙測全 200 log 零 ERROR。
隨棒:CM-1461 身分 13 支死路徑照 .18 正解拆 Create Route 類(500→405,POST 仍活),已回該卡勾銷;依賴衛生順掃(套件補三個 route 層相依宣告)。
追加改名(user 指示「以前叫 identity 的換 iam」):blueprint identity→iam (.13 殘留、.G 漏改;三 repo 對 identity.* endpoint 引用皆 0,對外 URL 零影響)、 common/identity.py→iam_ports.py、core/identity_wiring.py→iam_wiring.py。
教訓(接續上方編號):
POST /login,一路挖出四道宿主前提(init_db/PROPAGATE_EXCEPTIONS=True/ JWT_IDENTITY_CLAIM/column_property 用的 6 支 DB 函式),原本 harness 只記兩道。 其中 PROPAGATE_EXCEPTIONS 最陰:flask-restful 的 Api 會包掉 handle_user_exception 自行處理所有例外,於是 register_error_handlers 註冊成功卻完全不生效, 401/403/404 一律變 500——「註冊成功但不生效」這種病,靜態看程式碼看不出來。"identity." (contract 測試與 harness),它們壞掉的方式是靜默匹配到 0 條而不是報錯——測試會 變成「掃了個寂寞卻顯示通過」。已改成從 plugin.DEFAULT_BLUEPRINT_NAME 取。identity 兩種意思混用。 JWT_IDENTITY_CLAIM/user_identity 欄位/get_jwt_identity()/resolve_identity() /Accept-Encoding: identity 都不能改(對外契約或第三方 API,改 JWT claim 名會讓 所有既有 token 當場失效)。我一度把 test_middleware_identity_core.py 也改了, 翻開發現它測的正是 resolve_identity 就改回去——檔名要跟著它測的東西走。common/ 不得反向 import 上層是活的守衛:宿主接線檔初版放 common/,被 test_common_has_no_reverse_imports 當場抓到(它要 import app/ 與 infra/)。 組裝根的正確位置是 core/,與 app_factory.py 同層。歷程:
jedi_identity 不留殼直接死名)。docs/features/architecture-handbook/——插件架構指南+模組化路線圖,自 FR-069 站移出改名);插件指南擴至九節(使用流程五步/接線 QA 三題/資料關聯三律/查詢實戰 QA 六題+API 歸屬總表——「各檔口的菜檔口自己賣,套餐才開櫃檯窗口」)。docs/analysis/2026-08-31-cross-boundary-aggregation-api-inventory.md:最重聚合在 infra 跨 schema raw SQL 非 service 注入——8 支聚合 query 約 2,000 行、冠軍一句 SQL 19 表七疆界;真聚合端點 25–30 條;三條建議待裁——readmodel 正名/AI registry 自註冊/TaskAssigneeService 切法)。教訓(後棒引以為戒):
git commit <files> -m),事後 reset --soft 拆開可救;平行棒在跑期間首腦最好完全不 commit。歷程:
python -m pytest 否則假紅的坑在此發現)→ 3.6(CM-1472 退役四支,monorepo 26→24)→ 發版裁決:user 質疑四殼為何要發,重盤後裁不發改退役(.18 清單的前提已被 3.1 拔 pin 推翻)→ CM-1473 發版棒(11 支上 Nexus、pin 入版收三批託付、四殼刪除 24→20、守衛換軌)。教訓(後棒引以為戒):
git diff --cached --stat 核清單,已入所有後續 prompt。背景:CM-1482 問卷疆界合併(P11)搬家後,user 手測抓到 8002 問卷共編整組失效—— 進問卷頁跳「即時同步失敗」,join/update 一律回「更新失敗,請稍後再試」。
歷程:
97133497):實測複現坐實根因——socket handler 搬進套件後走 ctx() 讀 app.extensions["jedi_survey"],而該鑰匙只在掛 REST blueprint 時由 record_once 寫入;socketio 模式刻意不載 REST(FR-063.1c 不可倒退), 於是每個事件呼叫 ctx() 就 RuntimeError。連線與身分驗證皆通過(排除 auth/Redis/CORS); 不載 REST 的 app 其 extensions 清單實查確認無 jedi_survey。notification 的 socket handler 不經 ctx() 故未中招,對照吻合。register(..., mount_api=False) 簽名有、 行為不夠——該分支只做 _configure_runtime() 就返回,不寫 extensions。即 「不掛路由」與「拿得到 context」在套件裡是綁死的二選一,宿主端無論怎麼接線都補不出。 照卡片邊界停手回報(不繞道、不在 8002 掛 REST、不自行改套件)。為確認「缺的只有這一項」, 本機暫時改 site-packages 驗一輪即還原(md5 比對確認與發版版本逐位元組相同)。28e8be3,jedi-survey 0.1.1 上 Nexus)+主專案 pin 跟上(9312ba3d)。語意刻意與 record_once 一致 ——無條件覆寫、最後一次贏(那邊是 __setitem__ 不做 already-exists 檢查; library 模式若寫成「已存在就跳過」,同一 app 先後接兩份 adapters 會得到相反結果)。/surveys/menu 回 200;守衛 77 綠(併 iam_wiring 85); 兩模式重啟乾淨;DB 零淨異動(探測值與在線名單殘留皆清)。突變兩發:拿掉寫入 → 2 failed、 改 skip-if-exists → 1 failed。user 瀏覽器手測通過收 Done。決策:
教訓(後棒引以為戒):
mount_api=False,簽名確實有, 但實際行為缺一半。契約要看實作不看簽名;runner 照邊界停手回報,是這次沒被繞成 「在 socket 模式掛 REST」(會倒退 FR-063.1c)的原因。tests/unittest/ 有 48 failed/42 errors,用 git stash 抽掉改動跑同一份指令對照確認改動前就存在, 本次是 +2 passing。不先量 baseline 就會把存量債誤記在自己頭上(同型教訓見第 12 條 3.2 的 16 ERROR 案)。推翻了什麼:
pip install -e 六支,poetry install 即可(該條交接事項已從 STATE 移除)。歷程:
mount_api=False 簽名有行為不夠,補 4 行發 0.1.1);②檢測「agent 怪怪的」=DB agent uid 與 agent 自持 uid 漂移(JWT aud 對不上)+分派表也存舊 uid+.151 SSH 公鑰遺失,三處修完派工鏈通;③SMTP 測試信=CM-1496 修(.get() 用錯+RLS 讀不到 ROOT 設定)。GRC_400105 一碼兩用修成 GRC_400121 三 repo 同步。/socket.io 錯指 8000 的既有設定錯誤。教訓(接續編號):
mount_api=False 逃生門存在但不寫 extensions,宿主端無論怎麼寫都補不出鑰匙。契約要看實作不看簽名;runner 照邊界停手回報而非繞道(繞道=在 socket 模式掛 REST,會倒退 FR-063.1c)。