Track B 前置分析報告

日期:2026-05-21 狀態:分析完成,待 raymond 確認決策點後開工 範圍:B1 ~ B6 六個 phase 的現況盤點、設計選項、疑問清單


§1

一、現有基礎設施盤點

已可直接利用

資源 路徑 B 階段用途
SystemSecurityPlanYamlMapper jedi-oscal/.../ssp_yaml_mapper.py B5 YAML/JSON 序列化基礎
ModuleFramePartyService.list_parties() app/module_frame/service/ B2 MF 來源 parties
ModuleFrameSspResourcesService.list_resources() app/module_frame/service/ B2 MF 來源 devices/info_systems
ModuleFrameControlDefaultService app/module_frame/service/ B2 MF 來源控制項說明
ModuleFrameControlObjectiveDefaultService app/module_frame/service/ B2 MF 來源 AO 說明
ModuleFrameReferenceDocumentService app/module_frame/service/ B2 MF 來源程序書
SspVersioningService app/oscal/service/ B2 SSP 版本來源
SspControlImplImportService.export_excel() app/oscal/service/ 唯一現有 export 模式參考
ControlImplementationObjectiveMapper jedi-oscal/.../ssp/ B5 AO 序列化參考
CMMC SSP reference docx docs/features/.../reference/ASIA-CMMC-SSP-DRAFT-with-user-info-202604.docx B1 章節結構來源

yaml_mapper 現有覆蓋範圍

OSCAL block 現況 B5 需補
metadata.title/version/published/last-modified/oscal-version ✅ 已支援
metadata.parties[] 未序列化 需補
metadata.responsible-parties[] 未序列化 需補
metadata.roles[] ❌ 未序列化 視需求補
system-characteristics.system-name/description/status ✅ 基礎支援
system-characteristics.security-sensitivity-level
system-characteristics.authorization-boundary 未序列化 需補(SC 有 scope_description)
system-characteristics.security-impact-level 未序列化 有 CIA 三欄需補
system-implementation.components[]
system-implementation.inventory-items[]
system-implementation.leveraged-authorizations[]
system-implementation.users[] ❌ 不做(requirement-understanding §2 決議)
control-implementation.implemented-requirements[].description/status
control-implementation.implemented-requirements[].statements[] 未序列化(ci.objectives 沒走) 需補(AO = OSCAL statements)
back-matter.resources[] 未序列化(程序書) 需補

結論ssp_yaml_mapper.py 約完成了 55% 的 OSCAL SSP 序列化。B5 需在 jedi-oscal 裡補 4 個區塊:parties、statements(AO)、back-matter、部分 system-characteristics 欄位。


§2

二、B1 — Docx 樣板 + Generator 骨架

章節結構建議(從 CMMC SSP draft 推導)

封面 (Cover)
版本更新紀錄表 (Version History)
目錄 (TOC — Word auto field)
├── 1. 系統概述 (System Overview)
│   ├── 1.1 系統基本資料 (system-characteristics: name / abbreviation / description)
│   ├── 1.2 系統邊界 (authorization-boundary)
│   ├── 1.3 網路架構 (network-architecture)
│   └── 1.4 資料流 (data-flow)
├── 2. FIPS 199 安全分類 (Security Classification)
│   └── 機密性 / 完整性 / 可用性 + overall sensitivity
├── 3. 系統所有人與聯絡人 (Parties / Roles)
│   ├── 3.1 組織 (parties type=organization)
│   └── 3.2 人員 (parties type=person)
├── 4. 授權邊界系統元件 (System Implementation)
│   ├── 4.1 設備清單 (inventory-items / hardware)
│   ├── 4.2 資訊系統 / 元件 (components)
│   └── 4.3 外部利用服務 (leveraged-authorizations)
├── 5. 控制項實作說明 (Control Implementation)
│   └── 5.x <control_id>: description + status
│       └── 5.x.y <statement_id> (AO): description + status
└── 6. 附錄:參考程序書 (Back Matter / Reference Documents)

