級別:中型 依賴:無 被依賴:C2(party endpoint dropdown)/ C8(清理
_ROLE_LABEL_MAP後同步)
把目前散落於程式碼的 OSCAL SSP 角色(hardcoded enum / regex / label map)統一為 system_menus 內的 master data,FE dropdown 改 API 讀取,新增角色零 code 變更。
system_menus (group='ssp_party_role')(UI / API dropdown 用)oscal.oscal_roles(OSCAL 文件結構用,B 階段匯出 metadata.roles[] 需要)oscal-role 區段(zh-tw + en)| 表 | 用途 |
|---|---|
system_menus (group='ssp_party_role') |
UI / API dropdown master data — 給 FE picker 用 |
oscal.oscal_roles |
OSCAL 文件結構 — B 階段匯出 SSP 為 JSON/XML/YAML 時,spec 規定 metadata.roles[] 區段必須列出該 SSP 用到的所有角色定義(id / title / description) |
兩個服務不同對象(FE UI vs OSCAL 文件規格),同 9 個內容同步維護。新增第 10 個角色時兩邊都要加。
| sort | key(OSCAL role_id 標準) | value | zh-tw label | en label | party_type 提示 |
|---|---|---|---|---|---|
| 10 | responsible-organization |
responsible-organization |
專案負責單位 | Responsible Organization | organization |
| 20 | system-owner |
system-owner |
系統所有者 | System Owner | person |
| 30 | system-security-officer |
system-security-officer |
系統安全官 (ISSO) | System Security Officer | person |
| 40 | authorizing-official |
authorizing-official |
授權機關 (AO) | Authorizing Official | person |
| 50 | information-owner |
information-owner |
資料所有者 | Information Owner | person |
| 60 | information-provider |
information-provider |
資料提供者 | Information Provider | person |
| 70 | information-receiver |
information-receiver |
資料接收者 / 窗口 | Information Receiver | person |
| 80 | prepared-by |
prepared-by |
文件撰寫者 | Prepared By | person |
| 90 | prepared-for |
prepared-for |
文件對象 | Prepared For | organization |
約定:
value 與 key 相同(皆為英文 OSCAL role_id)— FE 從 key 查 i18nsort 預留 10 為間隔,未來插隊不用大規模 renumberenable=1、public=1 全部公開啟用檔名:scripts/sql/2026-MM-DD-seed-oscal-ssp-roles.sql
-- Date: 2026-MM-DD
-- Purpose: C1 — seed OSCAL SSP 9 個角色到 system_menus + oscal.oscal_roles
-- system_menus:UI dropdown 用
-- oscal_roles:OSCAL 文件結構用(B 階段匯出 metadata.roles[])
-- 帳號:cmmgr(避 RLS)
BEGIN;
-- ─────────────────────────────────────────────────────────────────────────────
-- A. seed system_menus (2026-MM-DD) — UI / API dropdown master
-- ─────────────────────────────────────────────────────────────────────────────
-- 前置:確認 UNIQUE(group, key) constraint 已存在;若無,先加 (pre-flight 確認)
-- ALTER TABLE system_menus ADD CONSTRAINT uq_system_menus_group_key UNIQUE ("group", key);
INSERT INTO system_menus ("group", key, value, sort, enable, public) VALUES
('ssp_party_role', 'responsible-organization', 'responsible-organization', 10, 1, 1),
('ssp_party_role', 'system-owner', 'system-owner', 20, 1, 1),
('ssp_party_role', 'system-security-officer', 'system-security-officer', 30, 1, 1),
('ssp_party_role', 'authorizing-official', 'authorizing-official', 40, 1, 1),
('ssp_party_role', 'information-owner', 'information-owner', 50, 1, 1),
('ssp_party_role', 'information-provider', 'information-provider', 60, 1, 1),
('ssp_party_role', 'information-receiver', 'information-receiver', 70, 1, 1),
('ssp_party_role', 'prepared-by', 'prepared-by', 80, 1, 1),
('ssp_party_role', 'prepared-for', 'prepared-for', 90, 1, 1)
ON CONFLICT DO NOTHING; -- idempotent
-- ─────────────────────────────────────────────────────────────────────────────
-- B. seed oscal.oscal_roles (2026-MM-DD) — OSCAL 文件結構(給 B 階段匯出用)
-- ─────────────────────────────────────────────────────────────────────────────
-- OSCAL spec: metadata.roles[] 必須含每個被引用 role 的 id + title + description
-- 內容必須跟 system_menus 同步(同 9 個 role_id),但描述可詳細些供 OSCAL 文件用
-- idempotent:role_id 既存就跳過
INSERT INTO oscal.oscal_roles (role_id, title, description) VALUES
('responsible-organization', 'Responsible Organization',
'The organization responsible for the system''s operation at the organizational level.'),
('system-owner', 'System Owner',
'The individual ultimately responsible for the system''s security and operation, including risk decisions.'),
('system-security-officer', 'System Security Officer',
'The individual responsible for implementing system security controls and monitoring.'),
('authorizing-official', 'Authorizing Official',
'The individual with authority to formally accept the system''s operational risks.'),
('information-owner', 'Information Owner',
'The individual with authority over access, use, and disposition of specific information categories.'),
('information-provider', 'Information Provider',
'The individual or organization providing information to the system.'),
('information-receiver', 'Information Receiver',
'The individual or organization receiving output information from the system.'),
('prepared-by', 'Prepared By',
'The individual responsible for preparing this SSP document.'),
('prepared-for', 'Prepared For',
'The organization for which this SSP document is primarily prepared.')
ON CONFLICT (role_id) DO NOTHING; -- idempotent;前提:oscal_roles 已有 UNIQUE(role_id)
-- 注意:若 oscal_roles 無 UNIQUE(role_id) constraint,pre-flight 先加:
-- ALTER TABLE oscal.oscal_roles ADD CONSTRAINT uq_oscal_roles_role_id UNIQUE (role_id);
COMMIT;
-- 驗證
-- SELECT * FROM system_menus WHERE "group" = 'ssp_party_role' ORDER BY sort;
-- → 應看到 9 筆
-- SELECT role_id, title FROM oscal.oscal_roles ORDER BY role_id;
-- → 應看到 9 筆注意 (pre-flight):
system_menus 目前是否有 UNIQUE(group, key)?若無,先加再 seedoscal.oscal_roles 目前是否有 UNIQUE(role_id)?若無,先加再 seed無新 endpoint,沿用 system_menu 模組既有:
GET /system/menu/ssp_party_role
{ "code": 1, "data": { "menus": [{ key, value, sort, enable, public }, ...] } }sort,過濾 enable=1檔案:src/config/locales/i18n/zh-tw/oscal-role.json(新增)
{
"oscal_role": {
"ssp_party_role": {
"responsible-organization": {
"label": "專案負責單位",
"description": "對系統運作負組織級責任的單位"
},
"system-owner": {
"label": "系統所有者",
"description": "對系統的安全與營運負最終責任,包括風險決策"
},
"system-security-officer": {
"label": "系統安全官 (ISSO)",
"description": "負責執行系統安全控制與監控"
},
"authorizing-official": {
"label": "授權機關 (AO)",
"description": "有權正式接受系統運作風險的人員"
},
"information-owner": {
"label": "資料所有者",
"description": "對特定資料類別的存取、使用與處置擁有權限"
},
"information-provider": {
"label": "資料提供者",
"description": "提供資料給系統使用的人員或單位"
},
"information-receiver": {
"label": "資料接收者 / 窗口",
"description": "接收系統輸出資料的人員或單位"
},
"prepared-by": {
"label": "文件撰寫者",
"description": "負責撰寫此 SSP 文件的人員"
},
"prepared-for": {
"label": "文件對象",
"description": "此 SSP 文件主要服務的對象單位"
}
}
}
}同步建立 en 版本 src/config/locales/i18n/en/oscal-role.json(label/description 換英文)。
i18n key 對映規則:lang.oscal_role.ssp_party_role.<role_id>.label / .description
| 情境 | 行為 |
|---|---|
| seed migration 重跑 | ON CONFLICT DO NOTHING 跳過已存在,不報錯 |
| FE 拿到 system_menus 但 i18n key 找不到 | fallback 顯示 key 字串(FE 已有此 fallback pattern) |
某角色 enable=0 後 |
GET /system/menu/<group> 自動過濾,dropdown 看不到,但既有 SSP party 已寫入該角色不影響 |
| 新增第 10 個角色 | INSERT 一行 + 補 i18n 兩個檔案,不用 deploy code |
任何需要 OSCAL SSP 角色 dropdown 的地方,都應:
system_menus 讀GET /system/menu/ssp_party_role 拉lang.oscal_role.ssp_party_role.<key>.label禁止:在 service 層用 hardcoded role string list 做業務邏輯判斷(除了「party_type 推導」這種架構級邏輯)。
| 編號 | 決策 |
|---|---|
| C1-D1 | ✅ 加 UNIQUE(group, key);pre-flight verify 既有無重複後再加 |
| C1-D2 | ✅ description 文案以 OSCAL spec / NIST SP 800-18 慣例為準(已寫入 §3.2 SQL) |
| C3-D5 | ✅ oscal.oscal_roles 同步 seed 9 個(UI / OSCAL 文件雙用途) |
system_menus 表內多 9 筆 ssp_party_role 資料(UI dropdown 用)oscal.oscal_roles 表內多 9 筆對應 role row(OSCAL 文件用)oscal-role.json)