對應 design:design-C3.md 級別:重型(兩階段執行 PR1 + PR2) 預估工時:PR1 = BE 3 天 + test 1 天;PR2 = BE 1 天
# 1. C2 + C4 已 ship?
git log --oneline | grep -E "C2|C4" | head -5
# 2. 既有 mapping 表 row count(baseline)
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -c \
"SELECT 'pdm', COUNT(*) FROM compliance.project_device_mapping
UNION ALL
SELECT 'pis', COUNT(*) FROM compliance.project_information_systems;"
# 3. 沒 active AP 的 project 數(這些 backfill 會 skip)
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -c \
"SELECT p.id, p.uid, p.name
FROM compliance.projects p
WHERE NOT EXISTS (
SELECT 1 FROM compliance.project_assessment_plan_mapping papm
JOIN oscal.assessment_plans ap ON ap.id = papm.assessment_plan_id
WHERE papm.project_id = p.id AND ap.status = 'active'
);"
# 4. 確認所有 caller 都已盤點(重要!避免 PR2 撞遺漏 caller)
grep -rn "project_device_mapping\|ProjectDeviceMapping\|project_information_systems\|ProjectInformationSystem" \
--include="*.py" | grep -v __pycache__ | grep -v test | awk -F: '{print $1}' | sort -u
# 5. 額外 grep raw SQL 引用(容易漏)
grep -rn "project_device_mapping\|project_information_systems" \
--include="*.sql" --include="*.py" scripts/ infra/grc/ | grep -v __pycache__
# 6. RLS / view 依賴
psql -h 192.168.50.188 -p 25432 -U cmmgr -d guidant_ai_dev -c \
"SELECT viewname FROM pg_views WHERE definition ILIKE '%project_device_mapping%' OR definition ILIKE '%project_information_systems%';"_resolve_current_ssp_id helper(BE)新檔:domain/oscal/service/ssp_current_resolver.py
內容:
project_id,反查 latest active AP 對應 SSP idGrcProjectService.get_project + ProjectCurrentSspRoute (C2.T6)Unit test:
Commit:feat(oscal): C3.PR1 add _resolve_current_ssp_id helper
GrcProjectService.get_project() 改 derived response(BE)檔案:app/grc/service/project_service.py
動作:
dto.devices / dto.audit_systems 改從 current SSP items derive[](C3-Q5 決策)dto.audit_systems 內每筆需含 uid / name / abbreviation(對齊既有 AuditSystemSummaryDto 格式)dto.devices 內每筆需含 device 資訊(從 device_id 反查 public.devices)注意:
_device_mapping_service / _pis_domain_service 注入(PR2 再拆)__init__ 是 dead injectionUnit test:
[]Commit:feat(grc-project): C3.PR1 get_project response derived from current SSP
GrcProjectService.update_project() 拿掉 Section 3 + 5(BE,搭配 C4)檔案:app/grc/service/project_service.py
動作:對齊 C4 T4
__init__ 拿掉 device_mapping / pis service 參數(PR1 即拿,避免 C4 撞 DI 註解問題)Commit:合併到 C4 T4 commit 或同 PR
oscal_project_service.start_oscal_project 拿掉 Step C(BE,搭配 C4)檔案:app/project/service/oscal_project_service.py
動作:對齊 C4 T3
Commit:合併到 C4 T3 commit
/project-device* 寫操作 endpoint 改 410(BE)— 用 GrcErrorCode檔案:
common/code/grc_error_code.py — 加 2 個 410 error codeapi/project/routes/project_device_route.pyapi/project/routes/project_route.py(JobExecutionDevice 系列)新增 error code(對齊 design-C3.md §5.3):
GRC_PROJECT_DEVICE_DEPRECATED = ("專案設備寫操作已下架,請至 SSP 編輯頁維護受評範圍", "GRC_410001")
GRC_PROJECT_INFO_SYSTEM_DEPRECATED = ("專案資訊系統寫操作已下架,請至 SSP 編輯頁維護受評範圍", "GRC_410002")改動:
GrcErrorCode 結構(FE 用 error_code 查 i18n)BadRequestError / NotFound 等 jedi exception class(沒有 GoneError),改手動 return tuple, 410FE 端配套(C5 / C6 期間):
src/config/locales/i18n/zh-tw/error.json 加 GRC_410001 / GRC_410002 對應文案data.error_code → i18n 顯示注意:
Commit:refactor(project-api): C3.PR1 device write endpoints 改回 410 + GrcErrorCode
新檔:scripts/sql/2026-MM-DD-c3-backfill-ssp-items-from-project-mappings.sql
內容:對齊 design-C3.md §4.1
執行流程:
cmmgr / guidant_ai_dev / 192.168.50.188:25432SELECT name, device_id, implementation_type
FROM oscal.ssp_system_implementation_items
WHERE updated_user = 'c3-backfill-2026-MM-DD'
ORDER BY created_at DESC LIMIT 10;Commit:chore(sql): C3.PR1 backfill SSP items from project mappings
新測試:
tests/test_c3_derived_response.py
[]tests/test_c3_410_endpoints.py
Regression:
tests/test_grc_project_service.py get_project 部分需 update fixturetests/test_oscal_project_service.py start_project 部分需 update(Step C 拿掉後沒 device INSERT 期待)Commit:test(c3): C3.PR1 derived response + 410 endpoint tests
檔案:docs/changelog/YYYY-MM-DD-refactor-c3-scope-migrate-to-ssp-pr1.md
---
type: tweak
breaking: true
modules: [grc, project, oscal]
issue: docs/features/FR-011.3-2605-ssp-edit-in-project/design-C3.md
---
## 需求說明
C3 PR1 — 受評範圍從 project 層 derive 改為 SSP 層:
- get_project response 內 devices / audit_systems 從 current AP 對應 SSP items derive
- 寫操作 endpoint (POST/PUT/DELETE /project-device 等) 改回 410 Gone
- 同期 backfill SQL 把既有 project_device_mapping / project_information_systems
資料補進對應 current SSP 的 implementation_items
- mapping 表保留(PR2 才 DROP)
## 變更範圍
### BE
- 新 helper: domain/oscal/service/ssp_current_resolver.py
- GrcProjectService.get_project: dto.devices / dto.audit_systems derived
- 寫操作 route 改 410
- DI: 拿掉 device_mapping / pis service 注入(搭配 C4)
- Backfill SQL: scripts/sql/2026-MM-DD-c3-backfill-ssp-items-from-project-mappings.sql
## 行為差異
| 情境 | 改前 | 改後 |
|------|------|------|
| GET /grc/project/<uid> | devices/audit_systems from mapping 表 | derived from current SSP items |
| POST /project-device | 寫入 mapping 表 | 410 Gone |
| start_oscal_project devices payload | 寫入 mapping 表 | unknown=EXCLUDE 忽略(搭配 C4)|
## Breaking change 警示
- 寫操作 endpoint 已下架(410),FE 必須改用 /ssp/<uid>/ssp-resources/items (C2)
## 後續行動
- 1-2 週觀察期,監控 mapping 表無新 INSERT
- 之後 ship C3 PR2 (DROP TABLE + 拆 stack)目的:確認沒有遺漏 caller 還在寫 mapping 表
SQL:
-- 觀察期每天跑一次
SELECT 'pdm', COUNT(*) FROM compliance.project_device_mapping
WHERE created_at > '<PR1-ship-date>'
UNION ALL
SELECT 'pis', COUNT(*) FROM compliance.project_information_systems
WHERE created_at > '<PR1-ship-date>';
-- 預期:0 / 0異常處理:若觀察到非 0:
created_user 找 caller 路徑前提:PR1 ship 1-2 週後,T9 監控 query 持續 0,無 regression report
新檔:scripts/sql/2026-MM-DD-c3-drop-project-mapping-tables.sql
內容:對齊 design-C3.md §7.1
前置驗證 SQL(跑 DROP 前手動跑一次):
-- 觀察期內 0 新 INSERT?
SELECT created_at, created_user
FROM compliance.project_device_mapping
WHERE created_at > '<PR1-ship-date>'
LIMIT 5;
-- 預期:0 row
-- 沒有 view / 其他物件依賴?
SELECT viewname FROM pg_views
WHERE definition ILIKE '%project_device_mapping%'
OR definition ILIKE '%project_information_systems%';
-- 預期:0 rowCommit:chore(sql): C3.PR2 DROP project_device_mapping + project_information_systems
⚠️ 實作矯正紀錄(2026-05-23 PR2 ship 時補):原 plan 寫
git rm -r三個 associations 資料夾 +整檔刪 associations_containers.py過度激進。實際不能整 rm —app/associations/內 8 個 mapping 服務只有 2 個是 device 相關 (project_device_mapping+job_execution_device_mapping),而job_execution_device_mapping還屬「任務指派裝置」 不是 C3 scope(AI dashboard registry + 4 個 API 還在用)。實際只逐檔拆 device 系列 10 檔 + jedi_information_system 內 project_* 系列 7 檔。詳見 changelog2026-05-23-tweak-c3-pr2-drop-project-mapping-tables.md§「Plan 矯正」。
動作(原 plan 寫法,僅供參考):
git rm -r domain/associations/
git rm -r infra/associations/
git rm -r app/associations/
# jedi_information_system 內 project_* 系列
git rm jedi_information_system/domain/entity/project_information_system_entity.py
git rm jedi_information_system/domain/entity/project_information_system_query_entity.py
git rm jedi_information_system/domain/repository/project_information_system.py
git rm jedi_information_system/domain/service/project_information_system_domain_service.py
git rm jedi_information_system/infra/mapper/project_information_system_mapper.py
git rm jedi_information_system/infra/models/project_information_system.py
git rm jedi_information_system/infra/repository/project_information_system_repo_impl.py實際動作(精準逐檔,PR2 ship 時調整):device 10 檔 + pis 7 檔 + 8 個 __init__.py 拿掉對應 export + DI cleanup 3 處(associations_containers / information_system_container 拿掉 project_*_provider 但不整檔刪、oscal_containers 拿掉 SspWriteStrategy.project_info_system_repo wiring)+ ssp_write_strategy __init__ 拿掉 dead injection param。
DI container 清理:
di_containers/associations/associations_containers.py — 整檔刪project_device_mapping_repo / project_device_mapping_domain_service / project_device_mapping_service 三個 provider + importdi_containers/information_system/information_system_container.py — 拿掉 project_information_system_repo + project_information_system_domain_service provider + importdi_containers/grc/grc_containers.py — 確認 PR1 已拿掉di_containers/project/project_containers.py — 確認 PR1 已拿掉di_containers/oscal/oscal_containers.py:657 — 拿掉 SspWriteStrategy 的 project_info_system_repo=information_system_container.project_information_system_repo wiring其他清理:
domain/oscal/strategy/ssp_write_strategy.py — __init__ 拿掉 project_info_system_repo param + self._project_info_sys_repo 賦值(dead injection 從未被使用);line 275 原「TODO Phase E.2」段換成 PR2 落地註記app/__init__.py / domain/__init__.py — 移除對應 import__init__.py 8 個分別 拿掉 device 系列 exportconfig/app_modules.py — 確認沒有 associations module 註冊Commit:refactor(c3): C3.PR2 拆 device + pis Python stack + 撤 410 endpoint(143f354)
檔案:
api/project/routes/project_device_route.pyapi/project/__init__.py 拿掉對應 add_resource 註冊結果:URL 變 404 而非 410
Commit:refactor(project-api): C3.PR2 撤除已 deprecated 的 device write endpoints
動作:
pytest 確認沒有 test 還在引用已刪檔tests/test_project_device_mapping_*.py 等Commit:test(c3): C3.PR2 拆 stack 後 tests 清理
檔案:docs/changelog/YYYY-MM-DD-refactor-c3-scope-migrate-to-ssp-pr2.md
---
type: tweak
breaking: false
modules: [grc, project, associations]
issue: docs/features/FR-011.3-2605-ssp-edit-in-project/design-C3.md
---
## 需求說明
C3 PR2 — 完成 project_device_mapping / project_information_systems 廢除:
- DROP 兩張表
- 拆 domain/associations/ + infra/associations/ + app/associations/ 整套 stack
- 拆 jedi_information_system project_information_system 系列
- 撤 410 endpoint (改 404)
- 拿掉 ssp_write_strategy.py 內 Phase E.2 TODO 段
## 變更範圍
- BE: 大量 git rm + DI cleanup
- DB: DROP TABLE
- API: 404 取代 410
## 後續清理 (可選)
- OscalRole ORM model(C 階段尾聲評估)| 階段 | 測試 | 內容 |
|---|---|---|
| PR1 | Unit | derived response 對齊 SSP items / 空回 [] / 寫操作 410 |
| PR1 | Integration | 建專案 → AP → SSP items → get_project response 正確 |
| PR1 | Regression | 既有 grc / project test 全綠 |
| PR1 | Migration | backfill SQL 跑前後 row count 對等性驗證 |
| PR2 | Unit | 撤 stack 後 import 全找不到 |
| PR2 | Integration | 跑既有 e2e 不撞 missing module |
| PR2 | DB | psql \dt 確認 mapping 表已不存在 |
DELETE FROM oscal.ssp_system_implementation_items WHERE updated_user='c3-backfill-2026-MM-DD'| Checkpoint | PR1 | PR2 |
|---|---|---|
| Migration SQL 跑過 | ✅ backfill | ✅ DROP |
| Tests 全綠 | ✅ | ✅ |
| Changelog | ✅ breaking | ✅ |
| Tracker 更新 | ✅ (in progress) | ✅ (shipped) |
| Smoke test | ✅ | ✅ |
| 觀察期 | 1-2 週 | — |