# Phase A0.1 Implementation Plan — SSP system-implementation 結構重整補丁

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan. Steps use checkbox (`- [ ]`) syntax.

**Goal**：把 A0 ship 的 `system_security_plan_system_implementations` 表結構正確化 — rename 成 `ssp_system_implementation_items`、補上 OSCAL `system-implementation` block 主檔 `ssp_system_implementations`、補上 inventory-item ↔ component M:N join 表、改 column `information_system_id` 為 `system_characteristic_id`。

**Architecture**：DDD 嚴格分層擴充 — 1 個 SQL migration 完成所有 schema change（含 rename / new tables / column rename / backfill）；jedi-oscal 套件 ORM rename + 新 main/join 完整 stack；主 BE caller 改寫；既有 A0 32 個測試 fix。dev 期續 poetry path dep，feature 完工才推 Nexus。

**Tech Stack**：Python 3.11 / SQLAlchemy 2.0 / PostgreSQL / pytest / poetry path dep / jedi-oscal local

**Spec**：[design-A0.1.md](design-A0.1.md)
**前置**：A0 已 ship（commits c4804d7 / a1b80cc / e73b1aa / 60a4a2e / 5d8626e）
**Branch**：`feature/ssp-import-export-phase2`

---

## 全域實作規範（每個 task 都要遵守）

1. **TDD strict**：先寫測試 → 確認 fail → 改實作 → 確認 pass → commit
2. **顯式 git add**：`git add <file1> <file2>`，**禁用 `-am` / `-A`**
3. **commit 前必 `git status --short`** 確認 staged 區只有自己 add 的檔案
4. **Co-Authored-By footer**：`Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>`
5. **不自動 publish jedi-oscal**：保持 path dep
6. **SQL migration 用 `cmmgr`**
7. **改 BE service 後提醒 user 重啟**

---

## File Structure

### compliance-manager-be（主專案）

| 動作 | 路徑 |
|------|-----|
| 已有（不動）| `scripts/sql/2026-05-19-ssp-system-implementation-restructure.sql` ★ user 已自行執行 |
| Modify | `app/oscal/service/ssp_versioning_service.py`（既有 caller，import + clone main 流程）|
| Modify | `app/associations/service/project_device_mapping_service.py`（既有 caller，upsert main 前置）|
| Modify | `di_containers/oscal/oscal_containers.py`（wiring main + join service）|
| Modify | `tests/test_ssp_system_implementation_regression.py`（A0 regression 更名）|
| Create | `tests/test_ssp_system_implementation_main_regression.py`（main upsert smoke）|
| Create | `docs/changelog/2026-05-19-tweak-ssp-system-impl-restructure.md`|

### jedi-oscal 套件

**Rename 既有檔（class + 檔名改）**：
| 既有 | 改後 |
|------|-----|
| `infra/model/ssp/ssp_system_implementation.py` (class `OscalSystemSecurityPlanSystemImplementation`) | `infra/model/ssp/ssp_system_implementation_items.py` (class `OscalSspSystemImplementationItem`) |
| `domain/entity/ssp/ssp_system_implementation_entity.py` (class `SystemImplementationEntity`) | `.../ssp_system_implementation_item_entity.py` (class `SspSystemImplementationItemEntity`) |
| `domain/entity/ssp/ssp_system_implementation_query_entity.py` | `.../ssp_system_implementation_item_query_entity.py` |
| `infra/repository/ssp/ssp_system_implementation_repo_impl.py` (class `SystemImplementationRepoImpl`) | `.../ssp_system_implementation_item_repo_impl.py` (class `SspSystemImplementationItemRepoImpl`) |
| `domain/repository/ssp/system_implementation_repo.py` (class `ISystemImplementationRepo`) | `.../system_implementation_item_repo.py` (class `ISspSystemImplementationItemRepo`) |
| `infra/mapper/ssp/system_implementation_mapper.py` (class `SystemImplementationMapper`) | `.../system_implementation_item_mapper.py` (class `SspSystemImplementationItemMapper`) |
| `app/dto/ssp/ssp_system_implementation_dto.py` (class `SystemImplementationDTO`) | `.../ssp_system_implementation_item_dto.py` (class `SspSystemImplementationItemDTO`) |
| `domain/services/ssp/system_implementation_domain_service.py` (class `SystemImplementationDomainService`) | `.../system_implementation_item_domain_service.py` (class `SspSystemImplementationItemDomainService`) |

