狀態:v2 — brainstorm 收斂後完整版,待 review 整體脈絡:
../README.md依賴:Spec 1(已 ship)/ Spec 2(已 ship)
建立新 AP 時 user 可挑選流程範本;AP 建立同時 snapshot clone 該範本 BPMN 到該 AP 專屬的 workflow_execution。後續範本被改 / 刪不影響進行中 AP;多 AP 之間流程獨立運作;未來可開放「中途調整 AP 自己的流程」也不會污染兄弟 AP 或 master。
| Q | 主題 | 決議 |
|---|---|---|
| Q1 | dropdown 是否按合規資源庫過濾 | 純開放 — 全部 active 範本都列;YAGNI,不加 compatible_frameworks 欄位 |
| Q2 | 預覽 Dialog 呈現 | 跟 Spec 1 對齊 — reuse 既有 FlowTemplatePreviewDialog.vue(唯讀 BPMN + 階段清單並列) |
| Q3 | launch_new_round 預設值 | Derived from latest AP — 不加 project 層級欄位,dropdown default 取「最新一輪 AP 的 master template uid」 |
| Q4 | Snapshot 儲存方式 | Reuse Spec 2 — 直接用 WorkflowTemplateSnapshotService.clone_master_as_snapshot,零 migration |
| Q5 | 第一輪 AP 入口 | 保留雙入口 — ProjectCreateView 跟 ProjectApListView 兩處都加 dropdown;Spec 2 lifecycle 假設不動 |
| API | flow_template_uid 必填 vs optional | 必填 — 一次拔乾淨 Spec 2 hardcode default 邏輯,不留技術債 |
審核 Spec 2 落地時發現的重要事實:
start_oscal_project:487 已呼叫 _bind_main_workflow_to_ap → 第一輪 AP 有 snapshot + assessment_plan_extensions + main workflow_executionlaunch_new_round 從未呼叫該 helper → 後續輪 AP 沒綁 main workflow,Banner 找不到 stage → 推進按鈕永遠 disabled→ Spec 3 不只是「加 dropdown + 把 hardcode 換成 parameter」,還要把 main workflow snapshot 整段 helper 邏輯接上 launch_new_round。
這同時是 Phase E launchProjectViaUi / launchAuditViaUi 系列 fixture 撞 disabled button 的潛在根因(fixture 是新一輪 flow 上跑的)。
| 場景 | 保證機制 |
|---|---|
| 兩個 AP 選同一個 master | 各自 clone 一份新的 snapshot row(不同 uid);engine template_id 用各自 snapshot.id |
| Admin 改 master XML | engine 讀的是 snapshot row,現有 AP 的 snapshot.xml 不動 |
| Admin 軟刪 master | 現有 AP snapshot row 仍存在;engine 仍能跑;只影響後續新 AP 不能再選此 master |
| 未來「中途調整 AP 流程」 | snapshot row 是 per-AP 私有資料 → 直接 mutate 該 AP 的 snapshot.xml 不污染 master 也不污染兄弟 AP |
來源:requirement.md §「想法(粗略)」§3
workflow_executionstart_oscal_project 簽章加 flow_template_uid: str (required)launch_new_round 簽章加 flow_template_uid: str (required) + 補做 main workflow snapshot_bind_main_workflow_to_ap 改為接 flow_template_uid 必填(拔 default lookup)FlowTemplateDomainService.DEFAULT_MAIN_WORKFLOW_TEMPLATE_NAME / get_default_main_workflow_template()flow_template enrichment(snapshot uid / master uid / master name)GRC_FLOW_MAIN_TEMPLATE_NOT_FOUND(412022);reuse 既有 GRC_FLOW_TEMPLATE_NOT_FOUND(404027)ProjectCreateView 加流程範本 dropdown + 預覽 DialogProjectApListView「新增稽核計畫」Dialog 加流程範本 dropdown + default 帶值邏輯| 入口 | URL / View | 動作 | API |
|---|---|---|---|
| 建專案(自動帶第一輪 AP) | /project/projects/new → ProjectCreateView |
建立專案 + 自動建第一輪 AP | POST /oscal/projects/start → OscalProjectService.start_oscal_project |
| 建後續輪 AP | /project/projects/:id → ProjectApListView「新增稽核計畫」Dialog |
建新 AP | POST /grc/project/<uid>/launch-new-round → OscalProjectService.launch_new_round |
兩處共用同一個「流程範本 dropdown + 📋 詳細選擇」UX,預覽元件 reuse Spec 1 FlowTemplatePreviewDialog.vue。
建專案 / 建後續輪 AP(任一入口)
↓ (使用者選範本 uid)
flow_template_uid (UUID, required) ──┐
▼
start_oscal_project / launch_new_round
│
▼
_bind_main_workflow_to_ap(ap, project_uid, flow_template_uid, curr_user)
│
┌────────────────────┴────────────────────┐
▼ ▼
FlowTemplateDomainService GRC_FLOW_TEMPLATE_NOT_FOUND
.get_by_uid(flow_template_uid) (uid 不存在 / is_active=false)
│
▼
master (FlowTemplateEntity)
│
▼
WorkflowTemplateSnapshotService.clone_master_as_snapshot(
master_uid=master.uid,
master_name=master.name,
xml=master.bpmn_xml,
description=master.description,
curr_user=curr_user,
)
│
▼
snapshot (engine `public.workflow_templates`,per-AP immutable)
│
▼
workflow_execution_service.start_workflow_execution(template_id=snapshot.id, ...)
│
▼
main_wf (engine `workflow_executions`)
│
▼
ap_extension_domain_service.upsert(
assessment_plan_id=ap.id,
workflow_execution_uid=main_wf.uid,
flow_template_snapshot_uid=snapshot.uid,
curr_user=curr_user,
)
│
▼
assessment_plan_extensions row written
│
▼
AP 詳情 API enrichment ◀── 從 ext 反查 snapshot → 反查 master → 三段組合回傳
OscalProjectService.start_oscal_project@transaction
def start_oscal_project(
self,
project_name,
...既有參數...,
flow_template_uid: str, # 新增,必填
curr_user: str = None,
locale: str = None,
) -> Project:
...既有 21 個 step 不動...
# Step 22 改造:把 flow_template_uid 顯式傳入
self._bind_main_workflow_to_ap(
ap=ap,
project_uid=str(project.uid),
flow_template_uid=flow_template_uid,
curr_user=curr_user,
)
return projectOscalProjectService.launch_new_round@transaction
def launch_new_round(
self,
project_uid: str,
user_id: int,
title: str,
flow_template_uid: str, # 新增,必填
start_date=None, end_date=None,
curr_user: str = None,
locale: str = None,
) -> dict:
...既有 step 1–9 不動(含 sub-workflow per-task clone,跟 main 不衝突)...
# 新增 step 10:補做 main workflow snapshot(Spec 2 的 gap)
self._bind_main_workflow_to_ap(
ap=ap,
project_uid=str(project_entity.uid),
flow_template_uid=flow_template_uid,
curr_user=curr_user,
)
return {...既有...}_bind_main_workflow_to_ap 改造def _bind_main_workflow_to_ap(
self,
ap,
project_uid: str,
flow_template_uid: str, # 新增,必填
curr_user: str,
) -> None:
"""Spec 3 — 接 caller 指定的 flow_template_uid,clone 一份 per-AP 凍結 snapshot
到 engine 表,啟動 workflow_execution,寫 assessment_plan_extensions。
Caller 已開 @transaction scope,本 helper 不再加。
"""
if (self._flow_template_domain_service is None
or self._ap_extension_domain_service is None
or self._workflow_template_snapshot_service is None):
raise PreconditionFailedError(GrcErrorCode.GRC_FLOW_TEMPLATE_NOT_FOUND)
master = self._flow_template_domain_service.get_by_uid(flow_template_uid)
if master is None or not master.is_active:
raise NotFound(GrcErrorCode.GRC_FLOW_TEMPLATE_NOT_FOUND)
snapshot = self._workflow_template_snapshot_service.clone_master_as_snapshot(
master_uid=str(master.uid),
master_name=master.name,
xml=master.bpmn_xml,
description=master.description,
curr_user=curr_user,
)
main_wf = self.workflow_execution_service.start_workflow_execution(
template_id=snapshot.id,
name=f"AP-{ap.uid}-main",
params={"ap_uid": str(ap.uid), "project_uid": project_uid},
created_user=curr_user,
description=f"AP main workflow for {ap.title}",
)
self._ap_extension_domain_service.upsert(
assessment_plan_id=ap.id,
workflow_execution_uid=str(main_wf.uid),
flow_template_snapshot_uid=str(snapshot.uid),
curr_user=curr_user,
)domain/flow_engine/service/flow_template_domain_service.py:
# 全部移除
# DEFAULT_MAIN_WORKFLOW_TEMPLATE_NAME = "完整稽核流程"
# def get_default_main_workflow_template(self) -> Optional[FlowTemplateEntity]: ...IFlowTemplateRepo.get_builtin_by_name() — grep 確認無其他 caller 即刪。
| Endpoint | Request schema 改動 |
|---|---|
POST /oscal/projects/start |
加 flow_template_uid: fields.UUID(required=True) |
POST /grc/project/<uid>/launch-new-round |
同上 |
兩個 endpoint 都要回 flow_template enrichment:
GET /grc/project/<uid>/aps(列表)— FE default 帶值靠這個,必須有GET /grc/project/<uid>/aps/<apUid>(詳情)— FE 顯示「目前用的範本」靠這個Response 加欄位:
{
...既有欄位...,
"flow_template": {
"snapshot_uid": "engine-snapshot-uid",
"master_uid": "compliance-master-uid",
"master_name": "完整稽核流程",
"is_master_active": true
}
}Enrichment path(app service 層):
assessment_plan_extensions by assessment_plan_id → flow_template_snapshot_uidworkflow_templates row → source_template_uidcompliance.flow_templates row → name + is_activeEnrichment 行為(lenient):若任一段缺失(舊資料 ext 不存在 / master 已硬刪等 edge case),flow_template 回 null,不擋 API 回應。is_master_active=false 代表 master 已軟刪但 snapshot 仍正常運作(這 AP 還能跑完現有 lifecycle)。
對比 — 建立路徑(strict,見 §十一):建 AP 時 master.is_active=False 直接 404 拒絕。語意:「進行中 AP 容忍 master 被刪、但新 AP 不能選已刪的 master」。
列表 enrichment 效能考量:列表 enrichment 會 N+1 — N AP × 3 段 query。實作要用 batch query:一次取所有 ext rows → 一次 batch 取 snapshot rows → 一次 batch 取 master rows,三段 dict join 完拼 response。
workflow_template_uid 欄位(注意:清掉 §906)launch_new_round 既有程式碼(line 906–924)對 task 動態 setattr task.workflow_template_uid — 那是 sub-workflow per-task 的 template uid,跟本 spec 加的 main workflow flow_template 是不同層級。保留,不衝突。但 §9.6 enrichment 必須清楚標 flow_template 是 main workflow(AP 級別),跟 task-level sub-workflow 分開不混淆。
| Code | 動作 |
|---|---|
GRC_FLOW_TEMPLATE_NOT_FOUND (404027) |
reuse,原本 Spec 1 已有 |
GRC_FLOW_MAIN_TEMPLATE_NOT_FOUND (412022) |
刪除(default lookup 拔了就用不到) |
GRC_AR_DATA_MISSING (500001) |
順手 fix:改 412024(Spec 2 carry-forward M1) |
ProjectCreateView.vue加在「基本資訊」step(建議 Step 0 末尾):
基本資訊
├── 專案名稱 *
├── 描述
├── 合規資源庫 *
├── 第一輪稽核計畫流程範本 * ← 新增
│ ├── <Dropdown
│ │ v-model="form.flowTemplateUid"
│ │ :options="flowTemplates"
│ │ optionLabel="name"
│ │ optionValue="uid"
│ │ :loading="loading"
│ │ :class="{'p-invalid': v$.flowTemplateUid.$error}" />
│ └── <Button text size="small" @click="openPreview">📋 詳細選擇</Button>
└── ...
ProjectApListView.vue 新增稽核計畫 Dialog新增稽核計畫
├── 計畫名稱 *
├── 起訖日期
├── 流程範本 * ← 新增
│ ├── <Dropdown ... v-model="form.flowTemplateUid" />
│ └── <Button text>📋 詳細選擇</Button>
└── ...
Default 帶值(依賴 §9.6 — AP 列表 API 必須含 flow_template enrichment):
const defaultFlowTemplateUid = computed(() => {
// apList 排序:最新一輪在 [0](既有排序慣例,新 spec 不改)
// 依賴 GET /grc/project/<uid>/aps 回 flow_template enrichment
const latest = props.apList?.[0]
return latest?.flow_template?.master_uid ?? null
})
watch(() => props.visible, (v) => {
if (v) form.value.flowTemplateUid = defaultFlowTemplateUid.value
})注意: 若 §9.6 列表 enrichment 未實作,dropdown default 會永遠 null,user 每次都要重選。列表 enrichment 是 dropdown UX 的硬依賴,不能延後到 v2 補。
<FlowTemplatePreviewDialog
v-model:visible="previewVisible"
:template-uid="form.flowTemplateUid"
/>
Spec 1 已 ship src/views/flow-template/components/FlowTemplatePreviewDialog.vue — input 是 master uid,內部自己拉 detail + render BPMN + 列階段清單。
src/service/FlowTemplateService.js(Spec 1 已有 list / detail),新增方法可直接 reuse。
zh-tw:
project:
flowTemplate:
label: 流程範本
firstRoundLabel: 第一輪稽核計畫流程範本
placeholder: 請選擇流程範本
detailBtn: 詳細選擇
requiredError: 請選擇流程範本
inheritedHint: 預設沿用上一輪稽核計畫的流程範本en 對應 key 一併補。
| 情境 | 行為 |
|---|---|
缺 flow_template_uid |
400 by webargs schema validation |
flow_template_uid 非 UUID 格式 |
400 by webargs schema validation |
| UUID 形式正確但查無此 master(不存在 / 軟刪) | 404 GRC_FLOW_TEMPLATE_NOT_FOUND |
master.is_active = False |
404 同上 |
| Cross-tenant master | RLS 自動 filter → 404 |
| Engine snapshot 建立失敗 / start_workflow_execution 失敗 | 整個 @transaction rollback;不留半成品 AP |
| 案例 | 範圍 | 對應 |
|---|---|---|
start_oscal_project 帶 valid uid → 第一輪 AP 有 snapshot + ext |
integration | §9.1 |
start_oscal_project 帶 invalid uid → 404 |
unit | §11 |
start_oscal_project 缺 uid → 400 |
unit | §11 |
launch_new_round 帶 valid uid → 新輪 AP 有獨立 snapshot + ext(與第一輪不同 snapshot uid) |
integration | §9.2 + §三 |
| Snapshot 隔離:建 AP 後改 master XML → AP snapshot.xml 不動 | integration | §四 |
AP 詳情 API 回 flow_template enrichment |
integration | §9.6 |
AP 詳情 enrichment 在 master 已軟刪時 is_master_active=false |
integration | §9.6 |
順手補 Spec 2 carry-forward test gap(同 commit 或鄰近 commit):
WorkflowTemplateSnapshotService — clone 行為 unit testStageAdvanceService — stage 推進 path unit testoscal_stage_preconditions — precondition function 個別 unit testoscal_stage_handlers — handler 個別 unit testAssessmentPlanExtensionRepoImpl — upsert / 查詢 unit test留到 Spec 3 BE+FE 都 ship 後 — 跟 Phase E 4 fixture bug 一起重做:
ProjectCreatePage.addFourParticipantsWithRoles(適配 wizard step 數變化 + 新增範本 dropdown)launchProjectViaUi / launchAuditViaUi / launchRemediationViaUi□ 建專案選範本 A「完整稽核流程」→ 第一輪 AP1
└ Banner 顯示 stage = 規劃 → 推進按鈕作用 → status preparing→active
└ 連續推完 4 個 stage 到 closed
□ 同專案 launch_new_round 選範本 B「自我評估稽核」(少 poam)
└ AP2 snapshot uid 跟 AP1 不同
└ Banner stage 組合對應 B 的 BPMN(規劃→執行→稽核→closed,跳 poam)
└ 推到 audit 完成後直接 closed
□ DB 驗:
- assessment_plan_extensions 兩列,flow_template_snapshot_uid 不同
- public.workflow_templates 兩列 snapshot row,source_template_uid 對應 A/B master
- 兩個 workflow_execution 互不影響
□ Smoke 通不過 → 根因落 Spec 2 stage_advance_service / oscal_stage_handlers
→ 此時補 Spec 2 carry-forward 5 service test gap 是必做(不再延後)
| 里程碑 | 範圍 | 提交策略 |
|---|---|---|
| M0 | Pre-flight verify:grep get_default_main_workflow_template / get_builtin_by_name / DEFAULT_MAIN_WORKFLOW_TEMPLATE_NAME 所有 caller;確認除了 _bind_main_workflow_to_ap 外無其他 reference |
無 commit,純檢查 |
| M1 | BE error code 整理(刪 412022、fix 500001→412024) | 獨立 commit |
| M2 | BE AP 列表 + 詳情 API 補 flow_template enrichment(純加欄位,不破 caller) |
獨立 commit |
| M3 | BE Spec 2 carry-forward 5 service test gap 補測試(move ahead — 防 M4 改動踩到沒測過的 service) | 獨立 commit |
| M4 | BE _bind_main_workflow_to_ap 接 flow_template_uid;start_oscal_project + launch_new_round 加參數;拔 default lookup;加 launch_new_round 呼叫 helper |
獨立 commit |
| M5 | FE ProjectCreateView dropdown + 預覽 Dialog + i18n |
獨立 commit |
| M6 | FE ProjectApListView Dialog dropdown + default 帶值 + i18n |
獨立 commit |
| M7 | Manual smoke(必過項,§12.3)+ 收尾 changelog | 必過 |
| M8 | (事後)Phase E 4 fixture bug 修補 + 3 個 Banner BDD scenario 重寫 | 跨 repo |
M3/M4 順序理由:M4 改 _bind_main_workflow_to_ap + 拔 get_default_main_workflow_template 會影響 StageAdvanceService / WorkflowTemplateSnapshotService 等 carry-forward 服務。先 M3 補測試,再做 M4 改動,M4 出 regression 時 test 立刻 fail,不會拖到 M7 smoke 才發現。
| 依賴 | 提供方 | 狀態 |
|---|---|---|
GET /flow-engine/flow-templates(列表) |
Spec 1 | ✅ 已 ship |
GET /flow-engine/flow-templates/<uid>(detail + BPMN XML) |
Spec 1 | ✅ 已 ship |
FlowTemplatePreviewDialog.vue |
Spec 1 FE | ✅ 已 ship |
WorkflowTemplateSnapshotService.clone_master_as_snapshot |
Spec 2 | ✅ 已 ship |
_bind_main_workflow_to_ap helper(既有 backbone) |
Spec 2 | ✅ 已 ship(Spec 3 改造參數) |
assessment_plan_extensions table + repo |
Spec 2 | ✅ 已 ship |
v1 列的 5 個 open questions 全部 resolved(見 §二)。
v2 沒有新增 open question — 進入 Phase 3 implementation-plan.md 階段。
Phase 5 實作 + M7 smoke + Phase 6 review 後,下列為實作偏離 v2.1 plan 的條目。記錄目的:給未來讀者保留決策軌跡,不是補救。
| # | Plan 寫的 | 實作的 | 原因 |
|---|---|---|---|
| 1 | M2 plan 寫「修改 jedi-flow-engine 加 get_many_by_uids」 |
不動套件,用 enricher 內 _engine_get_many_by_uids adapter 把既有 get_workflow_templates_by_uids list 結果轉 dict |
jedi-flow-engine 0.0.27 已有此 method(返回 list),M2 implementer 評估後 reuse |
| 2 | M2.4 plan 範例 AP detail GET endpoint | AssessmentPlanDetailResource 只有 PUT/DELETE 沒 GET |
grep 後發現 FE 也未呼叫 detail GET;enricher 改在 PUT response 補;FE 未來要 detail GET 時再加 |
| 3 | M2.4 plan 寫 integration test 用 app_client + DB fixture |
test/ 目錄無 DB fixture infra | 改 service-level unit test 用 mock enricher 驗 service 把 enrichment 塞到 dto;integration coverage deferred |
| 4 | M4.2 plan 寫的 schema class 名 StartProjectRequestSchema |
實際是 OscalProjectStartRequest(StartProjectRequest 是 module_frame 另一 route 用) |
Plan 寫錯 class 名 |
| 5 | M4.3 plan 寫 launch_new_round 簽章 flow_template_uid: str(無 default required positional) |
實際 flow_template_uid: str = "" + 內部 raise BadRequestError(GRC_FLOW_TEMPLATE_UID_REQUIRED) |
與既有 schema integration test pattern 對齊;schema 已 enforce required,service 層的 default 是 defensive guard(後續 PR-TD2 計畫改回必填) |
| 6 | M0 Step 6 poetry show jedi-flow-engine 預期輸出 location path |
實際只輸出 version 0.0.27 | 用 grep pyproject.toml 直接驗 path mode 取代 |
| 7 | Plan 假設 dropdown 落點在 ProjectBasicInfoForm.vue |
實際加在 ProjectCreateView.vue 內 inline 跟既有 profile dropdown 同 slot 層 |
M0 Step 8 grep 後決定(ProjectBasicInfoForm 是 generic component) |
| 8 | Plan 範例 FE service method FlowTemplateService.getList({ is_active: true }) |
實際既有 service listFlowTemplates({page, size, name, is_builtin}) 回 {items, total, page, size} |
M5 implementer 對齊既有 API;BE service 已固定 is_active=True filter,FE 不用傳 |
| 9 | Plan 範例 FlowTemplatePreviewDialog 接 :template-uid |
實際 prop 是 :template Object(含 bpmn_xml) |
既有 component 設計;M5 加 openFlowTemplatePreview helper 串接 list summary + detail fetch |
| # | 問題 | 修補 commit |
|---|---|---|
| M9 | 自建「規劃→執行→End」流程:執行任務 stage advance 後 AP 卡 auditing 出不來,因為 task_execution stage 的 hardcode handler 是 launch_audit(假設下一步是 audit stage) |
BE a365d71 — 加 _is_terminal_user_task 偵測 + TerminalCloseHandler + OscalAuditService.terminal_close_ap 寧包子 close |
| M10 | 自建流程沒含 audit/poam stage 時 FE「稽核結果」「改善計劃」按鈕仍依 status 顯示但點進去無資料 | BE d0e6465 — enricher 加 stage_codes from BPMN;FE 152d1a1 — 兩個 view button v-if 加 stage gate |
| M11 | 「規劃→End」流程:AP 已 closed 但 project.status 卡 PENDING(M9 漏寫 project.status sync),列表顯示「未開始」跟詳情「已結案」不一致 |
BE 02512b9 — terminal_close_ap 加 project_uid 參數 + project.status → COMPLETED;dev DB 既有資料用 SQL 手動修 |
| M12 | 自建流程沒掛 OSCAL task 時 completion_rate 永遠 0%;AP closed 後 user 預期 100% 但仍 0% | BE 728add2 + b979a71(hotfix String import)— 4 處 completion_rate 計算路徑加「ap_status='closed' → 100%」分支 |
| M13 | ApListView row buttons 只判斷 status+stage_codes,缺角色維度;ProjectAuditorOverview 右上同名 button 已對齊角色但 ApListView 沒 | FE 695baad — 加 isAuditor computed + v-else-if 三維度判斷對齊 |
| Path | 設計 | 實作 |
|---|---|---|
建立 AP (_bind_main_workflow_to_ap) |
strict — master 不存在 / is_active=False 拒絕 404 |
✅ raise NotFound(GRC_FLOW_TEMPLATE_NOT_FOUND) |
列表 / 詳情 enrichment (FlowTemplateEnricher) |
lenient — master 軟刪 is_master_active=False 不擋 API |
✅ 三段 batch query lenient fallback,缺 ext / 缺 master 都回 None 或 partial |
BPMN parse 失敗 (_extract_stage_codes) |
M10 新加 — lenient(不擋 enrichment) | ✅ try/except 回 [] 加 log warning |
terminal stage detection (_is_terminal_user_task) |
M9 新加 — get_next_elements 空 → terminal |
✅ 含 Gateway 視為 non-terminal(user 確認的 rule) |
整理自 Phase 6 review docs/issues/pending/2026-05-13-spec3-phase-6-followups.md:
| 項目 | 優先級 |
|---|---|
_sync_project_status_to_completed helper 統一三處不一致 |
High |
terminal_close_ap 加角色檢查 |
High |
launch_audit dead code 54 行刪除 |
Medium |
| M12 completion_rate 補 unit test | Medium |
| FE default flow_template guard(master 軟刪 UX) | Medium |
| Type design 重構(payload dataclass / StartProjectDto 必填 / StageInfoDto) | 中期 |
User Phase 5 + smoke 收尾後提出「stage 物件能否 plugin 化」討論,當前不做,紀錄在 docs/analysis/2026-05-13-stage-plugin-architecture-thoughts.md。3 個 phase(FE button config / StagePlugin 整合 / auto-discovery)+ 觸發條件已記錄。
| 版本 | 日期 | 變更 |
|---|---|---|
| v1 | 2026-05-13 | 初版大方向,5 條 open questions 待 brainstorm |
| v2 | 2026-05-13 | brainstorm 收斂完整版:Q1–Q5 全部 resolved;補 §三 launch_new_round Spec 2 gap 認知;補 §四 snapshot 三層獨立性;BE/FE 改動細節到位;落地順序拆 M1–M8 |
| v2.1 | 2026-05-13 | spec reviewer 反饋整合:§9.6 列表 + 詳情兩個 endpoint 都要 enrichment(strict vs lenient 行為對比);§10.2 標明 default 帶值靠 list enrichment(硬依賴);§13 加 M0 pre-flight verify、M3/M4 順序對調防 regression |
| v3 | 2026-05-13 | summary 收尾加 §16 Implementation Reality / Reconciliation:plan 偏差 9 條 + M7 smoke 補 M9–M13 5 條 + strict/lenient 落地對照 + Phase 6 follow-up 引用 + plugin 化討論 reference |