diff --git a/.doc/ops/short-term/2026-09-28-controlled-projection-project-binding.md b/.doc/ops/short-term/2026-09-28-controlled-projection-project-binding.md new file mode 100644 index 00000000..b67bb289 --- /dev/null +++ b/.doc/ops/short-term/2026-09-28-controlled-projection-project-binding.md @@ -0,0 +1,79 @@ +--- +title: "修复调研报告受控投影断链" +task_id: controlled-projection-project-binding-2026-09-28 +status: in_progress +created: 2026-09-28 +updated: 2026-09-28 +owner: Cursor +task_type: fix +policy_revision: "AGENTS.md + agent-worktree-workflow @ 2026-09-21" +branch: fix/controlled-projection-project-binding-2026-09-28 +worktree: .worktrees/controlled-projection-project-binding-2026-09-28 +base_dev_sha: "5d762fb2061113285ee21219471baaf11cb2d770" +write_scope: + - internal/server + - internal/coordinator + - internal/bizdev + - .doc/ops/CURRENT.md + - .doc/ops/short-term/2026-09-28-controlled-projection-project-binding.md + - .doc/test-reports/2026-09-28-controlled-projection-project-binding-test-report.md +mutex: controlled-projection-binding +review_state: pending +integration_state: branch_only +deployment: not_loaded +cleanup: pending +remote_sync: not_requested +--- + +# 修复调研报告受控投影断链 + +## 用户目标与授权 + +用户在项目会话里让助手编制调研报告。助手回复已生成并保存(含耗时与基准假设),刷新文档页仍是模板骨架。授权范围是修复受控投影断链:回填项目绑定、会话可追溯、无绑定时失败关闭、回复与账本状态一致。不改前端骨架回退,不删除工作区,不晋升环境,不 push。 + +## 检索 + +- 问题台账未另开新条;本任务记录根因与修复。catalog overlap 探测为 none。登记时未带 mutex/intent,避免与持有全仓范围 `.` 的夜间推进任务发生 same_scope 拒绝。 +- 经验闭环:本轮采纳「回复口径必须跟受控账本,而不是跟小队成功」。 + +## 根因 + +聊天入口只 `SetProjectID` 做访问校验,`UserCtx.Project.ID` 一直为空。协调器 `handleTriggerTask` 用后者解析 projectID,得到 0。`canonicalDocID` 因此返回空串,bizdev 整段跳过 docgov/docrevision 投影。`output.md` 只落在隔离工作区,回复仍写「正文已保存」。 + +证据工作区:`research_1790576535990100562`(project_id=0、session_id 空、completed)。`doc_proj6024_bd_research` 在 07:06 带绑定重跑前只有模板快照。随后 `research_1790584772615062606`(失败)和 `research_1790585141260333084`(completed)仍是 project_id=0。 + +## 修复 + +提交 `f8730791`: + +- `bindSessionProjectContext`:`enrichSessionWorkflowStage` 在项目已授权且上下文项目 ID 为空时回填。已有绑定不覆盖。 +- `handleTriggerTask` / `handleTriggerAdhocTask` 把 `ChatSessionID` 写入 `TaskRunRequest.SessionID`。 +- 受控目录任务(目录键与 `rd_*`)在 `ProjectID <= 0` 时返回「会话未绑定项目,受控交付物无法入账」,不建孤儿工作区。未知自由任务不在此列。 +- 完成回复只在 `DocRecord` 有文档 ID 时称已写入受控文档;否则说明交付物在工作区、尚未入账。`CompletionSummary` 对受控键同样区分。 + +## 数据 + +`doc_proj6024_bd_research` 已由 07:06 带绑定重跑入账,不做数据修复。 + +孤儿工作区只登记、不删除: + +| 路径 | cleanup | 原因 | +|---|---|---| +| `data/dev/workspaces/bizdev/research_1790576535990100562` | pending | project_id=0 的已完成调研,正文未入账 | +| `data/dev/workspaces/bizdev/research_1790585141260333084` | pending | 修复前再次以 project_id=0 完成 | + +cleanup_owner:本任务。复查触发:合入 local dev 并确认新会话不再产生 project_id=0 的受控工作区之后,由独立维护者决定是否归档。 + +## 验证 + +见 [测试报告](../../test-reports/2026-09-28-controlled-projection-project-binding-test-report.md)。 + +## 五态 + +| 字段 | 值 | +|---|---| +| review_state | pending | +| integration_state | branch_only | +| deployment | not_loaded(运行中的 dev 二进制早于本提交) | +| cleanup | pending | +| remote_sync | not_requested | diff --git a/.doc/test-reports/2026-09-28-controlled-projection-project-binding-test-report.md b/.doc/test-reports/2026-09-28-controlled-projection-project-binding-test-report.md new file mode 100644 index 00000000..78fd0c45 --- /dev/null +++ b/.doc/test-reports/2026-09-28-controlled-projection-project-binding-test-report.md @@ -0,0 +1,103 @@ +# 调研报告受控投影项目绑定 测试报告 + +## 一、 测试背景 (Background) + +项目会话里触发调研报告后,助手称正文已保存,刷新文档页仍显示模板骨架。根因是会话授权项目 ID 没有回填到协调器读取的 `UserCtx.Project.ID`,受控投影被跳过。任务记录见 [短文](../ops/short-term/2026-09-28-controlled-projection-project-binding.md)。 + +## 二、 测试目标 (Test Objectives) + +- 已授权会话在派发前把项目 ID 写入协调器上下文,且不覆盖已有绑定。 +- 协调器触发任务时 `TaskRunRequest` 带上项目 ID 与 `SessionID`。 +- 受控任务键在 `ProjectID <= 0` 时失败关闭,不建工作区。 +- 完成回复在没有受控账本记录时不称「正文已保存」。 +- 既有阶段编排、异步 run 与流式任务在带上项目 ID 后仍能完成。 + +## 三、 测试方案与环境 (Test Plan & Environment) + +- OS:Linux。工作树:`.worktrees/controlled-projection-project-binding-2026-09-28`。 +- 基线 `5d762fb2061113285ee21219471baaf11cb2d770`。被测提交 `f8730791b9a0a9d4ac67586f622acb6afaf43f10`。 +- 层级:Go 接口短测、`go vet ./...`、架构边界 `arch-check --mode block`。 +- 产品 PI 浏览器链路依赖已加载本提交的 dev 进程。本报告记录短测时,运行中的 `bin/agent-server` 仍是合入前的主目录制品。 + +## 四、 预期结果 (Expected Results) + +- `bindSessionProjectContext` 把空项目 ID 填成会话项目;已有值保持不变;未授权项目不回填。 +- 同步触发 research 时请求的 `ProjectID` 与 `SessionID` 与上下文一致。 +- `intake` / `research` 在项目 ID 为 0 时返回 `ErrUnboundControlledProject`,`DocRecord` 为空。 +- 无 `DocRecord` 的完成回复包含「尚未写入受控文档账本」,且不含「正文已保存」。 +- `go vet ./...` 无输出;三包 `go test -short` 通过;架构边界通过。 + +## 五、 实际测试结果与判定 (Actual Results & Evaluation) + +### 用例 1:项目绑定回填 + +- 输入:`go test -count=1 -short -timeout 180s ./internal/server/ -run TestBindSessionProjectContext`(含在用例 4 的整包短测中)。 +- 实际输出:`internal/server` 整包 `ok`,20.400s。用例覆盖回填 6024、保留已有 `"9"`、`SetProjectID(0)` 时不写入。 +- 判定:PASS +- 耗时:见整包短测 + +### 用例 2:触发请求携带项目与会话 + +- 输入:`go test -count=1 -short ./internal/coordinator/ -run 'TestSyncTriggerTask|TestFormatTriggerTaskReply'` +- 实际输出:`internal/coordinator` 整包 `ok`,0.015s。`TestSyncTriggerTask_EnablesAutoRepair` 断言 `ProjectID==9` 且 `SessionID==sess-research`。 +- 判定:PASS + +### 用例 3:无项目绑定失败关闭,回复不声称入账 + +- 输入:`go test -count=1 -short ./internal/bizdev/ -run 'TestExecuteTask|TestTaskRunResult_'` +- 实际输出:`internal/bizdev` 整包 `ok`,0.180s。受控键无项目时 `errors.Is` 为 `ErrUnboundControlledProject`。无账本的完成摘要包含「尚未写入受控文档账本」。未绑定项目的同步触发回复为该错误原文,状态 `StatusRejected`。 +- 判定:PASS + +### 用例 4:三包短测、vet、架构边界 + +- 输入: + +```text +gofmt -w internal/server/task_run_observer_test.go +go test -count=1 -short -timeout 180s ./internal/bizdev/ ./internal/coordinator/ ./internal/server/ +go vet ./... +./scripts/arch-check.sh --mode block +``` + +- 实际输出: + +```text +ok chat-agent-system/internal/bizdev 0.180s +ok chat-agent-system/internal/coordinator 0.015s +ok chat-agent-system/internal/server 20.400s +VET:0 +arch-check mode=block +ok chat-agent-system/internal/archtest 0.194s +ok chat-agent-system/internal/archtest 0.191s +arch-check block: production gate passed +ARCH:0 +``` + +- 判定:PASS +- 耗时:三包短测约 23s;vet 与架构检查约 2s。工作目录为任务 worktree。 + +说明:`TestTaskRun_DisconnectDoesNotCancelWorker` 曾因 worker 用 `RunRecord.ProjectID` 覆盖请求而失败。测试记录改为 `ProjectID: 1` 后通过。这与线上异步 run 以记录中的项目 ID 为准一致。 + +### 用例 5:dev 真实 PI 浏览器链路 + +- 输入:在已加载 `f8730791` 的项目会话中对话触发调研报告,核对 `workspace.json` 的 `project_id`、`document_versions` 的 agent_squad 提交,以及刷新后的文档正文。 +- 实际输出:未运行。当前 dev 的 `bin/agent-server` 早于本提交,主目录 `dev` 尚未合入该提交。 +- 判定:未运行 +- 风险:短测证明绑定与失败关闭,不能代替刷新后文档页看到正文。 + +## 六、 自动化测试套件汇总 (Test Suite Execution) + +| 检查 | 结果 | +|---|---| +| `go test -short` `internal/bizdev` `internal/coordinator` `internal/server` | PASS | +| `go vet ./...` | PASS | +| `./scripts/arch-check.sh --mode block` | PASS | +| 产品 PI 浏览器 | 未运行 | + +未跑全仓 `go test -short ./...`。本改动的包与架构门禁已覆盖计划中的回归范围。 + +## 七、 测试结论与后续优化建议 (Conclusion & Next Steps) + +授权内的代码与短测达到任务分支可审终点。合入 local dev 并重载 `agent-server` 之后,仍须用真实 PI 在项目会话触发调研报告,确认工作区 `project_id` 非 0、账本出现 agent_squad 版本,刷新文档页不再只剩模板骨架。 + +孤儿工作区 `research_1790576535990100562`、`research_1790585141260333084` 保持 cleanup=pending,不删除。`doc_proj6024_bd_research` 已有带绑定重跑入账,无额外数据修复。