Spec 3「AP 套用流程」Phase 6 Follow-up 執行計劃

For agentic workers: REQUIRED SUB-SKILL: 使用 superpowers:subagent-driven-development(推薦)或 superpowers:executing-plans 逐 task 執行。步驟用 checkbox (- [ ]) 追蹤。

Goal: 完成 Spec 3 Phase 6 review 揭露的 Tier 1 follow-up(PR1-4)+ M8 跨 repo 測試補強,作為 Spec 3 task arc 的真正收尾,全部完成後一次性 push + 部署 stg。

Architecture:feature/project-flow-engine-integrate branch 上以 milestone-style commit 逐項 land;每個 Task = 1 commit(含 changelog)= 對應 follow-up tracker 的 1 個 PR。BE service 異動後必重啟。

Tech Stack: Python 3.11 / Flask-RESTful / SQLAlchemy / pytest(BE);Vue 3 + TS + PrimeVue(FE);Cucumber.js + Playwright(compliance-manager-test)

範圍對齊(user 已決策):

  • Tier 1 全做(PR1-4)+ M8
  • 全做完才 push + 部署 stg
  • 全部 commit 在 feature/project-flow-engine-integrate
  • 計劃存於:docs/features/FR-026-2605-project-flow-engine-integrate/03-ap-binding/phase6-followup-plan.md
  • 對應 issue tracker:docs/issues/pending/2026-05-13-spec3-phase-6-followups.md

§1

〇、共通約定

Commit 規則:

  • 每個 Task 完成 = 1 commit(含 code + test + changelog)
  • 顯式檔名 git add <files>禁用 -am / -A / .(memory rule feedback_subagent_explicit_git_add
  • Commit message 用 HEREDOC 格式,結尾不加 Co-Authored-By(本專案不要求)

Changelog 規則:

  • 每個 Task 一份 docs/changelog/2026-05-14-tweak-spec3-followup-prN-*.md
  • frontmatter:type: tweak(refactor / test 補強 / housekeeping,都不算讓使用者能做新事情)
  • modules: [project, grc][frontend]
  • issue: docs/issues/pending/2026-05-13-spec3-phase-6-followups.md
  • 結尾 commit 後回填 commit: <hash>

測試規則:

  • BE:PYTHONPATH=. pytest test/<file>.py -v
  • 採 TDD:先 RED → 實作 → GREEN
  • 改 BE service 後必重啟(memory rule feedback_be_restart_after_service_change):
    pkill -9 -f main_socketio.py && python main_socketio.py > /dev/null 2>&1 &

Issue tracker 同步:

  • 每完成 1 PR 在 docs/issues/pending/2026-05-13-spec3-phase-6-followups.md 第四節「處理狀態追蹤」表格填入 commit hash
  • 全部完成後執行 git mv pending/...md resolved/...md,並在 issue 開頭加 ## ⓪ Resolution

§2

一、Pre-flight verification(開工前必跑)

驗證 follow-up tracker 跟現況一致,避免 plan-vs-reality drift。

  • cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
    git status        # 預期:clean,HEAD = b6f4eec
    git log --oneline -3
  • grep -n "def activate_project\|def confirm_audit\|def terminal_close_ap\|def launch_audit" \
        app/project/service/oscal_audit_service.py

    預期:

    • activate_project:line 77
    • launch_audit:line 169
    • confirm_audit:line 329
    • terminal_close_ap:line 425
  • python -c "from jedi_project.common.code.status_code import ProjectStatus; print([s.name for s in ProjectStatus])"

    預期至少含:PENDING / IN_PROGRESS / COMPLETED / SUSPENDED / ARCHIVED

    • 若 SUSPENDED / ARCHIVED 任一缺,回頭跟 user 對齊(PR1 黑名單 → 白名單修正的「保護目標」可能不存在)
  • sed -n '149,163p' app/grc/service/oscal_stage_handlers.py

    預期:execute(self, ap_uid, project_uid, user_id, curr_user, ctx) 已有 user_id,但 call terminal_close_ap 沒傳 — PR1 只需把它接上即可。

  • sed -n '216,275p' app/project/service/oscal_audit_service.py

    預期:line 222 為 raise PreconditionFailedError(...),之後到 # ── 首次稽核模式 ──(約 line 273-274)為 dead code(含 return 在 line 270)。實際刪除範圍以 sed 結果為準,不照 tracker 寫的 "218-271"

  • sed -n '745,760p' infra/grc/repository/grc_project_repo_impl.py

    預期:line 755-758 有 if ap_status_str == "closed": completion_rate = 100.0 else: ...tracker 寫的 app/grc/repo/grc_project_repo_impl.py 是錯的,實際在 infra/

  • cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
    git status        # 預期:clean,HEAD = 695baad
    git log --oneline -3
  • sed -n '103,140p' src/views/project/ProjectApListView.vue
  • cd ~/Projects/Billows/Audit-Manager/compliance-manager-test
    git status; git log --oneline -3
    • 確認可執行 cucumber:npm run test:regression 或對應指令(看 package.json scripts)

§3

二、Task 1:PR1 — _sync_project_status helper + terminal_close_ap 加權限檢查

Priority: High(一次清掉 Pattern 1 + Pattern 2 + Silent #1/#3/#6)

Files:

  • Modify: app/project/service/oscal_audit_service.py
    • 新增 helper _sync_project_status(project_uid, target_status, curr_user, allowed_from)(約插入 line 75 上方)
    • 替換 line 131-133(activate_project state sync)
    • 替換 line 415-417(confirm_audit state sync)
    • 替換 line 463-465(terminal_close_ap state sync)
    • terminal_close_ap signature line 425 加 user_id: int 參數
    • terminal_close_ap method body 加 self._check_participant_role(project_uid, user_id, ["manager", "auditor"])
  • Modify: app/grc/service/oscal_stage_handlers.py
    • TerminalCloseHandler.execute 內呼叫 terminal_close_apuser_id=user_id(line ~158)
  • Test: test/test_oscal_audit_service.py(若不存在則新建)
  • Test: test/test_oscal_stage_handlers.py 更新既有 TerminalCloseHandler test

2.1 寫測試(RED)

  • 新增 test/test_oscal_audit_service.py(若已存在則 append):

    # test/test_oscal_audit_service.py
    import pytest
    from unittest.mock import MagicMock
    from app.project.service.oscal_audit_service import OscalAuditService
    from jedi_project.common.code.status_code import ProjectStatus
    
    
    class TestSyncProjectStatus:
        def _make_service(self, project_status):
            service = OscalAuditService.__new__(OscalAuditService)
            project = MagicMock(status=project_status, updated_user=None)
            service._project_domain_service = MagicMock()
            service._project_domain_service.get_one.return_value = project
            service._project_domain_service.update = MagicMock()
            return service, project
    
        def test_allowed_transition_writes_target(self):
            svc, project = self._make_service(ProjectStatus.PENDING)
            svc._sync_project_status("uid", ProjectStatus.IN_PROGRESS, "u", {ProjectStatus.PENDING})
            assert project.status == ProjectStatus.IN_PROGRESS
            assert project.updated_user == "u"
            svc._project_domain_service.update.assert_called_once()
    
        def test_not_in_allowed_from_skips(self):
            svc, project = self._make_service(ProjectStatus.SUSPENDED)
            svc._sync_project_status("uid", ProjectStatus.COMPLETED, "u",
                                     {ProjectStatus.PENDING, ProjectStatus.IN_PROGRESS})
            assert project.status == ProjectStatus.SUSPENDED   # 不變
            svc._project_domain_service.update.assert_not_called()
    
        def test_archived_project_protected(self):
            svc, project = self._make_service(ProjectStatus.ARCHIVED)
            svc._sync_project_status("uid", ProjectStatus.COMPLETED, "u",
                                     {ProjectStatus.PENDING, ProjectStatus.IN_PROGRESS})
            assert project.status == ProjectStatus.ARCHIVED
            svc._project_domain_service.update.assert_not_called()
    
        def test_project_not_found_returns_silently(self):
            svc = OscalAuditService.__new__(OscalAuditService)
            svc._project_domain_service = MagicMock()
            svc._project_domain_service.get_one.return_value = None
            svc._sync_project_status("uid", ProjectStatus.COMPLETED, "u",
                                     {ProjectStatus.IN_PROGRESS})
            svc._project_domain_service.update.assert_not_called()
  • PYTHONPATH=. pytest test/test_oscal_audit_service.py::TestSyncProjectStatus -v

    預期:4 個全 FAIL(AttributeError: '_sync_project_status' not defined 或類似)

2.2 實作 helper(GREEN)

  • 插入位置:app/project/service/oscal_audit_service.py 約 line 75(activate_project 之前):

        def _sync_project_status(
            self,
            project_uid: str,
            target_status,        # ProjectStatus enum
            curr_user: str,
            allowed_from: set,    # set[ProjectStatus]
        ) -> None:
            """Idempotent project.status transition.
    
            只在 project.status ∈ allowed_from 時推進;否則 skip + warning log。
            Caller 必須給 allowed_from 白名單 — 防止 SUSPENDED / ARCHIVED 被誤推到 COMPLETED
            破壞 archive 語意(Phase 6 review Pattern 1)。
            """
            from jedi_project.domain.entity.project.project_query_entity import ProjectQueryEntity
    
            project = self._project_domain_service.get_one(ProjectQueryEntity(uid=project_uid))
            if not project:
                return
            if project.status not in allowed_from:
                logger.warning(
                    f"[sync_project_status] project={project_uid} status={project.status} "
                    f"not in allowed_from={allowed_from}, skip"
                )
                return
            project.status = target_status
            project.updated_user = curr_user
            self._project_domain_service.update(project)

    注意ProjectQueryEntity import path 用實際路徑(pre-flight 可先 grep 確認);logger 用模組頂部既有 logger。

  • PYTHONPATH=. pytest test/test_oscal_audit_service.py::TestSyncProjectStatus -v

    預期:4 個全 PASS。

2.3 替換三處 call site

  • # 原:
    if project and project.status == ProjectStatus.PENDING:
        project.status = ProjectStatus.IN_PROGRESS
        self._project_domain_service.update(project)
    # 改:
    self._sync_project_status(
        project_uid, ProjectStatus.IN_PROGRESS, curr_user,
        allowed_from={ProjectStatus.PENDING},
    )
  • # 原:
    if project and project.status == ProjectStatus.IN_PROGRESS:
        project.status = ProjectStatus.COMPLETED
        self._project_domain_service.update(project)
    # 改:
    self._sync_project_status(
        project_uid, ProjectStatus.COMPLETED, curr_user,
        allowed_from={ProjectStatus.IN_PROGRESS},
    )
  • # 原(黑名單,會誤推 SUSPENDED / ARCHIVED):
    if project and project.status != ProjectStatus.COMPLETED:
        project.status = ProjectStatus.COMPLETED
        self._project_domain_service.update(project)
    # 改(白名單):
    self._sync_project_status(
        project_uid, ProjectStatus.COMPLETED, curr_user,
        allowed_from={ProjectStatus.PENDING, ProjectStatus.IN_PROGRESS},
    )

    :替換時,原 block 內可能含其他邏輯(如 fetch project entity 的 line),看 sed 結果決定要不要保留。若 project = self._project_domain_service.get_one(...) 只為了 state sync 用途,可一併刪除(helper 內部會自己 fetch)。

  • # line 425 簽章改:
    def terminal_close_ap(
        self,
        project_uid: str,
        ap_uid: str,
        user_id: int,           # ← 新增
        curr_user: str,
    ) -> dict:
        self._check_participant_role(project_uid, user_id, ["manager", "auditor"])  # ← 新增
        # ...既有邏輯
  • # app/grc/service/oscal_stage_handlers.py:158
    return self._oscal_audit_service.terminal_close_ap(
        project_uid=project_uid,
        ap_uid=ap_uid,
        user_id=user_id,            # ← 新增
        curr_user=curr_user,
    )

2.4 更新 TerminalCloseHandler test

  • 搜尋 TestTerminalCloseHandler,更新 terminal_close_ap mock assertion 加 user_id 參數,加一個 assert 確認 service 收到的 user_id 是來自 ctx 的值。

    grep -n "TestTerminalCloseHandler\|terminal_close_ap.assert" test/test_oscal_stage_handlers.py

2.5 跑全套測試 + 重啟 + commit

  • PYTHONPATH=. pytest test/test_oscal_audit_service.py test/test_oscal_stage_handlers.py -v

    預期:全部 PASS。

  • pkill -9 -f main_socketio.py && python main_socketio.py > /dev/null 2>&1 &
    sleep 2 && tail -20 log/app.log

    預期:log 末段顯示 server started,無 traceback。

  • docs/changelog/2026-05-14-tweak-spec3-followup-pr1-sync-project-status-helper.md

    ---
    type: tweak
    modules: [project, grc]
    issue: docs/issues/pending/2026-05-13-spec3-phase-6-followups.md
    ---
    
    # PR1:抽 `_sync_project_status` helper + terminal_close_ap 加權限檢查
    
    ## 為什麼
    Spec 3 Phase 6 review 揭露 Pattern 1(state sync 3 處不一致:activate 白名單 / confirm 白名單 / terminal_close 黑名單)+ Pattern 2(terminal_close_ap 缺角色檢查)。黑名單會強推 SUSPENDED/ARCHIVED → COMPLETED 破壞 archive 語意。
    
    ## 變更範圍
    - `app/project/service/oscal_audit_service.py`:新增 `_sync_project_status` helper + 替換 3 處 call site + terminal_close_ap 加 `user_id` 參數 + `_check_participant_role`
    - `app/grc/service/oscal_stage_handlers.py`:TerminalCloseHandler.execute 傳 user_id 給 service
    - `test/test_oscal_audit_service.py`(新增):4 個 helper test
    - `test/test_oscal_stage_handlers.py`:更新既有 TerminalCloseHandler test
    
    ## 行為差異
    - SUSPENDED / ARCHIVED project:terminal_close_ap 不再強推 COMPLETED,改 skip + warning log
    - terminal_close_ap:外部 caller 必須帶 user_id + 是 manager / auditor 才能呼叫(API 路由 / handler 已配對)
    
    ## 測試結果
    `pytest test/test_oscal_audit_service.py test/test_oscal_stage_handlers.py -v` 全 PASS。
    
    ## 參考
    - Phase 6 review master:`docs/review/2026-05-13-spec3-review.md` Pattern 1 + 2
    - Silent failure:`docs/review/2026-05-13-spec3-review-silent-failures.md` #1 / #3 / #6
  • git add app/project/service/oscal_audit_service.py \
            app/grc/service/oscal_stage_handlers.py \
            test/test_oscal_audit_service.py \
            test/test_oscal_stage_handlers.py \
            docs/changelog/2026-05-14-tweak-spec3-followup-pr1-sync-project-status-helper.md
    git commit -m "$(cat <<'EOF'
    tweak(spec3 followup PR1): _sync_project_status helper + terminal_close_ap 加權限檢查
    
    - 抽 _sync_project_status(project_uid, target_status, curr_user, allowed_from) 統一 3 處 state sync
    - terminal_close_ap 從黑名單 (!= COMPLETED) 改白名單 ({PENDING, IN_PROGRESS}) — 修 Pattern 1
    - terminal_close_ap 加 user_id 參數 + _check_participant_role — 修 Pattern 2 + Silent #6
    - TerminalCloseHandler.execute 把 ctx.user_id 傳給 service
    
    Refs: docs/review/2026-05-13-spec3-review.md (Pattern 1+2, Silent #1/#3/#6)
    EOF
    )"
  • docs/issues/pending/2026-05-13-spec3-phase-6-followups.md 第四節「處理狀態追蹤」PR1 row 填入 commit hash。

    git log -1 --format=%h
    # 把 hash 填進 tracker,再 commit tracker 變更

    注意:tracker 更新可以累積,最後一個 Task 完成時一起 commit;不需要每個 PR 都 commit tracker。


§4

三、Task 2:PR2 — 刪 launch_audit dead code

Priority: Medium(housekeeping)

Files:

  • Modify: app/project/service/oscal_audit_service.py(刪 line 222 之後到 line ~273 之前的 unreachable block)

3.1 確認刪除範圍

  • grep -n "raise PreconditionFailedError(GrcErrorCode.GRC_AP_REVIEW_VIA_CLOSE_ROUND)\|# ── 首次稽核模式 ──" \
        app/project/service/oscal_audit_service.py

    記下 raise 行(保留)跟 # ── 首次稽核模式 ── 行(保留)。中間都刪。

3.2 刪除 + 加註解

  • 原 block 約 50 行(從 # ── 以下覆核模式邏輯已停用(保留 reference)... 開始,到 return {"warning": False, "ap_uid": ap_uid, "status": "auditing", "run_no": new_run_no} 結束,共 ~50 行)。

    保留 raise PreconditionFailedError(...) 那行 + 改其上方 comment 為:

    if is_review_mode:
        # Spec 2(2026-05-13 review fix H1 + Spec 3 Phase 6 PR2 cleanup):
        # 「發起覆核」副作用已搬到 close_round handler(Banner「完成改善」推進路徑)。
        # 此分支保留純粹防 dup — 外部 curl / legacy code 若在 remediation 狀態
        # 觸發 launch_audit 會建出第二份 run_no=N+1 ar_data,所以直接 raise 防 data corruption。
        # 對應覆核流程實作見 `oscal_audit_service.close_round()` (line ~427)。
        raise PreconditionFailedError(GrcErrorCode.GRC_AP_REVIEW_VIA_CLOSE_ROUND)

3.3 跑測試 + commit

  • PYTHONPATH=. pytest test/ -v -k "launch_audit or LaunchAudit"
  • pkill -9 -f main_socketio.py && python main_socketio.py > /dev/null 2>&1 &
  • docs/changelog/2026-05-14-tweak-spec3-followup-pr2-launch-audit-dead-code.md

  • git add app/project/service/oscal_audit_service.py \
            docs/changelog/2026-05-14-tweak-spec3-followup-pr2-launch-audit-dead-code.md
    git commit -m "$(cat <<'EOF'
    tweak(spec3 followup PR2): 刪 launch_audit is_review_mode unreachable block
    
    Line 222 的 raise 之後是 dead code (Spec 2 H1 fix 後 close_round handler 接手覆核)。
    保留 raise 防 legacy curl / external caller dup,但 unreachable 邏輯刪 ~50 行。
    
    Refs: docs/review/2026-05-13-spec3-review-be-code.md #2
    EOF
    )"

§5

四、Task 3:PR3 — M12 completion_rate 補 unit test

Priority: Medium(hotfix b979a71 暴露 import smoke 不夠)

Strategy:tracker 寫 4 個 case 都是 Python if ap_status_str == "closed" 那段,邏輯非常單純(3 行)— 採「extract pure helper + unit test」比 mock session.query 乾淨太多。

Files:

  • Modify: infra/grc/repository/grc_project_repo_impl.py(line 755-758 區域,抽出 _compute_completion_rate static method)
  • Test: test/test_grc_project_repo.py(新建,純 Python unit test)

4.1 RED:寫測試

  • # test/test_grc_project_repo.py
    import pytest
    from infra.grc.repository.grc_project_repo_impl import GrcProjectRepoImpl
    
    
    class TestComputeCompletionRate:
        @pytest.mark.parametrize("ap_status, completed, total, expected", [
            ("closed", 0,  0,   100.0),
            ("closed", 2,  2,   100.0),
            ("closed", 0,  10,  100.0),
            ("open",   0,  0,     0.0),
            ("open",   1,  2,    50.0),
            ("open",   3,  10,   30.0),
            ("open",   None, 5,   0.0),    # defensive:completed_tasks 為 None
            (None,     1,  2,    50.0),    # ap_status_str 為 None
        ])
        def test_compute(self, ap_status, completed, total, expected):
            result = GrcProjectRepoImpl._compute_completion_rate(ap_status, completed, total)
            assert result == expected
  • PYTHONPATH=. pytest test/test_grc_project_repo.py -v

    預期:FAIL(_compute_completion_rate 不存在)

4.2 GREEN:抽 helper + 替換 inline 邏輯

  • @staticmethod
    def _compute_completion_rate(ap_status_str, completed_tasks, total_tasks) -> float:
        """AP 已 closed 視為 100% 完成;否則回 completed/total 比例 (四捨五入 1 位)。
        total_tasks=0 且未 closed 回 0.0(不除以零)。"""
        if ap_status_str == "closed":
            return 100.0
        if not total_tasks:
            return 0.0
        return round((completed_tasks or 0) * 100 / total_tasks, 1)
  • # 原:
    if ap_status_str == "closed":
        completion_rate = 100.0
    else:
        completion_rate = round(completed_tasks * 100 / total_tasks, 1) if total_tasks > 0 else 0.0
    # 改:
    completion_rate = self._compute_completion_rate(ap_status_str, completed_tasks, total_tasks)
  • PYTHONPATH=. pytest test/test_grc_project_repo.py -v

    預期:8 個 case 全 PASS。

4.3 commit

  • pkill -9 -f main_socketio.py && python main_socketio.py > /dev/null 2>&1 &
  • docs/changelog/2026-05-14-tweak-spec3-followup-pr3-completion-rate-unit-test.md

    git add infra/grc/repository/grc_project_repo_impl.py \
            test/test_grc_project_repo.py \
            docs/changelog/2026-05-14-tweak-spec3-followup-pr3-completion-rate-unit-test.md
    git commit -m "$(cat <<'EOF'
    tweak(spec3 followup PR3): M12 completion_rate 抽 helper + 補 unit test
    
    M12 hotfix b979a71 (String 漏 import) 暴露 import smoke 對 method-body unresolved
    name 不敏感的弱點 — 補純 Python helper + unit test 把 compute path 釘住。
    
    Refs: docs/review/2026-05-13-spec3-review-be-code.md #1
    EOF
    )"

§6

五、Task 4:PR4 — FE default flow_template guard

Priority: Medium(UX 問題 — master 軟刪時 inherited hint 仍顯示 + 預設值失效)

工作目錄切換cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe

Files:

  • Modify: src/views/project/ProjectApListView.vue(line 111 defaultFlowTemplateUid computed 加 guard + line 480-484 inherited hint 同步)

5.1 改 computed

  • 實際現況(pre-flight 驗證後修正):computed 來源是 apList.value?.[0]?.flow_template?.master_uid,不是 masterUid.value

    // 原(line 111-115):
    const defaultFlowTemplateUid = computed<string | null>(() => {
        const latest = apList.value?.[0]
        return latest?.flow_template?.master_uid ?? null
    })
    // 改:
    const defaultFlowTemplateUid = computed<string | null>(() => {
        const latest = apList.value?.[0]
        const uid = latest?.flow_template?.master_uid
        if (!uid) return null
        // master 軟刪 / 不在 active flowTemplates list 內 → 不當 default
        const found = flowTemplates.value.find((t: any) => t.uid === uid)
        if (!found) return null
        // 若 BE 有 is_master_active 旗標再多防一層(沒回則 found 為準)
        if (found.is_master_active === false) return null
        return uid
    })

    驗證:subagent 開工前先 sed -n '108,135p' src/views/project/ProjectApListView.vue 確認現況;若已被 user 動過則對齊實際內容。

  • src/views/project/ProjectApListView.vue line 480-484:

    <!-- 原 -->
    <small v-if="defaultFlowTemplateUid && dialogForm.flowTemplateUid === defaultFlowTemplateUid">
        {{ t('lang.project_ap_list.flow_template_inherited_hint') }}
    </small>

    這個條件本身已經 OK — 因為 defaultFlowTemplateUid 為 null 時 hint 自然不顯示。無需再改,但要驗證行為(軟刪後 hint 該消失)。

5.2 手動 UI 驗證

  • cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe
    npm run dev          # 看 package.json scripts 確認 command

    在 dev DB 找一個 project 的 master flow_template,UPDATE 設 is_active=FALSE,FE 重整 ProjectApListView,預期:

    • dropdown 顯示「請選擇」(無 default)
    • hint「預設沿用上一輪」不顯示
    • 仍可手動選任一 active flow_template 送出

    :UI 驗證為主,無需寫 E2E 測試(FE 沒有對應 unit test 框架慣例給 computed)。

5.3 commit

  • docs/changelog/2026-05-14-tweak-spec3-followup-pr4-default-flow-template-guard.md(路徑:FE repo 的 docs/changelog/)

    git add src/views/project/ProjectApListView.vue \
            docs/changelog/2026-05-14-tweak-spec3-followup-pr4-default-flow-template-guard.md
    git commit -m "$(cat <<'EOF'
    tweak(spec3 followup PR4): defaultFlowTemplateUid 加 master 軟刪 guard
    
    master_uid 不在 flowTemplates list 內 / is_master_active=false 時,預設值改 null。
    inherited hint 因條件已含 defaultFlowTemplateUid && === 對齊,自然不再顯示。
    
    Refs: docs/review/2026-05-13-spec3-review-fe-code.md #1
    EOF
    )"

