# 🟢 START HERE — FR-038 import-ssp：資源庫範本 SSP 匯入（設計定稿 + P1 套件寫入核心已完成，接手 P2）

> **給下個 session 的 prompt**：「讀 `docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-import-ssp-P2-START-HERE-handoff.md`，先過「🧭 WHY」+ §0 讀序硬 gate 懂 import-ssp 要解決什麼（design `import-ssp-design.md` 全讀 + api-contract §2），能答冷接自檢 4 問再碰 code。跑 §6 pre-flight + §7 verify P1，接手 P2（Excel 匯入，Model A+B 兩種 source 落 v2）。」
> 本檔自包含。狀態為 **2026-06-15**（設計定稿 + spec review 通過 + P1 套件單元測試綠 + BE BOOT/pytest 持平）。

> ## 🔖 交接現況（2026-06-15 換 session 當下）
> | 項目 | 值 |
> |------|----|
> | **進度** | import-ssp 設計定稿（`import-ssp-design.md`，user 校對後補正兩模型）+ **P1 套件寫入核心 DONE**。**下一棒 = P2（Excel 匯入打通，Model A+B）** |
> | 主專案 branch / HEAD | `feature/oscal-refactor` / **`2dca0f2b`** |
> | 主專案 working tree | **乾淨，只有 `M pyproject.toml`**（jedi-oscal-v2 dev path-dep，照規範勿 commit）|
> | 套件 branch / HEAD | `~/Projects/Jedicogy/module/jedi-python-package` `feature/oscal-refactor` / **`a930bfd`**（P1，領先 origin 1，**未 push**）|
> | ⚠️ 套件未 commit dev 改動（B1 留下，本 session 沒碰） | `M catalog_service.py`、`M framework_service.py`、`?? tests/catalog/test_catalog_service_import.py`。**dev path-dep，BE 重啟即生效；發版等整 feature + user 明示。別搞丟。** |
> | 跑得起來嗎 | `create_app()` BOOT OK；BE `python -m pytest test/` = **56 failed + 50 errors**（持平 baseline、零新回歸）+ 9 skipped；套件 `tests/io_export/` = **10 passed**（7 export + 3 import）|
> | push | **BE + 套件都未 push**，等 user 明示 |
> | **未收尾（user 還沒下收尾命令）** | 本 session 的 Q1（5 增量）+ import-ssp 設計/P1 的 changelog / analysis / SUMMARY / memory / Notion **全都還沒寫**；連前面 Wave 2B（B4.2/B5/匯出/AO-fix）的收尾也還掛著。下次 user 說「收尾」時一起補。 |

---

## 🧭 WHY：import-ssp 要解決什麼 + P2 在大圖位置（先懂才准碰 code）

**一句話**：合規資源庫（公版範本）原本就有「從文件/Excel 匯入 SSP」功能（**新增資源庫**從文件建、**編輯資源庫**時匯入並**差異比對**再更新），整套 **FE + 解析 + 流程 + 差異比對都還在且 FE 已是 v2**。FR-038 把 OSCAL 重寫成 v2 schema 後，這功能的**寫入層**（confirm 把資料存進 DB）還寫舊 v1 的表 → 被 disable。**import-ssp 這個 arc = 只換寫入層，讓它存進 v2 OSCAL 的表**，其餘不動。

**產品情境**（白話）：
- 合規資源庫 = 公版範本答案卷（catalog + profile + 範本 SSP），多個專案 clone 它當起點。
- 顧問/管理員手上常有現成 SSP 文件（Word/Excel）→ 上傳自動填進範本 SSP，不用人工一條條打。
- 兩種模型（user 2026-06-15 拍板兩種都做）：
  - **Model A — 編輯既有資源庫**：`source_type=module_frame`，import 解析該資源庫的 `template_ssp_id` 填/更新；再匯入時做**差異比對**（update-diff）。
  - **Model B — 新增資源庫**：`source_type=framework_version`，選一個框架版本（提供 catalog）+ 上傳文件 → **建新資源庫**（clone catalog→profile→空範本 SSP，= B1 既有 `clone_resource_library` 能力）→ 再填範本 SSP。每次都建新 = 永遠 create、不需 diff。
  - （`source_type=ssp` = 專案 SSP / Project Planning，**範圍外**。）

