文件版本:1.0 撰寫日期:2026-04-30 文件性質:SA — API 介面契約 依據:
raw-requirement/requirement.md+ Phase 1 brainstorming 9 項決策狀態:草稿,等使用者 review 簽字後才會撰寫 SD(design.md)
SSP 文件解析器(SSP Docx Parser)讓使用者上傳 Word 格式的 System Security Plan(SSP),系統用純規則層抽取每個合規控制項的實作描述(implementation description)與評估目標(assessment objectives),自動填入 ssp_control_implementation / module_frame_control_defaults 等對應表,省去人工逐條 copy-paste。
✅ 做:
information_systems❌ 不做(v2 候選):
| # | Method | URL | Route Class | 說明 |
|---|---|---|---|---|
| 1 | POST | /api/1.0/ssp-docx-imports/parse |
SspDocxImportParseRoute |
上傳 docx + 解析(同步) |
| 2 | GET | /api/1.0/ssp-docx-import/<parse_uid> |
SspDocxImportRoute |
取得 parse job 狀態與預覽結果(Step 1 用) |
| 3 | POST | /api/1.0/ssp-docx-import/<parse_uid>/confirm |
SspDocxImportConfirmRoute |
確認匯入,寫入目標表(Step 2) |
| 4 | DELETE | /api/1.0/ssp-docx-import/<parse_uid> |
SspDocxImportRoute |
放棄此次 parse job(清檔) |
依優先順序:
| AC | 描述 | 涵蓋的 brainstorm 決策 |
|---|---|---|
| AC-1 | 使用者在「專案規劃 / SSP 控制項現況頁」可看到「匯入 docx」按鈕(與既有「匯入 Excel」並排),點擊後開啟 3 步驟 wizard | D-Q1 D2, D-Q2 α |
| AC-2 | 使用者在「合規資源庫 / 新增 module_frame」第 2 步驟可選擇「從 docx 匯入控制項」啟動解析(Full mode) | D Q1 → A1 |
| AC-3 | 使用者在「合規資源庫 / 新增 module_frame」第 3 步驟可選擇「從 docx 匯入現況」啟動解析(Statement-only mode) | D Q1 → A2 |
| AC-4 | 上傳 docx 時必須先選 framework(CMMC L1 / NIST 800-171 / ISO27001),未選不能繼續 | C-Q1 = C2 |
| AC-5 | 5MB / 273 段 / 25 控制項範例 docx 同步解析時間 < 10 秒 | F → v1A 同步 |
| AC-6 | 規則層命中(信心 ≥ 0.8)的控制項自動配對到 baseline 對應的 control_id | A → 純規則 |
| AC-7 | 規則層失敗(信心 < 0.8)的段落出現在「待人工確認」清單,使用者可拖拉到任意控制項的主述或特定 (a)/(b)/(c) objective | G-Q1 β |
| AC-8 | 同一段落可拖拉到多個控制項(複製到每個) | G-Q2 β |
| AC-9 | 拖拉到已有 docx 內容的目標時,跳出 conflict modal 讓使用者選 append / replace | G-Q3 β |
| AC-10 | Re-parse 時 Step 1 預覽顯示「現有值 vs docx 值」,使用者可逐筆選擇 keep_current / use_docx / skip;空欄位預設 use_docx,衝突欄位預設 keep_current | E-Q1 E3, E-Q2 |
| AC-11 | 評估目標 (a)(b)(c) 每筆獨立顯示一行,獨立選 keep/use_docx/skip | E-Q4 α |
| AC-12 | Step 1 提供批次操作:「全部使用 docx」/「全部保留現有」/「只匯入空白項」 | E-Q3 |
| AC-13 | docx 解析至 baseline 內找不到的控制項時,留 implementation_description 為空,UI 提示使用者後續手動補(不阻擋匯入) | C-Q3 α |
| AC-14 | 使用者選定的 framework 與 docx 內主要控制項 ID 模式不符時(例如選 CMMC L1 但 docx 全是 NIST 800-53 ID),標 FATAL 拒絕匯入 | G-Q4 |
| AC-15 | docx 內找不到任何 heading / 控制項 ID 時,標 FATAL 拒絕匯入 | G-Q4 |
| AC-16 | 規則命中率 < 50% 或 baseline 缺漏 > 30% 控制項時,Step 1 頂部顯示 PARTIAL warning banner,使用者可選擇繼續或放棄 | G-Q4 |
| AC-17 | parse job 創建後 1 小時內若 status 仍非 'completed' 或 'failed',自動標 'expired',使用者必須重新上傳 | G-Q5 α + 短 expire |
| AC-18 | Step 2 確認匯入時,使用者標「不匯入」的段落記在 parsed_result.skipped_paragraphs(JSONB 欄位) |
G-Q6 β |
| AC-19 | docx 內所有 rich text 格式(粗體、斜體、底線)抽取時轉成純文字;表格內容用換行分隔 row、: 或 | 分隔 cell |
I-Q1 α |
| AC-20 | docx 內嵌入圖片(網路架構圖、資料流)完全忽略不抽取 | I-Q2 α |
| AC-21 | 評估目標 table 第 2 欄當 description,第 1 欄忽略(objective_text 由 baseline 提供) |
I-Q3 γ |
| AC-22 | 入口 A 操作權限:tenant admin OR project manager;入口 B 操作權限:project manager only | F-Q2, F-Q3 α |
| AC-23 | docx 抽出的 SSP metadata(Introduction tables)寫入 information_systems 對應欄位(name, description, security_sensitivity_level, deployment_model, authorization_boundary 等),與專案 project_information_systems 關聯 |
B → metadata 寫 information_systems |
| 詞彙 | 定義 |
|---|---|
| SSP | System Security Plan,OSCAL 規範的系統安全計畫文件 |
| module_frame | 「合規資源庫」table — 租戶層級的合規範本(如:「我們公司的 CMMC L1 SSP 範本」),給專案啟動時 copy 用 |
| baseline | 一個合規 framework + level 的控制項清單(如 CMMC L1 = 17 個控制項) |
| framework | 合規規範(CMMC / NIST 800-171 / ISO27001) |
| catalog | 一個 framework 的控制項目錄(OSCAL 概念) |
| profile | 一個 catalog 的 subset,用於某個 baseline(OSCAL 概念) |
| assessment objective | 控制項下的 (a)(b)(c) 評估細項 |
| implementation_description | 控制項實作現況的描述文字 |
| Full mode | docx 解析模式:抽出控制項清單 + 現況描述(用於合規資源庫 Step 2) |
| Statement-only mode | docx 解析模式:只抽出現況描述(用於合規資源庫 Step 3 + 專案規劃端) |
| parse job | 一次完整的 parse 任務(從上傳到匯入完成),對應 ssp_docx_parse_jobs 表的一 row |
/api/1.0/ssp-docx-imports/parse — 上傳 docx + 解析(同步)SspDocxImportParseRoutesource_type 分支驗證
source_type = 'module_frame' → tenant admin OR managersource_type = 'project_ssp' → project managermultipart/form-data| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
file |
File | Y | docx 檔(≤ 10MB,副檔名必須 .docx) |
framework |
string | Y | cmmc-l1 | cmmc-l2 | nist-800-171 | iso27001(C-Q1 = C2 手動選) |
source_type |
string | Y | module_frame | project_ssp |
source_uid |
string | Y | 對應的 module_frames.uid 或 SSP 的 uid(取決於 source_type) |
mode |
string | Y | full(限 source_type=module_frame)| statement_only |
{
"status": true,
"data": {
"parse_uid": "uuid-string",
"status": "awaiting_review",
"summary": {
"total_paragraphs": 273,
"matched_controls_count": 22,
"unmatched_paragraphs_count": 8,
"missing_baseline_controls_count": 3,
"framework_detected_id_pattern": "AC.L1-b.1.x",
"warning_level": null
}
}
}{
"status": true,
"data": {
"parse_uid": "uuid-string",
"status": "awaiting_review",
"summary": {
"total_paragraphs": 273,
"matched_controls_count": 8,
"unmatched_paragraphs_count": 50,
"missing_baseline_controls_count": 12,
"warning_level": "partial",
"warnings": [
"規則命中率 < 50% — 建議檢查 framework 選擇是否正確",
"Baseline 中 12/17 控制項在 docx 找不到對應描述"
]
}
}
}{
"status": false,
"msg": "上傳的 docx 不包含任何可辨識的控制項 ID(GRC_422001)",
"data": {
"parse_uid": null,
"fatal_reason": "no_control_id_found"
}
}GRC_400005 上傳檔案不是 docx 格式GRC_400006 上傳檔案超過大小限制(10MB)GRC_400007 framework 必填GRC_400008 source_type 必填且必須為 module_frame / project_sspGRC_400009 mode 為 full 時 source_type 必須為 module_frameGRC_403002 無權限操作此 source_uidGRC_404021 source_uid 對應的資源不存在GRC_422001 docx 內無法辨識任何控制項 ID(FATAL)GRC_422002 docx framework 偵測與使用者選擇不符(FATAL)GRC_422003 docx 結構損壞 / 解析失敗/api/1.0/ssp-docx-import/<parse_uid> — 取得 parse 結果(Step 1 用)Route Class:SspDocxImportRoute
認證:JWT Required
權限:建立此 parse job 的使用者本人 OR 同 source_uid 的有權使用者
Path Parameters:
parse_uid — ssp_docx_parse_jobs.uidResponse (200)(status=awaiting_review,給 Step 1 預覽 UI 用):
{
"status": true,
"data": {
"parse_uid": "uuid",
"status": "awaiting_review",
"source_type": "project_ssp",
"source_uid": "ssp-uid",
"framework": "cmmc-l1",
"mode": "statement_only",
"summary": {
"total_paragraphs": 273,
"matched_controls_count": 22,
"unmatched_paragraphs_count": 8,
"missing_baseline_controls_count": 3,
"warning_level": null
},
"matched_controls": [
{
"control_id": "AC.L1-b.1.i",
"control_name": "Authorized Access Control",
"score": 0.95,
"score_reason": "H3 標題完整 ID 命中",
"current_implementation_description": "我們透過 AD 控制使用者群組...",
"parsed_implementation_description": "我們透過 AD + Okta 控制使用者群組,並...",
"default_action": "use_docx",
"objectives": [
{
"objective_key": "a",
"objective_text": "authorized users are identified",
"current_description": "已透過 SOP-001 識別",
"parsed_description": "已透過 SOP-001 識別並由 IT 部審核",
"default_action": "use_docx"
}
]
}
],
"unmatched_paragraphs": [
{
"paragraph_idx": 142,
"text": "我們的安全事件處理由 SOC team 24x7 監控...",
"context": {
"preceding_h1": "Control Implementation",
"preceding_h2": null,
"preceding_h3": null
},
"rule_score": 0.45,
"rule_guess_control_id": "IR.L2-...",
"rule_guess_confidence": 0.45
}
],
"missing_baseline_controls": [
{
"control_id": "SC.L1-b.1.x",
"control_name": "Boundary Protection",
"reason": "baseline 中存在但 docx 沒提到"
}
],
"predicted_module_frame_controls": [
// 僅在 mode=full 時提供 — D-Q5 β 預選清單,給 Step 2 勾選 UI
{ "control_id": "AC.L1-b.1.i", "preselected": true },
{ "control_id": "AC.L1-b.1.ii", "preselected": true }
],
"created_at": "2026-04-30 10:00:00",
"expires_at": "2026-04-30 11:00:00"
}
}completed,已匯入完成的 job 查詢):{
"status": true,
"data": {
"parse_uid": "uuid",
"status": "completed",
"import_summary": { "created": 22, "updated": 5, "skipped": 3 },
"completed_at": "2026-04-30 10:05:00"
}
}failed / expired):{
"status": true,
"data": {
"parse_uid": "uuid",
"status": "failed",
"error_code": "GRC_422001",
"error_message": "docx 內無法辨識任何控制項 ID"
}
}GRC_404022 parse_uid 不存在GRC_403003 無權限存取此 parse job/api/1.0/ssp-docx-import/<parse_uid>/confirm — 確認匯入(Step 2)Route Class:SspDocxImportConfirmRoute
認證:JWT Required
權限:建立此 parse job 的使用者本人;且 status 必須為 awaiting_review
Path Parameters:parse_uid
Request Body:
{
"decisions": [
{
"control_id": "AC.L1-b.1.i",
"action": "use_docx | keep_current | skip",
"objectives": [
{ "objective_key": "a", "action": "use_docx" },
{ "objective_key": "b", "action": "keep_current" }
]
}
],
"manual_assignments": [
{
"paragraph_idx": 142,
"targets": [
{ "control_id": "IR.L2-x", "level": "implementation", "merge_action": "append" },
{ "control_id": "AC.L1-b.1.i", "level": "objective", "objective_key": "c", "merge_action": "replace" }
]
}
],
"skipped_paragraph_idxs": [85, 142, 200],
"predicted_controls_user_selection": [
"AC.L1-b.1.i", "AC.L1-b.1.ii", "AC.L1-b.1.iv"
// 僅在 mode=full 時必填 — 使用者在 Step 1 微調過的最終控制項勾選清單
]
}| 欄位 | 必填 | 說明 |
|---|---|---|
decisions[] |
Y | 每個 matched_control 一筆,action 決定 keep/use_docx/skip |
decisions[].objectives[] |
N | objective 層級的決策,未列出的 objective 沿用 control 層的 default_action |
manual_assignments[] |
N | 使用者拖拉指派的段落(unmatched_paragraphs) |
manual_assignments[].targets[] |
Y(若有 manual_assignment) | 段落可拖拉到多個 target(G-Q2 β) |
manual_assignments[].targets[].merge_action |
Y | append | replace(G-Q3 β) |
skipped_paragraph_idxs[] |
N | 使用者明確選「不匯入」的段落 idx,記錄供審計(AC-18 / G-Q6 β) |
predicted_controls_user_selection[] |
Y(mode=full)/ N(其他) | 合規資源庫 Step 2 使用者最終勾選的控制項清單 |
{
"status": true,
"data": {
"parse_uid": "uuid",
"status": "completed",
"import_summary": {
"created": 22,
"updated": 5,
"skipped": 3,
"manual_assigned": 8,
"manual_skipped": 3,
"missing_left_blank": 3
},
"redirect_url": "/projects/<uid>/ssp/control-implementations"
}
}GRC_404022 parse_uid 不存在GRC_412009 parse job 狀態不是 awaiting_review,無法確認匯入GRC_412010 parse job 已 expiredGRC_400010 decisions 缺漏(matched_controls 中有未決策的 control_id)GRC_400011 predicted_controls_user_selection 缺漏(mode=full 時必填)/api/1.0/ssp-docx-import/<parse_uid> — 放棄此次 parse jobRoute Class:SspDocxImportRoute
認證:JWT Required
權限:建立此 parse job 的使用者本人
Response (200):
{
"status": true,
"data": { "parse_uid": "uuid", "status": "discarded" }
}副作用:MinIO 上的 file 同步刪除;DB row 軟刪除(is_active=False)。
Error codes:
GRC_404022 parse_uid 不存在GRC_412011 已完成的 job 無法再放棄(status=completed)依 CLAUDE.md 命名規則 GRC_<HTTP狀態碼><3位序號>,序號接續既有 common/code/grc_error_code.py。
| Constant Name | 訊息 | Code |
|---|---|---|
GRC_DOCX_INVALID_FILE |
上傳檔案不是 docx 格式 | GRC_400005 |
GRC_DOCX_FILE_TOO_LARGE |
上傳 docx 超過大小限制(10MB) | GRC_400006 |
GRC_DOCX_FRAMEWORK_REQUIRED |
必須選擇合規 framework | GRC_400007 |
GRC_DOCX_SOURCE_TYPE_INVALID |
source_type 必須為 module_frame 或 project_ssp | GRC_400008 |
GRC_DOCX_MODE_FULL_REQUIRES_MODULE_FRAME |
full mode 僅限 source_type=module_frame | GRC_400009 |
GRC_DOCX_DECISIONS_INCOMPLETE |
decisions 缺漏,請對所有匹配控制項做選擇 | GRC_400010 |
GRC_DOCX_PREDICTED_CONTROLS_REQUIRED |
full mode 必須提供 predicted_controls_user_selection | GRC_400011 |
GRC_DOCX_NO_PERMISSION_FOR_SOURCE |
無權限操作此 source_uid | GRC_403002 |
GRC_DOCX_PARSE_JOB_NO_PERMISSION |
無權限存取此 parse job | GRC_403003 |
GRC_DOCX_SOURCE_NOT_FOUND |
source_uid 對應的資源不存在 | GRC_404021 |
GRC_DOCX_PARSE_JOB_NOT_FOUND |
parse job 不存在 | GRC_404022 |
GRC_DOCX_PARSE_JOB_NOT_AWAITING |
parse job 狀態不是 awaiting_review,無法確認匯入 | GRC_412009 |
GRC_DOCX_PARSE_JOB_EXPIRED |
parse job 已過期,請重新上傳 | GRC_412010 |
GRC_DOCX_PARSE_JOB_ALREADY_COMPLETED |
已完成的 job 無法放棄 | GRC_412011 |
GRC_DOCX_NO_CONTROL_ID_FOUND |
docx 內無法辨識任何控制項 ID | GRC_422001 |
GRC_DOCX_FRAMEWORK_MISMATCH |
docx 主要控制項 ID 模式與選擇的 framework 不符 | GRC_422002 |
GRC_DOCX_PARSE_FAILED |
docx 結構損壞,解析失敗 | GRC_422003 |
| 操作 | 入口 | 角色 |
|---|---|---|
| Upload + Parse(POST /parse) | A 合規資源庫 | tenant admin OR manager |
| Upload + Parse(POST /parse) | B 專案規劃 | project manager |
| GET preview(GET /<parse_uid>) | 任一 | parse job 建立者 |
| Confirm import(POST /confirm) | A 合規資源庫 | tenant admin OR manager(建立者本人) |
| Confirm import(POST /confirm) | B 專案規劃 | project manager(建立者本人) |
| Discard(DELETE) | 任一 | parse job 建立者 |
備註:
project manager = project_participants.role == 'manager'tenant admin = users.is_admin == Trueauditor 角色不可進行任何 docx import 操作(F-Q3 α 嚴格)ModuleFrame.vue 編輯/新增 wizard 第 2 步(選控制項)SspDocxImportDialog 子 wizardfull,source_type = module_frameModuleFrame.vue 編輯/新增 wizard 第 3 步(填現況)statement_only,source_type = module_framestatement_only,source_type = project_sspPOST /parse| 編號 | 問題 | 可能影響 |
|---|---|---|
| OPEN-1 | information_systems 寫入時:使用者已手動建了該專案的 information_system 怎麼辦?docx 匯入要 merge / 新建 / 報錯讓使用者選?brainstorm 沒展開細節 |
AC-23 細節 |
| OPEN-2 | 多個使用者同時操作同一個 source_uid(如多人匯入同一份 SSP)時的 race condition:兩個 parse_uid 同時 confirm,後者覆蓋前者?要不要加 lock | F → 多人協作 v1 不展開但需有對策 |
| OPEN-3 | docx 解析的 framework 偵測邏輯:用什麼比例 / 邏輯判定「framework mismatch」?例如 80% H3 ID 不符選擇的 framework 才報錯? | AC-14 / GRC_422002 細節 |
| OPEN-4 | predicted_controls_user_selection 的 default 行為:若使用者完全沒勾選任何(清空),是不是該擋下?或當作「全不選」 |
mode=full 邊界條件 |
| OPEN-5 | 拖拉指派到 objective 層時,objective_key 是否驗證屬於該 control?(e.g., 防止拖到不存在的 objective) | AC-7 / AC-8 細節 |
| OPEN-6 | 1 小時 expire 是否合適?對顧問處理 100+ 控制項可能不夠 | AC-17 細節 |
這些 OPEN 不影響 SA 主結構,會在 SD(design.md)階段細化解決方案。
ssp_control_implementation / ssp_control_implementation_objective 現況可直接寫入(API 已存在)module_frame_control_defaults / module_frame_control_objective_defaults 表結構穩定information_systems + project_information_systems 的 CRUD service 已可呼叫information_systems 寫入 / merge 邏輯(OPEN-1)若沒 set 好,可能造成多 row 髒資料raw-requirement/requirement.mdraw-requirement/reference/ASIA-CMMC-SSP-DRAFT-202604.docxdocs/api/module-frame/api/oscal/serializers/ssp/ssp_control_implementation.py~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/infra/model/ssp/jedi_information_system/(主專案內)Sign-off 區(Phase 2.1 結束時填寫)
| 角色 | 名字 | 日期 | 備註 |
|---|---|---|---|
| Product / 需求 | |||
| Tech Lead | |||
| FE Lead |
簽完後進 Phase 2.2(design.md / SD 撰寫)。
跟 docx 匯入相關的編輯介面(編輯頁的「參與單位 / 人員」對話框)需要 BE 提供 CRUD endpoints 直接管理某個 module_frame 的 oscal_parties,不再透過 docx import 流程。
GET /module-frame/<uid>/parties列出該 module_frame 連結的所有 parties。
Request:無 body
Response 200:
{
"status": true,
"data": [
{
"uid": "uuid",
"party_type": "person | organization",
"name": "Pan Pan",
"role": "system-security-officer",
"title": "Security Manager",
"email_address": "bls.tech.pan@gmail.com",
"telephone_number": null,
"address": null,
"matched_user_id": 23,
"matched_org_unit_id": null,
"matched_user_name": "Pan Pan",
"matched_org_unit_name": null
}
]
}POST /module-frame/<uid>/parties新增 party + 連結到該 module_frame。
Request body:
{
"party_type": "person | organization",
"name": "string",
"role": "system-owner | ...",
"title": "string?",
"email_address": "string?",
"telephone_number": "string?",
"address": "string?",
"matched_user_id": 23,
"matched_org_unit_id": null
}role 為 5 個固定 role 之一(responsible-organization / information-provider / information-receiver / system-owner / system-security-officer)。
PUT /module-frame/<uid>/parties/<party_uid>編輯 party 內容 / 重新連結。party_type 與 name 不可改(保 dedupe key 穩定,re-import 時才能找到同一筆)。role 改變時自動 re-point responsible_party 連結。
DELETE /module-frame/<uid>/parties/<party_uid>只移除「該 module_frame 跟此 party 的連結」(responsible_party row),party row 本身保留供其他 module_frame 重用。
| Code | HTTP | 說明 |
|---|---|---|
GRC_INVALID_PARTY_TYPE |
400 | party_type 不是 person / organization |
GRC_PARTY_NAME_REQUIRED |
400 | name 缺漏 |
GRC_PARTY_ROLE_REQUIRED |
400 | role 缺漏 |
GRC_PARTY_NOT_FOUND |
404 | party_uid 不存在 |
GRC_MODULE_FRAME_NOT_FOUND |
404 | module_frame uid 不存在 |
/ssp-docx-import/<uid>/confirm) 新欄位| 欄位 | 型別 | 用途 |
|---|---|---|
content_overrides |
dict | FE inline 編輯 overlay:{"<control_id>": {"implementation": "...", "objectives": {"(a)": "..."}}},BE 在 strategy.write 前 mutate parsed.matched_controls 對應欄位 |
GET /ssp-docx-import/<uid> 回傳的 parsed_result 多了:
| 欄位 | 用途 |
|---|---|
baseline_controls[].catalog_control_uid |
catalog_control UUID — FE pre-create module_frame 必須送這個給 add_profile(不是 control_id 字串) |
matched_controls[].catalog_objective_descriptions |
catalog 來的 AO 題目(dict: AO key → 題目文字),跟 current_objectives_descriptions(user 答案)刻意分開 |