| 項目 | 內容 |
|---|---|
| FR 編號 | FR-032 |
| 提出日期 | 2026-06-04 |
| arc 關聯 | relates to FR-018(MF 範本預設)/ 概念上對齊 jedi_information_system 受評標的 |
| 狀態 | Design(brainstorm 完成,待 spec review + 寫 implementation plan) |
| 本期範圍 | A 範本層系統資產表 + picker、B 啟動專案 clone、C 專案內 SSP 編輯、D OSCAL 匯出 |
| 出 scope(後續分階段) | E 匯入回灌、F device 也加 picker、G jedi_information_system 拆套件 |
合規資源庫範本(Module Frame)與專案 SSP 目前的「資產清冊」只盛裝設備(device 性質的資產:IPv4 / MAC / 主機名稱 / 作業系統…)。實務上做資產盤點時,資訊系統本身也是一種資產,需要跟設備並列在盤點清冊裡。
使用者希望在 /module-frame/<uid>/template-edit 的「系統元件、外部授權與資產」tab,於既有「資產清冊」下方再多一張「系統資產」表,可以:
information_system 在系統中目前對應 OSCAL SSP 的 system-characteristics(受評標的):一份 SSP 只有一個受評主體。本期不改變受評標的的角色與資料。系統資產是「同一筆 information_system 在別的範本 / SSP 中以資產身分被引用」的新視角,與受評標的並存。
| 系統概念 | 對應 OSCAL | OSCAL 層 | 角色 |
|---|---|---|---|
| 合規資源庫(Module Frame) | Profile / Baseline | Control Layer | 評分標準(控制項基線) |
| 受評標的 | SSP → system-characteristics | Implementation Layer | 被評的主體系統(一份 SSP 一個) |
| 資訊系統(information_system) | system-characteristics(受評標的視角)/ inventory-item(資產視角,本期新增) | Implementation Layer | 雙重身分 |
合規資源庫(Profile)與受評標的(SSP)是不同層;受評標的與資訊系統現況是同一概念的兩個名字。本期讓資訊系統多一個資產身分。
同一筆 compliance.information_systems:
採用專案既有的 soft-ref + 快照 + 自由輸入模式(對齊 OSCAL party / 參與人員 matching:matched_user_id soft ref + enrich 顯示快照 + 配不到維持自由輸入):
ref_type + ref_id)選此模式而非純 reference / 純 copy 的理由:
| 做法 | 系統無資料時 | 匯出路徑 | 文件自包含 | 稽核快照 |
|---|---|---|---|---|
| 純 Copy(現況 device) | 可自由輸入 | 不改 | 是 | 凍結 |
| 純 Reference | 卡住(沒得選不能填) | 要新寫 join | 否 | 會被事後改 |
| **Hybrid(採用) | 可自由輸入 | 不改**(props 本來就有值) | 是 | 凍結 + 可溯源 |
設備維持 copy 行為不動(jedi-device 未接入,本就無 master 可鉤稽);系統因有 master(information_systems)才走 hybrid。兩者同桌(共用 inventory 表),靠 asset_type 鑑別,UI 渲染成兩張表。
在 inventory 表(範本 default 或 SSP copy)的任何編輯只動本地列,永不回寫 compliance.information_systems master。改 master 只能走資訊系統管理頁。
於 compliance.module_frame_inventory_item_defaults(範本層) 與 oscal.ssp_inventory_items(SSP 層) 各加:
| 新欄位 | 型別 | 說明 |
|---|---|---|
asset_type |
VARCHAR | 鑑別欄:hardware(既有設備,backfill 預設)/ information_system(系統資產) |
ref_type |
VARCHAR NULL | soft-ref 類型:information_system(本期);未來 device。自由輸入為 null |
ref_id |
VARCHAR NULL | 鉤稽 master uid(information_systems.uid)。自由輸入為 null |
Migration:
asset_type='hardware'、ref_type/ref_id=NULL → 設備行為完全不變GRANT 沿用(無需新授權)-- Date: YYYY-MM-DD(per SQL migration 規範)cmmgr 帳號跑(cm_app 受 RLS)| 落點 | 內容 |
|---|---|
description 欄 |
系統描述(OSCAL inventory-item description 必填;空則 fallback 系統名稱) |
props(JSONB) |
{ name, abbreviation, owner_id, owner_name, deployment_model, system_status } |
asset_type |
information_system |
ref_type / ref_id |
從清單挑:information_system / 系統 uid;自由輸入:null / null |
不放 CIA(security_objective_*)與 authorization_boundary — 那是受評標的層級,避免兩邊重疊混淆。
審計欄位:owner_id 配 owner_name(nickname)。注意:本設計的 owner_name 是 snapshot 當下凍結進 props 的值(符合 §2.2「凍結」原則),讀取時不再 re-enrich。這與一般「審計欄位 read 時批次轉 nickname」慣例不同 — 系統資產是時間點快照,刻意保留挑選當下的 owner nickname。snapshot 寫入時(BE 抓 master 那一刻)查 User.login_name → nickname 一次即可。implementation plan 要明確標示此點,避免被誤改成 read 時 re-enrich。
A. 範本層(template-edit 頁)
「系統資產」表 →「新增系統資產」
├─ 從清單挑:picker 撈 /information-systems/menu → 選定
│ → BE 用 ref_id 回 master 撈 → snapshot 欄位進 props + 存 ref_type/ref_id
│ (snapshot 由 BE 抓 master,不信 FE 傳值)
└─ 自由輸入:直接填 → ref 為 null
存入 compliance.module_frame_inventory_item_defaults(asset_type='information_system')
│
│ B. 啟動專案 clone(沿用既有 INSERT...SELECT,補上 3 個新欄位)
▼
oscal.ssp_inventory_items:props 快照逐字複製、ref 帶過來 → SSP 凍結評估當下狀態
│
│ C. 專案內 SSP 編輯(同表、同元件、同 picker;編輯只動該份 SSP copy,與 master 分岔可接受)
▼
D. 匯出:mapper 對 asset_type='information_system' 列吐 inventory-item + asset-type prop
clone 的最小改動:現有 clone step 已逐字複製 props,只需把 asset_type / ref_type / ref_id 加進 INSERT...SELECT 欄位清單;系統資產列同桌故自動 clone,不另寫管線。
沿用 /module-frame/<uid>/inventory 的 POST/PUT/DELETE,payload 多帶 asset_type + 選填 ref_type/ref_id:
ref_id 有值 → 注入 information_system_domain_service 回 master 撈、組 props 快照ref_id → 收 FE 自由輸入的 propsasset_type / ref_type / ref_id 欄位:既有 inventory route 若用嚴格 use_kwargs,未宣告的新 payload key 會在進 service 前被 silently drop(plan pre-flight 要驗 schema 是否嚴格、補欄位)沿用 FR-011.3「專案內直接編輯 SSP」既有的 SSP inventory 編輯端點,同樣加 asset_type 分支。 實作期 pre-flight 必驗:FR-011.3 既有端點的實際路徑與 service 方法(plan vs reality)。
直接重用既有 GET /information-systems/menu,不新增端點。
GRC_<HTTP><序號> 命名):information_system ref_id 無效時拋 NotFound
common/code/grc_error_code.py 查最大序號 +1,避免序號被佔(per plan vs reality)@transaction;snapshot 抓 master 走 information_system_domain_service驗證確認「資產清冊」live path = module_frame_inventory_item_defaults(範本,經 /module-frame/:uid/inventory)→ clone → oscal.ssp_inventory_items(SSP)。但匯出 loader 沒跟著 §11.29 遷移 repoint:
MfSspContentLoader(範本匯出)讀 resources(= 廢棄的 ssp_components devices/info_systems),不是 module_frame_inventory_item_defaults → 連 rich 設備列現在都匯不出(既有遺漏)SspVersionContentLoader(專案 SSP 匯出)讀永遠空的 ssp.system_implementations → inventory/components/leveraged 全空ssp_yaml_mapper)同讀空的 system_implementations把 docx/pdf/odt 匯出的 inventory builder repoint 到 live 表,同時吐 hardware + information_system:
MfSspContentLoader._build_inventory_items → 改讀 module_frame_inventory_item_defaults(按 asset_type 分流)SspVersionContentLoader._build_inventory_items → 改讀 ssp_inventory_items(按 asset_type 分流)SspInventoryItemExportItem.device_type='information-system' 區分;或視 generator 渲染另立 sub-section)system_implementations 是跟本功能無關的更深層既有缺陷(影響整個 system-implementation 區塊)→ 列 follow-up,不在 FR-032 修system-name/deployment-model 等 props)屬 OSCAL-native path,遞延到該 path 修復時再落地;本期 docx 走 rendered 列ref_id 本期不寫進任何匯出(round-trip 是 E 期)src/config/api/api.js 常數,per FE CLAUDE.md)| 決策 | 取捨 |
|---|---|
| 不做「重新帶入 / re-sync」按鈕 | 要更新就重選一次系統即可,省一個維護面 |
| snapshot 由 BE 從 master 抓 | 不信 FE 傳的鉤稽快照值,確保資料正確 |
| device 不在本期加 picker | jedi-device 未接入,留 F 期 |
| # | 待驗項 | 結果 |
|---|---|---|
| 1 | 匯出路徑聚合關係 | 已釐清:docx/pdf 走 MfSspContentLoader(讀廢棄 ssp_components)/ SspVersionContentLoader(讀空 system_implementations);OSCAL-native 走 ssp_yaml_mapper(讀空 system_implementations)。決策 D1-A:repoint docx/pdf loader 到 live 表;OSCAL-native 列 follow-up(§6) |
| 2 | SSP inventory 編輯端點 | 已確認:api/oscal/routes/ssp/ssp_inventory_items_route.py(GET/POST/PUT/DELETE /ssp/<ssp_uid>/inventory-items)→ SspInventoryItemsAppService;payload 走 raw request.get_json(),無嚴格 schema |
| 3 | clone INSERT...SELECT | 已確認:module_frame_template_copy_service.py:270-294 Step 6(10 INSERT 欄、需補 asset_type/ref_type/ref_id) |
| 4 | error code 序號 | 已確認:最大 GRC_404034 → 用 GRC_404035 |
| 5 | inventory route / schema 嚴格度 | 已確認:/module-frame/:uid/inventory(module_frame_inventory_service)走 raw request.get_json(),新 key 不會被 drop;ORM ModuleFrameInventoryItemDefault / entity 目前無 asset_type/ref 欄位,需補 |
已廢棄但可借用的成熟邏輯:SspResourcesContextService(§11.29 前的 info_systems 機制)已實作 information_system soft-ref + matched_* enrich + information_system_domain_service 注入 —— 搬這套邏輯到新 inventory 機制,不重新發明。
A/B/C/D/F 收尾(2026-06-04)逐條列出與原設計的偏差與原因。E-a 換 session 接手(見 handoff)。
| # | 偏差 / 落地實況 | 原因 |
|---|---|---|
| 11.1 | error code 沿用既有 GRC_INFORMATION_SYSTEM_NOT_FOUND(404020),未用原規劃的 GRC_404035 |
pre-flight 只比序號漏看名稱,發現已存在 → 沿用;404035 改給新 GRC_DEVICE_NOT_FOUND(F) |
| 11.2 | jedi-oscal 不需 poetry update,BE 重啟即生效 |
venv 內 jedi_oscal 是 symlink editable(指向 source);原 handoff 假設要 poetry update |
| 11.3 | 系統資產的 hybrid soft-ref 邏輯抽成 common/util/system_asset_snapshot.py 共用 |
範本層 + SSP 層兩入口共用(DRY),非各自 inline |
| 11.4 | 匯出(D)實況比 spec 複雜:原設計假設「加 asset-type prop 到 inventory-item」,實際匯出層是壞的 | 驗證發現 docx loader 讀廢棄 ssp_components / 空的 system_implementations;採 D1-A:repoint docx loader 到 live 表,OSCAL-native 列 follow-up |
| 11.5 | docx 系統資產獨立成 System Assets 表(非混入 inventory-item 帶 asset-type prop) | user 回報混表 + IP/OS 欄位不適用系統 → 拆獨立表(Name/Abbreviation/Owner/Deployment/Status);新增 SspSystemAssetExportItem + partition_inventory_rows |
| 11.6 | ssp_version docx 匯出原本就壞(ssp.system_implementations AttributeError),順手修 getattr 防呆 |
該路徑以前沒人跑過,repoint inventory 時引爆;components/leveraged 仍空(follow-up) |
| 11.7 | F(device picker)採 merge(device 填 hostname/ipv4/os,其餘 hardware 欄位保留 FE 輸入),非系統資產的 full-replace | DeviceEntity 只有 hostname/ip/os 三欄,full-replace 會丟失 mac/asset_id 等 |
| 11.8 | F 為了「挑選當下表單即帶 hostname/os」擴充 jedi-device device menu(加 hostname/os) | menu 原本只有 uid/name/ip;jedi-device 是 pin → dev 改 path dependency + user poetry update |
| 11.9 | F 順手把「資產清冊」文案改「設備資產」、「描述」改「名稱」(col_device_name 獨立 key) | user 要求;不共用 col_inventory_description 以免動到系統資產表的描述欄 |
| 11.10 | 廢棄機制(SspResourcesContextService,§11.29 前的 info_systems/devices)保留未動 | 走新 inventory 機制,廢棄機制不在本期 scope(避免 scope creep) |
| # | 偏差 / 落地實況 | 原因 |
|---|---|---|
| 11.11 | 系統資產走獨立「系統資產」sheet(非塞進「資產」sheet 加 asset_type 欄) | 系統欄位(name/abbreviation/owner/deployment/status)跟設備(IP/MAC/FQDN)不同;對稱 docx 獨立 System Assets 表 |
| 11.12 | 系統資產匯入依 name 比對 master(非 uid);deployment/status enum 直接用 OSCAL key | Excel 給名稱不給 uid;exact name match → 配到設 ref + master snapshot / 配不到自由輸入。enum 用 OSCAL key 免反向 mapping(master 也存同 key) |
| 11.13 | 範本 template-edit 下載填值原本「資產」+「系統資產」都空白 | MF-scoped _populate_filled_data 寫死 inventory_items=[](舊缺口)且未注入 MF inventory default service → 注入 + 新增 _fetch_mf_inventory_items/_fetch_mf_system_assets 依 asset_type 分流(commit e51bd809) |
| 11.14 | 「資產」改名「設備資產」+ 第一欄 header 描述→名稱 | user 要求對齊 docx / FE live 表;header i18n 改值,column key 仍 description(OSCAL 必填);舊名「資產」加 legacy alias |
| 11.15 | 設備資產 / 系統資產比照「參與人員」加 picker(matched_device / matched_info_system 下拉 + autofill) | 復用既有 v2 device/info_system lookup + autofill 基建(DEVICES/INFO_SYSTEMS helper);picker 放欄位尾端保 backward-compat(舊樣板無此欄 parser 讀 None 不錯位) |
| 11.16 | 設備鉤稽走 merge(hostname/ipv4/os 覆蓋、其餘 hardware 欄位保留),系統資產走 full-replace(snapshot) | DeviceEntity 只有 hostname/ip/os 三欄,full-replace 會丟 mac/asset_id(對齊 §11.7 F 期 merge 語意) |
| 11.17 | 系統資產匯入端不讀 matched_info_system(仍依 name 比對);設備匯入端讀 matched_device | autofill 已把 name 填成所挑系統 → name match 等效;設備 picker 才是 device 鉤稽的 key(device 名稱識別度較低) |
| 11.18 | 下載填值 round-trip 回填 picker(已鉤稽列反查 master 填 matched_device 'name (ip)' / matched_info_system 'abbr - name') | 下載 → picker 顯示已選 → re-upload 可重新鉤稽,round-trip 完整 |
| 11.19 | Excel VLOOKUP autofill 依賴 client 計算模式(MS Excel 手動模式不即時帶入) | openpyxl 寫的公式無 cached value,靠 fullCalcOnLoad + Excel 自動重算;公式經 LibreOffice recalc 驗證正確(非 bug)。不影響匯入結果(BE 從 master 填欄位)。follow-up:可於「說明」分頁加重算提示 |
jedi-information-system==0.0.1 推 Nexus;見 analysis 可行性評估 + changelog 2026-06-04-tweak-fr032-g-jedi-information-system-extract.md)