**FE 已經是 v2、不用動**（證據）：`compliance-manager-fe/src/views/module_frame/ImportExcelPage.vue` + `ImportDocxPage.vue`：`mode='create'|'update-mf'`、`sourceType = isCreate ? 'framework_version' : 'module_frame'`（第 95 行）、create 模式有 framework picker。路由 `module-frame-import-docx-update` / `module-frame-import-excel-update`，頁面 `ssp-docx-import-v2/`。FE 打的 BE 端點仍是 `/ssp-excel-imports/parse`+confirm、`/ssp-docx-imports/parse`+confirm（帶 `source_type`），這些端點目前 disabled。

**為何被卡（v2 翻地基留下的洞）**：confirm 的**寫入核心** `domain/oscal/strategy/ssp_write_strategy.py` / `module_frame_write_strategy.py` 還 import 舊 `jedi_oscal` v1 entity（`ControlImplementationEntity` / `PartyEntity` 等）→ 與 v2 撞同一 SQLAlchemy MetaData → 整條 route 被 `config/di_modules.py:EXCLUDE_MODULES` 擋掉。**P1 已把 v2 寫入機具（`OscalIoService.import_ssp`）做好**，P2/P3 把 confirm 改 delegate 到它、re-enable route。

**P2 在大圖位置**：P1（套件寫入核心）DONE → **P2（Excel，Model A+B）** → P3（Docx，A+B）→ P4（update-diff，Model A 再匯入）。

> **冷接自檢 4 問**（答不出回 §0 讀序）：① import-ssp 落點是哪份 SSP、為什麼是資源庫範本 SSP 不是專案 SSP？② Model A 與 Model B 差在哪、各自 `source_type` 是什麼？③ 為什麼說「只換寫入層」——哪些重用、哪些重建？④ P1 的 `OscalIoService.import_ssp` 涵蓋哪些子樹、為什麼是 create-only？

---

## §0 接手讀序（按序，1~2 是硬 gate）

1. 本檔「🧭 WHY」節 + 通讀本檔
2. 🔒 gate：[`import-ssp-design.md`](../import-ssp-design.md)（**全讀** — 兩模型 / source_type 分派 / 架構邊界 D-IS-1~3 / 4 phase / §9 pre-flight 假設）
3. 🔒 gate：[`api-contract.md`](../api-contract.md) §2（resource-library import-ssp 端點）+ [`design.md`](../design.md) §4.2（OscalIoService）/ §4.4（套件邊界）
4. P1 成品（套件）：`~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/jedi_oscal_v2/app/service/io/oscal_io_service.py` 的 `import_ssp` + `_import_*` helpers（P2 confirm 要呼叫它）；測試 `tests/io_export/test_oscal_import.py`
5. v1 excel app service（P2 要改寫其 confirm 寫入）：`app/oscal/service/ssp_excel_import_app_service.py`（`confirm_import` → `_confirm_superset_flow`(framework_version) / `_confirm_update_flow`(module_frame)）
6. v1 parser 輸出（P2 adapter 來源）：`app/oscal/service/excel_parser/` 的 `ParsedExcel` dataclass（`types.py`）

---

## §1 現況：本 session 做了什麼（2026-06-15，全 verified）

> 本 session 跨兩個 arc：先做完 Q1（接前一棒），再開 import-ssp（設計 + P1）。

### Q1 — 準備期 per-AO My Jobs（SHIPPED，commits 見 §11）
5 增量全完成 + verified（前一棒 handoff `2026-06-15-Q1-prep-jobs-START-HERE-handoff.md` 的任務）：
- Q1.1 綁定表 `workflow_execution_control_mapping` 加 `ao_part_id`（**校正**：表其實在 `public` 非 compliance，ORM `DEFAULT_SCHEMA=None`）+ seed 收證據 template。
- Q1.2 AO 推導抽共用 helper `app/grc/service/ao_derivation.py`（B5 + Q1 共用）。
- Q1.3 `project_start_app_service` 同步建 per-AO job（direct insert workflow_execution + job + mapping）。
- Q1.4 `vw_user_job_queue` rebind control_* + AO。
- Q1.5 端到端 smoke（catalog 729 真 L1 mf 412）PASS：17 控制 / 59 job / 指派後 view 查得 control+AO。
- migration（只套 dev）：`2026-06-15-fr038-q1-workflow-execution-control-mapping.sql`、`...-q1-seed-collect-evidence-template.sql`、`...-q1-vw-user-job-queue-rebind.sql`。