§7

六、Task 5:M8 — compliance-manager-test repo 4 fixture bug + 3 Banner BDD scenario

工作目錄切換cd ~/Projects/Billows/Audit-Manager/compliance-manager-test

Strategy:先看 test repo 的 CLAUDE.md / handoff 索引找出實際待修 4 fixture + 3 scenario 的清單;然後逐項修補 + 跑 cucumber 驗證。

6.1 盤點實際待辦

  • Spec 3 plan / test-plan 應該記了 M8 細項:

    grep -n "M8\|fixture.*bug\|Banner.*BDD\|3.*scenario" \
        ~/Projects/Billows/Audit-Manager/compliance-manager-be/docs/features/FR-026-2605-project-flow-engine-integrate/03-ap-binding/{test-plan.md,implementation-plan.md,design.md} \
        2>/dev/null | head -30
    • 把 4 個 fixture bug + 3 個 Banner scenario 條列出來
    • 若找不到細節,回頭跟 user 對齊
  • cd ~/Projects/Billows/Audit-Manager/compliance-manager-test
    cat package.json | grep -A 5 '"scripts"'    # 找 cucumber 跑法

6.2 修 fixture bug

    • 每個 fixture bug 修完跑 npm run test:<scenario> 或對應 cucumber tag 命令確認該 scenario 過

6.3 寫 3 個 Banner BDD scenario

    • features/.feature
    • steps/ 補 step 實作
    • 必要時在 pages/ 加 page object
    • 跑 cucumber 驗證

6.4 commit (test repo)

  • test repo 有自己的 changelog 慣例,看 docs/changelog/ 既有 file 模仿。

  • git add features/... steps/... pages/... fixtures/... docs/changelog/...
    git commit -m "tweak(spec3 followup M8): 修 4 fixture bug + 補 3 Banner BDD scenario"

    注意:test repo 是否要跟 BE/FE 同步 push 取決於 user 部署慣例。M8 完成後本計劃的「需求收尾」即告達成。


§8

七、Task 6:收尾(issue tracker / conversation history / push / deploy stg)

工作目錄切回cd ~/Projects/Billows/Audit-Manager/compliance-manager-be

7.1 Issue tracker 歸檔

  • docs/issues/pending/2026-05-13-spec3-phase-6-followups.md 開頭插入:

    ## ⓪ Resolution
    
    | 項目 | 內容 |
    |------|------|
    | 修復日期 | 2026-05-14 |
    | Commits | PR1 `<hash>` / PR2 `<hash>` / PR3 `<hash>` / PR4 `<hash>` (FE repo) / M8 `<hash>` (test repo) |
    | Changelog | docs/changelog/2026-05-14-tweak-spec3-followup-pr1~pr4-*.md |
    | Follow-up | PR5-8 + PR-TD1/2/3 留 backlog(Low priority);Stage plugin 架構依需求觸發 |
  • git mv docs/issues/pending/2026-05-13-spec3-phase-6-followups.md \
           docs/issues/resolved/2026-05-13-spec3-phase-6-followups.md
  • grep -l "issues/pending/2026-05-13-spec3-phase-6-followups" docs/changelog/2026-05-14-tweak-spec3-followup-*.md \
      | xargs sed -i '' 's|issues/pending/2026-05-13-spec3-phase-6-followups|issues/resolved/2026-05-13-spec3-phase-6-followups|g'
  • git log --oneline -10        # 找出 PR1-4 commit hash
    # 編輯 4 個 changelog 的 frontmatter,加 `commit: <hash>`
  • git add docs/issues/resolved/2026-05-13-spec3-phase-6-followups.md \
            docs/changelog/2026-05-14-tweak-spec3-followup-*.md
    git commit -m "docs(spec3 followup): issue tracker 歸檔 resolved + changelog 補 commit hash"

7.2 Conversation history 歸檔(per CLAUDE.md 強制)

  • follow-up 是接續 Spec 3 task arc,但屬獨立 milestone — 建議建新資料夾:

    mkdir -p docs/conversation-history/2026-05-14-spec3-phase6-followup

    解析逐輪 Q&A → 拆 part-NN-of-NN-* 檔(每個 PR 或每個 Task 一份):

    • part-01-of-06-pre-flight-and-pr1.md
    • part-02-of-06-pr2-dead-code.md
    • part-03-of-06-pr3-completion-rate-test.md
    • part-04-of-06-pr4-fe-guard.md
    • part-05-of-06-m8-test-repo.md
    • part-06-of-06-wrap-up.md
    • SUMMARY.md

    格式參考 docs/conversation-history/README.md

    注意:整個 docs/conversation-history/.gitignore 內,不入版控。但仍必做(per CLAUDE.md「『做 summary』指令的含義」)。

7.3 Push + deploy

  • cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git status
    cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe && git status
    cd ~/Projects/Billows/Audit-Manager/compliance-manager-test && git status
  • cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git log --oneline origin/feature/project-flow-engine-integrate..HEAD
    cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe && git log --oneline origin/feature/project-flow-engine-integrate..HEAD
    cd ~/Projects/Billows/Audit-Manager/compliance-manager-test && git log --oneline origin/main..HEAD  # test repo branch 視當下而定
  • # BE
    cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && \
        git push origin feature/project-flow-engine-integrate
    # FE
    cd ~/Projects/Billows/Audit-Manager/compliance-manager-fe && \
        git push origin feature/project-flow-engine-integrate
    # test
    cd ~/Projects/Billows/Audit-Manager/compliance-manager-test && \
        git push origin <branch>

    ⚠️ Push 屬「risky action」(CLAUDE.md「Executing actions with care」)— 計劃停在 Step 8 後,待 user 明確 say push 才執行。

  • 由 user 主導部署流程(依專案 CI/CD 慣例)。