**Modify**（父層引用更新）：
- `infra/model/ssp/ssp.py`（relationship `system_implementations`）
- `infra/mapper/ssp/system_security_plan_mapper.py`
- `infra/mapper/ssp/ssp_yaml_mapper.py`（**核心改動**：從 main 出發 + 三分支 + implemented-components）
- `domain/entity/ssp/ssp_entity.py`
- `app/dto/ssp/ssp_dto.py`
- `infra/repository/ssp/__init__.py`（export 改 class 名）
- `domain/repository/ssp/__init__.py`
- `domain/services/ssp/__init__.py`

**Create 新檔（main 完整 stack — 8 個）**：
- `infra/model/ssp/ssp_system_implementation.py`（class `OscalSspSystemImplementation`，main table ORM）
- `domain/entity/ssp/ssp_system_implementation_entity.py`（class `SspSystemImplementationEntity`，main entity）
- `domain/entity/ssp/ssp_system_implementation_query_entity.py`
- `domain/repository/ssp/system_implementation_repo.py`（class `ISspSystemImplementationRepo`）
- `infra/repository/ssp/ssp_system_implementation_repo_impl.py`（class `SspSystemImplementationRepoImpl`）
- `infra/mapper/ssp/system_implementation_mapper.py`（class `SspSystemImplementationMapper`）
- `app/dto/ssp/ssp_system_implementation_dto.py`（class `SspSystemImplementationDTO`）
- `domain/services/ssp/system_implementation_domain_service.py`（class `SspSystemImplementationDomainService`，含 `upsert_by_scope`）

**Create 新檔（join 完整 stack — 8 個）**：
- `infra/model/ssp/ssp_inventory_item_component.py`（class `OscalSspInventoryItemComponent`）
- `domain/entity/ssp/ssp_inventory_item_component_entity.py`
- `domain/entity/ssp/ssp_inventory_item_component_query_entity.py`
- `domain/repository/ssp/inventory_item_component_repo.py`
- `infra/repository/ssp/ssp_inventory_item_component_repo_impl.py`
- `infra/mapper/ssp/inventory_item_component_mapper.py`
- `app/dto/ssp/ssp_inventory_item_component_dto.py`
- `domain/services/ssp/inventory_item_component_domain_service.py`

**Modify 測試**：
- `tests/test_ssp_system_implementation_extension.py`（A0 32 個測試 — import / class 名 / column 名）

**Create 新測試**：
- `tests/test_ssp_system_implementation_main.py`（main 表 CRUD + UNIQUE + upsert_by_scope）
- `tests/test_ssp_inventory_item_component.py`（join 表 CRUD + M:N relationship）

---

## Task 0 — Pre-Flight Verification（**第一個 task，必跑**）

開工前確認：
1. SQL migration 已 user 自行執行於 dev DB
2. 既有 14 個 jedi-oscal 檔結構與 design 預期相符
3. 主 BE branch 正確

- [ ] **Step 1: Verify SQL migration 已執行**

```bash
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev <<'EOF'
\dt oscal.ssp_system_implementations
\dt oscal.ssp_system_implementation_items
\dt oscal.ssp_inventory_item_components
SELECT COUNT(*) FROM oscal.ssp_system_implementation_items WHERE system_implementation_id IS NULL;
EOF
```

Expected: 3 個表都存在、items.system_implementation_id 全部不 null。

若 SQL 未執行，**STOP 並通知 user 先跑** `scripts/sql/2026-05-19-ssp-system-implementation-restructure.sql`。