### import-ssp — 設計定稿 + P1（本檔主題）
- **設計**：`import-ssp-design.md`（brainstorm 收斂 + spec review 通過 + user 校對補正兩模型）。
- **P1 套件寫入核心 DONE**（套件 commit `a930bfd`）：`OscalIoService.import_ssp(oscal_ssp, *, target_ssp_id, curr_user, mode='create')` —— `export_oscal('ssp')` 的反向，讀 OSCAL ssp dict 寫 v2 SSP 樹。
  - 涵蓋 export round-trip 面：metadata roles/parties + system-characteristics + system-implementation/components + control-implementation/implemented-requirements（SoA 搭 props）。
  - **statements / by-components / LA / inventory 不在 P1**（export 也不 round-trip；LA/component/inventory 另有 FR-032 v2-bundle 路徑）。
  - create-only：目標 SSP 必須空（非空丟 ValueError）→ 重複匯入/覆寫是 P4。
  - 套件測試 `tests/io_export/test_oscal_import.py`：round-trip（import 後 export 比對）+ 非空 guard + 未知 target。io_export 全套 10 passed。

---

## §2 ⚠️ 會讓你做錯的陷阱 / 已驗地基（先看）

1. **綁定表 / SSP schema 落點**：jedi_common Base `DEFAULT_SCHEMA=None` → ORM table 不帶 schema → 走 search_path（`"$user", public`）落在 **public**，不是 compliance。Q1.1 踩過（handoff 誤導查錯 schema）。查表存在性要查對 schema。
2. **資源庫範本 SSP 建出來是空的**（已驗：mf 412 → `template_ssp_id=1481` → 0 control_impl / 0 sys_char / 0 sys_impl / 0 parties，只有 metadata 殼）。→ **P1 走 insert；首次匯入=create**。Model A 再匯入才需 overwrite/diff（P4）。
3. **export 本身是 MVP**：`OscalIoService._export_ssp` 不 round-trip statements/by-components/LA/inventory/users（看 `_implemented_requirement_to_json` / `_build_system_implementation`）。P1 import 鏡像此面，所以也不寫那些。**P2 adapter 別硬塞 P1 不支援的子樹**，會被丟掉。
4. **v2 表結構 ≠ v1**：不是 1:1 換 entity。AO 落 `oscal.catalog_control_parts`（name=assessment-objective）、SoA/方法落 `props`、SSP 12 子表 shape 改了。寫入一律走 P1 `import_ssp`，別自己拼 v2 entity。
5. **route disabled 原因 = v1 entity import**：`ssp_excel_import_route` / `ssp_docx_import_route` / `ssp_scoped_excel_import_route` 在 `EXCLUDE_MODULES`。re-enable 前必先把 app service / confirm 路徑裡的 `from jedi_oscal...` v1 import + v1 write strategy（`ssp_write_strategy` / `module_frame_write_strategy`）從匯入路徑拔掉，改 delegate P1。沒拔乾淨 → wire 進去 BE boot 撞 MetaData 直接炸。
6. **中介契約 = OSCAL ssp dict**（與 export 同形狀），不是獨立 dataclass。P2 的 adapter 把 `ParsedExcel` 轉成這個 dict 餵 `import_ssp`。
7. **pytest 用 `python -m pytest`**；BE boot 要補 dummy GITLAB/GITHUB env（見 §6）。
8. **plan 假設先 verify**（parser 輸出欄位 / confirm 分派 / source_type）才開工。不晶晶體。

---

## §3 P2 設計（方法，非既定，開工前 pre-flight 驗）

**目標**：Excel 匯入兩種 source 都落 v2，confirm 改 delegate P1 `import_ssp`，re-enable excel route。

**方法候選（推測，待 pre-flight verify）**：
1. **adapter「ParsedExcel → OSCAL ssp dict」**：新寫一個 adapter（主專案），把 `ParsedExcel`（controls / parties / system_characteristics / components...）轉成 P1 認的 OSCAL ssp dict（kebab keys，鏡像 export）。SoA → `implemented-requirement.props`。
   - ⚠️ verify：`ParsedExcel` 實際欄位（讀 `app/oscal/service/excel_parser/types.py`）；control-id 是否對得上目標資源庫的 v2 catalog（對不上 → warning 不擋，記 import_warnings）。
