For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 從零建立 jedi-oscal-v2 套件,把 FR-038 契約層定稿的 OSCAL v1.2.2 關聯式 schema 以 DDD 四層(entity / model / mapper / repository / service)落地 —— 本期涵蓋稽核生命週期主流程的 6 個 root(catalog / profile / ssp / ap / ar / poam)+ 共用層 + 全部 delta 表,並提供 snapshot / clone / profile-resolution / AR 全量判定矩陣 / 匯入匯出等 primitive 供主專案使用。
本期不做(follow-up):component-definition(
component_definitions+ 5 個cd_*表)。它是「可重用元件描述」的獨立 OSCAL model,不在 Catalog→Profile→SSP→AP→AR→POA&M 稽核主流程上,CMMC 內部稽核流程用不到(design §1 四邊界與 PM 教材皆未涉及)。等有「元件庫」需求再開。本期落地表數 = base 36(42 − 6 個 cd_*)+ delta 6 = 42 表 +compliance.project_audit_rounds(後者僅建 DB,Wave 2 才建 model)。
Architecture: DDD + Hexagonal,沿用舊 jedi-oscal 的分層慣例(domain/ entity+repo interface+service、infra/ model+mapper+repo impl+adapter、app/ app service+dto、ports/ parser、common/ enum+code)。Repo 繼承 jedi_common.session.database.repository.base_repository_impl.BaseRepositoryImpl;session 由主專案 @transaction 開啟,套件 method 為被呼叫端。import 名 jedi_oscal_v2,dist jedi-oscal-v2。dev 走 poetry path dependency,feature 完成才推 Nexus。
Tech Stack: Python 3.11+ / SQLAlchemy 2.0 (mapped_column) / PostgreSQL (schema oscal + compliance) / Poetry / pytest / jedi-common (BaseRepositoryImpl, BaseModel, exception, transaction)。
Scope: 本計畫只涵蓋 Wave 1(jedi-oscal-v2 套件)。主專案 BE(Wave 2)、FE(Wave 3)另立計畫。契約基準:design.md + oscal-v2-deltas.sql + requirement-analysis.md。
計畫撰寫到開工可能有時間差;method 改名 / 欄位改 shape / base 套件 signature 變動都可能發生。每個 module agent 開工第一步:
Phase 0 套件骨架 + DB 套用 + 共用層 【序列瓶頸,先做完】
│
├─ A1 catalog 樹 + framework + profile + resolution ┐
├─ A2 SSP + 12 子表 + UI→OSCAL 落點 │ 契約定稿後
├─ A3 AP + reviewed-controls + subjects + tasks ├ 5 模組可平行
├─ A4 AR 全量矩陣 + risk + remediation + milestone + POA&M │ (各自獨立 model)
└─ A5 parser adapter(CMMC/ISO/NIST)+ 匯入匯出 ┘
│
A6 OscalSnapshotService(clone/snapshot primitives) 【依賴 A1+A2,故排在其後】
│
Phase Z 套件層整合 smoke(全 model import + DI wire 驗證) 【序列收尾】
A6 為什麼不在 A1-A5 平行群:clone_resource_library 要同時複製 catalog(A1)+profile(A1)+ssp(A2) 三件組、snapshot_ssp 要深複製 SSP 12 子表(A2),橫跨 A1+A2 的 entity,不屬單一模組。故排在 A1+A2 的 model+mapper 落地後做(可由 A2 的 agent 接手,因它已有 SSP deep-copy 程式)。A5 的 export round-trip 測試 fixture 依賴 A6 產出的 cloned/frozen 物件,A6 完成後再補 A5 export 測試。
A1-A5 為什麼能平行:各模組對應不同 OSCAL model 的表,entity/model/mapper/repo/service 檔案不重疊;跨模組只靠「token / uuid 軟參照」(不建 FK 到別模組的 Python 物件),無共享可變狀態。唯一共用 = Phase 0 的 base class / common enum / DI container 骨架,故 Phase 0 必須先完成。 A4 內部順序:AR finding → risk → remediation → milestone 有 FK 依賴,同一 agent 內依序做;POA&M item 可獨立。 A1 的 catalog 是 A2/A3/A4 的「token 來源」但只是 token 字串參照,不阻塞平行(A2-A5 用測試假資料的 control_id token 即可)。
~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/
├── pyproject.toml # dist=jedi-oscal-v2, packages=jedi_oscal_v2
├── jedi_oscal_v2/
│ ├── common/
│ │ ├── enum/oscal_enums.py # StrEnum: RoundStatus, RoundType, FindingState, RiskStatus, ...
│ │ └── code/oscal_v2_error_code.py # 套件層 ErrorCode(OSCAL_V2_*)
│ ├── domain/
│ │ ├── entity/<model>/... # @dataclass entity(純資料,無 ORM)
│ │ ├── repository/<model>/..._repo.py # abstract repo interface
│ │ └── service/<model>/..._service.py # domain service(業務邏輯)
│ ├── infra/
│ │ ├── model/<model>/... # SQLAlchemy model(mapped_column)
│ │ ├── mapper/<model>/..._mapper.py # entity ↔ model 雙向
│ │ ├── repository/<model>/..._impl.py # BaseRepositoryImpl 衍生
│ │ └── adapter/{cmmc,iso,nist}/ # parser adapter(A5)
│ ├── app/
│ │ ├── service/<model>/... # app service(對外,主專案 import 點)
│ │ └── dto/<model>/... # app DTO
│ └── ports/oscal_parser_factory.py # parser factory(A5)
└── tests/
└── <model>/test_*.py
<model> ∈ framework / catalog / profile / ssp / ap / ar / poam / common(共用 metadata/roles/parties/resources 歸 common)。
from dataclasses import dataclass, field
@dataclass
class CatalogControlEntity:
id: int | None = None
uid: str | None = None # 若該表有對外 uid(root 有 uuid;子物件用自身 uuid)
control_id: str | None = None # OSCAL token
title: str | None = None
# ... 對齊 delta/base 欄位;JSONB 欄用 dict | list | Nonefrom sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy import BigInteger, Text, ForeignKey
from jedi_common.session.database.base_model import BaseModel # pre-flight 確認實際 base
class CatalogControlModel(BaseModel):
__tablename__ = "catalog_controls"
__table_args__ = {"schema": "oscal"}
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
catalog_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("oscal.catalogs.id", ondelete="CASCADE"))
control_id: Mapped[str] = mapped_column(Text, nullable=False)
# Python 保留字:class → mapped_column("class", ...) attr class_;metadata → metadata_id + attr metadata_mapped_column(JSONB)。compliance.project_audit_rounds → oscal.*)字串必帶 schema 前綴(記憶 feedback_cross_schema_fk_must_qualify)。class CatalogControlRepoImpl(ICatalogControlRepo,
BaseRepositoryImpl[CatalogControlEntity, CatalogControlQuery, CatalogControlModel, CatalogControlMapper]):
def __init__(self):
super().__init__(mapper=CatalogControlMapper, model=CatalogControlModel)
# self.session 自動有(lazy);禁止 __init__ 寫 self.session = get_session()class CatalogService:
def __init__(self, catalog_domain_service, ...):
self._svc = catalog_domain_service
# 注意:套件 app service 不自己加 @transaction —— 由主專案 caller 開 scope。
# 但套件可提供「被 @transaction 包住才安全」的 method,docstring 標明「caller 必須在 @transaction scope 內」。feedback_test_logger_patch_db_handler),patch jedi_oscal_v2.app.service.<m>.<svc>.logger。Files:
~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/pyproject.tomljedi-oscal-v2/jedi_oscal_v2/__init__.py(+ 各層 __init__.py)Files: Create jedi_oscal_v2/common/enum/oscal_enums.py、common/code/oscal_v2_error_code.py
這是「一張表」的標準 5 步 TDD micro-cycle。A1-A5 的每個表 task 都展開成這 5 步(agent 執行時照此 pattern,不需在計畫重抄)。以
framework_versions為例:
JSONB 欄:entity 用 dict|list|None,mapper 直通,不展開。 自巢狀表(catalog_groups/controls/parts、ap_tasks):parent_id self-FK,repo 提供 get_children(parent_id) + get_tree(root_id)。
負責 agent:A1。Files: */common/*、*/framework/*、*/catalog/*、*/profile/*、tests/{common,framework,catalog,profile}/*
⚠️ A1 是所有 root 的共同前置:OSCAL 共用層
oscal.metadata / roles / parties / resources被每個 root(catalog/profile/ssp/ap/ar/poam)的 model FK 參照(metadata_id等)。A1 必須先建這 4 張 common 表的 model,A2-A5 才能建各自 root(依賴 A1 的 metadata model)。這使「A1 先行、A2-A5 再平行」成為硬順序。
Files: Create domain/service/profile/profile_resolution_service.py、tests/profile/test_profile_resolution.py
負責 agent:A2。Files: */ssp/*、tests/ssp/*
負責 agent:A3。Files: */ap/*、tests/ap/*
負責 agent:A4(內部依序:finding→risk→remediation→milestone)。Files: */ar/*、*/poam/*、common/(observation/risk/finding 共用)、tests/{ar,poam}/*
負責 agent:A5。Files: infra/adapter/{cmmc,iso,nist}/*、ports/oscal_parser_factory.py、app/service/oscal_io_service.py、tests/adapter/*
負責 agent:A6(建議由 A2 agent 接手,重用 SSP deep-copy)。依賴:A1(catalog/profile entity+mapper)+ A2(SSP 12 子表 entity+mapper + deep_clone_ssp)已落地。Files: Create app/service/snapshot/oscal_snapshot_service.py、tests/snapshot/*
這是 design §1 四個「複製並固定」邊界中的②③,是架構核心。clone=可編副本、snapshot=凍結副本,但兩者都實際複製內容(獨立 rows、不存 id join 回母表)。
clone_resource_library(framework_version_uid)(邊界②,clone)snapshot_ssp(ssp_id) → frozen_ssp_id(邊界③,snapshot)OscalSnapshotService(clone_resource_library / snapshot_ssp / deep_clone_ssp 統一出口)+ app service test(logger patch fixture)。完成後回頭補 A5 的 export round-trip 測試 fixture(用 A6 產出的 cloned/frozen 物件當輸入)。
-am(記憶 feedback_subagent_explicit_git_add);套件 repo 與主專案分開 commit;push 等 user。