From f48a4d0381d4b51a8e515f5bda90c4cc2997e84a Mon Sep 17 00:00:00 2001 From: Albert Date: Mon, 27 Apr 2026 16:39:56 +0800 Subject: [PATCH] feat: add execution result writeback protocol --- backend/app/schemas/pmflow_os.py | 2 + .../services/pmflow_os_projection_service.py | 28 ++++++++++++- .../tests/test_pmflow_os_projection_api.py | 15 ++++++- docs/architecture/API_CONTRACTS.md | 21 ++++++++++ docs/architecture/ARCHITECTURE_INVENTORY.md | 1 + docs/architecture/CODE_REFERENCE_INDEX.md | 1 + .../PMFLOW_OS_PRODUCT_CONTRACT.md | 38 ++++++++++++++++- frontend/src/api/pmflowOs.ts | 10 ++++- .../pmflow-os/pages/ExecutionBridgePage.tsx | 41 ++++++++++++++++++- .../src/features/pmflow-os/store.api.test.tsx | 10 ++++- 10 files changed, 157 insertions(+), 10 deletions(-) diff --git a/backend/app/schemas/pmflow_os.py b/backend/app/schemas/pmflow_os.py index 20c8c2c1..4422df4e 100644 --- a/backend/app/schemas/pmflow_os.py +++ b/backend/app/schemas/pmflow_os.py @@ -202,6 +202,8 @@ class PMFlowExecutionProgressEventRequest(BaseModel): commit_sha: Optional[str] = Field(default=None, max_length=255) pr_number: Optional[int] = Field(default=None, ge=1) test_summary: Optional[str] = Field(default=None, max_length=4000) + blocker_summary: Optional[str] = Field(default=None, max_length=4000) + execution_status: Optional[PMFlowExecutionStatus] = None workflow_state: Optional[str] = Field(default=None, max_length=120) summary: Optional[str] = Field(default=None, max_length=4000) event_source_id: Optional[str] = Field(default=None, max_length=255) diff --git a/backend/app/services/pmflow_os_projection_service.py b/backend/app/services/pmflow_os_projection_service.py index 728335e7..8fa1ec2a 100644 --- a/backend/app/services/pmflow_os_projection_service.py +++ b/backend/app/services/pmflow_os_projection_service.py @@ -4223,9 +4223,29 @@ class PMFlowOSProjectionService: body: PMFlowExecutionProgressEventRequest, repo: Optional[RepoBinding], ) -> dict[str, Any]: - payload: dict[str, Any] = { - "workflow_state": body.workflow_state or self._default_workflow_state(body.event_type), + workflow_state = body.workflow_state or body.execution_status or self._default_workflow_state(body.event_type) + repo_full_name = f"{repo.owner}/{repo.repo_name}" if repo is not None else None + execution_result = { + "protocol": "execution_result_protocol_v1", + "eventType": body.event_type, "source": body.source, + "status": body.execution_status or workflow_state, + "branchName": body.branch_name, + "commitSha": body.commit_sha, + "prNumber": body.pr_number, + "testSummary": body.test_summary, + "blockerSummary": body.blocker_summary, + "repositoryFullName": repo_full_name, + } + payload: dict[str, Any] = { + "protocol_version": "execution_result_protocol_v1", + "workflow_state": workflow_state, + "source": body.source, + "execution_result": { + key: value + for key, value in execution_result.items() + if value is not None and (not isinstance(value, str) or value.strip()) + }, "boundary": "PMFlow records execution result metadata only; command execution stays in Local Bridge, Codex, GitHub agent, or manual runtime.", } for key, value in { @@ -4233,6 +4253,8 @@ class PMFlowOSProjectionService: "commit_sha": body.commit_sha, "pr_number": body.pr_number, "test_summary": body.test_summary, + "blocker_summary": body.blocker_summary, + "execution_status": body.execution_status, "event_source_id": body.event_source_id, }.items(): if value is not None and str(value).strip(): @@ -4251,6 +4273,8 @@ class PMFlowOSProjectionService: ) -> str: if body.summary: return body.summary + if body.blocker_summary: + return body.blocker_summary if body.test_summary: return body.test_summary repo_label = f"{repo.owner}/{repo.repo_name}" if repo is not None else "未绑定仓库" diff --git a/backend/tests/test_pmflow_os_projection_api.py b/backend/tests/test_pmflow_os_projection_api.py index c88747af..2b2976b6 100644 --- a/backend/tests/test_pmflow_os_projection_api.py +++ b/backend/tests/test_pmflow_os_projection_api.py @@ -1119,8 +1119,15 @@ async def test_execution_progress_writeback_records_branch_pr_commit_and_test(db assert link.branch_name == "codex/main-flow-d3" assert link.pr_number == 77 assert link.last_commit_sha == "abc123def456" - events = (await session.execute(select(ProgressEvent).where(ProgressEvent.task_id == task.id))).scalars().all() + events = ( + await session.execute(select(ProgressEvent).where(ProgressEvent.task_id == task.id).order_by(ProgressEvent.id.asc())) + ).scalars().all() assert [event.event_type for event in events] == ["commit", "pr_opened", "test_failed"] + assert events[0].payload["protocol_version"] == "execution_result_protocol_v1" + assert events[0].payload["execution_result"]["branchName"] == "codex/main-flow-d3" + assert events[0].payload["execution_result"]["commitSha"] == "abc123def456" + assert events[1].payload["execution_result"]["prNumber"] == 77 + assert events[2].payload["execution_result"]["testSummary"] == "单测失败:ExecutionBridgePage 回写表单缺少断言" @pytest.mark.asyncio @@ -1251,7 +1258,8 @@ async def test_release_gate_blocks_explicit_failed_event(db_env) -> None: "task_id": str(task.id), "event_type": "failed", "source": "codex", - "summary": "Codex 执行失败:typecheck 未通过", + "execution_status": "failed", + "blocker_summary": "Codex 执行失败:typecheck 未通过", }, ) @@ -1261,6 +1269,9 @@ async def test_release_gate_blocks_explicit_failed_event(db_env) -> None: assert payload["releaseGate"]["status"] == "blocked" assert "typecheck 未通过" in " ".join(payload["releaseGate"]["blockingReasons"]) assert payload["releaseGate"]["evidence"][0]["type"] == "failed" + event = (await session.execute(select(ProgressEvent).where(ProgressEvent.task_id == task.id))).scalar_one() + assert event.payload["execution_result"]["status"] == "failed" + assert event.payload["execution_result"]["blockerSummary"] == "Codex 执行失败:typecheck 未通过" @pytest.mark.asyncio diff --git a/docs/architecture/API_CONTRACTS.md b/docs/architecture/API_CONTRACTS.md index df3b76b2..6f48ce1e 100644 --- a/docs/architecture/API_CONTRACTS.md +++ b/docs/architecture/API_CONTRACTS.md @@ -2,6 +2,27 @@ ## PMFlow OS +### POST `/pmflow-os/projects/{project_id}/execution-progress-events` +- Purpose: record Codex / IDE / Local Bridge / GitHub agent execution results after an execution handoff has been consumed. +- Auth: project member, `OWNER`, or `REVIEWER`; tenant comes from JWT. +- Request body: + - `task_id: string` required; must belong to the current project. + - `event_type: "branch_created" | "commit" | "pr_opened" | "pr_synced" | "test_passed" | "test_failed" | "checks_passed" | "failed" | "blocked" | "merged" | "accepted" | "manual_progress"`. + - `source?: "pmflow_os" | "local_bridge" | "codex" | "github_agent" | "manual"`. + - `repo_binding_id?: string | null`. + - `branch_name?: string | null`, `commit_sha?: string | null`, `pr_number?: number | null`. + - `test_summary?: string | null`, `blocker_summary?: string | null`. + - `execution_status?: "queued" | "pushed" | "coding" | "pr_open" | "testing" | "synced" | "failed"`. + - `workflow_state?: string | null`, `summary?: string | null`, `event_source_id?: string | null`, `occurred_at?: string | null`. +- Behavior: + - Creates a `ProgressEvent` only; it does not run commands, push branches, create PRs, publish releases, or create formal tasks. + - Stores a backward-compatible flat payload plus `protocol_version: "execution_result_protocol_v1"` and `execution_result` envelope with `{eventType, source, status, branchName, commitSha, prNumber, testSummary, blockerSummary, repositoryFullName}`. + - Returns the latest `ExecutionSummaryProjection`, allowing `execution`, `releaseGate`, and Team Feed projection to consume the same standardized result metadata. + - `failed / blocked / test_failed` events fail-closed into `releaseGate.status="blocked"`; `test_passed / checks_passed` can move the gate to `ready` or `needs_confirmation` depending on PR/sync evidence. +- Failure semantics: + - `404 task_not_found` + - `422 repo_binding_not_found` + ### POST `/pmflow-os/projects/{project_id}/extractions` - Purpose: turn a project thread message into an open ChangeSet with pending_review ChangeItems. - Auth: project member, `OWNER`, or `REVIEWER`; tenant comes from JWT. diff --git a/docs/architecture/ARCHITECTURE_INVENTORY.md b/docs/architecture/ARCHITECTURE_INVENTORY.md index 187e1ba3..820b51f7 100644 --- a/docs/architecture/ARCHITECTURE_INVENTORY.md +++ b/docs/architecture/ARCHITECTURE_INVENTORY.md @@ -212,6 +212,7 @@ - `frontend/src/features/project-launch/types.ts`、`mockData.ts`、`useProjectLaunchState.ts` 是新项目启动台状态层;默认真实模式通过 `frontend/src/api/pmflowLaunch.ts` 创建 `pmflow_launch_draft_jobs`、轮询 job 状态、拿到草案后再确认创建真实项目,API 失败进入明确失败态,不静默 fallback 成 mock 成功;`mockData.ts` 仅在显式 `?demo=1` 或 `?dataSource=demo` 下作为演示状态机使用,真实模式的步骤摘要、操作流、审查建议、附件引用和计数从当前输入、job 状态、真实草案、warnings/evidence 派生。`components/LaunchShell.tsx` 负责桌面/移动分流,桌面端复用 `frontend/src/features/personal-space/components/SpaceChrome.tsx` 的空间左侧栏和顶部条,并通过真实补充输入弹层提交 `merge_supplement` revision,禁止硬编码追加示例文本;`SpaceSidebar` 承载稳定空间目的地(空间首页、暂存想法、草案箱、所有项目和管理配置),不展示最近项目、待我确认或 AI 正在推进等首页工作台区块项;真实模式下若 `AIReadiness.canGenerateProjectDraft=false`,优先渲染 `AISetupRequiredPanel`,普通成员只记录想法,空间负责人查看空间 AI 可用状态,平台 AI Ops 才进入平台 AI 能力处理,不提供空项目创建入口;低信息输入由后端 `input_needs_more_info` 422 拦截并进入补充输入失败态。`LaunchCanvases.tsx` 和 `MobileLaunchLayout.tsx` 承接高保真图中的意图输入、AI 工作、人类确认、草案修改、生成失败、创建成功过渡与移动端 bottom sheet 交互。 - `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest、单条 iteration recommendation -> ChangeSet draft 与同组 iteration recommendation 批量 -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。 - `frontend/src/features/pmflow-os/store.tsx` 仍保留演示用项目内 AI 结构化闭环状态机:`command_idle / ai_extracting / extraction_ready / review_open / writeback_success`。`frontend/src/features/pmflow-os/pages/StructuredExtractionPage.tsx` 是本轮结构化提取页,也是结构化读取检查层:从指挥台进入时主动作进入待确认或继续补充;从 `ChangeReviewPage.tsx` 进入时通过 `origin=change_review / returnTo=change_review / changeSetId / itemId` 保留审查上下文,主动作返回待确认队列,补充证据才回指挥台;已 committed 的轮次显示为结构化写入快照并转向项目记忆或新变更链路。`ChangeReviewPage.tsx` 是决策与待确认中心,真实模式下顶部识别统计、待确认列表与右侧 AI 副驾评估都只消费后端 `changeSets / changeItems / evidence`,没有真实待审项时显示空态,禁止用 demo 审查项补位;确认写入 Project Memory 前,`pending_review / accepted / edited / deferred` ChangeItem 均可继续 accept/edit/defer,只有 `committed / skipped` 或关闭轮次不可再操作;`MemoryWritebackResultPage.tsx` 是项目记忆写入结果页,`WorkGraphPage.tsx` 是项目工作台关系总览与对象投影视图,`ExecutionBridgePage.tsx` 是执行桥上下文包、交接按钮、运行端领取确认、PR/测试与回写状态页;`ReleaseGrowthPage.tsx` 真实模式读取 `execution-summary.releaseGate`、`latestValidationRun` 与 `releaseSignals`,从 branch、commit、PR、test、failed evidence 展示 `可发布 / 阻塞 / 需要人工确认 / 缺少证据` 判断、阻塞原因、下一步动作、发布验证记录和发布后 feedback/revenue/ROI/compliance/recommendation 信号,缺少发布后信号时显示 empty/warning,不执行发布命令、不展示 mock 发布结果;页面可展示 recommendation quality grouping,把真实 recommendation 单条或同组批量生成下一轮待确认 ChangeSet draft,生成后仍进入 Change Review accept/edit/defer,不自动 commit、不写 Project Memory、不创建正式任务;`ChangeReviewPage.tsx` 会展示 `iteration_recommendation_group` evidence 的来源组、feedback 数、recommendation 数、风险、影响和审查优先级;同时读取 Account notification preference helper,用 `releaseGate` 偏好控制发布门禁强调提醒,用 `failureAlert` 偏好控制失败强调提醒,偏好关闭时只静默提醒表达强度,不隐藏 release evidence、阻塞原因、审计状态、validation run 或 releaseSignals;静态发布/增长原型只保留在显式 demo 模式。A3 已接入后端 truth-state,C1 已接入真实 message/extraction/review 状态机,C2 已接入真实 ChangeSet commit,Phase D 已接入真实 execution-summary/dev-sync,D.2 已接入 `POST /execution-handoffs` 创建 PromptPackage + handoff_created progress event,P1 已接入 `POST /execution-handoffs/{prompt_package_id}/consume` 创建 handoff_consumed progress event 并把 execution status 投影为 coding,Release Growth P1 已接入 execution evidence gate,Release Validation P1 已接入 `POST /release-validation-runs` 创建 `ReleaseValidationRun` 快照,Release Feedback P1 已接入 `POST /release-feedback-ingests` 写入 `FeedbackIngest` 并生成 `IterationRecommendation`,Iteration Loop P1 已接入 `POST /iteration-recommendations/{recommendation_id}/change-set-drafts` 把 recommendation 回流到 Change Review,Iteration Loop P2 已接入 `POST /iteration-recommendation-groups/{group_key}/change-set-drafts` 并在 `releaseSignals.recommendationGroups` 暴露分组/去重/风险/影响合同;真实 AI extraction 已接入独立 `change_review` protected scene、`pmflow_change_review_extraction_v1` 运行合同、`PROMPT_PMFLOW_CHANGE_REVIEW_EXTRACTION` Prompt 资产与后端 `llm_service.generate_structured_object`,成功时生成 open ChangeSet、pending_review ChangeItems 与 thread message evidence,失败时 fail-closed,不把 mock 当真实失败 fallback,也不复用 Project Launch draft prompt/function。`ProjectMemoryPage.tsx` 在真实模式下展示后端 `truth_nodes + semantic_facts` 的 `projectMemory` 投影,accepted/edited ChangeItem 只有 commit 成功后才进入 Project Memory。`frontend/src/features/pmflow-os/components/ProjectStructuredLoop.tsx` 提供 `StatusLabel / EvidenceSnippet / ObjectChangeCard / ProjectConversationPanel / StructuredExtractionPanel / ProjectAICopilotPanel` 等闭环组件,并复用契约化 `InlineNotice` 表达“未确认不得写入”的局部提示。 +- Execution Result Protocol P1 将 Execution Bridge 的执行结果回写收敛为 `execution_result_protocol_v1`:`backend/app/schemas/pmflow_os.py` 接收 `execution_status` 与 `blocker_summary`,`backend/app/services/pmflow_os_projection_service.py` 写入 `ProgressEvent.payload.protocol_version` 与 `execution_result` envelope,同时保留 `branch_name / commit_sha / pr_number / test_summary / workflow_state` flat 字段兼容现有投影;`frontend/src/api/pmflowOs.ts` 和 `ExecutionBridgePage.tsx` 支持手动记录 status/blocker。该链路只记录 Codex/IDE/Local Bridge/GitHub agent 已产生的结果,不执行本机命令、不推送、不发布、不自动创建正式任务;`execution-summary`、`releaseGate` 与 Team Feed 均从 `ProgressEvent` 只读消费。 - Project Knowledge Hub 读模型由 `GET /pmflow-os/projects/{project_id}/project-memory/overview`、`items`、`items/{item_id}`、`assets`、`context-packs`、`writebacks` 承载,后端在 `backend/app/services/pmflow_os_projection_service.py` 从 `truth_nodes / tasks / semantic_facts / pmflow_evidence_links / pmflow_thread_messages / pmflow_change_sets / pmflow_change_items` 聚合首页雷达、全部记忆、项目资产、上下文包和写入版本记录。前端对应 `ProjectMemoryPage.tsx`、`ProjectMemoryCollectionPages.tsx` 与 `projectMemoryUtils.ts`:列表时间使用短格式,抽屉显示完整时间和北京时间;首页卡片与二级列表单条点击打开右侧详情抽屉,展示来源证据、影响对象和 AI 执行动作跳转;全部记忆支持类型、阶段、来源、状态、搜索筛选。该读模型只读,资产可自动索引,进入项目真相层仍必须经过 Change Review 或可信规则确认。 - `frontend/src/features/account-settings/AccountSettingsPanel.tsx` 是 `/me` 设置页实现:桌面端为个人资料摘要 + 分区设置面板,移动端为分组列表 + `MobileBottomSheet` 编辑;当前覆盖个人资料、默认语言、AI 回答深度、证据展示偏好、待确认、执行回写、发布门禁和失败提醒通知偏好,并在通知偏好区展示页面内提醒消费说明、提醒强度和证据展示配置。`frontend/src/api/accountSettings.ts` 是前端合同层;有 token 时优先调用 `backend/app/api/account_settings_api/`,失败时回落浏览器本机暂存;P2 新增 `shouldShowAccountNotification` 与 `getAccountNotificationConsumption`,将四类通知偏好映射为本地 UI 的待确认、Execution Bridge、Release/Growth 和失败提醒显示/静默状态;P3 新增 `getReleaseGateNotificationPreference(settings, event)`,把 Release/Growth 的 `release_gate_* / release_validation_*` 事件映射到 `releaseGate` 偏好,把 `failed_event` 映射到 `failureAlert` 偏好;P5/P6 的 `getPostReleaseNotificationPreference(settings, event)` 把发布后 `feedback_received / revenue_signal / compliance_blocked` 映射到 `releaseGate` 偏好,把 `iteration_recommendation / next_change_review` 映射到 `pendingConfirmation` 偏好和 `change_review` surface,并返回 `spaceReadable / evidencePolicy / reminderPolicy` 供 Main Flow 与 Space 消费;P7 将通知偏好扩展为 `reminderIntensity=silent|normal|prominent` 与 `evidenceDisplay=compact|expanded`,并新增 `getAccountReminderConsumption(settings, event)` 覆盖 `change_review_high_risk / iteration_draft_ready / release_blocked / execution_stalled`,输出 `notificationTransport=in_app_only / evidencePolicy=preserve_business_evidence / auditPolicy=preserve_audit_state`;P8 新增 `getReviewDecisionNotificationPreference(settings, event)` 覆盖 `review_requested / review_approved / review_changes_requested / review_rejected / high_risk_approval_required`,继续使用 `pendingConfirmation` 偏好和 `change_review` surface,并声明 reviewerRouting、approval、evidence、audit state 均必须保留。该合同只控制页面内提醒表达,不代表邮件、桌面推送或系统通知中心已接入;偏好关闭也不得隐藏业务证据、reviewerRouting、approval、recommendation、ChangeSet、执行状态或审计状态。`backend/app/schemas/account_settings.py` 定义账号偏好合同,`backend/app/services/account_settings_service.py` 当前复用既有 `integrations` 表按 `tenant_id + user_id` 保存 `account_settings_preferences` 配置包,`backend/app/api/router.py` 已挂载该 API;未改用户表、OAuth/JWT/auth core 或 PMFlow OS truth store。 - `frontend/src/features/pmflow-os/store.tsx` 当前是 PMFlow OS 共享 truth store:`?projectId=` 时通过 React Query 调用 `frontend/src/api/pmflowOs.ts` 读取真实 `GET /pmflow-os/projects/{project_id}/truth-state` 与 `GET /pmflow-os/projects/{project_id}/execution-summary`,真实模式下项目内 `sendMessage` 会写 thread message 并触发后端真实 AI extraction;显式 `proposed_items` extraction、ChangeItem `accept/edit/defer`、ChangeSet commit、review decision、AI feedback、Execution Handoff create/consume、execution progress writeback、release validation run create、release feedback ingest 与 iteration recommendation -> ChangeSet draft 均调用 `/pmflow-os` 后端并 refetch;AI feedback 写入 `pmflow_ai_feedback_events`,在 `truth-state.aiFeedback` 恢复当前用户选择,并由后端按 `prompt_key` 生成待复核 `JudgeRun / OptimizerCandidate` 质量优化候选,用户改回赞同时挂起此前候选,Prompt Asset quality summary 会聚合 PMFlow 反馈与优化候选;只有显式 `?demo=1` 或 `?dataSource=demo` 时才启用本地 mock 演示状态机。真实读取/动作失败会进入错误/空态,不静默 fallback 成 mock 成功;AI extraction 失败显示“结构化失败/待补充”,不创建模板 ChangeSet。 diff --git a/docs/architecture/CODE_REFERENCE_INDEX.md b/docs/architecture/CODE_REFERENCE_INDEX.md index 653b53c1..603223e9 100644 --- a/docs/architecture/CODE_REFERENCE_INDEX.md +++ b/docs/architecture/CODE_REFERENCE_INDEX.md @@ -101,6 +101,7 @@ - SSTC 任务更新接口:参考 `PATCH /projects/{project_id}/tasks/{task_id}` - SSTC 发布创建接口:参考 `POST /projects/{project_id}/releases` - PMFlow OS 真实读路径、C1 Change Review API、C2 backend commit、Execution Handoff、Release Evidence Gate、Release Validation Run、Release Feedback Signal、Iteration Loop draft、Team reviewer decision loop、Execution Capability Graph 与 Team Feed 只读动态流:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`backend/app/services/pmflow_change_review_service.py`、`backend/app/services/pmflow_ai_feedback_service.py`、`backend/app/services/team_execution_capability_service.py`、`backend/app/schemas/pmflow_os.py`;只读提供 `GET /pmflow-os/projects/{project_id}/truth-state`、`GET /pmflow-os/projects/{project_id}/execution-summary` 与 `GET /pmflow-os/projects/{project_id}/execution-capabilities`,复用 `truth_nodes / tasks / semantic_facts / ai_project_control / dev_sync / users / user_projects / user_workloads / user_skill_bindings`;`truth-state.aiFeedback` 返回当前用户对 AI chat answer / structured extraction / copilot answer 的反馈事件,底表为 `pmflow_ai_feedback_events`,用于按 `scene_key / prompt_key / prompt_release_id / runtime_profile_id` 汇总 prompt 质量信号;负反馈会归因到真实 `prompt_key` 并生成待平台 AI Ops 复核的 `JudgeRun / OptimizerCandidate`,用户改回赞同时挂起此前候选,不直接改 active prompt;`execution-capabilities` 只输出执行模式、个人执行准备度、成员执行能力与容量信号、warning,不自动分配任务、不自动请求审查、不写 ChangeSet;`truth-state.reviewerRouting` 是 `{status, warningCode, summary, items}` 读模型,会从 open/pending ChangeSet、待审 item 风险、跨对象类型、evidence source、项目成员与 `OWNER / REVIEWER` 角色生成 `sourceType / riskLevel / priority / suggestedReviewer / fallbackReviewerRole / targetUrl`,并返回 `reviewDecision / approvalState / requestedAt / decidedAt / decisionReason` 审查决策只读字段;`truth-state.feed` 是 Team Feed 只读动态流,从 review request / review decision / ChangeSet 状态 / `ProgressEvent` / release gate 聚合,不写审批、不写 ChangeSet、不写通知;P8 新增 `POST /pmflow-os/change-sets/{change_set_id}/review-decisions`,支持 `request_review / approve / request_changes / reject`,复用 `PMFlowApproval` 写审计,`approve` 满足 high-risk C2 approval gate 但不自动 commit,`request_changes / reject` 不写 Project Memory;C1 写 API 包括 thread message、`POST /pmflow-os/projects/{project_id}/extractions` 的真实 AI extraction 或显式 `proposed_items` draft、ChangeSet detail、ChangeItem `accept/edit/defer`、`POST /pmflow-os/projects/{project_id}/ai-feedback` 记录 AI 回答赞同/不赞同与原因标签;AI extraction 复用 `llm_service.generate_structured_object` 与 PMFlow prompt/config/routing 层,成功时生成 open ChangeSet + pending_review ChangeItems + thread message evidence,失败时返回 `ai_extraction_failed / extraction_evidence_insufficient` 且不创建模板假结果;C2 backend 已实现 `POST /pmflow-os/change-sets/{change_set_id}/commit`,默认 `mode=atomic`,将 `Requirement / Decision / Risk` 写入 `truth_nodes`,将 `Task` 写入 `truth_nodes(node_type='task') + tasks`,高风险项强制 approval gate;commit 会持久化 `pmflow_change_sets.committed_by` 并在 commit result、truth-state 与 Project Memory writebacks 中返回 `committedBy/committedById`,写入结果页不得再用前端硬编码人员;D.2 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs`,对已确认任务生成/复用 `PromptPackage` 并创建 `ProgressEvent(event_type='handoff_created')` 起点;P1 已实现 `POST /pmflow-os/projects/{project_id}/execution-handoffs/{prompt_package_id}/consume`,记录运行端领取、更新 `PromptPackage.package_payload.latest_handoff.handoff_state=consumed` 并创建 `ProgressEvent(event_type='handoff_consumed', derived_task_status='coding')`;Release Growth P1 已在 `execution-summary.releaseGate` 中从 branch / commit / PR / test_passed / checks_passed / failed / blocked 等 `ProgressEvent` 推导 `ready / blocked / needs_confirmation / empty`;Release Validation P1 新增 `POST /pmflow-os/projects/{project_id}/release-validation-runs`,从当前 `releaseGate` 固化 `ReleaseValidationRun` 快照并在 `releaseGate.latestValidationRun` 返回最新记录,无 evidence 时返回 `release_validation_evidence_empty`;Release Feedback P1 新增 `POST /pmflow-os/projects/{project_id}/release-feedback-ingests`,复用 `FeedbackIngest / IterationRecommendation` 写入真实发布后反馈,并在 `execution-summary.releaseSignals` 投影真实 `feedback / revenueEvents / roiSnapshots / complianceChecks / recommendations`、风险计数与 release gate / validation run / business signal types 关系;Iteration Loop P1/P2 支持 recommendation 单条或同组生成 ChangeSet draft;这些步骤均不执行发布命令、外部支付、真实推送、自动 Project Memory commit 或正式 Task 创建。 +- Execution Result Protocol P1:`POST /pmflow-os/projects/{project_id}/execution-progress-events` 的最小协议在 `backend/app/schemas/pmflow_os.py` 与 `backend/app/services/pmflow_os_projection_service.py`,写入 `ProgressEvent.payload.protocol_version='execution_result_protocol_v1'` 和 `execution_result={eventType, source, status, branchName, commitSha, prNumber, testSummary, blockerSummary, repositoryFullName}`;前端合同位于 `frontend/src/api/pmflowOs.ts`,手动回写入口位于 `frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx`。该协议只记录 Codex/IDE/Local Bridge/GitHub agent 已产生的 branch/commit/PR/test/blocker/status,不执行命令、不创建正式任务、不发布;`ExecutionSummary.releaseGate` 与 Team Feed 继续从 `ProgressEvent` 只读投影。 - Project Knowledge Hub / 项目记忆读模型:参考 `backend/app/api/pmflow_os_api/`、`backend/app/services/pmflow_os_projection_service.py`、`frontend/src/api/pmflowOs.ts`、`frontend/src/features/pmflow-os/pages/ProjectMemoryPage.tsx`、`frontend/src/features/pmflow-os/pages/ProjectMemoryCollectionPages.tsx` 与 `frontend/src/features/pmflow-os/projectMemoryUtils.ts`;只读接口为 `GET /pmflow-os/projects/{project_id}/project-memory/overview`、`items`、`items/{item_id}`、`assets`、`context-packs`、`writebacks`。首页只做知识库雷达,二级页承接全部记忆、项目资产库、上下文包、写入与版本记录,writeback 行展示真实写入人 `committedBy` 与时间;时间展示统一走 `formatProjectMemoryTime`,列表短格式、抽屉完整格式并标注北京时间;`items` 支持类型/阶段/来源/状态/搜索筛选,`assets` 支持资产类型/来源/状态/搜索筛选;单条点击打开右侧抽屉展示来源证据、影响对象和 AI 可执行跳转。资产可自动索引,但进入项目真相层仍必须经 Change Review 或可信规则确认。 - Adaptive Human Capacity:参考 `backend/app/services/adaptive_capacity_service.py`、`backend/app/models/human_capacity_override.py`、`backend/alembic/versions/20260427_add_human_capacity_overrides.py`、`frontend/src/api/pmflowOs.ts`、`frontend/src/api/space.ts`、`frontend/src/features/pmflow-os/pages/TeamLayerPage.tsx` 与 `frontend/src/features/personal-space/PersonalSpacePage.tsx`;接口为 `GET /pmflow-os/projects/{project_id}/capacity-plan`、`POST /pmflow-os/projects/{project_id}/capacity-overrides/simulate`、`POST /pmflow-os/projects/{project_id}/capacity-overrides`、`GET /pmflow-os/projects/{project_id}/capacity-overrides`,并在 `execution-capabilities.adaptiveCapacitySummary` 返回今日容量摘要。每日计划由当前任务池实时计算,不持久化固定基线;`human_capacity_overrides` 只存负责人手工调整、原因、过期时间和审计;写接口不自动派任务、不请求审查、不写 ChangeSet;旧 `/workloads*` 前端路由只兼容跳转到 `/team-layer?view=capacity`,传统工时明细不再作为用户可见一级入口。 - Project Launch 真实创建链:参考 `backend/app/api/pmflow_launch_api/`、`backend/app/services/pmflow_launch_service.py`、`backend/app/services/ai_scene_runtime_policy.py`、`backend/app/services/ai_function_runtime_profile_service.py`、`backend/app/schemas/pmflow_launch.py` 与模型 `backend/app/models/pmflow_launch.py`;任务化生成接口为 `POST /pmflow-launch/draft-jobs`、`GET /pmflow-launch/draft-jobs/{job_id}`、`POST /pmflow-launch/draft-jobs/{job_id}/cancel` 与 `POST /pmflow-launch/drafts/{draft_id}/revise`,任务状态落 `pmflow_launch_draft_jobs(job_id/revision_id/status/result_draft_id/changed_fields)`,补充一句或重新生成会 supersede 同用户运行中的旧 job;兼容同步接口 `POST /pmflow-launch/drafts` 仍只写 `pmflow_launch_drafts`,`POST /pmflow-launch/drafts/{draft_id}/confirm` 在同一事务内创建 `projects / user_projects / pmflow_threads / pmflow_thread_messages / truth_nodes / tasks / semantic_facts seed / operation_logs`;生成入口在调用 LLM 前会拦截低信息输入并返回 `input_needs_more_info` 422,避免纯问候或无产品线索输入生成空草案;draft prompt 通过 `PROMPT_PMFLOW_LAUNCH_DRAFT` 和 `PromptRuntimeService` 优先读取平台 Prompt 运营中心 active release,生成预算、修复预算、temperature、响应模式、前台等待、provider/job 超时、最大重试和大输入策略通过 Function Runtime Profile active 版本读取;`provider_timeout_seconds` 进入 provider HTTP client,`job_timeout_seconds` 包住结构化生成任务,`max_retries` 覆盖 transient retry budget,`large_input_policy=preprocess_then_generate` 在 large 桶压缩材料后再渲染 prompt,draft job response 返回 `runtime_policy` 供前端按 `foreground_wait_seconds` 调整等待状态;默认按可变现 MVP 闭环生成 7-10 条 requirements、10-16 条 tasks、4-7 条 risks、3-6 条 decisions,数量或商业闭环不足时写入 warnings;调用 LLM 前必须先按输入规模写入 Project Launch 场景 timeout 默认策略,再由已发布 Function Runtime Profile 覆盖可运营字段,超时显式落 `structured_timeout/draft_job_timeout`,禁止冒充成功。 diff --git a/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md b/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md index f170afe4..2825c95c 100644 --- a/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md +++ b/docs/architecture/PMFLOW_OS_PRODUCT_CONTRACT.md @@ -61,7 +61,7 @@ pmflow_evidence_links | `TestRun` | `model_eval_runs`、`release_validation_runs`、`progress_events(event_type=test*)` 投影 | 否 | `truth-state`、`execution-summary` | dev-sync progress event 或 release validation | `pending/running -> passed/failed/blocked` | 测试日志和 PR/commit 可作为 evidence | 当前没有通用 test_runs 表,V1 先按投影聚合 | | `Release` | `truth_nodes.node_type='release'` + `release_validation_runs` | 否 | `truth-state`;SSTC release APIs | SSTC release 创建;`run_release_validation` | `check_pending -> ready -> shipping -> live/watch` | release gate、rollback_plan、validation_summary | 发布对象和发布验证结果需要在 projection 中分清 | | `MetricSignal` | `feedback_ingests.metrics`、`revenue_events`、`roi_snapshots`、增长埋点结果投影 | 否 | `truth-state`;`/roi-snapshots` | `feedback-ingest`、`revenue-events` | `draft/proposed -> confirmed` | 指标来源和采样窗口必须展示 | 只做轻量反馈,不引入完整财务系统 | -| `ExecutionContext` | `prompt_packages`、`repo_bindings`、`task_code_links`、`progress_events`、`dev_sessions` | 否 | `GET /pmflow-os/projects/{project_id}/execution-summary` 聚合 `GET /projects/{project_id}/dev-sync/summary` | `POST /pmflow-os/projects/{project_id}/execution-handoffs` 生成/复用 `PromptPackage` 并创建 `ProgressEvent` 起点;repo 绑定仍走 dev-sync/GitHub App | `queued -> pushed -> coding -> pr_open -> testing -> synced/failed` | progress events、deliveries、repo/PR/commit 是执行证据 | 不得重造 dev-sync;PMFlow OS 只做项目内执行投影和 handoff contract,不直接执行本机命令 | +| `ExecutionContext` | `prompt_packages`、`repo_bindings`、`task_code_links`、`progress_events`、`dev_sessions` | 否 | `GET /pmflow-os/projects/{project_id}/execution-summary` 聚合 `GET /projects/{project_id}/dev-sync/summary` | `POST /pmflow-os/projects/{project_id}/execution-handoffs` 生成/复用 `PromptPackage` 并创建 `ProgressEvent` 起点;`POST /pmflow-os/projects/{project_id}/execution-progress-events` 写入 `execution_result_protocol_v1` 执行结果;repo 绑定仍走 dev-sync/GitHub App | `queued -> pushed -> coding -> pr_open -> testing -> synced/failed` | progress events、deliveries、repo/PR/commit/test/blocker 是执行证据 | 不得重造 dev-sync;PMFlow OS 只做项目内执行投影和 handoff/result contract,不直接执行本机命令 | | `TeamFeed / TeamInbox` | `pmflow_change_sets`、`pmflow_approvals`、`progress_events`、release/feedback 事件聚合 | 暂不新增专表,先 projection | `truth-state` | 由各业务动作写入源表 | feed item 不承载主状态 | 必须能追溯到源对象 | Phase F 才补 Role Inbox/Decision Desk 的真实模型 | ## 3. ProjectTruthState 后端投影 @@ -423,6 +423,40 @@ POST /pmflow-os/projects/{project_id}/execution-handoffs - PMFlow backend 只创建 handoff 合同、PromptPackage 与 progress event,不直接执行本机命令。 - 本地 `stdio`、宿主机配置写入和真实执行必须经过 Local Bridge、Codex、GitHub agent 或兼容 MCP host;PMFlow 后端不得写 `~/.codex/config.toml`。 +### 4.9 写回执行结果 + +```text +POST /pmflow-os/projects/{project_id}/execution-progress-events +``` + +请求: + +```json +{ + "task_id": "123", + "repo_binding_id": "456", + "event_type": "pr_opened", + "source": "codex", + "branch_name": "codex/execution-result-protocol-p1", + "commit_sha": "abc123def456", + "pr_number": 77, + "test_summary": "pytest 与 typecheck 通过", + "blocker_summary": null, + "execution_status": "pr_open" +} +``` + +行为: + +- 只记录 Codex / IDE / Local Bridge / GitHub agent 已产生的执行结果,不执行本机命令、不推送、不发布。 +- 写入 `ProgressEvent` 时同时保留兼容 flat payload 与最小协议 envelope:`protocol_version='execution_result_protocol_v1'`、`execution_result={eventType, source, status, branchName, commitSha, prNumber, testSummary, blockerSummary, repositoryFullName}`。 +- 返回最新 `ExecutionSummaryProjection`,其中 `execution` 展示 branch/commit/PR/test/status,`releaseGate` 基于 passed/failed/blocked/checks 事件推导 `ready / blocked / needs_confirmation / empty`,`truth-state.feed` 后续只读展示执行进展。 +- `blocker_summary` 优先作为 failed/blocked 事件的客户可见阻塞原因;`test_summary` 优先作为测试类事件摘要。 + +边界: + +- 该接口是执行结果回写协议,不是任务创建、发布执行或命令代理;缺证据时只能返回 pending/needs_confirmation/blocked,不得伪造测试或发布成功。 + 执行责任与分派边界: - 执行交接只能针对已属于当前 project / tenant 的正式 `Task`,不得针对 `pending_review` ChangeItem 直接创建 handoff。 @@ -479,7 +513,7 @@ PMFlow OS 不重造开发执行系统。执行桥复用: - `PromptPackage`:开发上下文包,创建接口为 `POST /projects/{project_id}/charters/{charter_id}/prompt-packages`。 - `RepoBinding / GitHubAppInstallation / GitHubAppRepository`:仓库与 GitHub App 授权状态。 - `TaskCodeLink`:任务与 branch/PR/commit 绑定。 -- `ProgressEvent`:commit、PR、test、manual progress 的统一回写事件。 +- `ProgressEvent`:branch、commit、PR、test、blocker、manual progress 的统一回写事件;PMFlow OS 写入 `execution_result_protocol_v1` envelope,保留执行状态和外部执行证据。 - `DevSession`:开发活跃会话。 - `GET /projects/{project_id}/dev-sync/summary`:当前执行桥最小真实读路径。 diff --git a/frontend/src/api/pmflowOs.ts b/frontend/src/api/pmflowOs.ts index 6f18fcc1..6d577d32 100644 --- a/frontend/src/api/pmflowOs.ts +++ b/frontend/src/api/pmflowOs.ts @@ -10,11 +10,17 @@ import type { PMFlowDailyCapacityPlan, PMFlowExecutionContext, PMFlowExecutionCapabilitySnapshot, + PMFlowExecutionStatus, PMFlowReviewDecisionAction, PMFlowRiskLevel, ProjectTruthState, } from "@/features/pmflow-os/types"; -export type { PMFlowReviewerRouting, PMFlowReviewerRoutingItem, PMFlowReviewerSuggestion } from "@/features/pmflow-os/types"; +export type { + PMFlowExecutionStatus, + PMFlowReviewerRouting, + PMFlowReviewerRoutingItem, + PMFlowReviewerSuggestion, +} from "@/features/pmflow-os/types"; type ApiEnvelope = { code: number; message: string; data: T }; const API_BASE = "/api"; @@ -348,6 +354,8 @@ export interface PMFlowExecutionProgressEventRequest { commit_sha?: string | null; pr_number?: number | null; test_summary?: string | null; + blocker_summary?: string | null; + execution_status?: PMFlowExecutionStatus | null; workflow_state?: string | null; summary?: string | null; event_source_id?: string | null; diff --git a/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx b/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx index 8dd9a1a9..4df4b618 100644 --- a/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx +++ b/frontend/src/features/pmflow-os/pages/ExecutionBridgePage.tsx @@ -27,6 +27,7 @@ import type { PMFlowExecutionProgressSource, PMFlowExecutionSummary, PMFlowExecutionRuntimeTarget, + PMFlowExecutionStatus, PMFlowProgressEvent, PMFlowRecentDelivery, PMFlowRepoBinding, @@ -471,6 +472,8 @@ function ExecutionWorkspacePanel({ commit_sha?: string | null; pr_number?: number | null; test_summary?: string | null; + blocker_summary?: string | null; + execution_status?: PMFlowExecutionStatus | null; workflow_state?: string | null; summary?: string | null; }) => void; @@ -602,6 +605,8 @@ function ExecutionWritebackPanel({ commit_sha?: string | null; pr_number?: number | null; test_summary?: string | null; + blocker_summary?: string | null; + execution_status?: PMFlowExecutionStatus | null; workflow_state?: string | null; summary?: string | null; }) => void; @@ -614,6 +619,8 @@ function ExecutionWritebackPanel({ const [commitSha, setCommitSha] = useState(task?.code_link?.last_commit_sha || ""); const [prNumber, setPrNumber] = useState(task?.code_link?.pr_number ? String(task.code_link.pr_number) : ""); const [testSummary, setTestSummary] = useState(""); + const [blockerSummary, setBlockerSummary] = useState(""); + const [executionStatus, setExecutionStatus] = useState(""); const [workflowState, setWorkflowState] = useState(""); const canSubmit = Boolean(task); @@ -665,6 +672,23 @@ function ExecutionWritebackPanel({ +