2. **confirm 改寫**：`SspExcelImportAppService._confirm_superset_flow`（framework_version=Model B）/ `_confirm_update_flow`（module_frame=Model A）：
   - Model A：解析 module_frame `{uid}` → `template_ssp_id` → adapter → `import_ssp(target_ssp_id, mode='create')`。
   - Model B：先 `clone_resource_library`（= B1 能力，從 framework_version clone catalog→profile→空範本 SSP）拿 new module_frame + template_ssp_id → adapter → `import_ssp(...)`。
   - 丟掉 v1 `ssp_write_strategy` / `module_frame_write_strategy` 在匯入路徑的呼叫。
3. **re-enable route**：把 `api.oscal.routes.ssp.ssp_excel_import_route` 從 `EXCLUDE_MODULES` 拿掉；DI wire `ssp_excel_import_app_service`（注入 P1 `OscalIoService` / `clone_resource_library` 能力）；確認 app service 不再 import v1 entity（boot 不炸）。
4. **OscalIoService 注入主專案**：套件 `OscalIoService` 要在主專案 DI container 暴露（目前可能只在套件內 export 用過；確認主專案有 provider，沒有就加）。

> **直覺推測（待 verify）**：P2 主力是「拔 v1 寫入 + 接 adapter + re-enable」，量比想像輕（FE/parser/流程/diff UI 都不動）。但「拔乾淨 v1 import 讓 boot 不炸」可能要逐一追 import chain，是最容易卡的點。

---

## §4 開工順位（P2，每段 smoke + 顯式 git add commit）

1. **pre-flight**：跑 §6 + §7（確認 P1 在、BE boot、pytest baseline）。
2. **讀 live**：`ssp_excel_import_app_service.py` 的 confirm 三分派 + `excel_parser/types.py` 的 `ParsedExcel` + `clone_resource_library` 簽章 + `OscalIoService` 在主專案 DI 現況。
3. **adapter**：寫 `ParsedExcel → OSCAL ssp dict`（先 Model A 單純路徑跑通）。
4. **confirm 改寫 + 拔 v1**：`_confirm_update_flow`(A) 先；`_confirm_superset_flow`(B) 後（多一步 clone 建殼）。
5. **re-enable route + DI wire**：移出 EXCLUDE_MODULES、wire app service、確認 boot 不炸。
6. **smoke**（真資料，用 catalog 729 資源庫 mf 412 當 Model A 底材；Model B 選一個 framework_version）：上傳 excel → 範本 SSP 填好（查 `oscal.ssp_implemented_requirements` 等）→ 量 BOOT OK + pytest 不多紅 → 顯式 git add commit。
7. **P3（docx）** 比照；**P4（update-diff）** 最後。

---

## §5 該讀 / 預期改動的檔案

| 檔案 | 為何 |
|------|------|
| `~/Projects/.../jedi-oscal-v2/.../app/service/io/oscal_io_service.py` | P1 成品 `import_ssp`，P2 confirm 要呼叫（已完成，讀懂簽章/涵蓋面）|
| `app/oscal/service/ssp_excel_import_app_service.py` | P2 改寫 confirm 寫入（`_confirm_update_flow` / `_confirm_superset_flow`）|
| `app/oscal/service/excel_parser/types.py` | `ParsedExcel` 欄位（adapter 來源）|
| 新檔 `app/oscal/service/...（excel→oscal dict adapter）` | P2 新增 adapter |
| `config/di_modules.py` | 從 `EXCLUDE_MODULES` 移出 excel import route |
| `di_containers/oscal/oscal_containers.py` | wire `ssp_excel_import_app_service` + 暴露 `OscalIoService` |
| `domain/oscal/strategy/ssp_write_strategy.py` / `module_frame_write_strategy.py` | v1 寫入，**退役**（從匯入路徑移除呼叫；不刪檔，確認無 live consumer 後另開 cleanup）|

---

## §6 Pre-flight（必跑，可複製貼）

