# C7 — 06_外部利用服務 sheet 恢復（可選）

> **級別**：小（兩行修改 + sheet 顯示）
> **依賴**：無
> **被依賴**：無
> **可選性**：可不做（C 階段尾聲評估），不做不影響其他 phase

---

## 1. 目標

把 v2.0.0（2026-05-21）從 Excel 樣板隱藏的 06_外部利用服務 sheet 恢復，讓 user 能透過 Excel 匯入「外部利用服務 / Leveraged Services」資料到 SSP。

## 2. 為什麼當初被隱藏

`sheet_definitions.py:255-258` 註解原文：
```
v2.0.0 (2026-05-21)：06_外部利用服務 暫時從 ALL_SHEETS 隱藏（系統內無對應
資料來源；party / date_authorized picker 拿掉後沒有 user 能填的鉤稽路徑）。
SHEET_LEVERAGED 定義保留 — 未來有需求時加回 ALL_SHEETS + 還原 parser 對應段
即可，不必重寫 SheetDef。
```

當時 A4/A5 階段決策：
- 系統內無「FedRAMP authorized service catalog」master data → user 純打字 UX 差
- party picker 拿掉後無鉤稽路徑 → 跟「直接在 SSP UI 編」沒差別

## 3. 為什麼現在可恢復

- Phase C 走向「**SSP 在專案內直接編輯**」，純文字輸入到 SSP 是合理的（編輯頁本就是直填）
- Excel 匯入是另一種輸入路徑，跟 SSP UI 編輯並存
- 既有後端全 stack 仍存在（DB / write strategy / DI / handler）
- 兩行修改即可恢復

## 4. 範圍

### In scope

| 檔案 | 改動 |
|------|------|
| `app/module_frame/excel_template/sheet_definitions.py:259 ALL_SHEETS` | tuple 內加回 `SHEET_LEVERAGED`（位置：SHEET_INFO_SYSTEMS 後，SHEET_CONTROLS 前）|
| `app/oscal/service/excel_parser/parser.py:70` | `leveraged=[]` 改回呼叫 `sh.parse_leveraged_sheet(sh.get_ws(wb, sh.SHEET_NAMES["leveraged"]), validation_errors)` |

### Out of scope

- `SHEET_LEVERAGED` 欄位調整（沿用 v2.0 精簡版 3 欄：service_name / provider / purpose）
- DB schema / write strategy / DI 不動
- master data（FedRAMP catalog）不引入

## 5. 改動詳情

### 5.1 `sheet_definitions.py`

**改動前**：
```python
ALL_SHEETS: tuple[SheetDef, ...] = (
    SHEET_METADATA,
    SHEET_ORGS,
    SHEET_PERSONS,
    SHEET_DEVICES,
    SHEET_INFO_SYSTEMS,
    SHEET_CONTROLS,
    SHEET_REF_DOCS,
)
```

**改動後**：
```python
ALL_SHEETS: tuple[SheetDef, ...] = (
    SHEET_METADATA,
    SHEET_ORGS,
    SHEET_PERSONS,
    SHEET_DEVICES,
    SHEET_INFO_SYSTEMS,
    SHEET_LEVERAGED,   # C7 (2026-MM-DD) 恢復：純文字輸入路徑
    SHEET_CONTROLS,
    SHEET_REF_DOCS,
)
```

### 5.2 `parser.py`

**改動前**：
```python
# 06_外部利用服務 (SHEET_LEVERAGED) 自 v2.0.0 從 ALL_SHEETS 隱藏 —
# 樣板不產出此 sheet，parser 也跳過讀取以免噴 SHEET_MISSING 警告。
# ParsedExcel.leveraged 留 default_factory=list，下游空 list 安全。
leveraged=[],
```

**改動後**：
```python
# C7 (2026-MM-DD) 恢復讀取 — 純文字 service_name / provider / purpose
leveraged=sh.parse_leveraged_sheet(
    sh.get_ws(wb, sh.SHEET_NAMES["leveraged"]), validation_errors,
),
```

### 5.3 樣板版本 bump

`sheet_definitions.py:TEMPLATE_VERSION` 或對應位置：
- 從 `v2.0.0` → `v2.1.0`（minor bump，因為 sheet 數量改變屬 schema 變動）
- A2 parser 內既有「相容性 anchor」要確認能處理 v2.0.0 + v2.1.0 兩個版本（**pre-flight 確認**）

## 6. UX 設計

### 06_外部利用服務 sheet 欄位（沿用 v2.0 精簡版）

| 欄位 | 必填 | 寬度 | 說明 |
|------|------|------|------|
| `service_name` | ✅ | 30 | 服務名稱（純文字，如 "AWS S3" / "Microsoft 365"）|
| `provider` | ✅ | 30 | 服務提供者（純文字，如 "Amazon Web Services" / "Microsoft"）|
| `purpose` | — | 40 | 用途說明 |