技術選型

建議:延用 CLAUDE.md 規範的 python-docx 手寫模式(不用 pandoc,跟既有 system-design DOCX 規範一致)。

generator 分兩層:

  1. SspDocxGenerator(jedi-oscal 或主專案)— 純版面/結構,只知道 python-docx API
  2. SspContentAssembler(主專案)— 組裝資料,產出 SspExportDataModel,傳給 generator

待決問題 B1-Q1

要不要讀真實的 .docx 樣板檔(template mode)vs 純 python-docx 程式碼生成?

選項 優點 缺點
A:template.docx 作為樣式基礎 字型/色彩/間距可用 Word 調 部署需帶樣板檔;樣板改版要重建
B:純 python-docx 程式碼 無外部依賴;自動化友好 版面調整要改 code

建議選 B,與既有 system-design DOCX 模式一致,部署簡單。


§3

三、B2 — 中間層 data model 設計(最重要的架構決策)

問題核心

兩個來源的資料存放位置不同:

資料領域 MF 來源 SSP 版本來源
Metadata module_frame 本身的欄位 ssp.oscal_metadataMetadataEntity
Parties oscal_parties (context_type='module_frame') ssp.oscal_metadata.parties[]
控制項說明 module_frame_control_defaults ssp.control_implementations[]
AO 說明 module_frame_control_objective_defaults ssp.control_implementations[].objectives[]
Devices ssp_system_implementation_items (scope_type='module_frame' 或 via MF 的 SSP) ssp.system_implementations[] (hardware)
程序書 module_frame_reference_documents ssp_reference_documents (context_type='ssp')

選項分析

選項 A:統一 SspExportDataModel 中間層(推薦)