```bash
cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
git branch --show-current                      # feature/oscal-refactor
git status --short                             # 僅 ' M pyproject.toml'
git log --oneline -3                            # 2dca0f2b / d6e88221 / dabf1ebc
( cd ~/Projects/Jedicogy/module/jedi-python-package && git log --oneline -1 && git status -sb | head -6 )  # a930bfd P1 + B1 dev 改動還在
set -a; source .env 2>/dev/null; set +a
export GITLAB_API_VERSION=4 GITLAB_URL=http://localhost GITLAB_PRIVATE_TOKEN=dummy GITHUB_PRIVATE_TOKEN=dummy
poetry run python -c "
from dotenv import load_dotenv; load_dotenv()
import eventlet; eventlet.monkey_patch(all=False, socket=True)
import sys; sys.setrecursionlimit(5000)
from core.app_factory import create_app; create_app(); print('BOOT OK')"
poetry run python -m pytest test/ -q -p no:cacheprovider --continue-on-collection-errors 2>&1 | tail -1  # 56 failed,...,50 errors（持平）
```
> `.env` 在 shell source 會在 JSON 行報 parse error（無害）；python script 一律用 `load_dotenv()`。

---

## §7 Verify P1 確實 close（必跑，可複製貼）

```bash
cd /Users/chouraymond/Projects/Billows/Audit-Manager/compliance-manager-be
export GITLAB_API_VERSION=4 GITLAB_URL=http://localhost GITLAB_PRIVATE_TOKEN=dummy GITHUB_PRIVATE_TOKEN=dummy
# P1 套件單元測試（round-trip + 非空 guard + 未知 target）→ io_export 全套應 10 passed
poetry run python -m pytest ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/tests/io_export/ -q -p no:cacheprovider 2>&1 | tail -3
# import_ssp 存在且簽章正確
poetry run python -c "from jedi_oscal_v2.app.service.io.oscal_io_service import OscalIoService; import inspect; print(inspect.signature(OscalIoService.import_ssp))"
```

---

## §8 行為規範重要提醒（適用本棒）

- **不切 branch**（兩 repo 都在 `feature/oscal-refactor`；branch 不對停下問 user）。
- **可自行階段性 commit**（顯式 `git add` 檔名、**禁 `-am`**；各 repo 分開）；**push / 收尾 / 套件發版等 user 明示**。
- `pyproject.toml` path-dep **勿 commit**；套件 dev 改動 commit 進套件 branch 防搞丟，但**不發 Nexus**。
- **改 BE 後提醒 user 重啟 BE**（無 hot reload）；服務 user 自己起。
- **跨 repo 動 FE 前先讀 FE CLAUDE.md**（本棒理論上不動 FE，但若要碰先讀）。
- **動匯入管線 = 敏感子系統**：pre-flight 驗 live 路徑、自己讀關鍵檔（別憑摘要）、多寫入路徑全 wire（memory `feedback_preflight_before_touching_pipeline`）。
- **遇到架構/資料模型取捨、需選方案、scope 邊界 → 停下問 user**。
- SQL migration（若 P2 有）：cmmgr + `--single-transaction -v ON_ERROR_STOP=1`，新表 GRANT cm_app、收尾 INSERT schema_migrations；**本期 migration 只套 dev**。

---

## §9 收尾流程（整 import-ssp arc 做完 + user 明示才做）

盤點 commits（BE + 套件）→ changelog（type=feat，import-ssp 各 phase）→ analysis（兩模型取捨 / 只換寫入層 / OSCAL-dict 當中介）→ SUMMARY → 回頭更新橫向文件（design §4.2/§4.4 拿掉 import defer 標記、api-contract §2、import-ssp-design 標完工、frontend-overview 若動 payload）→ memory feedback（≥1 條：如「import-ssp 只換寫入層、FE 已 v2」「v1 import route disabled 因 v1 entity import 撞 v2 MetaData」）→ Notion。**push 等 user。**

> **本 session 自身的收尾（Q1 + import-ssp 設計/P1 的 changelog / SUMMARY / analysis / memory / Notion）也都還沒做** —— user 還沒下收尾命令，全留著。連更前面 Wave 2B（B4.2/B5/匯出/AO-fix）的收尾也掛著。下次 user 說「收尾」一起補。

---