- [ ] **Step 2: Verify jedi-oscal path dep 仍 active**

```bash
grep "jedi-oscal" ~/Projects/Billows/Audit-Manager/compliance-manager-be/pyproject.toml
```

Expected: 看到 `jedi-oscal = { path = "...", develop = true }` active。若否，先 uncomment 並 `poetry update jedi-oscal`。

- [ ] **Step 3: Verify branch**

```bash
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git branch --show-current
```

Expected: `feature/ssp-import-export-phase2`

- [ ] **Step 4: Verify jedi-oscal 既有檔存在**

```bash
ls ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/infra/model/ssp/ssp_system_implementation.py
ls ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/domain/entity/ssp/ssp_system_implementation_entity.py
ls ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/infra/mapper/ssp/system_implementation_mapper.py
ls ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/app/dto/ssp/ssp_system_implementation_dto.py
```

Expected: 全部存在。

---

## Task 1 — jedi-oscal 既有檔 rename（items 層 8 個 stack）

**目的**：把 items 表對應的 8 個 ORM/entity/repo/mapper/dto/domain_service 檔案 + class 名 rename 到 `*_item*` / `Ssp*Item*`。

**注意**：先做檔名 rename + class rename，**不改 column** — column rename 在 Task 3 做（拆分減少單 commit 範圍）。

### Step 1: ORM rename

- [ ] **1.1: 改 ORM 檔**

`mv ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal/jedi_oscal/infra/model/ssp/ssp_system_implementation.py ssp_system_implementation_items.py`

編輯內容：
- `class OscalSystemSecurityPlanSystemImplementation` → `class OscalSspSystemImplementationItem`
- `__tablename__ = "system_security_plan_system_implementations"` → `__tablename__ = "ssp_system_implementation_items"`
- 既有 `__table_args__` index 名同步：`ix_ssp_sys_impl_*` → `ix_ssp_si_items_*`
- 既有所有 column 維持（含 information_system_id — Task 3 才改）

- [ ] **1.2: Verify import path 一致**

```bash
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal
poetry run python -c "from jedi_oscal.infra.model.ssp.ssp_system_implementation_items import OscalSspSystemImplementationItem; print('OK')"
```

Expected: `OK`。

### Step 2~8: Entity / Query Entity / Repo (interface+impl) / Mapper / DTO / Domain Service rename

對每個檔案重複類似步驟：
- `mv` 改檔名
- 改 class 名
- 同步 `__init__.py` export
- Verify import path

詳細 class rename 對照見 design-A0.1.md §4.1。

### Step 9: 父層引用更新

更新 4 個父層檔案內的 import / class 名引用：
- [ ] **9.1**: `infra/model/ssp/ssp.py`：`OscalSystemSecurityPlanSystemImplementation` → `OscalSspSystemImplementationItem`
- [ ] **9.2**: `infra/mapper/ssp/system_security_plan_mapper.py`：`SystemImplementationMapper` → `SspSystemImplementationItemMapper`
- [ ] **9.3**: `domain/entity/ssp/ssp_entity.py`：`SystemImplementationEntity` → `SspSystemImplementationItemEntity`
- [ ] **9.4**: `app/dto/ssp/ssp_dto.py`：`SystemImplementationDTO` → `SspSystemImplementationItemDTO`

### Step 10: 既有 32 個測試 fix（只改 import / class 名，column 不改）

- [ ] **10.1**: 改 `tests/test_ssp_system_implementation_extension.py`：
  - `from jedi_oscal.infra.model.ssp.ssp_system_implementation import OscalSystemSecurityPlanSystemImplementation` → `... ssp_system_implementation_items import OscalSspSystemImplementationItem`
  - 全文 `SystemImplementationEntity` → `SspSystemImplementationItemEntity`（依此類推所有 class 名）

### Step 11: Run tests + commit