@dataclass
class SspExportDataModel:
    # 來源識別
    source_type: Literal["module_frame", "ssp_version"]
    source_uid: str  # mf.uid 或 ssp_group_uid

    # 組裝後的欄位(格式一致)
    title: str
    version: str
    system_name: str
    system_name_short: Optional[str]
    description: str
    sensitivity_level: str
    authorization_boundary: Optional[str]
    # ... metadata 欄位

    parties: List[SspPartyExportItem]
    inventory_items: List[SspInventoryItemExportItem]
    components: List[SspComponentExportItem]
    leveraged_authorizations: List[SspLeveragedExportItem]
    control_implementations: List[SspControlImplExportItem]
    reference_documents: List[SspRefDocExportItem]
  • 優點:Docx generator 和 OSCAL serializer 只依賴 SspExportDataModel,不知道來源差異
  • 缺點:需要寫兩個 loader(MfSspContentLoader + SspVersionContentLoader
  • 這是 B2 的主要工作

選項 B:直接組裝 SystemSecurityPlanEntity

從 MF 資料「偽造」一個 SystemSecurityPlanEntity,讓現有 yaml_mapper 直接用。

  • 優點:直接複用 yaml_mapper
  • 缺點:SystemSecurityPlanEntity 是 DB ORM 對應的 entity,「偽造」會讓型別語義混亂;且 yaml_mapper 本身還有缺口需補

選項 C:在 yaml_mapper 加 MF 來源支援(jedi-oscal 層改)

SystemSecurityPlanYamlMapper 加一個 module_frame_to_dict(mf_data) 方法。

  • 缺點:把業務邏輯(MF vs SSP)推進了套件層,不合適

結論:選 A,主專案定義 SspExportDataModel,兩個 loader 分別組裝。

待決問題 B2-Q1

SspExportDataModel 要放在哪一層?

  • domain/oscal/export/ — 純 domain dataclass,DDD 合規
  • app/oscal/service/export/ — 跟 loader 放在一起

建議放 domain/oscal/export/ssp_export_model.py,因為 Docx generator 和 OSCAL serializer 都可能在 jedi-oscal 層使用它。

待決問題 B2-Q2

MF 來源的 devices/info_systems 要從哪裡撈?

ModuleFrameSspResourcesService 是透過 MF 的 linked SSP 撈 ssp_system_implementation_items。 但如果一個 MF 沒做過 Excel 匯入(沒有 SSP shell),這裡會回傳空清單。

匯出時,MF 沒有 Excel 匯入過的設備應該怎麼處理?

  • 選項 A:只匯有資料的,空清單正常(最小成本)
  • 選項 B:提示 user「設備/系統資料尚未匯入」

§4

四、B3 — Docx Export API

Endpoint 設計

POST /api/1.0/module-frame/<uid>/export?format=docx
POST /api/1.0/projects/<project_uid>/ssp/<version_id>/export?format=docx|pdf|odt|json|xml|yaml

注意:format 放 query string(RESTful,不改 URL 路徑)。

回傳格式

Flask send_file + BytesIO,Content-Disposition 帶檔名:

# 範例
filename = f"SSP_{system_name}_{date.today().isoformat()}.docx"
return send_file(
    BytesIO(docx_bytes),
    mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    as_attachment=True,
    download_name=filename,
)

待決問題 B3-Q1

兩個 endpoint 要放在哪個 Blueprint?

  • 選項 A:api/oscal/routes/ssp_export_route.py(新 route,擴 oscal blueprint)
  • 選項 B:api/module_frame/routes/mf_export_route.py(MF 端) + api/oscal/routes/ssp_export_route.py(SSP 版本端)

建議選 B,因為兩個 endpoint 的 URL 前綴不同(/module-frame vs /projects)。


§5

五、B4 — PDF + ODT 補上

技術選型問題

方案 優點 缺點 現有依賴
A:LibreOffice headless 高品質轉換;支援 docx→pdf/odt;開源免費 需要在 server 安裝 LibreOffice;spawn 子行程
B:既有 preview-as-pdf endpoint 不需新依賴;直接複用 需先上傳再轉換 → 兩次 HTTP;語義不對(export ≠ preview) upload-file/<uid>/preview-as-pdf
C:python-docx2pdf Python 原生 在 Linux server 需要 Word 或 LibreOffice
D:Gotenberg / WeasyPrint 容器化,方便 額外服務依賴

建議選 A(LibreOffice headless),指令:

libreoffice --headless --convert-to pdf --outdir /tmp /tmp/ssp.docx
libreoffice --headless --convert-to odt --outdir /tmp /tmp/ssp.docx

待決問題 B4-Q1

部署環境是否已安裝 LibreOffice?

這決定了 B4 是 1 天工作還是要先處理環境。 請 confirm:which libreoffice 在 prod/staging server 上的結果。


§6

六、B5 — OSCAL JSON / XML / YAML 匯出(重型)

現有缺口清單(需補到 jedi-oscal)

缺口 1:AO statements 序列化

# 現況(ssp_yaml_mapper._control_implementation_to_dict)
r: Dict = {"control-id": str(control_id)}
# 只有 description + status + remarks
# ❌ 缺 ci.objectives → OSCAL statements[]

# 需補
if ci.objectives:
    r["statements"] = [
        {
            "statement-id": obj.statement_identifier,
            "uuid": _stringify_uuid(obj.uid),
            "description": obj.implementation_description or "",
            "remarks": obj.remarks,
        }
        for obj in ci.objectives
    ]

缺口 2:parties + responsible-parties 序列化

_metadata_to_dict 目前只輸出 title/version 等標量欄位。需補:

# MetadataEntity 已有 .parties[] (List[PartyEntity])
# 和 .responsible_parties[] (List[ResponsiblePartyEntity])

if metadata.parties:
    out["parties"] = [_party_to_dict(p) for p in metadata.parties]
if metadata.responsible_parties:
    out["responsible-parties"] = [_rp_to_dict(rp) for rp in metadata.responsible_parties]

缺口 3:back-matter 序列化(程序書)

OSCAL back-matter.resources[]:

back-matter:
  resources:
    - uuid: "..."
      title: "程序書名稱"
      rlinks:
        - href: "..."

SystemSecurityPlanEntity 目前沒有 reference_documents 屬性。 需要在 ssp_entity 加 relationship 或由 B2 的 assembler 填充。

缺口 4:system-characteristics 現有 DB 欄位限制

已確認 OscalSystemSecurityPlanSystemCharacteristic ORM 欄位實際清單:

欄位 現有 mapper 有無輸出
name ✅ (system-name)
description
system_identifier ✅ (system-id)
security_sensitivity_level
target_type
scope_description ✅(輸出為 scope.description
status ✅ (status.state)
network_architecture ❌ 不存在
data_flow ❌ 不存在
authorization_boundary ❌ 不存在(只有 scope_description)
CIA 三欄 (confidentiality/integrity/availability) ❌ 不存在

結論:system-characteristics 的 B5 缺口比預期小 — DB 根本沒有這些欄位,無法序列化。 mapper 目前的 7 個欄位已涵蓋現有 DB 資料的全部。缺口 4 實際上不需要補

若未來需要 CIA 分類和 network-architecture,那是另一個 feature(schema 擴充 + UI)。

OSCAL JSON / XML 的做法

格式 做法 成本
YAML 現有 yaml.safe_dump(entity_to_dict(...)) ✅ 已有
JSON import json; json.dumps(entity_to_dict(...), ensure_ascii=False, indent=2) 極低(1行)
XML 見下方選項

XML 選項

方案 做法 OSCAL 合規性
A:xmltodict xmltodict.unparse({"system-security-plan": {...}}) 低(namespace 缺失)
B:lxml etree 手寫 逐層 SubElement,可加 namespace 高,但繁瑣
C:xmltodict + 手補 namespace 先轉再後處理加 xmlns 中等

OSCAL XML 根元素需要:

<system-security-plan xmlns="http://csrc.nist.gov/ns/oscal/1.0"
                       uuid="...">

建議選 C(xmltodict + namespace patch),成本低,足夠滿足結構正確性要求。若後續需要完整 schema validation 再改 B。

待決問題 B5-Q1

OSCAL 標準合規程度:要完整符合 OSCAL 1.1.x spec(含所有 required 欄位),還是「最大努力」輸出?

OSCAL 1.1.x 對 system-security-plan 的 required 欄位:

  • uuid
  • metadata.title
  • metadata.last-modified
  • metadata.version
  • metadata.oscal-version
  • system-characteristics.system-id[] (我們有 system_identifier
  • system-characteristics.system-name
  • system-characteristics.description
  • system-characteristics.security-sensitivity-level
  • system-characteristics.status
  • system-implementation.components[](at least 1 required) — 有資料時 ✅,空 MF 時 ❌
  • control-implementation.implemented-requirements[].uuid
  • control-implementation.implemented-requirements[].control-id

建議:「最大努力」— 輸出我們有的資料,缺少的 required 欄位補空字串或省略;加一個 _validate_oscal_completeness() 方法輸出 warning log 但不擋住匯出。

待決問題 B5-Q2

AO statements 資料路徑確認

ControlImplementationEntity.objectives 這個 list 是否在現有 SSP query 時有被 eager load? 現有 yaml_mapper 呼叫端是哪裡?需要確認 caller 在 load SSP entity 時有無帶 objectives

待決問題 B5-Q3

MF 來源的 control-implementation 資料路徑

MF 的控制項說明在 module_frame_control_defaults(不是 system_security_plan_control_implementations)。 OSCAL yaml_mapper 接 ControlImplementationEntity — 但 MF source 的資料型態是 ModuleFrameControlDefaultEntity

B2 的 assembler 必須把 ModuleFrameControlDefaultEntityControlImplementationEntity(或 直接 map 到 SspControlImplExportItem dataclass)才能餵給 yaml_mapper。

這是 B2 最重要的轉換工作,需要確認兩個 entity 欄位對應關係。


§7

七、B6 — 前端 UI(輕型)

入口位置(需確認 FE 現有頁面)

頁面 入口 元件類型
合規資源庫列表/詳細頁 右上 action button 或詳細頁 header SplitButton(同 A1 的下載樣板)
專案規劃頁(AP / SSP 視圖) 頁面 header action 區 Button + Dialog(選版本 + 格式)

格式選項

[ docx ]  [ pdf ]  [ odt ]  [ OSCAL-JSON ]  [ OSCAL-XML ]  [ OSCAL-YAML ]

建議用 dialog + radio group,UX 一致,不用 6 個按鈕。


§8

八、整體決策清單(開工前需要 raymond 拍板)

必須決策(阻 B1/B2 開工)

# 問題 決策 確認日期
D1 B4 轉換工具:LibreOffice 是否在 server 上可用? 已安裝,現有 PDF 轉換已在用 2026-05-22
D2 B2 中間層:用 SspExportDataModel dataclass 還是直接 reuse SystemSecurityPlanEntity SspExportDataModel dataclass(選項 A) 2026-05-22
D3 B1 Generator:純 python-docx code vs 讀 .docx 樣板? docxtpl(Jinja2-in-Word .docx 模板),非純 code 2026-05-22

D3 補充:匯出 docx 不設計成 round-trip 匯入格式,優先做可讀交付物。round-trip 是未來獨立 feature。

可先開工、途中決策(不阻 B1)

# 問題 選項 傾向
D4 B5 OSCAL 合規程度:「最大努力」vs 完整 required 欄位? 最大努力 / 完整 傾向「最大努力」
D5 B5 XML 方案:xmltodict+patch vs lxml 手寫? C(xmltodict+patch)/ B(lxml) 傾向 C
D6 MF 沒有 Excel 匯入時的空設備/系統:靜默空清單 vs 提示? A(空清單)/ B(提示) 傾向 A
D7 B3 endpoint 位置:單一 blueprint vs 分開? B(分開) 傾向 B

§9

九、建議開工順序

B1(1~2 天)
  → 分析 CMMC reference docx 章節結構(需讀 .docx 內容)
  → 建 SspDocxGenerator 骨架 + 空殼輸出(封面/TOC/章節框架)

B2(2~3 天)  ← 最重要,其他都等這個
  → 定義 SspExportDataModel
  → MfSspContentLoader(MF 來源,7 個 service 聚合)
  → SspVersionContentLoader(SSP 版本來源,從 SSP entity)
  → SspDocxGenerator 填入真實資料

B3(0.5 天)  ← B2 完成後很快
  → 兩支 export endpoint + streaming response

B4(0.5~1 天,視 LibreOffice 環境)
  → LibreOffice headless subprocess wrapper

B5(2~3 天)  ← 平行於 B3/B4 可開
  → jedi-oscal yaml_mapper 補 4 個缺口
  → SspExportDataModel → ControlImplementationEntity 映射
  → JSON/XML 輸出方法

B6(1 天)  ← 等 B3/B4/B5 全部完成
  → FE 匯出按鈕 + dialog + 格式選擇

預估總工時:B1~B5 約 7~10 天(視 B4 環境複雜度),B6 FE 另外 1 天。


§10

十、明天開工前建議的一次性準備

  1. 讀 CMMC reference docx:用 python-docx 寫一個 dump 腳本,把所有 heading 列出來,確認章節結構(我沒辦法直接讀 .docx binary)
  2. 確認 LibreOffice 環境which libreoffice on dev server
  3. 拍板 D1~D3:三個必要決策
  4. 確認 ssp_system_characteristics DB schema:確認 CIA 三欄(confidentiality/integrity/availability)和 network-architecture/data-flow 是否存在
# dev DB 確認 schema 指令
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev \
  -c "\d oscal.system_security_plans_system_characteristics"