## §10 不在本期 scope（別順手做）

- **專案 SSP 匯入**（`source_type=ssp` / Project Planning）—— 本 arc 只做資源庫（module_frame + framework_version）。
- **statements / by-components / leveraged-authorizations / inventory** 的匯入寫入 —— P1 不支援（export 也不 round-trip）；LA/component/inventory 另有 FR-032 v2-bundle 路徑，要接另議。
- **P4 的 update-diff** —— P2/P3 先做 create；diff 留 P4。
- **FE 改動** —— FE 已 v2，原則不動。
- **套件發 Nexus / `pyproject.toml` 改回 pin** —— 整 feature 完 + user 明示。
- **正式環境 migration**（stg/poc/prod）—— 本期所有 migration 只套 dev。
- **Q1 / Wave 2B / import-ssp 的收尾文件** —— 等 user 下收尾命令。

---

## §11 本 session commits

### 主專案（branch `feature/oscal-refactor`，未 push；HEAD `2dca0f2b`）
```
2dca0f2b docs(FR-038): import-ssp spec 補正 — 兩種資源庫匯入模型 + source_type 分派
d6e88221 docs(FR-038): import-ssp 設計定稿（資源庫範本 SSP 匯入，docx+excel 落 v2）
dabf1ebc feat(FR-038): Wave 2B Q1.4 — vw_user_job_queue rebind control_* + AO
394fe2c1 feat(FR-038): Wave 2B Q1.3 — project_start 同步建 per-AO 準備期 job
7cfe3565 fix(FR-038): Q1.1 綁定表改 ALTER public（非 CREATE compliance）
6429ec5f feat(FR-038): Wave 2B Q1.2 — AO 推導抽共用 helper（ao_derivation）
86a4099e feat(FR-038): Wave 2B Q1.1 — 建 workflow_execution_control_mapping + seed 收證據 template
```
（更前面 c89063db 起為前一棒，見該 commit。）

### 套件（`~/Projects/Jedicogy/module/jedi-python-package` branch `feature/oscal-refactor`，未 push；HEAD `a930bfd`，領先 origin 1）
```
a930bfd feat(oscal-v2): OscalIoService.import_ssp — OSCAL SSP → v2 relational tree (FR-038 P1)
```
- 套件另有 B1 未 commit dev 改動：`M catalog_service.py`、`M framework_service.py`、`?? tests/catalog/test_catalog_service_import.py`（別搞丟、別發版）。
- dev DB：Q1 三個 migration 已套 dev（stg/poc/prod 待辦）+ Q1 smoke 殘留已清。

---

## §12 給 fresh session 的超短 prompt

```
讀 docs/features/FR-038-2606-oscal-redesign/handoff/2026-06-15-import-ssp-P2-START-HERE-handoff.md。
先過「🧭 WHY」+ §0 讀序硬 gate（import-ssp-design.md 全讀 + api-contract §2 + design §4.2/§4.4），
能答冷接自檢 4 問（落點為何資源庫範本 SSP / Model A vs B 與其 source_type / 為何「只換寫入層」/ P1 import_ssp 涵蓋面與為何 create-only）才往下。
跑 §6 pre-flight（BOOT OK + pytest 56f/50e）+ §7 verify P1（io_export 10 passed）。
接手 P2：Excel 匯入兩種 source 落 v2 — adapter(ParsedExcel→OSCAL dict) → confirm 改 delegate 套件 import_ssp（拔 v1 write strategy）→ re-enable excel route → 真資料 smoke + commit。
每段 smoke + 顯式 git add commit。push/收尾/套件發版等 user 明示；不切 branch；pyproject.toml dev path-dep 勿 commit。
```

---
### 冷接可行性自檢 ✅
看本檔 + §0 讀序 + 跑 §6/§7 → 能確認現況（Q1 shipped + import-ssp 設計定稿 + P1 套件寫入核心 done + commits + 套件 dev 改動）、懂 import-ssp WHY（只換寫入層、FE 已 v2、兩模型、為何卡）、知道 P2 怎麼接（adapter + confirm 改寫 + re-enable）、知道地基陷阱（schema 落 public、範本 SSP 空、export MVP 面、拔 v1 import 才不炸）、知道規範界線。不需 user 額外解釋即可開工。