- [ ] **11.1**: `cd jedi-oscal && poetry run pytest tests/test_ssp_system_implementation_extension.py -v`
- [ ] **11.2**: Expected: **32 passed**（功能不變只改名）
- [ ] **11.3**: Commit:

```bash
cd ~/Projects/Jedicogy/module/jedi-python-package
git add jedi-oscal/jedi_oscal/infra/model/ssp/ \
        jedi-oscal/jedi_oscal/domain/entity/ssp/ \
        jedi-oscal/jedi_oscal/domain/repository/ssp/ \
        jedi-oscal/jedi_oscal/infra/repository/ssp/ \
        jedi-oscal/jedi_oscal/infra/mapper/ssp/ \
        jedi-oscal/jedi_oscal/app/dto/ssp/ \
        jedi-oscal/jedi_oscal/domain/services/ssp/ \
        jedi-oscal/tests/test_ssp_system_implementation_extension.py
git commit -m "$(cat <<'EOF'
refactor(ssp-system-impl): rename items stack to Ssp* + _items 後綴

Phase 2 A0.1：既有 items 表 ORM + entity + repo + mapper + DTO + domain service
全部 rename 對齊新表名 ssp_system_implementation_items。
功能不變、所有測試綠。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

---

## Task 2 — jedi-oscal 新建 main 完整 stack（8 個檔）

**目的**：建立 `oscal.ssp_system_implementations` main 表對應的 DDD 完整 stack。

### Step 1: ORM model

- [ ] Create `jedi_oscal/infra/model/ssp/ssp_system_implementation.py`：

```python
import uuid
from typing import Optional
from datetime import datetime
from sqlalchemy import String, Text, UUID, DateTime, Integer, JSON, UniqueConstraint, Index, func
from sqlalchemy.orm import Mapped, mapped_column
from jedi_common.session.database.declarative_base import Base


class OscalSspSystemImplementation(Base):
    """OSCAL system-implementation block 主檔，1:1 per (scope_type, scope_id)。

    對應 OSCAL spec system-implementation block（無 UUID，但 DB 規範保留 uid，
    OSCAL 匯出時 mapper 排除 uid）。
    """
    __tablename__ = "ssp_system_implementations"
    __table_args__ = (
        UniqueConstraint("scope_type", "scope_id", name="uq_ssp_system_implementations_scope"),
        Index("ix_ssp_system_implementations_scope", "scope_type", "scope_id"),
        {"schema": "oscal"},
    )

    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    uid: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
    scope_type: Mapped[str] = mapped_column(String(20), nullable=False,
        comment="'ssp' or 'module_frame'")
    scope_id: Mapped[int] = mapped_column(Integer, nullable=False,
        comment="對應 scope_type 指向的 id (soft FK)")
    remarks: Mapped[Optional[str]] = mapped_column(Text)
    props_jsonb: Mapped[Optional[dict]] = mapped_column(JSON)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=func.now(), nullable=False)
    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=func.now(), onupdate=func.now(), nullable=False)
    created_user: Mapped[Optional[str]] = mapped_column(String(50))
    updated_user: Mapped[Optional[str]] = mapped_column(String(50))
```

### Step 2~7: Entity / Query Entity / Repo / Mapper / DTO / Domain Service

依 pattern 建立。Domain service 需含特殊方法 `upsert_by_scope`:

```python
def upsert_by_scope(self, scope_type: str, scope_id: int, **kwargs) -> SspSystemImplementationEntity:
    """找 (scope_type, scope_id) 對應 main row；不存在就建。"""
    existing = self._repo.find_by_scope(scope_type, scope_id)
    if existing:
        return existing
    new_entity = SspSystemImplementationEntity(scope_type=scope_type, scope_id=scope_id, **kwargs)
    return self._repo.add(new_entity)
```

### Step 8: TDD 測試

- [ ] Create `tests/test_ssp_system_implementation_main.py`：

```python
def test_main_table_exists():
    from jedi_oscal.infra.model.ssp.ssp_system_implementation import OscalSspSystemImplementation
    assert OscalSspSystemImplementation.__tablename__ == "ssp_system_implementations"


