feat: add execution result writeback protocol
This commit is contained in:
parent
37056f8fac
commit
f48a4d0381
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 "未绑定仓库"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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=<id>` 时通过 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=<id>` 时通过 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。
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -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`:当前执行桥最小真实读路径。
|
||||
|
||||
|
|
|
|||
|
|
@ -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<T> = { 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;
|
||||
|
|
|
|||
|
|
@ -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<PMFlowExecutionStatus | "">("");
|
||||
const [workflowState, setWorkflowState] = useState("");
|
||||
const canSubmit = Boolean(task);
|
||||
|
||||
|
|
@ -665,6 +672,23 @@ function ExecutionWritebackPanel({
|
|||
<WritebackInput label="PR 编号" value={prNumber} onChange={setPrNumber} placeholder="128" />
|
||||
<WritebackInput label="流程状态" value={workflowState} onChange={setWorkflowState} placeholder="开发中 / 测试中 / 已回写" />
|
||||
</div>
|
||||
<label className="grid gap-1.5 text-xs font-semibold text-slate-600">
|
||||
执行状态
|
||||
<select
|
||||
className="h-9 rounded-lg border border-[#dfe7f3] bg-white px-3 text-sm font-medium text-slate-800 outline-none transition focus:border-[#2463EB]"
|
||||
value={executionStatus}
|
||||
onChange={(event) => setExecutionStatus(event.target.value as PMFlowExecutionStatus | "")}
|
||||
>
|
||||
<option value="">自动推导</option>
|
||||
<option value="queued">queued</option>
|
||||
<option value="pushed">pushed</option>
|
||||
<option value="coding">coding</option>
|
||||
<option value="pr_open">pr_open</option>
|
||||
<option value="testing">testing</option>
|
||||
<option value="synced">synced</option>
|
||||
<option value="failed">failed</option>
|
||||
</select>
|
||||
</label>
|
||||
<label className="grid gap-1.5 text-xs font-semibold text-slate-600">
|
||||
测试摘要
|
||||
<textarea
|
||||
|
|
@ -674,6 +698,15 @@ function ExecutionWritebackPanel({
|
|||
placeholder="测试通过、失败原因或检查结果"
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-1.5 text-xs font-semibold text-slate-600">
|
||||
阻塞说明
|
||||
<textarea
|
||||
className="min-h-20 resize-y rounded-lg border border-[#dfe7f3] bg-white px-3 py-2 text-sm font-medium leading-6 text-slate-800 outline-none transition placeholder:text-slate-400 focus:border-[#2463EB]"
|
||||
value={blockerSummary}
|
||||
onChange={(event) => setBlockerSummary(event.target.value)}
|
||||
placeholder="如无阻塞可留空;blocked / failed 时写清失败点、影响和下一步"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -685,6 +718,8 @@ function ExecutionWritebackPanel({
|
|||
onClick={() => {
|
||||
if (!task) return;
|
||||
const parsedPr = prNumber.trim() ? Number(prNumber) : null;
|
||||
const cleanedTestSummary = testSummary.trim();
|
||||
const cleanedBlockerSummary = blockerSummary.trim();
|
||||
onRecordProgress({
|
||||
task_id: task.id,
|
||||
event_type: eventType,
|
||||
|
|
@ -693,9 +728,11 @@ function ExecutionWritebackPanel({
|
|||
branch_name: branchName.trim() || null,
|
||||
commit_sha: commitSha.trim() || null,
|
||||
pr_number: Number.isFinite(parsedPr) ? parsedPr : null,
|
||||
test_summary: testSummary.trim() || null,
|
||||
test_summary: cleanedTestSummary || null,
|
||||
blocker_summary: cleanedBlockerSummary || null,
|
||||
execution_status: executionStatus || null,
|
||||
workflow_state: workflowState.trim() || null,
|
||||
summary: testSummary.trim() || null,
|
||||
summary: cleanedBlockerSummary || cleanedTestSummary || null,
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1271,12 +1271,20 @@ describe("PMFlowOsProvider real change review actions", () => {
|
|||
source: "github_agent",
|
||||
branch_name: "codex/main-flow-d3",
|
||||
pr_number: 88,
|
||||
execution_status: "pr_open",
|
||||
blocker_summary: null,
|
||||
})
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(createExecutionProgressEventMock).toHaveBeenCalledWith(
|
||||
"123",
|
||||
expect.objectContaining({ task_id: "task-99", event_type: "pr_opened", source: "github_agent", pr_number: 88 })
|
||||
expect.objectContaining({
|
||||
task_id: "task-99",
|
||||
event_type: "pr_opened",
|
||||
source: "github_agent",
|
||||
pr_number: 88,
|
||||
execution_status: "pr_open",
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue