# C1 — OSCAL SSP 角色 Master Data 化

> **級別**：中型
> **依賴**：無
> **被依賴**：C2（party endpoint dropdown）/ C8（清理 `_ROLE_LABEL_MAP` 後同步）

---

## 1. 目標

把目前散落於程式碼的 OSCAL SSP 角色（hardcoded enum / regex / label map）統一為 `system_menus` 內的 master data，FE dropdown 改 API 讀取，新增角色零 code 變更。

## 2. 範圍

- **In scope**：
  - DB seed 9 個 OSCAL 角色到 `system_menus` (`group='ssp_party_role'`)（UI / API dropdown 用）
  - DB seed 同 9 個 OSCAL 角色到 `oscal.oscal_roles`（OSCAL 文件結構用，B 階段匯出 `metadata.roles[]` 需要）
  - FE i18n 檔新增 `oscal-role` 區段（zh-tw + en）
- **Out of scope**：
  - 既有 hardcoded role 的 caller 改造（屬 C8 範圍）

### 為什麼兩個表都要 seed

| 表 | 用途 |
|----|------|
| `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 個角色時兩邊都要加。

## 3. 資料設計

### 3.1 Master data（9 個角色）

| 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` 查 i18n
- `sort` 預留 10 為間隔，未來插隊不用大規模 renumber
- `enable=1`、`public=1` 全部公開啟用

### 3.2 SQL Migration

檔名：`scripts/sql/2026-MM-DD-seed-oscal-ssp-roles.sql`

```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)`？若無，先加再 seed
- `oscal.oscal_roles` 目前是否有 `UNIQUE(role_id)`？若無，先加再 seed
- 兩個 table seed 前先 audit 既有資料：是否有 role_id 衝突

## 4. API 設計

**無新 endpoint**，沿用 `system_menu` 模組既有：

- `GET /system/menu/ssp_party_role`
  - Response: `{ "code": 1, "data": { "menus": [{ key, value, sort, enable, public }, ...] } }`
  - 排序 by `sort`，過濾 `enable=1`
  - FE 直接餵 PrimeVue Dropdown

## 5. FE i18n 結構

檔案：`src/config/locales/i18n/zh-tw/oscal-role.json`（新增）

```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`

## 6. 邊界條件

| 情境 | 行為 |
|------|------|
| 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 |

## 7. 開發者使用約定

**任何需要 OSCAL SSP 角色 dropdown 的地方**，都應：
1. 後端 NOT hardcode 角色 enum，從 `system_menus` 讀
2. 前端 dropdown 的 options 從 `GET /system/menu/ssp_party_role` 拉
3. 顯示文字用 `lang.oscal_role.ssp_party_role.<key>.label`

**禁止**：在 service 層用 hardcoded role string list 做業務邏輯判斷（除了「party_type 推導」這種架構級邏輯）。

## 8. 決策（已 finalized）

| 編號 | 決策 |
|------|------|
| 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 文件雙用途）|

---

## 9. 開發後狀態

- `system_menus` 表內多 9 筆 `ssp_party_role` 資料（UI dropdown 用）
- `oscal.oscal_roles` 表內多 9 筆對應 role row（OSCAL 文件用）
- FE 多 2 個 i18n 檔（zh-tw / en `oscal-role.json`）
- 既有任何 hardcoded role enum **暫不動**（屬 C2 / C8 範圍）