def test_main_entity_has_required_attrs():
    from jedi_oscal.domain.entity.ssp.ssp_system_implementation_entity import SspSystemImplementationEntity
    e = SspSystemImplementationEntity(scope_type="ssp", scope_id=42, remarks="test")
    assert e.scope_type == "ssp" and e.scope_id == 42


def test_main_mapper_round_trip():
    # to_entity + to_model round-trip
    ...


def test_main_repo_has_find_by_scope_and_upsert():
    from jedi_oscal.infra.repository.ssp.ssp_system_implementation_repo_impl import SspSystemImplementationRepoImpl
    assert hasattr(SspSystemImplementationRepoImpl, "find_by_scope")


def test_main_domain_service_upsert_by_scope():
    # 確認 upsert 行為（mock repo）
    ...
```

### Step 9: Run tests + commit

- [ ] **9.1**: `poetry run pytest tests/test_ssp_system_implementation_main.py -v`
- [ ] **9.2**: Expected: 5+ passed
- [ ] **9.3**: Commit:

```bash
git add jedi-oscal/jedi_oscal/infra/model/ssp/ssp_system_implementation.py \
        jedi-oscal/jedi_oscal/domain/entity/ssp/ssp_system_implementation_entity.py \
        jedi-oscal/jedi_oscal/domain/entity/ssp/ssp_system_implementation_query_entity.py \
        jedi-oscal/jedi_oscal/domain/repository/ssp/system_implementation_repo.py \
        jedi-oscal/jedi_oscal/infra/repository/ssp/ssp_system_implementation_repo_impl.py \
        jedi-oscal/jedi_oscal/infra/mapper/ssp/system_implementation_mapper.py \
        jedi-oscal/jedi_oscal/app/dto/ssp/ssp_system_implementation_dto.py \
        jedi-oscal/jedi_oscal/domain/services/ssp/system_implementation_domain_service.py \
        jedi-oscal/tests/test_ssp_system_implementation_main.py

git commit -m "$(cat <<'EOF'
feat(ssp-system-impl): 新增 main 表 OscalSspSystemImplementation 完整 stack

Phase 2 A0.1：OSCAL system-implementation block 鏡像主檔 (1:1 per scope)。
含 ORM + entity + repo + mapper + DTO + domain service（含 upsert_by_scope）。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
```

---

## Task 3 — Column rename: information_system_id → system_characteristic_id

**目的**：把 items 表的 `information_system_id` column 在 ORM / entity / repo / mapper / DTO 全層 rename 為 `system_characteristic_id`。

**注意**：DB schema 已在 SQL migration rename，這 task 只動 jedi-oscal 程式碼層。

- [ ] **Step 1**: items ORM 改 column 名

`jedi_oscal/infra/model/ssp/ssp_system_implementation_items.py`：
```python
# 將
information_system_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, ...)
# 改成
system_characteristic_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True,
    comment="soft FK → oscal.system_security_plans_system_characteristics.id")