**設計選擇**：
- 不引入下拉選單（無 master data）
- 不引入 party picker（v2.0 已拿掉）
- 不引入 date_authorized（v2.0 已拿掉）
- 純文字輸入，匯入後寫到 `ssp_system_implementation_items.implementation_type='leveraged-authorization'`

### 匯入後資料路徑

```
Excel 06 sheet
  ↓
parser → ParsedLeveraged(service_name, provider, purpose)
  ↓
LeveragedWriteStrategy._parsed_to_item_payload
  ↓
ssp_system_implementation_items (
    name=service_name,
    title=provider,
    purpose=purpose,
    implementation_type='leveraged-authorization',
    party_uuid=None,                  # 無 picker
    date_authorized=None,             # 無 picker
    scope_type='ssp' / 'module_frame',
    scope_id=...
)
```

## 7. 邊界條件

| 情境 | 行為 |
|------|------|
| Excel 06 sheet 完全空白 | parser 回空 list，下游不寫資料 |
| `service_name` 缺 (必填) | parser 報 validation error，user 修正後重匯 |
| 既有 v2.0.0 樣板被匯入 (無 06 sheet) | parser 容錯：找不到 sheet → `validation_errors` 加 non-blocking SHEET_MISSING + leveraged=[] |
| Re-import update flow | A5 bugfix 已修「整份覆蓋」語義 (`base.py write()` delete-before-insert) — leveraged 同 SSP + 同 kind 自動覆蓋 |

## 8. 待 user 拍板的小決策

| 編號 | 問題 | 我建議 |
|------|------|--------|
| C7-D1 | 樣板版本 v2.0.0 → v2.1.0？影響 A2 parser 相容性檢查 | **要 bump**，pre-flight 確認 parser 對舊版本 (v2.0.0) 不會強制報錯 |
| C7-D2 | 此 phase 是否要做？或留到 C 階段尾聲再評估 | **建議做**，兩行改動成本極低；但你若想先聚焦 C3 也可 defer |

---

## 9. 開發後狀態

- Excel 樣板下載含 06_外部利用服務 sheet（純文字三欄）
- Excel 匯入流程處理 06 sheet 資料寫進 SSP items
- 樣板版本 v2.1.0
- C2 SSP-scoped excel-import endpoint 自動也支援（共用 parser）

---

## 10. Implementation Reality / Reconciliation（2026-05-23 Phase C 結構化欄位）

C7 first ship (commit `e7f29e3`, 2026-05-22) 走「純文字 3 欄」精簡版（service_name /
provider / purpose）。Phase C 同期 user 反饋這結構不夠（CMMC / FedRAMP reference doc
有多種 service 類別 + 服務狀態欄位），加上 SSP UI 編輯也需要結構化欄位（不是只有 Excel）。

走「方案 B」：minimal schema extension — 加 2 個新欄位 + 補 2 個既有 OSCAL 欄位
expose 到 UI，不引入 FedRAMP catalog master data。

### 10.1 jedi-oscal 套件擴充（commit `8de7991`）

`OscalSspSystemImplementationItem` 加 2 個 nullable 欄位：

| 欄位 | 型別 | 用途 |
|------|------|------|
| `provider` | VARCHAR(255) | 供應商名稱（service_name 之外的單獨欄位）|
| `category` | VARCHAR(50) | 服務類別（6 個 enum）|

對應 entity / mapper 同步擴充。

### 10.2 BE 端到端擴充（commit `18a1110`）

- migration SQL: `scripts/sql/2026-05-23-add-leveraged-provider-category.sql`（cmmgr 跑）
- `SspLeveragedContextService` add/update/list/_to_dict 都 wire provider + category
- 順手補之前漏接的 OSCAL `status`（之前 design §6 列了但沒實作）+ OSCAL `purpose`（同）
- Excel 06 sheet `SHEET_LEVERAGED` 加 category column + 6 個 enum_values + i18n header

### 10.3 FE 表單擴充

`SspLeveragedSection.vue` 從原 design 的 4 欄位（名稱 / 日期 / 描述 + party_uuid）擴充到
7 欄位：

| # | 欄位 | UI | 對應 OSCAL |
|---|------|-----|-----------|
| 1 | 類別 | Dropdown 6 enum | category (新欄位) |
| 2 | 服務名稱 | InputText | title |
| 3 | 供應商 | InputText | provider (新欄位) |
| 4 | 授權日期 | Calendar | date_authorized |
| 5 | 服務狀態 | Dropdown 3 enum | status (新 expose) |
| 6 | 用途 | Textarea | purpose (新 expose) |
| 7 | 描述 | Textarea | description |