7.4 最終 summary 報告

  • docs/conversation-history/2026-05-14-spec3-phase6-followup/SUMMARY.md(gitignored):

    • 本次 5 個 Task 各自 commit hash + 改動範圍 + 行為差異
    • 規範文件清單(4 個 changelog + 1 個 issue resolution)
    • 已知 follow-up(PR5-8 + PR-TD1/2/3 留 backlog)
    • 部署 handover 提醒(M4 是 breaking change,BE/FE 必須一起部署)

§9

八、執行注意 / 風險點

  1. PR1 是這次最大 surface change — 替換 3 處 call site 後一定要重啟 + 跑既有所有 oscal_audit_service test。若任一原 test 紅 → 可能 _check_participant_role 對 confirm_audit / activate_project 已含 _check(被 helper 影響),需要 case-by-case 看。
  2. terminal_close_ap 簽章變化是 breaking — 但目前只有 TerminalCloseHandler 一個 caller,PR1 內一併改完即可。grep terminal_close_ap 全 codebase 應只 1-2 hit(service + handler + test)。
  3. _sync_project_status import pathProjectQueryEntity 在 jedi-project 套件內,pre-flight 要先確認 import path 正確。若 jedi-project 套件版本不對齊,會 ImportError。
  4. PR3 抽 static methodGrcProjectRepoImpl._compute_completion_rate@staticmethod 不依賴 self.session,避開 RLS / session_scope 問題(純 Python helper)。
  5. PR4 UI 驗證 — 需要 dev DB 有一筆 master_uid 軟刪的測試資料;若沒有,先 SQL UPDATE 製造再驗。
  6. M8 細節未知 — Step 1 找不到 4 fixture bug + 3 scenario 清單時必須回頭跟 user 對齊,不要憑空編造。