```

- [ ] **Step 2**: items entity 改 attribute

`ssp_system_implementation_item_entity.py`：
```python
# Constructor 參數 + self.X 賦值
information_system_id → system_characteristic_id
```

- [ ] **Step 3**: items query entity 同步

- [ ] **Step 4**: items mapper 兩向都改

`system_implementation_item_mapper.py` 內 `to_entity` 跟 `to_model` 兩個 method 內所有 `information_system_id` 字串改 `system_characteristic_id`。

- [ ] **Step 5**: items DTO 同步

- [ ] **Step 6**: items repo find_by_information_system_id 方法改名

`SspSystemImplementationItemRepoImpl` + interface：
- `find_by_information_system_id(information_system_id)` → `find_by_system_characteristic_id(system_characteristic_id)`

- [ ] **Step 7**: 既有 32 個測試對應字串改名

`tests/test_ssp_system_implementation_extension.py`：搜尋 `information_system_id` 全部換 `system_characteristic_id`。

- [ ] **Step 8**: Run tests

```bash
cd jedi-oscal && poetry run pytest tests/test_ssp_system_implementation_extension.py -v
```

Expected: 32 passed。

- [ ] **Step 9**: Commit

```bash
git add jedi-oscal/jedi_oscal/infra/model/ssp/ssp_system_implementation_items.py \
        jedi-oscal/jedi_oscal/domain/entity/ssp/ssp_system_implementation_item_entity.py \
        jedi-oscal/jedi_oscal/domain/entity/ssp/ssp_system_implementation_item_query_entity.py \
        jedi-oscal/jedi_oscal/infra/mapper/ssp/system_implementation_item_mapper.py \
        jedi-oscal/jedi_oscal/app/dto/ssp/ssp_system_implementation_item_dto.py \
        jedi-oscal/jedi_oscal/infra/repository/ssp/ssp_system_implementation_item_repo_impl.py \
        jedi-oscal/jedi_oscal/domain/repository/ssp/system_implementation_item_repo.py \
        jedi-oscal/tests/test_ssp_system_implementation_extension.py
git commit -m "refactor(ssp-system-impl): items column rename information_system_id → system_characteristic_id ..."
```

---

## Task 4 — items 表加 `system_implementation_id` FK + relationship

**目的**：items ORM 加 FK column + relationship 到 main。

- [ ] **Step 1**: items ORM 加 column + relationship

```python
# ssp_system_implementation_items.py
system_implementation_id: Mapped[int] = mapped_column(
    Integer,
    ForeignKey("oscal.ssp_system_implementations.id", ondelete="CASCADE"),
    nullable=False,
    comment="FK → ssp_system_implementations.id"
)
system_implementation: Mapped["OscalSspSystemImplementation"] = relationship(...)
```

- [ ] **Step 2**: items entity / query entity / mapper / DTO 加對應 attribute

- [ ] **Step 3**: 既有測試補 fixture 給 system_implementation_id

Tests 內既有測試構造 `OscalSspSystemImplementationItem(...)` 時要補 `system_implementation_id`（用 fixture 先建 main row）。

- [ ] **Step 4**: Run tests + commit

---

## Task 5 — 新建 join 表完整 stack

**目的**：建 `oscal.ssp_inventory_item_components` M:N join 表的 8 層 stack。

[Step 1~9 同 Task 2 pattern，建立 ORM + entity + repo + mapper + DTO + domain service + 測試]

詳細 column 規格見 design-A0.1.md §3.3。

關鍵：domain service 內加 application 層驗證:

```python
def add_link(self, inventory_item_id: int, component_id: int):
    inv = self._items_repo.get_by_id(inventory_item_id)
    comp = self._items_repo.get_by_id(component_id)
    if inv.implementation_type != "hardware":
        raise BadRequestError("inventory_item_id 必須指向 type=hardware")
    if comp.implementation_type not in ("component", "system", "subsystem", "service", "software"):
        raise BadRequestError("component_id 必須指向 type IN component family")
    # ... add link
```

---

## Task 6 — yaml_mapper 三分支 + implemented-components 整合

**目的**：`ssp_yaml_mapper.py` 從 main 出發、items 三分支、加 implemented-components join 輸出。

- [ ] **Step 1**: 寫新測試覆蓋 main → items 路徑

```python
def test_yaml_mapper_uses_main_block_level_fields():
    # main 有 remarks → output 含 "remarks"
    ...

def test_yaml_mapper_inventory_item_implemented_components():
    # join 表有資料 → inventory-item 輸出含 "implemented-components": [...]
    ...
