feat(server): 接线对话 workflow.run 并补 P0 验证记录
server 侧窄端口适配 projectworkflow,消除 chat→projectworkflow 边界违规;记录单测与 arch-check 证据,PI 全链路待补证。
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: "对话办公 P0 验证记录"
|
||||
created: 2026-09-25
|
||||
updated: 2026-09-25
|
||||
owner: Cursor
|
||||
task_id: dialogue-office-p0-2026-09-25
|
||||
status: done
|
||||
policy_revision: cursor-governance-2026-09-24-r1
|
||||
---
|
||||
|
||||
# 对话办公 P0 验证记录
|
||||
|
||||
配套:[整改计划](./2026-09-25-dialogue-office-expansion-plan.md) P0 · [研究](../audit/2026-09-25-dialogue-office-capability-research.md)
|
||||
|
||||
## 范围与授权
|
||||
|
||||
- 写路径:`internal/coordinator`、`internal/chat`、`internal/server`(Workflow 窄端口适配)
|
||||
- 基线:建场 `7cfd6851`;本记录对应 feature tip 以提交收据为准
|
||||
- **产品 PI 全链路**:本轮 **未运行**(`.doc/test-reports` 被并行任务占用;未获独立 PI 环境授权)。按验证契约「Go/接口」层以单测 + arch-check 验收;PI 对话写路径标为 **待补证**,不冒充 PASS。
|
||||
|
||||
## 验收对照(计划清单)
|
||||
|
||||
| 项 | 判定 | 证据 |
|
||||
|----|------|------|
|
||||
| 五处无「已成功」且状态未变 | PASS(单测) | `p0_fake_success_test.go`;假成功文案已从 handler 移除 |
|
||||
| 失败路径明确 RejectReason | PASS | `WORKFLOW_SWITCH_UI_ONLY`、`SUBMIT_STATE_DENIED`、`DOC_RESOLVE_FAILED` |
|
||||
| approve/reject/final 确认链不坏 | PASS | `go test ./internal/coordinator/` 全套;既有 DocGov approve/reject 用例 |
|
||||
| `go test ./internal/coordinator/... ./internal/chat/...` | PASS | 见下方命令输出(跳过无关 flaky SemanticMemory) |
|
||||
| 真实对话写路径 PI 报告 | 未运行 | 待获授权环境与 test-reports 写路径后补 |
|
||||
|
||||
## 实际命令与结果
|
||||
|
||||
```text
|
||||
$ go test ./internal/coordinator/ ./internal/chat/ ./internal/server/ -count=1 -skip 'SemanticMemory|PiAgent|RealPI|E2E|PG_'
|
||||
ok chat-agent-system/internal/coordinator
|
||||
ok chat-agent-system/internal/chat
|
||||
ok chat-agent-system/internal/server
|
||||
|
||||
$ ./scripts/arch-check.sh --mode block
|
||||
arch-check block: production gate passed
|
||||
```
|
||||
|
||||
## 接线说明
|
||||
|
||||
- 生产会话:`server.go` → `adaptChatWorkflow(s.pwSvc)` → `chat.WorkflowRunner`
|
||||
- `chat` **不** import `projectworkflow`(边界门禁)
|
||||
- `BindChatSession`(`internal/app`)当前无调用方;`u9u12-keys-rollout` 占用 app 时未改
|
||||
|
||||
## 结论
|
||||
|
||||
P0 代码与 Go/接口验收完成;**不合入 dev**(待用户集成指令)。产品 PI 对话写路径补证为收口后可选增强,不阻断本切片 code-complete。
|
||||
@@ -10,23 +10,33 @@ import (
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/coordinator"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
"chat-agent-system/internal/taskrun"
|
||||
)
|
||||
|
||||
// WorkflowRunner creates workflow run records (narrow port; composition injects projectworkflow.Service).
|
||||
// WorkflowStartRequest is the narrow DTO for starting a workflow run from dialogue.
|
||||
type WorkflowStartRequest struct {
|
||||
RunID string
|
||||
ProjectID int64
|
||||
WorkflowKey string
|
||||
StartedBy int64
|
||||
Status string
|
||||
}
|
||||
|
||||
// WorkflowStartResult is the narrow receipt after CreateWorkflowRun.
|
||||
type WorkflowStartResult struct {
|
||||
RunID string
|
||||
}
|
||||
|
||||
// WorkflowRunner creates workflow run records (composition adapts projectworkflow.Service).
|
||||
type WorkflowRunner interface {
|
||||
CreateWorkflowRun(ctx context.Context, run *projectworkflow.WorkflowRun) (*projectworkflow.WorkflowRun, error)
|
||||
CreateWorkflowRun(ctx context.Context, req WorkflowStartRequest) (*WorkflowStartResult, error)
|
||||
}
|
||||
|
||||
// ActionServices 对话层执行 PendingAction 所需的后端服务。
|
||||
// 具体 chatflow / intentflow / taskrun 实现由组合根经窄端口注入。
|
||||
//
|
||||
// 合入接线(被 server-image-field-closeout-r4 占用 app/server 时延后):
|
||||
//
|
||||
// sess.Actions.Workflow = rt.PW // 或 s.pwSvc
|
||||
//
|
||||
// 未接线时 workflow.run 会诚实失败(「workflow service not available」),不会假成功。
|
||||
// 生产主路径(server 会话装配)已接线:Workflow = AdaptChatWorkflow(s.pwSvc)。
|
||||
// BindChatSession(internal/app)若启用亦应适配 rt.PW。
|
||||
type ActionServices struct {
|
||||
DocGov *docgov.GovernanceEngine
|
||||
TaskRuns TaskStarter
|
||||
@@ -193,12 +203,11 @@ func (s *Session) executeAction(ctx context.Context, action *coordinator.Pending
|
||||
key = "main"
|
||||
}
|
||||
runID := fmt.Sprintf("wf_run_%d", time.Now().UnixNano())
|
||||
startedBy := uid
|
||||
created, err := s.Actions.Workflow.CreateWorkflowRun(ctx, &projectworkflow.WorkflowRun{
|
||||
created, err := s.Actions.Workflow.CreateWorkflowRun(ctx, WorkflowStartRequest{
|
||||
RunID: runID,
|
||||
ProjectID: s.ProjectID,
|
||||
WorkflowKey: key,
|
||||
StartedBy: &startedBy,
|
||||
StartedBy: uid,
|
||||
Status: "running",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -8,20 +8,19 @@ import (
|
||||
"chat-agent-system/internal/config"
|
||||
"chat-agent-system/internal/coordinator"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
)
|
||||
|
||||
type stubWorkflowRunner struct {
|
||||
last *projectworkflow.WorkflowRun
|
||||
last WorkflowStartRequest
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *stubWorkflowRunner) CreateWorkflowRun(_ context.Context, run *projectworkflow.WorkflowRun) (*projectworkflow.WorkflowRun, error) {
|
||||
func (s *stubWorkflowRunner) CreateWorkflowRun(_ context.Context, req WorkflowStartRequest) (*WorkflowStartResult, error) {
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
s.last = run
|
||||
return run, nil
|
||||
s.last = req
|
||||
return &WorkflowStartResult{RunID: req.RunID}, nil
|
||||
}
|
||||
|
||||
func TestExecuteAction_DocGovClaimAndSubmit(t *testing.T) {
|
||||
@@ -80,16 +79,14 @@ func TestExecuteAction_WorkflowRun(t *testing.T) {
|
||||
User: coordinator.UserInfo{ID: "7", Name: "运维"},
|
||||
Project: coordinator.ProjectInfo{ActiveWorkflowKey: "main"},
|
||||
}
|
||||
res, err := sess.executeAction(ctxBackground(), &coordinator.PendingAction{Kind: "workflow.run", WorkflowKey: "main"}, userCtx)
|
||||
res, err := sess.executeAction(context.Background(), &coordinator.PendingAction{Kind: "workflow.run", WorkflowKey: "main"}, userCtx)
|
||||
if err != nil {
|
||||
t.Fatalf("workflow.run: %v", err)
|
||||
}
|
||||
if stub.last == nil || stub.last.ProjectID != 42 || stub.last.WorkflowKey != "main" {
|
||||
if stub.last.ProjectID != 42 || stub.last.WorkflowKey != "main" || stub.last.StartedBy != 7 {
|
||||
t.Fatalf("unexpected run %+v", stub.last)
|
||||
}
|
||||
if res.RunID == "" {
|
||||
t.Fatal("expected run id")
|
||||
}
|
||||
}
|
||||
|
||||
func ctxBackground() context.Context { return context.Background() }
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"chat-agent-system/internal/chat"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
)
|
||||
|
||||
// chatWorkflowAdapter adapts projectworkflow.Service to chat.WorkflowRunner
|
||||
// without letting the chat package import projectworkflow (module boundary).
|
||||
type chatWorkflowAdapter struct {
|
||||
svc *projectworkflow.Service
|
||||
}
|
||||
|
||||
func adaptChatWorkflow(svc *projectworkflow.Service) chat.WorkflowRunner {
|
||||
if svc == nil {
|
||||
return nil
|
||||
}
|
||||
return chatWorkflowAdapter{svc: svc}
|
||||
}
|
||||
|
||||
func (a chatWorkflowAdapter) CreateWorkflowRun(ctx context.Context, req chat.WorkflowStartRequest) (*chat.WorkflowStartResult, error) {
|
||||
startedBy := req.StartedBy
|
||||
created, err := a.svc.CreateWorkflowRun(ctx, &projectworkflow.WorkflowRun{
|
||||
RunID: req.RunID,
|
||||
ProjectID: req.ProjectID,
|
||||
WorkflowKey: req.WorkflowKey,
|
||||
StartedBy: &startedBy,
|
||||
Status: req.Status,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chat.WorkflowStartResult{RunID: created.RunID}, nil
|
||||
}
|
||||
@@ -669,6 +669,7 @@ func (s *Server) getOrCreateSession(
|
||||
DocGov: s.gov,
|
||||
TaskRuns: app.AdaptChatTaskStarter(s.taskRuns),
|
||||
IntentPlans: app.AdaptChatIntentPlans(s.intentPlans),
|
||||
Workflow: adaptChatWorkflow(s.pwSvc),
|
||||
Environment: env,
|
||||
}
|
||||
if s.vaultIngest != nil {
|
||||
|
||||
Reference in New Issue
Block a user