§10

九、Plan vs Reality 偏差 log(pre-flight 修正紀錄)

Tracker / Plan 初版 實際 修正位置
app/grc/repo/grc_project_repo_impl.py:746-758 infra/grc/repository/grc_project_repo_impl.py:755-758 PR3 路徑
terminal_close_ap signature 有 user_id 沒有 user_id(簽章 (self, project_uid, ap_uid, curr_user) PR1 加參數而非「補上呼叫」
TerminalCloseHandler.execute 沒 user_id 已有 user_id(line 153)— 只是沒傳給 service PR1 只需把它接到 service call
dead code line 218-271 line 220 raise → line 270 結束(共 50 行) PR2 用 grep # ── 首次稽核模式 ── 找實際邊界
PR4 假設 defaultFlowTemplateUid 來源是 masterUid.value 實際是 apList.value?.[0]?.flow_template?.master_uid PR4 Step 1(已修正 v1.1)
test repo 預期在 main branch user 已於 pre-flight 後切到 feature/project-flow-engine-integrate M8 開工 cd 後再次 git status 確認即可

§11

文件版本

版本 日期 變更
v1 2026-05-14 初版(Tier 1 PR1-4 + M8 收尾)
v1.1 2026-05-14 pre-flight 後修正:PR4 defaultFlowTemplateUid 來源改為 apList[0].flow_template.master_uid;Plan vs Reality 表加第 5/6 條