```

- [ ] **Step 2**: 改 `_system_implementation_to_dict` 簽章從 items list 改成接 main entity

```python
@staticmethod
def _system_implementation_to_dict(main_entity, items_list, join_links) -> Dict:
    out = {}
    if main_entity:
        if main_entity.remarks: out["remarks"] = main_entity.remarks
        if main_entity.props_jsonb: out["props"] = main_entity.props_jsonb
    # 三分支 items（既有邏輯）
    ...
    # implemented-components 從 join_links 填入
    ...
```

- [ ] **Step 3**: 父層 `entity_to_dict` 改 call signature
- [ ] **Step 4**: Run tests + commit

---

## Task 7 — 主 BE caller 改寫

**目的**：兩個既有 caller 改用新 class + main upsert pattern。

### Task 7a: `project_device_mapping_service.py`

- [ ] **Step 1**: import 改新 class
- [ ] **Step 2**: DI 加 main domain service 參數
- [ ] **Step 3**: 寫 item 前 `upsert_by_scope` 拿 system_implementation_id：

```python
main = self._main_domain_service.upsert_by_scope(scope_type="ssp", scope_id=int(ssp_id))
item_entity = SspSystemImplementationItemEntity(
    system_implementation_id=main.id,  # ← 新
    system_security_plan_id=int(ssp_id),
    scope_type="ssp",
    scope_id=int(ssp_id),
    name=device_entity.name,
    description=device_entity.description,
    implementation_type=SystemImplementationType.HARDWARE,
)
```

### Task 7b: `ssp_versioning_service.py`

- [ ] **Step 1**: import 改新 class
- [ ] **Step 2**: clone SSP 版本時 **先 clone main**:

```python
def _clone_system_implementations(self, session, old_ssp, new_ssp, curr_user):
    # 1. 先 clone main (1 row)
    old_main = session.query(OscalSspSystemImplementation).filter_by(
        scope_type="ssp", scope_id=old_ssp.id
    ).first()
    if old_main:
        new_main = OscalSspSystemImplementation(
            uid=uuid.uuid4(),
            scope_type="ssp",
            scope_id=new_ssp.id,
            remarks=old_main.remarks,
            props_jsonb=old_main.props_jsonb,
            created_user=curr_user,
            updated_user=curr_user,
        )
        session.add(new_main)
        session.flush()
        # 2. clone items with new main_id
        for old_item in old_main.items:  # via relationship
            new_item = OscalSspSystemImplementationItem(
                system_implementation_id=new_main.id,
                ...
            )
            session.add(new_item)
    session.flush()
```

### Task 7c: DI container update

`di_containers/oscal/oscal_containers.py`：
- 加 main + join domain service / repo wiring
- 既有 system_implementation 改用新 class 名

### Task 7d: 既有 A0 regression test fix

`tests/test_ssp_system_implementation_regression.py`：所有 import / class 名 / column 名同步改。

### Task 7e: 新 regression test for main upsert

`tests/test_ssp_system_implementation_main_regression.py`：

```python
def test_caller_upsert_main_idempotent():
    # 重複 upsert 同 scope 不會建多筆 main
    ...