`party_uuid` 仍**隱藏**（user 不會知道 UUID 是什麼）— 保留 model + comment 預留未來 picker。

### 10.4 Category 6 個 enum 值

對齊 CMMC + FedRAMP common scenarios：

- `cloud_infrastructure`（雲端基礎設施 - IaaS）
- `cloud_platform`（雲端平台 - PaaS）
- `cloud_software`（雲端軟體 - SaaS）
- `managed_security`（資安託管服務 - MSSP）
- `identity_authentication`（身分驗證 / IAM）
- `other`（其他）

### 10.5 跟原 design §6 的差異

原 design §6 EntityFactory pattern 沿用，但 entity 構建時新欄位（provider / category /
status / purpose）跟著一起 build；ssp_id 寫入時走 mapper hasattr guard（避免老 schema
ORM model 沒新欄位時硬塞）。

## 11. Track B Reconciliation（2026-05-23 — Excel chain 補完）

Phase C C7 ship 後（leveraged 結構化欄位進 entity + SSP UI），Excel 端尚未跟上。Track B
補完整個 Excel chain（樣板 / parser / reconciler / write strategy / FE 預覽），詳見
[2026-05-23-feat-ssp-excel-system-characteristic-and-leveraged-structured.md](../../changelog/2026-05-23-feat-ssp-excel-system-characteristic-and-leveraged-structured.md)。

### 11.1 Excel 樣板欄位 iteration

從 v2.1.0 → v2.4.0 共 3 次 schema bump：

| 版本 | 變更 | 觸發 |
|-----|------|------|
| **v2.2.0** | leveraged 加 status enum；同 phase 加 受評標的 sheet | 初始 Track B |
| **v2.3.0** | sheet 名拿掉「01_」「02_」前綴；parser 加 legacy alias backward compat | user 反饋雜亂 |
| **v2.3.1** | SC + leveraged status enum 對齊 Billows convention (`active` vs `operational`) | user 反饋預覽頁狀態無法帶出 |
| **v2.4.0** | leveraged 加 date_authorized + description；provider 改 optional | user 反饋對齊 SSP UI edit dialog 欄位 |

### 11.2 LeveragedWriteStrategy 修法（vs 原 Phase C 設計）

原 Phase C 設計：entity 加 provider 後直接從 `parsed.provider` 寫到 `entity.provider`。

Track B 發現的 corner case：SSP UI display 讀的是 `entity.title`，而 LeveragedWriteStrategy 原本只寫 `entity.name`。Excel 匯入後 SSP UI 看到 bold 「—」+ small「服務名稱」(被誤當 provider 顯示)。

**Fix**：`service_name` 同時寫 `entity.name` + `entity.title`。BE 兩個欄位都填，UI 端讀 title 為主。

`_dict_to_parsed_leveraged` 拿掉 `provider=provider or service_name` cross-fill — user 沒填 provider 就維持空字串（之前 fallback 灌 service_name 到 provider 欄位）。

### 11.3 ParsedLeveraged 加 description

對齊 Excel v2.4.0 補的 description 欄位。`_dict_to_parsed_leveraged` 讀 dict.description；
LeveragedWriteStrategy payload 帶 description；entity.description 寫入。

### 11.4 Person role validator 改 non-blocking

03 參與人員 sheet 的 role validator 原本強制 4 個 Billows participant role
(`manager / reviewer / auditor / viewer`)，但 MF 內 OSCAL `responsible_parties`
合法持有 OSCAL 標準角色 (`system-owner` / `system-security-officer` 等，OSCAL
文件結構需要)。Filled-mode 寫 OSCAL role 進 Excel，parser self-reject。

**Fix**：`validate_person_role` blocking=True → False；code `INVALID_ROLE` →
`ROLE_NOT_BILLOWS_PARTICIPANT`。Confirm-time 加 `_LEGACY_BLOCKING_CODES_NOW_WARNING`
過濾名單，舊 parse_job stored blocking=True INVALID_ROLE 也跟著放寬。

### 11.5 filled-mode 設備 / 資訊系統 / 外部利用服務 sheet 空白

User 反饋 Excel 下載 3 個 sheet 全空。Root cause：items 實際存
`scope_type='ssp', scope_id=ssp_id`（MF template-edit panel 走
`ModuleFrameSspResourcesService` 寫入時 force scope='ssp'，C2.1 commit 設計），
但 `_fetch_mf_items` 用 `scope_type='module_frame'` 查 → 永遠空。

**Fix**：新 `_fetch_mf_items_via_ssp(mf)` — resolve chain mirror Track B
`_build_system_characteristic`：mf → profile → SSP → items by ssp_id。

