diff --git a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md index a17ea45c..d3f1a185 100644 --- a/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md +++ b/docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md @@ -554,13 +554,14 @@ commit gate 判定: 当前最建议下一步执行: -**Phase C2:ChangeSet commit 入库。** +**Phase C2:ChangeSet commit 后端与前端实现。** 原因: - A3 已完成真实只读投影,Phase B 已完成真实 Project Launch 创建链。 - Phase C1 已把项目内消息、结构化候选、审查动作和证据链落到真实表。 -- 当前缺口是把已接受/已编辑的 ChangeItem 经 commit 写入 `truth_nodes / tasks / semantic_facts`,并补齐高风险 approval gate。 +- C2-contract 已锁定 commit API、原子提交规则、响应合同和失败语义。 +- 当前缺口是按合同把已接受/已编辑的 ChangeItem 经 commit 写入 `truth_nodes / tasks / semantic_facts`,并补齐高风险 approval gate。 ### 7.1 Phase A 建议拆成三个小任务 @@ -610,6 +611,37 @@ commit gate 判定: ## 8. 进度台账 +### 2026-04-26 / G0 Freeze + C2 Contract / done + +完成内容: + +- 将当前非删除工作区成果直接 checkpoint 到 `master`,形成 `b52428f docs: codify PMFlow product delivery governance` 与 `e04ea4d chore: checkpoint current PMFlow workspace` 两个提交;删除痕迹未提交,避免未确认地固化旧截图、表格、tunnel 脚本和临时文件删除。 +- 收口 Phase C2 commit 合同:`POST /pmflow-os/change-sets/{change_set_id}/commit` 的请求体、权限、原子提交模式、响应结构、错误码、高风险 approval gate、C2 V1 objectType 白名单和 ProjectTruthState 投影规则。 +- 明确 C2 V1 默认 `mode=atomic`,任一 eligible item 写入失败时整个 commit 回滚,禁止半写入 Project Memory。 +- 明确 C2 必须实现 `Requirement / Decision / Task / Risk`;`Release` 可选;`ModelDecision / MetricSignal` 暂缓并必须 fail-closed。 + +涉及文件: + +- `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md` +- `docs/architecture/API_CONTRACTS.md` +- `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` + +验证: + +- `git status --short --branch` 已确认 checkpoint 后仅剩未提交删除痕迹。 +- `git diff --cached --name-status` 阶段检查未发现删除项被纳入 checkpoint。 +- 本轮 C2-contract 只改合同文档,未新增代码、Schema 或 migration,未运行编译/单测。 + +未完成或风险: + +- 工作区仍有未提交删除痕迹,需用户单独确认是否删除或恢复。 +- C2 后端 route/service/schema 与前端 API/store 尚未实现。 + +下一步建议: + +- 执行 `C2-backend`:实现 atomic ChangeSet commit service、route、权限、事务、高风险 gate 与后端测试。 +- 合同稳定后并行执行 `C2-frontend`:接入 commit API,替换真实模式下的 demo-only `promoteAcceptedToGraph`。 + ### 2026-04-26 / Parallel Delivery Governance / done 完成内容: diff --git a/docs/architecture/API_CONTRACTS.md b/docs/architecture/API_CONTRACTS.md index dad75d56..b7a85f80 100644 --- a/docs/architecture/API_CONTRACTS.md +++ b/docs/architecture/API_CONTRACTS.md @@ -1,5 +1,40 @@ # API CONTRACTS +## PMFlow OS + +### POST `/pmflow-os/change-sets/{change_set_id}/commit` +- Purpose: commit reviewed ChangeSet items into the official project truth layer. +- Auth: project member, `OWNER`, or `REVIEWER`; tenant comes from JWT. +- Request body: + - `item_ids?: string[]` optional; default commits all `accepted / edited` items. + - `mode: "atomic"` required for C2 V1. + - `note?: string` audit note only. +- Behavior: + - Processes only `accepted / edited` ChangeItems. + - C2 V1 is atomic: any eligible item write failure rolls back the whole commit. + - Required C2 objectType support: `Requirement / Decision / Task / Risk`. + - Optional C2 objectType support: `Release`. + - Unsupported for C2 V1: `ModelDecision / MetricSignal`, which must fail with `422 unsupported_object_type`. + - `Requirement / Decision / Risk / Release` write to `truth_nodes`. + - `Task` writes `truth_nodes(node_type='task')` plus `tasks.truth_node_id`. + - High-risk items require item-level approval with `decision in ('approved','edited')` and `after_status in ('accepted','edited')`. +- Response data: + - `changeSet`: `{ id, status, rawStatus, committedAt, committedItemIds, skippedItemIds, failedItemIds }` + - `committedItems[]`: `{ changeItemId, objectType, targetType, targetId, status, evidenceIds, semanticFactStatus }` + - `skippedItems[]`: `{ changeItemId, objectType, status, reason }` + - `failedItems[]`: `{ changeItemId, objectType, code, message }` + - `projectTruthState?`: optional backend projection after commit. +- Failure semantics: + - `404 changeset_not_found` + - `409 changeset_not_reviewed` + - `409 changeset_already_committed` + - `409 no_accepted_items` + - `409 high_risk_approval_required` + - `422 invalid_commit_item_ids` + - `422 unsupported_object_type` + - `422 invalid_change_payload` + - `500 commit_failed` + ## Signal & SSTC ### GET `/projects/{project_id}/signals` diff --git a/docs/architecture/CODE_REFERENCE_INDEX.md b/docs/architecture/CODE_REFERENCE_INDEX.md index 7fd7ccc9..bb253d8b 100644 --- a/docs/architecture/CODE_REFERENCE_INDEX.md +++ b/docs/architecture/CODE_REFERENCE_INDEX.md @@ -89,7 +89,7 @@ - SSTC 负责人推荐接口:参考 `POST /projects/{project_id}/tasks/{task_id}/recommend-assignee` - SSTC 任务更新接口:参考 `PATCH /projects/{project_id}/tasks/{task_id}` - SSTC 发布创建接口:参考 `POST /projects/{project_id}/releases` -- PMFlow OS 真实读路径与 C1 Change Review API:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`backend/app/services/pmflow_change_review_service.py`、`backend/app/schemas/pmflow_os.py`;只读提供 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,并读取 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;C1 写 API 包括 thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`,不实现 commit +- PMFlow OS 真实读路径与 C1 Change Review API:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`backend/app/services/pmflow_change_review_service.py`、`backend/app/schemas/pmflow_os.py`;只读提供 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync`,并读取 `pmflow_threads / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`;C1 写 API 包括 thread message、`proposed_items` extraction、ChangeSet detail、ChangeItem `accept/edit/defer`,C2 commit 合同已锁定为 `POST /pmflow-os/change-sets/{change_set_id}/commit`,默认 `mode=atomic`,实现前必须先读 `docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md` 与 `docs/architecture/API_CONTRACTS.md` - Project Launch 真实创建链:参考 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/schemas/pmflow_launch.py` 与模型 `backend/app/models/pmflow_launch.py`;接口为 `POST /pmflow-launch/drafts` 与 `POST /pmflow-launch/drafts/{draft_id}/confirm`,draft 只写 `pmflow_launch_drafts`,confirm 在同一事务内创建 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs` - PMFlow Change Review 模型与 migration:参考 `backend/app/models/pmflow_change_review.py` 与 `backend/alembic/versions/20260426_add_pmflow_change_review_tables.py`;只包含 `pmflow_change_sets / pmflow_change_items / pmflow_approvals / pmflow_evidence_links`,本阶段不得新增或依赖 `/commit` diff --git a/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md b/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md index b0bd8594..da1593a2 100644 --- a/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md +++ b/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md @@ -2,7 +2,7 @@ 版本日期:2026-04-26 -本文档是 PMFlow OS 从本地高保真主链走向真实数据闭环的产品级合同。它锁定对象真源、读写边界、状态流转和后续开发接口,避免 `frontend/src/features/pmflow-os/` 继续围绕 mock store 扩散。A3 已完成真实只读 projection,Phase B 已完成 Project Launch 创建链,Phase C1 已完成 Change Review 数据底座与审查状态机;ChangeSet commit 入库仍未实现。 +本文档是 PMFlow OS 从本地高保真主链走向真实数据闭环的产品级合同。它锁定对象真源、读写边界、状态流转和后续开发接口,避免 `frontend/src/features/pmflow-os/` 继续围绕 mock store 扩散。A3 已完成真实只读 projection,Phase B 已完成 Project Launch 创建链,Phase C1 已完成 Change Review 数据底座与审查状态机;Phase C2 commit 合同已收口,后续实现不得再自行改变 API shape。 产品方向真源仍以 `docs/PMFLOW_PRODUCT_BLUEPRINT.md` 为准;UI 行为与状态标签以 `docs/PMFLOW_UI_CONTRACT.md` 为准;推进节奏以 `docs/PMFLOW_PRODUCT_DELIVERY_STANDARD.md` 为准。 @@ -40,7 +40,7 @@ pmflow_approvals pmflow_evidence_links ``` -当前仍未实现 `/commit`,因此 accepted/edited ChangeItem 不得被投影成 confirmed Project Memory。 +当前仍未实现 `/commit` 代码,因此 accepted/edited ChangeItem 不得被投影成 confirmed Project Memory;但 C2 实现必须遵守本文 4.6 和第 5 节合同。 ## 2. 对象映射表 @@ -261,29 +261,102 @@ POST /pmflow-os/change-items/{change_item_id}/defer ### 4.6 提交 ChangeSet -Phase C1 尚未实现该接口;以下是 Phase C2 合同草案。 +Phase C2 必须按此合同实现,后端与前端不得自行发明其它请求或响应形状。 ```text POST /pmflow-os/change-sets/{change_set_id}/commit ``` +Auth: + +- 当前用户必须是项目成员、`OWNER` 或 `REVIEWER`。 +- `tenant_id` 必须来自当前 JWT,不得从请求体传入。 + +Request: + +```json +{ + "item_ids": ["12", "13"], + "mode": "atomic", + "note": "确认写入项目记忆" +} +``` + +字段规则: + +- `item_ids` 可选;为空时提交该 ChangeSet 下全部 `accepted / edited` 项。 +- `mode` C2 V1 只允许 `atomic`;未来若支持部分提交,必须先更新本文档。 +- `note` 可选,仅进入审计,不参与业务字段生成。 + 行为: - 只处理 `accepted / edited` 的 ChangeItem。 +- `pending_review / deferred / committed / skipped` 不写正式对象,只进入 `skippedItems` 明细。 +- C2 V1 使用单事务原子提交:任一 eligible item 写入失败时,整个 commit 回滚,不允许出现半写入 Project Memory。 - 按 `objectType` 写入正式对象: - `Requirement / Decision / Risk / Release` -> `truth_nodes` - - `Task` -> `tasks` + 关联 `truth_nodes` + - `Task` -> `truth_nodes(node_type='task')` + `tasks.truth_node_id` - `ModelDecision` -> `project_model_bindings` 或绑定选择记录 - `MetricSignal` -> 反馈/收入/ROI 相关记录,或生成下一轮 recommendation 草案 - 创建或更新 `pmflow_evidence_links`。 - 提交成功后触发 semantic facts 抽取/刷新。 -- ChangeSet 进入 `committed` 或 `partially_committed`。 +- 全部 eligible item 成功后 ChangeSet 进入 `committed`。 +- `partially_committed` 是未来扩展状态,C2 V1 不应写出该状态。 + +Response `data`: + +```json +{ + "changeSet": { + "id": "10", + "status": "committed", + "rawStatus": "committed", + "committedAt": "2026-04-26T10:00:00+08:00", + "committedItemIds": ["12"], + "skippedItemIds": ["14"], + "failedItemIds": [] + }, + "committedItems": [ + { + "changeItemId": "12", + "objectType": "Requirement", + "targetType": "truth_node", + "targetId": "301", + "status": "committed", + "evidenceIds": ["44"], + "semanticFactStatus": "queued" + } + ], + "skippedItems": [ + { + "changeItemId": "14", + "objectType": "Task", + "status": "deferred", + "reason": "item_not_accepted_or_edited" + } + ], + "failedItems": [], + "projectTruthState": {} +} +``` + +响应规则: + +- `projectTruthState` 可选;若返回,必须是 commit 后的后端真实 projection,不得由前端本地拼装。 +- `committedItems[].targetId` 必须是正式对象 id,如 `truth_nodes.id`、`tasks.id`、`project_model_bindings.id`。 +- `semanticFactStatus` 取值:`queued / seeded / skipped / failed`;失败不能影响已完成的正式对象写入,但必须进入审计或 failed detail。 失败语义: -- `409 no_accepted_items`:没有可提交项。 +- `404 changeset_not_found`:ChangeSet 不存在或不属于当前 tenant/project。 +- `409 changeset_not_reviewed`:仍存在 `pending_review` 项且请求未显式只提交 `item_ids`。 +- `409 changeset_already_committed`:ChangeSet 已经提交,不能重复提交。 +- `409 no_accepted_items`:没有 `accepted / edited` 可提交项。 - `409 high_risk_approval_required`:高风险项缺少 required approval。 -- `500 commit_partial_failed`:部分项写入失败,必须返回成功/失败 item 明细,不得全部冒充成功。 +- `422 invalid_commit_item_ids`:`item_ids` 不属于该 ChangeSet 或包含不可提交状态。 +- `422 unsupported_object_type`:`objectType` 不在 C2 白名单。 +- `422 invalid_change_payload`:有效 payload 不满足正式对象必填字段。 +- `500 commit_failed`:写入正式对象失败;C2 V1 必须回滚整个事务,并返回 `failedItems` 明细,不得冒充部分成功。 ### 4.7 获取执行桥摘要 @@ -301,22 +374,42 @@ GET /pmflow-os/projects/{project_id}/execution-summary `ChangeItem.objectType` 的提交规则: +有效 payload 生成规则: + +- `edited` 项优先使用 `edited_payload`,否则使用 `payload`。 +- `PMFlowChangeItem.title / summary / diff` 是展示与审计主字段;payload 中同名字段只能补充,不能覆盖人工 edit 后的主字段。 +- 每个正式对象 content/payload 必须写入:`source=pmflow_change_set_commit`、`change_set_id`、`change_item_id`、`evidence_ids`、`confidence`、`risk_level`、`committed_by`、`committed_at`。 +- 所有正式写入必须带 `tenant_id` 查询边界;`truth_nodes` 历史模型若无显式 `tenant_id` 字段,必须通过所属 `project_id` 的 tenant 校验间接保证租户边界。 + | objectType | 正式写入目标 | 必填字段 | 写入后前端状态 | | --- | --- | --- | --- | -| `Requirement` | `truth_nodes(node_type='requirement')` 或 charter content requirement block | `title`、`summary`、`stage`、证据 | `confirmed` | +| `Requirement` | `truth_nodes(node_type='requirement')` | `title`、`summary`、`stage`、证据 | `confirmed` | | `Decision` | `truth_nodes(node_type='decision')` | `title`、`summary`、`owner`、证据 | `confirmed` | -| `Task` | `tasks`,必要时创建关联 `truth_nodes` | `title`、`description/summary`、`priority`、`acceptanceCriteria` | `draft` 或 `in_progress` | +| `Task` | `truth_nodes(node_type='task')` + `tasks` | `title`、`description/summary`、`priority`、`acceptanceCriteria` | `draft` | | `Risk` | `truth_nodes(node_type='risk')` | `title`、`summary`、`riskLevel`、`mitigation`、证据 | `confirmed` | | `ModelDecision` | `project_model_bindings` 或 `model_recommendation_snapshots` 引用 | `catalog_item_id` 或推荐快照引用 | `confirmed` | | `Release` | `truth_nodes(node_type='release')` + release validation 引用 | `title`、`summary`、gate items | `check_pending/ready` | | `MetricSignal` | `feedback_ingests`、`revenue_events`、`roi_snapshots` 投影或 recommendation 草案 | source、value、window | `confirmed` | +C2 V1 白名单: + +- 必须实现:`Requirement / Decision / Task / Risk`。 +- 可实现但不阻塞 C2:`Release`。 +- 暂缓实现:`ModelDecision / MetricSignal`,若收到这两类 item,必须返回 `422 unsupported_object_type`,不得假成功。 + 禁止规则: - 禁止 `accept` 时直接写正式对象。 - 禁止没有 evidence 的高风险项进入 `commit`。 - 禁止把 AI 生成内容直接投影为 `Project Memory` confirmed facts。 - 禁止让前端用本地 `promoteAcceptedToGraph` 代替后端 commit。 +- 禁止 commit 后仍让 `activeChangeSetId` 指向已 committed 的 ChangeSet;projection 应选择最近 open/reviewed ChangeSet,或返回 `null`。 + +高风险 approval gate: + +- `risk_level=high` 的 item 必须存在同 item 的 `pmflow_approvals` 记录,且 `decision in ('approved','edited')`、`after_status in ('accepted','edited')`。 +- 高风险 item 必须能解析到至少一个 evidence link;ChangeSet 级 evidence 可以继承给 item,但 response 里必须显式返回 `evidenceIds`。 +- gate 失败时整个 commit 不写入正式对象。 ## 6. Execution Bridge 复用合同