```

- [ ] **Step**: Run all + commit

```bash
poetry run pytest tests/test_ssp_system_implementation_regression.py tests/test_ssp_system_implementation_main_regression.py -v
```

Expected: All pass。

---

## Task 8 — BE Smoke Boot + Caller Integration

- [ ] **Step 1**: Kill / restart BE

```bash
lsof -ti:8000 | xargs kill -9 2>/dev/null
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
set -a; source .env; set +a
nohup poetry run python main_app.py > /tmp/be_boot_a01.log 2>&1 &
sleep 8
tail -50 log/app.log
```

Expected: 無 schema mismatch / IntegrityError。

- [ ] **Step 2**: Caller smoke — 模擬一次 device mapping 寫入

```bash
# manual API call or pytest integration test
# 確認 main + item 雙 row 寫入
PGPASSWORD='jedi@123!' psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev <<'EOF'
SELECT COUNT(*) FROM oscal.ssp_system_implementations;
SELECT COUNT(*) FROM oscal.ssp_system_implementation_items WHERE system_implementation_id IS NOT NULL;
EOF
```

- [ ] **Step 3**: Cleanup BE

---

## Task 9 — Changelog + Tracker 更新

- [ ] **Step 1**: 寫 `docs/changelog/2026-05-19-tweak-ssp-system-impl-restructure.md`（type=tweak，breaking=false 因為 commit 鏈完整且不影響外部 API）

- [ ] **Step 2**: 更新 `docs/features/FR-011.2-2605-ssp-import-export-phase2/README.md`：
  - A0 row 標 `A0.1 follow-up shipped`
  - 加 A0.1 commit hash 清單

- [ ] **Step 3**: 更新 `requirement-understanding.md` 加 v5 校正段（OSCAL spec verify 結果 + Path A + Naming + items/main/join 三表結構）

- [ ] **Step 4**: Commit

---

## Task 10 — 通知 user 重啟 BE + 收口

提供 user 通知文字：

> A0.1 完工。改動範圍：
> - SQL schema rename + 新建 main / join 表（user 已執行）
> - jedi-oscal 全 stack rename + 新 main/join + column rename
> - 主 BE 兩個 caller 改寫
> - 測試全綠
>
> **請 restart BE**：
> ```bash
> lsof -ti:8000 | xargs kill -9
> cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
> set -a; source .env; set +a
> nohup poetry run python main_app.py > /dev/null 2>&1 &
> ```

---

## Acceptance Criteria

- [ ] SQL migration 跑通（user 自行執行，A0.1 plan 開工前已 done）
- [ ] jedi-oscal items stack 8 個檔 rename 完成 + 32 個測試綠
- [ ] jedi-oscal main stack 8 個新檔完成 + 新測試綠
- [ ] jedi-oscal join stack 8 個新檔完成 + 新測試綠
- [ ] yaml_mapper 從 main 出發 + implemented-components 整合測試綠
- [ ] 主 BE 2 個 caller 改寫完 + regression test 綠
- [ ] BE smoke boot 通過
- [ ] Caller integration 寫入測試（model + item 雙 row）成功
- [ ] Changelog / tracker / requirement-understanding 對齊新命名
- [ ] Working tree 乾淨

---

## 風險與緩解

| 風險 | 緩解 |
|------|-----|
| jedi-oscal rename 破壞既有 import path | Task 1 完成立刻跑 32 個既有測試，破了立即發現 |
| caller upsert main 漏處理 → INSERT items 失敗（NOT NULL system_implementation_id） | Task 8 smoke 階段先測 |
| yaml_mapper 改簽章破壞其他 caller | grep caller 確認；Task 6 開頭先盤點 |
| 既有 32 個測試大量 fix 引入新 bug | TDD 每 task 跑全測試確認 |
| jedi-oscal 父層引用沒改全（ssp.py / mapper 父層）| Task 1 Step 9 顯式列 4 個父層檔案 |

---

## 不在 A0.1 範圍

- ❌ `system_characteristics` 改 1:1（未來評估）
- ❌ `system-implementation.users[]` 表（客戶有需求才做）
- ❌ `hardware` enum 改 `inventory-item`（最後統整優化清單）
- ❌ Phase 2 後續 A1~B6 phase

---

## 接手指引（給下個 session）

1. **先讀 SUMMARY**：`docs/conversation-history/2026-05-19/ssp-import-export-phase2-A0.1/SUMMARY.md`
2. **執行順序**：
   - Step 1: User 已執行 SQL（或 user 通知執行完）
   - Step 2: 跑 Task 0 pre-flight 驗證
   - Step 3: 按 Task 1~10 順序執行（建議用 subagent-driven）
3. **重型 Tasks**：Task 1 / Task 6 / Task 7 屬於高風險，subagent 完成後務必 spec + code review 雙 reviewer
4. **commit 規矩**：每 task ship 一個 commit；commit 前必跑 `git status --short` 確認 staged 區乾淨
