Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed29cb5200 | ||
|
|
e9d57ea772 | ||
|
|
241bfccc1e | ||
|
|
f2a7fa94d2 | ||
|
|
ea3eeadb77 |
@@ -42,7 +42,6 @@
|
|||||||
| 架构设计 | [system-design/README.md](system-design/README.md) |
|
| 架构设计 | [system-design/README.md](system-design/README.md) |
|
||||||
| 测试报告 | [test-reports/](test-reports/) |
|
| 测试报告 | [test-reports/](test-reports/) |
|
||||||
| 历史功能设计 | [archive/design/superpowers/](archive/design/superpowers/) |
|
| 历史功能设计 | [archive/design/superpowers/](archive/design/superpowers/) |
|
||||||
| Herdr 角色、审批与备援 | [ops/2026-09-13-herdr-agent-roster.md](ops/2026-09-13-herdr-agent-roster.md) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# ADR:模块化单体解耦——契约通信、单一写者、编排下沉
|
||||||
|
|
||||||
|
## 状态
|
||||||
|
|
||||||
|
已接受(2026-09-14;Wave 0 契约骨架与 arch-check warn 基线已落地,未占集成槽)
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
多轮审计与实测表明:一处修改波及多处的根因不是模块数量不足,而是——
|
||||||
|
`internal/server`(17k 行)同时是组合根、跨模块闭包事件总线、编排载体与文件系统/SSE 细节持有者;`internal/bizdev` 是第二组合根(依赖 16 个内部包);`UserProjectContext` 被 chat/coordinator 双向直改;`document_records` 有 5 个写者;工作区路径与 squad 文件 schema 是无版本化的隐性公共契约;`intent→bizdev` 反向依赖。
|
||||||
|
证据与完整诊断见 [../system-design/2026-09-14-module-decoupling-architecture.md](../system-design/2026-09-14-module-decoupling-architecture.md) §1。
|
||||||
|
|
||||||
|
## 决策
|
||||||
|
|
||||||
|
1. **架构形态**:维持模块化单体(不做微服务拆分),新增 `internal/contracts` 作为跨模块接口/DTO/事件/共享枚举的唯一 SSOT;模块间仅允许「调 contracts 接口」与「发布/订阅 contracts 事件」两种通信。
|
||||||
|
2. **单一写者**:每张表、每类文件唯一属主(docgov 独占 `document_records`;chatlog 独占 chat_* 表;workspace 独占工作区路径);跨模块写走属主端口方法。
|
||||||
|
3. **编排下沉**:跨模块流程收敛到应用编排层(chatflow/stageflow/docflow,拆自 bizdev/coordinator/chat);HTTP handler 只做参数→服务→DTO 三行式。
|
||||||
|
4. **组合根唯一**:wiring 迁 `internal/app`;`server.NewServer` 只剩 HTTP 装配;SSE 帧协议下沉 `internal/stream/transport`。
|
||||||
|
5. **squadrun 契约**:系统备好工作区与 `InputBundle`,小队按 `OutputContract` 交货并写 `RunManifest`(SchemaVer=1);外部禁止直读/覆写工作区内部文件。
|
||||||
|
6. **机器门禁**:`scripts/arch-check.sh` + `module-boundaries.yaml`(import allowlist / 表属主 / 路径字面量属主),warn 两周后 block 进 CI 与集成槽位冒烟。
|
||||||
|
7. **兼容承诺**:HTTP URL 与响应结构在 Wave 1–5 不变;**零表结构迁移**;旧路径设一版兼容期后删除。
|
||||||
|
8. **实施顺序**:Wave 0 立规矩 → 1 止血 → 2 读路径 API 化 → 3 小队契约化 → 4 单写者 → 5 编排事件化(可拆 5a/5b)→ 6 传输收口;每 Wave 独立分支、独立回滚,遵守 AGENTS.md §6 mutex/槽位纪律,避开在途认领。
|
||||||
|
|
||||||
|
## 后果
|
||||||
|
|
||||||
|
- 新增包:contracts / app / chatlog / contextasm / docquery / squadrun(squad 换壳);
|
||||||
|
- `internal/bizdev` 与 `internal/ctxmgr` 将被拆解退役(知识迁移到 use case 与两个子包);
|
||||||
|
- 所有后续新能力(含软件项目自举)必须基于新边界实现,禁止再挂 bizdev 门面;
|
||||||
|
- 任何契约(contracts 中已发布的 DTO/事件/文件 schema)变更须走 ADR。
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
title: "Herdr Agent 角色、备援与自动审批治理"
|
title: "Herdr Agent 角色、备援与自动审批治理"
|
||||||
status: done
|
status: done
|
||||||
created: 2026-09-13
|
created: 2026-09-13
|
||||||
updated: 2026-09-13
|
updated: 2026-09-14
|
||||||
owner: Codex
|
owner: Codex
|
||||||
branch: feature/herdr-agent-governance
|
branch: feature/herdr-agent-governance
|
||||||
worktree: .worktrees/feature-herdr-agent-governance
|
worktree: .worktrees/feature-herdr-agent-governance
|
||||||
@@ -11,6 +11,8 @@ mutex: ops-governance
|
|||||||
|
|
||||||
# Herdr Agent 角色、备援与自动审批治理
|
# Herdr Agent 角色、备援与自动审批治理
|
||||||
|
|
||||||
|
> **superseded 2026-09-14**:活文档与 `scripts/herdr-agent-*.sh` 已移除;本文件仅作历史证据。见 [2026-09-14-remove-herdr-agent-settings.md](./2026-09-14-remove-herdr-agent-settings.md)。
|
||||||
|
|
||||||
## 目标
|
## 目标
|
||||||
|
|
||||||
建立可审计的 Herdr Agent 能力分工、额度保护、故障接管和免人工工具审批入口,并明确 Herdr PI 与产品容器 PI Agent Runner 不是同一执行体。
|
建立可审计的 Herdr Agent 能力分工、额度保护、故障接管和免人工工具审批入口,并明确 Herdr PI 与产品容器 PI Agent Runner 不是同一执行体。
|
||||||
@@ -47,9 +49,10 @@ mutex: ops-governance
|
|||||||
|
|
||||||
## 相关链接
|
## 相关链接
|
||||||
|
|
||||||
- Agent roster: [../../ops/2026-09-13-herdr-agent-roster.md](../../ops/2026-09-13-herdr-agent-roster.md)
|
- Agent roster: [2026-09-13-herdr-agent-roster.md](./2026-09-13-herdr-agent-roster.md)
|
||||||
- Handoff: [../../ops/handoffs/2026-09-13-herdr-agent-governance.md](../../ops/handoffs/2026-09-13-herdr-agent-governance.md)
|
- Handoff: [handoffs/2026-09-13-herdr-agent-governance.md](./handoffs/2026-09-13-herdr-agent-governance.md)
|
||||||
- Test report: 本任务文“证据 / 备注”
|
- Test report: 本任务文“证据 / 备注”
|
||||||
|
- 移除设定任务: [./2026-09-14-remove-herdr-agent-settings.md](./2026-09-14-remove-herdr-agent-settings.md)
|
||||||
|
|
||||||
## 证据 / 备注
|
## 证据 / 备注
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -1,10 +1,12 @@
|
|||||||
# Herdr Agent 角色、审批与备援 SSOT
|
# Herdr Agent 角色、审批与备援 SSOT
|
||||||
|
|
||||||
**更新**: 2026-09-13
|
> **archived / superseded 2026-09-14**:不再作为活协作规范;脚本已删除。历史证据保留本文件。移除任务见 [./2026-09-14-remove-herdr-agent-settings.md](./2026-09-14-remove-herdr-agent-settings.md)。
|
||||||
**owner**: `orchestrator_codex`
|
|
||||||
**status**: active
|
|
||||||
|
|
||||||
本文只管理工程协作终端,不管理产品运行时智能体。通用分支、worktree、mutex、集成槽、密钥和破坏性操作规则仍以仓库根 [AGENTS.md](../../AGENTS.md) 为准。
|
**更新**: 2026-09-13(归档标注 2026-09-14)
|
||||||
|
**owner**: `orchestrator_codex`
|
||||||
|
**status**: archived
|
||||||
|
|
||||||
|
本文只管理工程协作终端,不管理产品运行时智能体。通用分支、worktree、mutex、集成槽、密钥和破坏性操作规则仍以仓库根 [AGENTS.md](../../../AGENTS.md) 为准。
|
||||||
|
|
||||||
## 1. 身份边界
|
## 1. 身份边界
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
title: "移除 Herdr 工作与角色设定"
|
||||||
|
status: done
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
owner: Cursor-Auto
|
||||||
|
branch: feature/remove-herdr-agent-settings
|
||||||
|
worktree: "-"
|
||||||
|
mutex: agent-collab-docs
|
||||||
|
---
|
||||||
|
|
||||||
|
# 移除 Herdr 工作与角色设定
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
从仓库活文档与脚本中移除 Herdr Agent 角色、审批、备援与派发设定,协作规范回到通用 AGENTS / worktree / mutex 流程。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- 删除 `scripts/herdr-agent-start.sh`、`scripts/herdr-agent-dispatch.sh`
|
||||||
|
- 归档 roster / 相关 handoff
|
||||||
|
- 从 `AGENTS.md`、`PROJECT.md`、`.doc/README.md` 去掉 Herdr 专章与索引
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不改写历史 audit / remediation / 已归档任务中的执行者名(如 `implementer_cursor`)作为证据
|
||||||
|
- 不卸载本机 Herdr 客户端
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
1. [x] 从 `dev` 开 `feature/remove-herdr-agent-settings` 并认领
|
||||||
|
2. [x] 移除活文档与脚本中的 Herdr 设定
|
||||||
|
3. [x] 归档 roster / handoff 并更新 CURRENT
|
||||||
|
4. [x] 提交
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- [x] 活文档与 `scripts/` 无 Herdr 角色/派发入口
|
||||||
|
- [x] 历史证据在 `archive/` 可追溯
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Roster(已归档): [./2026-09-13-herdr-agent-roster.md](./2026-09-13-herdr-agent-roster.md)
|
||||||
|
- 既有治理归档: [./2026-09-13-herdr-agent-governance.md](./2026-09-13-herdr-agent-governance.md)
|
||||||
|
- Handoff(已归档): [./handoffs/2026-09-13-herdr-agent-governance.md](./handoffs/2026-09-13-herdr-agent-governance.md)
|
||||||
|
|
||||||
|
## 证据 / 备注
|
||||||
|
|
||||||
|
- 删除:`scripts/herdr-agent-start.sh`、`scripts/herdr-agent-dispatch.sh`
|
||||||
|
- 文档:去掉 `AGENTS.md` §6.6、`PROJECT.md` §10 Herdr 章与索引行、`.doc/README.md` roster 入口
|
||||||
|
- 归档:roster + handoff → `.doc/archive/ops/`
|
||||||
+6
-4
@@ -8,6 +8,8 @@ status: closed
|
|||||||
|
|
||||||
# 交接:Herdr Agent 治理分支待集成
|
# 交接:Herdr Agent 治理分支待集成
|
||||||
|
|
||||||
|
> **superseded 2026-09-14**:Herdr 角色/脚本已从活文档移除;本交接仅作历史证据。见 [.././2026-09-14-remove-herdr-agent-settings.md](.././2026-09-14-remove-herdr-agent-settings.md)。
|
||||||
|
|
||||||
## 目标(下一位 Agent 应完成什么)
|
## 目标(下一位 Agent 应完成什么)
|
||||||
|
|
||||||
治理规范已完成并准备合入 `dev`;本文件保留为集成证据。
|
治理规范已完成并准备合入 `dev`;本文件保留为集成证据。
|
||||||
@@ -36,9 +38,9 @@ status: closed
|
|||||||
|
|
||||||
| 路径 | 说明 |
|
| 路径 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `.doc/ops/2026-09-13-herdr-agent-roster.md` | 角色与路由 SSOT |
|
| `.doc/archive/ops/2026-09-13-herdr-agent-roster.md` | 角色与路由 SSOT(已归档) |
|
||||||
| `scripts/herdr-agent-start.sh` | 免人工审批启动入口 |
|
| `scripts/herdr-agent-start.sh` | 免人工审批启动入口(已删除) |
|
||||||
| `scripts/herdr-agent-dispatch.sh` | 主备派发与防重复策略 |
|
| `scripts/herdr-agent-dispatch.sh` | 主备派发与防重复策略(已删除) |
|
||||||
| `AGENTS.md` / `PROJECT.md` | 强制入口与 PI 身份边界 |
|
| `AGENTS.md` / `PROJECT.md` | 强制入口与 PI 身份边界 |
|
||||||
|
|
||||||
## 如何验证
|
## 如何验证
|
||||||
@@ -59,5 +61,5 @@ git diff --check
|
|||||||
## 相关链接
|
## 相关链接
|
||||||
|
|
||||||
- CURRENT: [CURRENT.md](../CURRENT.md)
|
- CURRENT: [CURRENT.md](../CURRENT.md)
|
||||||
- Task: [2026-09-13-herdr-agent-governance.md](../../archive/ops/2026-09-13-herdr-agent-governance.md)
|
- Task: [2026-09-13-herdr-agent-governance.md](./2026-09-13-herdr-agent-governance.md)
|
||||||
- Checklist: 任务文中的验收标准
|
- Checklist: 任务文中的验收标准
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: "arch-check Wave 0 基线"
|
||||||
|
status: done
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
severity: medium
|
||||||
|
---
|
||||||
|
|
||||||
|
# arch-check Wave 0 基线
|
||||||
|
|
||||||
|
## 现象
|
||||||
|
|
||||||
|
模块边界此前只能靠人工约定。Wave 0 用当前 `go list` 生成 import allowlist,并用目标属主扫描表名与路径字面量,得到可重复的 warn 基线。
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
`document_records`、`output.md`、`data/dev` 被多个包直接提及,与「单一写者 / 路径属主」目标不一致。这些不是本轮回归,而是 Wave 1+ 的收口对象。
|
||||||
|
|
||||||
|
## 严重度
|
||||||
|
|
||||||
|
`medium`
|
||||||
|
|
||||||
|
## 关联整改
|
||||||
|
|
||||||
|
- ADR: [../adr/2026-09-14-modular-monolith-decoupling.md](../adr/2026-09-14-modular-monolith-decoupling.md)
|
||||||
|
- 设计: [../system-design/2026-09-14-module-decoupling-architecture.md](../system-design/2026-09-14-module-decoupling-architecture.md)
|
||||||
|
- 任务: [../ops/short-term/2026-09-14-module-decoupling-wave0.md](../ops/short-term/2026-09-14-module-decoupling-wave0.md)
|
||||||
|
|
||||||
|
## 证据
|
||||||
|
|
||||||
|
命令:`./scripts/arch-check.sh --mode warn`(2026-09-14,本工作区)
|
||||||
|
|
||||||
|
- import 相对基线 allowlist:**0** 条新边
|
||||||
|
- 目标属主扫描:**21** 条 warn(按包去重)
|
||||||
|
|
||||||
|
```
|
||||||
|
WARN R5 table document_records mentioned in authz, workspace, docduty, server, bizdev, docrevision
|
||||||
|
WARN R5 table freeform_documents mentioned in docrevision
|
||||||
|
WARN R5 table task_runs mentioned in server, projectinstance, docgov
|
||||||
|
WARN R6 path output.md mentioned in coordinator, server, bizdev, docgov, docrevision, agent
|
||||||
|
WARN R6 path data/dev mentioned in coordinator, server, bizdev, storage
|
||||||
|
WARN R6 path workspaces/ mentioned in server
|
||||||
|
violations=21
|
||||||
|
arch-check warn: recorded 21 baseline findings
|
||||||
|
```
|
||||||
|
|
||||||
|
`--mode block` 会因这 21 条失败,故 **不得**在 warn 期满前把脚本挂进集成冒烟。
|
||||||
|
|
||||||
|
## 后续
|
||||||
|
|
||||||
|
- Wave 1 起按属主清零上表,再把对应规则转 block
|
||||||
|
- 本轮不占集成槽、不 `env-service` rebuild
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
title: "workflow-plan 409 冻结冲突(项目 143)与三权设置粒度调整需求"
|
||||||
|
status: done
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
severity: medium
|
||||||
|
---
|
||||||
|
|
||||||
|
# workflow-plan 409 冻结冲突(项目 143)与三权设置粒度调整需求
|
||||||
|
|
||||||
|
## 现象
|
||||||
|
|
||||||
|
用户在项目设置(`ProjectSettingsModal`)中把项目内所有文档任务的编制/审核/批准指派为「系统管理员 / 李审核 / 王总监」,点击「校验并预览」无反应。控制台报错:
|
||||||
|
|
||||||
|
```text
|
||||||
|
projectSettings.ts:51 PUT https://frp-mix.com:58880/api/v1/projects/143/workflow-plan 409 (Conflict)
|
||||||
|
```
|
||||||
|
|
||||||
|
同时用户提出新需求:**三权设置粒度按文档控制**——对某个允许省略人工审批的文档,一旦设置职责,必须三个职责(编制/审核/批准)都指派负责人;三个职责都未设置时,该文档仍可生成和进入版本账本,但不进入人工审批及阶段批准门禁。它不是“非受控文档”;若不需要生成该文档,应关闭文档任务。
|
||||||
|
|
||||||
|
## 根因分析
|
||||||
|
|
||||||
|
### 409 的直接原因:项目 143 已激活工作流,规划已冻结(PLAN_FROZEN)
|
||||||
|
|
||||||
|
dev 库(`shuimu-context-pg-dev` / `agent_system_dev`)实测:
|
||||||
|
|
||||||
|
```text
|
||||||
|
id | name | lifecycle_status | active_workflow_key | workflow_selection_version | workflow_plan_revision
|
||||||
|
-----+------+------------------+---------------------+----------------------------+------------------------
|
||||||
|
143 | 测试 | active | main | 2 | 1
|
||||||
|
|
||||||
|
id | project_id | status | config_revision | has_hash | created_at
|
||||||
|
----+------------+--------+-----------------+----------+-------------------------------
|
||||||
|
89 | 143 | active | 1 | t | 2026-09-13 01:31:33.212283+00
|
||||||
|
```
|
||||||
|
|
||||||
|
项目 143 于 2026-09-13 已激活 `main` 工作流(实例 89,status=active)。后端保存链路:
|
||||||
|
|
||||||
|
- `internal/projectinstance/service.go:131-133`(`SaveWorkflowPlan`):`if lifecycle != LifecyclePlanning { return nil, ErrPlanFrozen }`,冲突检查发生在最前;
|
||||||
|
- `internal/server/project_settings_handlers.go:164-165`:`ErrPlanFrozen` → HTTP 409 `{"error":"PLAN_FROZEN","message":"工作流已激活,规划已冻结"}`。
|
||||||
|
|
||||||
|
前端未能把该语义展示出来,构成「点击无反应」的体验:
|
||||||
|
|
||||||
|
- `web/src/api/projectSettings.ts:60-63`:非 2xx 直接抛出普通 `Error`。`parseError`(`projectSettings.ts:200-213`)其实已经能取到后端 `message`,所以“工作流已激活,规划已冻结”并未丢失;真正缺口是错误对象不保留 HTTP status/code/issues,UI 无法按 `PLAN_FROZEN`、版本冲突等类型切换冻结态或执行定向恢复;
|
||||||
|
- `web/src/components/project/ProjectSettingsModal.tsx:161-162`:catch 后写入 `error` state。弹窗在 `configure` 步骤的「校验并预览」按钮(538-557 行)点击后仅更新顶部 error 区(276-293 行),视口停留在长表单底部时用户看不到顶部横幅,感知即「无反应」。
|
||||||
|
|
||||||
|
### 前端缺少冻结态守卫
|
||||||
|
|
||||||
|
`ProjectSettingsModal.tsx` 的 `loadSettings`(53-81 行)只要 `s.plan` 存在就 `setStep('configure')`,`handleSelectWorkflow` / `handleSavePlan`(91-166 行)均未检查 `settings.lifecycle_status !== 'planning'` 或 `can_edit_plan === false`。后端 `GetSettings` 已正确返回 `can_edit_plan=false`(`internal/projectinstance/service.go:59-60,78-79`),前端没用它。因此已激活项目仍可一路走到保存,必然撞 409。
|
||||||
|
|
||||||
|
### 现行三权校验为「逐文档三职全必填」
|
||||||
|
|
||||||
|
- 后端 `internal/projectinstance/validator.go:93-95`:`dt.Enabled` 即调用 `validateDuties`;`validateDuties`(143-179 行)对启用的文档强制 `DUTY_AUTHOR/REVIEWER/APPROVER_REQUIRED` + 互斥校验;
|
||||||
|
- 前端 `ProjectSettingsModal.tsx:130-145`:成员数 <3 直接拦截;每个启用文档三职任一未指派即报错。
|
||||||
|
|
||||||
|
即当前不存在「整文档留空」的合法表达,与用户新需求冲突。门禁侧 `internal/projectinstance/gate.go:23-51` 对 `enabled` 的文档按工作流定义的 `BlocksWhenEnabled()` 计入阶段就绪——若允许留空却不改门禁,阻断型文档会产生永久死锁。因此**留空语义必须同步改门禁,但不能反过来让项目配置绕过工作流定义中不可省略的必需/阻断文档**。
|
||||||
|
|
||||||
|
## 方案复核发现的补充风险
|
||||||
|
|
||||||
|
1. **必需文档可被绕过**:若简单采用“全空即 `required=false`”,创建者可把定义中 `required/blocking` 且不可关闭的文档职责清空,从而绕过正式工作流门禁。全空只应允许用于工作流定义允许创建者裁剪的非必需文档;必需/阻断文档仍必须三职全满。
|
||||||
|
2. **零治理工作流**:原方案允许“全部启用文档全空后激活”。这超出“部分文档不设置”的需求,并会产生没有任何审核责任人的 active 工作流。保存草稿可以暂时全空,但激活时必须至少有一份三职完整的受治理文档。
|
||||||
|
3. **成员有效性未由后端保证**:现有 `validateDuties` 只检查 ID 为正且互异;`document_duty_assignments.user_id` 也只外键到 `users`,没有保证用户属于该项目且仍在任。保存和激活前都必须按服务端成员快照复核。
|
||||||
|
4. **权限语义缺失**:当前 active 实例中,创建者可操作无职责文档;其他成员的阶段范围会因没有职责而排除该文档。最终方案必须明确:无人工审批文档对有项目访问权的成员可只读可见,创建者可触发生成/编辑,review/approve 始终不可用。
|
||||||
|
5. **冻结快照可能残留旧 ID**:关闭文档任务或切换为“无需人工审批”时,如果不清空旧职责 ID,冻结 JSON、plan hash 和预览统计仍会携带过期职责。保存前必须服务端规范化,前端也应原子清空三职。
|
||||||
|
|
||||||
|
## 严重度
|
||||||
|
|
||||||
|
`medium`(既有冻结规则正确,但页面误导用户继续编辑;职责可选属于治理能力扩展,涉及门禁、权限与激活完整性)
|
||||||
|
|
||||||
|
## 关联整改
|
||||||
|
|
||||||
|
- Remediation: [2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md](../remediation/2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md)
|
||||||
|
- Short-term: [../ops/short-term/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](../ops/short-term/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
|
||||||
|
## 证据
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# dev DB 实测(2026-09-14)
|
||||||
|
docker exec shuimu-context-pg-dev psql -U app -d agent_system_dev \
|
||||||
|
-c "SELECT id,name,lifecycle_status,active_workflow_key,workflow_selection_version,workflow_plan_revision FROM projects WHERE id=143;"
|
||||||
|
docker exec shuimu-context-pg-dev psql -U app -d agent_system_dev \
|
||||||
|
-c "SELECT id,project_id,status,config_revision FROM project_workflow_instances WHERE project_id=143 ORDER BY id;"
|
||||||
|
# → lifecycle_status=active / active_workflow_key=main / 实例 89 status=active
|
||||||
|
|
||||||
|
# test/main 环境项目 143 不存在(0 rows),现场仅 dev
|
||||||
|
```
|
||||||
|
|
||||||
|
代码引用:
|
||||||
|
|
||||||
|
- `internal/projectinstance/service.go:123-180`(SaveWorkflowPlan:冻结/live/选版/乐观锁顺序)
|
||||||
|
- `internal/projectinstance/store.go:123-134`(BumpPlanRevision 乐观锁)
|
||||||
|
- `internal/server/project_settings_handlers.go:160-204`(错误码 → HTTP 状态映射)
|
||||||
|
- `internal/projectinstance/validator.go:9-126,143-179`(validatePlan / validateActivationPlan / validateDuties)
|
||||||
|
- `internal/projectinstance/gate.go:5-54`(阶段门禁)
|
||||||
|
- `web/src/components/project/ProjectSettingsModal.tsx:53-81,120-166`(loadSettings / handleSavePlan)
|
||||||
|
- `web/src/api/projectSettings.ts:45-65,200-213`(saveWorkflowPlan / parseError)
|
||||||
|
|
||||||
|
## 审核结论
|
||||||
|
|
||||||
|
- 409 根因成立;项目 143 的职责内容合法与否不会改变冻结判定。
|
||||||
|
- “部分指派”继续明确拒绝;顶层响应为 HTTP 400 `VALIDATION_FAILED`,具体 issue code 为 `DUTY_PARTIAL_ASSIGNMENT`。
|
||||||
|
- 无职责文档定义为“启用但不纳入人工三权审批”,仍保留生成、正文、版本账本和项目内只读可见性;禁止使用“非受控文档”描述。
|
||||||
|
- 全空只允许在非必需且允许项目侧裁剪的文档任务上使用;必需/阻断任务仍须三职完整。激活时至少存在一份三职完整文档。
|
||||||
|
- 项目 143 仅做冻结态只读回归;不得在验收中终止其工作流。若业务上确需改职责,必须先查看终止影响、确认取消运行任务及关闭实例的访问影响,再受控终止并重新规划、激活。
|
||||||
@@ -13,6 +13,20 @@
|
|||||||
|
|
||||||
## 核心文档(按阅读顺序推荐)
|
## 核心文档(按阅读顺序推荐)
|
||||||
|
|
||||||
|
### 模块边界 arch-check Wave 0 基线(2026-09-14)
|
||||||
|
|
||||||
|
**文件**: [2026-09-14-arch-check-baseline.md](./2026-09-14-arch-check-baseline.md)
|
||||||
|
|
||||||
|
**结论**: import 相对现状 allowlist 无新边;表/路径目标属主扫描 21 条 warn,作为 Wave 1+ 收口清单,不在本轮转 block。
|
||||||
|
|
||||||
|
### 项目 143 workflow-plan 409 与三权全有或全无(2026-09-14)
|
||||||
|
|
||||||
|
**文件**: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](./2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
|
||||||
|
**结论**: 项目 143 已于 2026-09-13 激活 main 工作流(实例 89),`SaveWorkflowPlan` 因 `lifecycle=active` 返回 409 `PLAN_FROZEN`;前端无冻结守卫、错误提示在视口外,表现为「校验并预览无反应」。按文档支持三权全有或全无,但全空仅用于定义允许裁剪的非必需文档,表示“不纳入人工审批”而非“非受控”;必需文档和零治理激活不得被绕过。
|
||||||
|
|
||||||
|
**整改**: [workflow-plan 409 + 三权全有或全无整改](../remediation/2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md)
|
||||||
|
|
||||||
### 项目 143 受控正文 canonical hash 漂移(2026-09-14)
|
### 项目 143 受控正文 canonical hash 漂移(2026-09-14)
|
||||||
|
|
||||||
**文件**: [2026-09-14-project143-controlled-content-404.md](./2026-09-14-project143-controlled-content-404.md)
|
**文件**: [2026-09-14-project143-controlled-content-404.md](./2026-09-14-project143-controlled-content-404.md)
|
||||||
|
|||||||
+9
-2
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
## 焦点
|
## 焦点
|
||||||
|
|
||||||
1. **DOC-CONTENT**: [受控正文 canonical hash 漂移](../audit/2026-09-14-project143-controlled-content-404.md)(真实 UI 验收已 PASS:项目 143 clean-run3 + 新项目 full-chain-final 主链及 restart 均全部通过)
|
1. **WORKFLOW-PLAN**: [冻结 409 与文档职责可选治理](../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)(Wave 1–4 已在 `fix/workflow-plan-409-frozen-and-optional-duties` 落地;Wave 5 占槽 e2e 进行中;后续治理原则见 [MVP 治理规则评审](../ops/research/2026-09-14-mvp-governance-rules-review.md))
|
||||||
2. **CHAT-INTENT**: [对话意图运行时状态](../audit/2026-09-13-conversation-intent-runtime-state.md)(`fix/conversation-intent-state`;与文档账本运行时无关)
|
2. **CHAT-INTENT**: [对话意图运行时状态](../audit/2026-09-13-conversation-intent-runtime-state.md)(`fix/conversation-intent-state`;与文档账本运行时无关)
|
||||||
3. **CHAT**: [对话历史刷新](../audit/2026-09-13-chat-history-refresh-disappears.md)(认领仍 `in_progress`;gap 行已 PASS,以代码/handoff 为准收敛)
|
3. **CHAT**: [对话历史刷新](../audit/2026-09-13-chat-history-refresh-disappears.md)(认领仍 `in_progress`;gap 行已 PASS,以代码/handoff 为准收敛)
|
||||||
|
|
||||||
@@ -46,14 +46,21 @@
|
|||||||
| Cursor-Auto | document-revision-evidence-count | fix/document-revision-evidence-count | - | document-revision-evidence-count | done |
|
| Cursor-Auto | document-revision-evidence-count | fix/document-revision-evidence-count | - | document-revision-evidence-count | done |
|
||||||
| Cursor-Auto | document-revision-session-close | feature/document-revision-session-close | - | document-revision-session-close | done |
|
| Cursor-Auto | document-revision-session-close | feature/document-revision-session-close | - | document-revision-session-close | done |
|
||||||
| Cursor-Auto | vite-frp-allowed-hosts | fix/vite-frp-allowed-hosts | - | web-devserver | done |
|
| Cursor-Auto | vite-frp-allowed-hosts | fix/vite-frp-allowed-hosts | - | web-devserver | done |
|
||||||
|
| Cursor-Auto | remove-herdr-agent-settings | feature/remove-herdr-agent-settings | - | agent-collab-docs | done |
|
||||||
|
| implementer_cursor | workflow-plan-409-frozen-and-optional-duties | fix/workflow-plan-409-frozen-and-optional-duties | .worktrees/fix-workflow-plan-409-frozen-and-optional-duties | workflow | in_progress |
|
||||||
|
| Cline | governance-action-level-authz | fix/governance-action-level-authz | - | workflow | blocked |
|
||||||
|
| Cursor | module-decoupling-wave0 | feature/remove-herdr-agent-settings | - | arch | done |
|
||||||
|
|
||||||
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。
|
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。
|
||||||
|
|
||||||
|
> `workflow-plan-409-frozen-and-optional-duties` 已由 implementer_cursor 接管:worktree 已创建,Wave 1–4 提交落地,Wave 5 占集成槽位(e2e)进行中。
|
||||||
|
> `governance-action-level-authz` blocked 仅为等待同 mutex `workflow` 的 Wave 5 收敛;收敛后从最新 `dev` 创建 `fix/governance-action-level-authz` 并改 `in_progress`,不得在当前 `feature/remove-herdr-agent-settings` 工作区编码。
|
||||||
|
|
||||||
## 集成槽位
|
## 集成槽位
|
||||||
|
|
||||||
| holder | phase | since | scope | queue |
|
| holder | phase | since | scope | queue |
|
||||||
|--------|-------|-------|-------|-------|
|
|--------|-------|-------|-------|-------|
|
||||||
| idle | idle | - | - | - |
|
| implementer_cursor | e2e | 2026-09-14 | workflow-plan 409 frozen + optional duties | governance-action-level-authz |
|
||||||
|
|
||||||
**phase**: `idle` | `merging` | `smoke` | `e2e`
|
**phase**: `idle` | `merging` | `smoke` | `e2e`
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
title: "MVP 治理规则方案评审(零治理创建 / 禁止零治理激活与执行 / 职责-动作对应)"
|
||||||
|
status: done
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
author: Cline
|
||||||
|
---
|
||||||
|
|
||||||
|
# MVP 治理规则方案评审(零治理创建 / 禁止零治理激活与执行 / 职责-动作对应)
|
||||||
|
|
||||||
|
## 评审对象
|
||||||
|
|
||||||
|
用户转来的治理原则提案(外部评审稿,自称「仅完成审核判断,尚未修改此前落盘方案或业务代码」),核心主张:
|
||||||
|
|
||||||
|
> 允许零治理创建项目和普通对话;禁止零治理激活和执行文档任务。文档任务按动作进行职责鉴权,编制、审核、批准只能由对应文档负责人执行,项目创建者不具有隐式越权能力。
|
||||||
|
|
||||||
|
## 结论(先说判断)
|
||||||
|
|
||||||
|
**方向合理、证据属实、与已实施的 DutyMode 架构(Wave 1–4)高度兼容,建议采纳为治理总原则;但存在 4 个必须先决策/修正的分歧点,其中 A(none 文档可否生成)与 B(pre-activation 202 契约)若按提案字面实施,会直接推翻刚落地的已验收语义与测试。**
|
||||||
|
|
||||||
|
## 证据核实矩阵(提案引用的代码全部属实)
|
||||||
|
|
||||||
|
| # | 提案主张 | 核实结果 | 证据位置 |
|
||||||
|
|---|----------|----------|----------|
|
||||||
|
| 1 | 无治理实例时文档任务访问直接放行 | ✅ 属实 | `internal/projectinstance/document_access.go:49-51`(`gov == nil → return allow`);`stage_access_test.go:163` 断言该行为 |
|
||||||
|
| 2 | 创建者可绕过文档职责检查 | ✅ 属实 | `document_access.go:115-117`(`isCreator → allow`);`stage_access.go:65,67,79`(`allow = isCreator \|\| hasDuty`) |
|
||||||
|
| 3 | 只判断是否三职之一,不区分动作 | ✅ 属实 | `stage_access.go:96-100`(`docTaskDutyForUser` 任意一职即通过;`DocAccessAction*` 常量已存在于 `document_access.go:8-13` 但未与职责绑定) |
|
||||||
|
| 4 | `initial_workflow_key` 直通 active | ✅ 属实 | `internal/projectworkflow/service.go:57-83`(非空即 `LifecycleStatus="active"`);E2E `project_workflow_api_test.go:101-105` 依赖该旁路 |
|
||||||
|
| 5 | 创建弹窗默认勾选创建者+推荐审核/批准(成员角色≠文档职责) | ✅ 属实 | `web/src/components/project/CreateProjectModal.tsx:50-69`(creator/reviewer/approver 为项目成员角色) |
|
||||||
|
| 6 | 启用文档须完整三职(现行 validator) | ✅ 属实(主树) | `internal/projectinstance/validator.go:93-95`;已实施 worktree 已改为 DutyMode none/partial/full |
|
||||||
|
|
||||||
|
## 与已实施代码的交叉分析(提案未覆盖的现场)
|
||||||
|
|
||||||
|
上一轮整改(409 冻结 + 全有或全无)**已在 `fix/workflow-plan-409-frozen-and-optional-duties` worktree 实施**:Wave 1–4 已提交(`classifyDocDuty`/`DutyMode` 统一校验、门禁/readiness/访问一致语义、前端逐文档「纳入人工审批」开关),Wave 5(dev 真实浏览器验收)进行中(e2e spec 与 test-reports assets 未提交,集成槽位已占用)。dev 库实测:`133 active / 204 planning`,其中 **81 个 planning 项目带非空 `active_workflow_key`**(`initial_workflow_key` 旁路/历史迁移遗留的异态存量)——提案第 4 差距点在 dev 有真实影响面。
|
||||||
|
|
||||||
|
### 分歧点 A(最关键):none 模式文档可否生成 —— **已决策(2026-09-14,用户拍板)**
|
||||||
|
|
||||||
|
- 提案:「编制、审核、批准全部为空:该文档**不启用,不可发起任务**,也不阻塞阶段」。
|
||||||
|
- 已实施 D4(remediation 76-78 行):`DutyModeNone` = 文档**启用**、不要求 approved、不阻塞阶段,**项目创建者可触发生成和人工编辑**,其他成员只读。
|
||||||
|
- **用户决策:保持现实现(none = 不走人工审批但可生成)**。提案的「不可发起任务」正式收窄为「不可发起**审批流**动作(提交审核/审核/批准)」,不推翻 Wave 4/5。后续实施以本决策为准。
|
||||||
|
|
||||||
|
### 分歧点 B:pre-activation 202 契约
|
||||||
|
|
||||||
|
- 提案:「未激活:不创建任务、不启动小队、不获取文档租约」。
|
||||||
|
- 已验收契约:`ResolveMessageScopeForTask` 从未激活 → `ScopePreActivation`;`POST /api/v1/tasks/runs` 必须 **202**(`TestCreateTaskRun_PreActivationAcceptedNot409`,`internal/server/task_run_busy_preflight_test.go:75`);E2E UI-02 以 2xx 为硬门禁(409 即 FAIL)。
|
||||||
|
- 按字面「不创建任务」会推翻该契约并破坏 E2E。**建议**修正表述为:保留 202 创建 run(进入 `blocked_pending_governance` 之类状态、不启动小队、不取租约),chat 流内返回自然语言提示(提案的示例文案即为此设计)。提案意图(不执行)与现状兼容,仅「不创建」三个字必须改。
|
||||||
|
|
||||||
|
### 分歧点 C:creator 越权移除的影响面与 view 范围
|
||||||
|
|
||||||
|
- 移除 `isCreator` 例外(`document_access.go:115`、`stage_access.go:65,67,79`)方向正确,但大量已验收测试断言 creator allow(如 `access_governing_pg_test.go:140`),需同步重写测试语义,工作量可控但必须列入计划。
|
||||||
|
- 提案职责表「查看文档 = 具有项目访问权的成员」与 `document_access.go:102-104`(view 全放行)一致;但 `ResolveStageAccess` 的 UI 导航收窄会把无职责成员/创建者限制在职责阶段。需明确:view 豁免 stage 收窄(按提案表格),`AllowedStageKeys` 仅约束非 view 动作与任务发起入口。
|
||||||
|
|
||||||
|
### 分歧点 D:initial_workflow_key 旁路的存量处理
|
||||||
|
|
||||||
|
- 关闭入口正确(`projectworkflow/service.go:80-83` 改为仅挂载目录、不置 active),但需同步:`CreateProjectInput.InitialWorkflowKey` 字段废弃(`types.go:96`、前端 `web/src/types/projectWorkflow.ts:66`)、E2E legacy 用例(`project_workflow_api_test.go:101-105`)语义更新、以及 dev 库 81 个 planning+with_wf 异态项目的分类处置(至少审计:真 legacy / 测试数据,不得静默改库)。
|
||||||
|
|
||||||
|
## 提案其余要点的核实意见
|
||||||
|
|
||||||
|
- 三概念分离(创建/激活/执行)优于「创建即治理」:成立。创建者自动入成员已有实现(`CreateProjectModal` 默认勾选 + `ensureCreatedProjectWithMembers` 持久化),无死锁。
|
||||||
|
- 「至少一个 full 文档才能激活;全部未启用可存草稿禁激活」:与已实施 Wave 2 校验(至少一份 `DutyModeFull` + active member 复核)**完全一致**,无需重复实施。
|
||||||
|
- 「审核人退回 → 待编制人修订,审核人不得直接启动重编」:与职责-动作对应表一致,合理;现状 `docTaskDutyForUser` 不区分动作是真实缺口。
|
||||||
|
- 「激活冻结提示留空文档不可补配」:与现有冻结语义一致,属 Wave 1 frozen 视图文案的自然扩展。
|
||||||
|
- 「项目管理员」概念:现有成员角色枚举(creator/reviewer/approver/viewer)无 admin;提案未定义 role 模型如何演进(谁可配成员/职责目前是创建者,`IsProjectCreator` 权威)。建议 MVP 维持「创建者=项目管理员」,role 枚举演进留 backlog。
|
||||||
|
|
||||||
|
## 建议实施时序
|
||||||
|
|
||||||
|
1. 等 `fix/workflow-plan-409-frozen-and-optional-duties` Wave 5 验收收敛(验收中途改语义会导致返工与报告失真)。
|
||||||
|
2. 用户先决策分歧点 A(其次 C 的 view 范围)。
|
||||||
|
3. 采纳后新开 short-term(建议 `fix/governance-action-level-authz`,mutex=`workflow`),分波次:后端动作-职责映射(`CheckDocTaskAccess` 按动作判 author/reviewer/approver + 移除 creator 例外 + gov==nil 由「放行」改为「未激活拦截」但保留 202 契约)→ 关闭 `initial_workflow_key` 直通 → 前端交互与提示 → 存量异态审计 → E2E 更新。
|
||||||
|
|
||||||
|
## 采纳与派生记录(2026-09-14)
|
||||||
|
|
||||||
|
- 分歧点 A:用户拍板「保持现实现(none 可生成)」——见上文。
|
||||||
|
- 分歧点 B/C/D:按本评审建议采纳(B:保留 202 契约 + blocked 状态;C:view 豁免阶段收窄、creator 仅保留配置权与 none 生成触发;D:关闭直通 + 字段废弃 + 存量只审计不改库)。
|
||||||
|
- 已派生实施任务:[2026-09-14-governance-action-level-authz.md](../short-term/2026-09-14-governance-action-level-authz.md)(status=blocked,等待 Wave 5 收敛;含采纳版职责-动作矩阵与 G1–G5 波次)。
|
||||||
|
- Wave 5 现状(2026-09-14 实测):`implementer_cursor` 占集成槽位 phase=e2e,worktree 存在未提交 e2e 产物;新任务已排入槽位 queue。
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Audit: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](../../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
- Remediation(已实施版): [2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md](../../remediation/2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md)
|
||||||
|
- 先例: [2026-09-13-workflow-access-chat-members-ui01-02.md](../../audit/2026-09-13-workflow-access-chat-members-ui01-02.md)(202 契约与三态作用域来源)
|
||||||
|
|
||||||
|
## 备注
|
||||||
|
|
||||||
|
主树 `.doc/ops/CURRENT.md`(`feature/remove-herdr-agent-settings` 分支)焦点 1 仍写「worktree 尚未创建、blocked」,已过时:worktree 已存在且 Wave 1–4 落地、Wave 5 进行中。该更新属实施分支职责,本评审不跨分支代改,仅在此记录提醒收敛时同步。
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
title: "治理鉴权升级:职责-动作严格对应 + 未激活拦截 + 关闭创建直通"
|
||||||
|
status: blocked
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
owner: Cline
|
||||||
|
branch: fix/governance-action-level-authz
|
||||||
|
worktree: "-"
|
||||||
|
mutex: workflow
|
||||||
|
---
|
||||||
|
|
||||||
|
# 治理鉴权升级:职责-动作严格对应 + 未激活拦截 + 关闭创建直通
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
采纳 [MVP 治理规则评审](../../ops/research/2026-09-14-mvp-governance-rules-review.md) 修正版原则:**允许零治理创建与普通对话;禁止零治理激活与执行文档任务(审批流动作)**。文档动作按职责鉴权,创建者不隐式越权,`initial_workflow_key` 不再直通 active。
|
||||||
|
|
||||||
|
## 决策记录(已定,不再开放)
|
||||||
|
|
||||||
|
- **A(用户拍板 2026-09-14)**:`DutyModeNone` 文档 = 不走人工审批但**可生成**(创建者触发,维持已实施 D4);「不可发起任务」收窄为不可发起**审批流动作**(submit/review/approve)。
|
||||||
|
- **B(按评审建议采纳)**:pre-activation 保留 202 契约——`POST /tasks/runs` 仍 202 创建 run,但置 blocked 状态、不启动小队、不取文档租约,chat 流内返回自然语言治理提示。
|
||||||
|
- **C(按评审建议采纳)**:移除 `isCreator` 对 full 文档的隐式全通行;`view` 豁免阶段收窄(项目访问成员可查看);创建者保留:配置成员/职责 + none 文档生成触发。
|
||||||
|
- **D(按评审建议采纳)**:关闭 `initial_workflow_key` 直通 active(仅挂载目录);后端/前端 types 字段废弃标注;E2E legacy 用例语义更新;dev 库 81 个 planning+with_wf 异态项目**只审计分类,不改库**。
|
||||||
|
|
||||||
|
## 采纳版职责-动作矩阵
|
||||||
|
|
||||||
|
| 动作 | full 文档 | none 文档 | 未激活(gov==nil) |
|
||||||
|
|------|-----------|-----------|--------------------|
|
||||||
|
| view | 项目访问成员 | 项目访问成员 | 项目访问成员 |
|
||||||
|
| run(首次生成/重生成) | 编制负责人 | **创建者**(无审批链) | 202 + blocked 状态,chat 内提示,不启小队/不取租约 |
|
||||||
|
| edit/submit(编辑/提交审核) | 编制负责人 | 拒绝(无审批链) | 拒绝 |
|
||||||
|
| review(审核/退回) | 审核负责人 | 拒绝 | 拒绝 |
|
||||||
|
| approve(批准/拒绝批准) | 批准负责人 | 拒绝 | 拒绝 |
|
||||||
|
| 配置成员/文档职责 | 创建者 | 创建者 | 创建者(planning 期配置正是本阶段语义) |
|
||||||
|
|
||||||
|
退回后形成「待编制人修订」;审核人不得直接启动重编(由编制负责人发起修订)。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- `internal/projectinstance/document_access.go`(动作-职责映射、creator 例外移除、gov==nil 未激活拦截)
|
||||||
|
- `internal/projectinstance/stage_access.go`(view 豁免语义;`docTaskDutyForUser` 拆分为按职责判定)
|
||||||
|
- `internal/projectinstance/message_scope.go` + taskrun/chat handler(blocked 状态 + 自然语言提示)
|
||||||
|
- `internal/projectworkflow/service.go`(关闭 `initial_workflow_key` 直通)+ `types.go` 前后端字段废弃
|
||||||
|
- 受影响单测重写(`access_governing_pg_test.go` creator 断言、`TestCreateTaskRun_PreActivationAcceptedNot409` 语义细化)与 E2E legacy 用例
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不改三权互斥与 DutyMode 校验(Wave 2 已完成)
|
||||||
|
- 不做运行中动态补配职责 / 按文档单独激活(冻结语义维持,激活页提示「留空文档不参与、不可补配」)
|
||||||
|
- 不做成员 role 枚举演进(MVP 维持创建者=项目管理员;role 演进留 backlog)
|
||||||
|
- 不改 dev 库存量数据(仅审计)
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
1. [x] 评审落盘 + 决策 A–D 收敛 + 本任务登记(blocked 等待 Wave 5 收敛、mutex `workflow` 空闲)
|
||||||
|
2. [ ] G1 后端:`CheckDocTaskAccess` 动作-职责映射 + creator 例外移除(保留 none 生成触发)+ gov==nil 未激活拦截(202 契约保留);重写受影响单测
|
||||||
|
3. [ ] G2 关闭 `initial_workflow_key` 直通 + 前后端字段废弃 + E2E legacy 用例更新
|
||||||
|
4. [ ] G3 前端:chat 治理拦截自然语言提示、按钮级职责禁用、激活确认页冻结提示补强
|
||||||
|
5. [ ] G4 dev 库 81 个 planning+with_wf 异态审计分类(只读报告)
|
||||||
|
6. [ ] G5 集成验证 + 测试报告归档(新项目全链路:零治理创建→对话→配置→激活→职责动作负例)
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- [ ] full 文档 API 负例:author 不可 review/approve;reviewer 不可 run/edit/submit;approver 不可 run/edit/review;creator 不可任一审批动作(view 除外)
|
||||||
|
- [ ] none 文档:创建者可触发生成;submit/review/approve 全拒
|
||||||
|
- [ ] 未激活项目 chat 输入「帮我生成立项文档」:消息正常显示、run 202 且 blocked、无小队/租约、chat 内明确治理提示文案
|
||||||
|
- [ ] `POST /projects` 带 `initial_workflow_key`:不再置 active,工作流目录仍自动挂载
|
||||||
|
- [ ] `go vet ./...`、`go test -short ./...`、`cd web && npm test && npm run build` 全绿
|
||||||
|
- [ ] 异态审计报告与测试报告归档并回链
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Research(评审与决策记录): [2026-09-14-mvp-governance-rules-review.md](../research/2026-09-14-mvp-governance-rules-review.md)
|
||||||
|
- 前序 Remediation(已实施 Wave 1–4): [2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md](../../remediation/2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md)
|
||||||
|
- 202 契约来源: [2026-09-13-workflow-access-chat-members-ui01-02.md](../../audit/2026-09-13-workflow-access-chat-members-ui01-02.md)
|
||||||
|
|
||||||
|
## 证据 / 备注
|
||||||
|
|
||||||
|
- Wave 5 占槽现状(2026-09-14 实测):`implementer_cursor` phase=e2e,worktree 存在未提交 e2e 产物;本任务排在集成槽位 queue。
|
||||||
|
- dev 库:133 active / 204 planning;81 个 planning 项目带非空 `active_workflow_key`(待 G4 审计分类)。
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: "模块解耦 Wave 0:契约包骨架 + 架构门禁基线"
|
||||||
|
status: done
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
owner: Cursor
|
||||||
|
branch: feature/remove-herdr-agent-settings
|
||||||
|
worktree: "-"
|
||||||
|
mutex: arch
|
||||||
|
---
|
||||||
|
|
||||||
|
# 模块解耦 Wave 0:契约包骨架 + 架构门禁基线
|
||||||
|
|
||||||
|
> 总方案:[../../system-design/2026-09-14-module-decoupling-architecture.md](../../system-design/2026-09-14-module-decoupling-architecture.md)
|
||||||
|
> ADR:[../../adr/2026-09-14-modular-monolith-decoupling.md](../../adr/2026-09-14-modular-monolith-decoupling.md)
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
建立 `internal/contracts` 骨架与 `scripts/arch-check.sh` 依赖门禁(warn 基线),使模块边界从此可度量、可执行,为 Wave 1–6 提供验收对照。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- `internal/contracts/`:vocabulary.go(MaturityStage 等共享枚举占位)、errors.go(错误码表)、events.go(首批事件类型定义,纯定义零实现)
|
||||||
|
- `scripts/arch-check.sh` + `.doc/system-design/module-boundaries.yaml`(以现状生成 allowlist,不收紧)
|
||||||
|
- 基线违规报告存 `.doc/audit/2026-09-14-arch-check-baseline.md`
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不改任何业务行为、不迁移任何包(属 Wave 1+)
|
||||||
|
- 不收紧任何依赖规则(block 转换在两周 warn 期后)
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
1. [x] 用户确认 ADR → 状态转「已接受」
|
||||||
|
2. [x] 建 contracts 骨架(纯定义,`go test ./internal/contracts` 绿)
|
||||||
|
3. [x] 写 arch-check.sh(import allowlist / 表属主 / 路径字面量三类检查,支持 --mode warn|block)
|
||||||
|
4. [x] 运行 warn 模式,基线报告落 `.doc/audit/`
|
||||||
|
5. [ ] 合 `dev` 占槽冒烟(本轮不占槽;集成槽仍由 workflow-plan Wave 5 持有)
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- [x] `./scripts/arch-check.sh --mode warn` 可运行并产出基线报告
|
||||||
|
- [x] `go vet ./... && go test -short ./...` 全绿(2026-09-14 本工作区;`internal/server` 9.823s)
|
||||||
|
- [x] ADR 状态「已接受」;本文件 status → done
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Checklist: -
|
||||||
|
- Handoff: -
|
||||||
|
- Test report: -
|
||||||
|
|
||||||
|
## 证据 / 备注
|
||||||
|
|
||||||
|
- 依赖图实测(2026-09-14):server 25 内部包 / 17,007 行;bizdev 16 包;详见总方案 §1.1
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: "workflow-plan 409 冻结态守卫 + 文档职责可选治理"
|
||||||
|
status: in_progress
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
owner: implementer_cursor
|
||||||
|
branch: fix/workflow-plan-409-frozen-and-optional-duties
|
||||||
|
worktree: ".worktrees/fix-workflow-plan-409-frozen-and-optional-duties"
|
||||||
|
mutex: workflow
|
||||||
|
---
|
||||||
|
|
||||||
|
# workflow-plan 409 冻结态守卫 + 文档职责可选治理
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
已激活项目的设置页进入明确的冻结只读态;非必需且允许裁剪的文档可选择“不纳入人工审批”,部分职责、必需文档空职责、非成员职责和零治理激活由服务端拒绝;门禁、权限、快照与页面语义保持一致。
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
- `web/src/components/project/ProjectSettingsModal.tsx`:frozen 视图、逐文档审批开关、内联/sticky 错误、并发恢复。
|
||||||
|
- `web/src/api/projectSettings.ts`:保留 status/code/message/issues 的 typed error。
|
||||||
|
- `internal/projectinstance/validator.go`:统一 DutyMode、工作流定义约束、结构与成员校验。
|
||||||
|
- `internal/projectinstance/service.go`:保存/预览/激活重复复核 active members。
|
||||||
|
- `internal/projectinstance/gate.go`、`stage_access.go`、`document_access.go`:none/full/disabled 一致语义。
|
||||||
|
- `internal/projectinstance/store.go`、`internal/workflow/runtime/orchestrator.go`:快照清理、零 assignment rows、零职责运行时契约。
|
||||||
|
- 激活预览统计与相应前后端测试。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- 不修改项目 143 的现有工作流配置,不在验收中终止项目 143。
|
||||||
|
- 不支持部分指派或自动补齐职责。
|
||||||
|
- 不允许项目配置降低工作流定义中必需/阻断文档的治理要求。
|
||||||
|
- 不把“无需人工审批”文档改成无版本、无审计的非受控文档。
|
||||||
|
- 不迁移历史 active 实例;冻结快照继续作为历史 SSOT。
|
||||||
|
|
||||||
|
## 步骤
|
||||||
|
|
||||||
|
1. [x] 定位项目 143 的 409 `PLAN_FROZEN` 根因并落盘 audit。
|
||||||
|
2. [x] 完成方案审核:补齐定义优先、零治理拦截、成员有效性、权限、快照清理和真实 UI 边界。
|
||||||
|
3. [ ] Wave 1:typed API error + frozen 独立只读视图 + sticky/可访问错误 + 两标签竞态恢复。
|
||||||
|
4. [ ] Wave 2:DutyMode、强制/可选任务策略、partial/成员/零治理校验、服务端规范化。
|
||||||
|
5. [ ] Wave 3:门禁、readiness、阶段/文档访问、运行时和预览统计统一。
|
||||||
|
6. [ ] Wave 4:前端逐文档“纳入人工审批”开关及 full/none/partial 交互测试。
|
||||||
|
7. [ ] Wave 5:合 dev、rebuild、真实浏览器新项目全链路;项目 143 只读回归;归档 test report。
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
- [ ] active 项目设置无编辑控件,页面显示冻结原因、工作流/阶段/职责快照和受控终止影响入口。
|
||||||
|
- [ ] 允许裁剪的非必需文档 full/none 均合法;partial 精确拒绝。
|
||||||
|
- [ ] 必需/阻断/不可裁剪文档全空被服务端拒绝。
|
||||||
|
- [ ] 保存和激活均拒绝非 active project member;预览后成员退出也 fail closed。
|
||||||
|
- [ ] 所有文档 none 时可保存草稿但不能激活。
|
||||||
|
- [ ] none 文档仍生成和入账本、项目成员只读可见、创建者可生成/编辑,但无 review/approve 且不阻塞阶段。
|
||||||
|
- [ ] disabled/none 的冻结快照、plan hash 和 assignment rows 不残留旧职责。
|
||||||
|
- [ ] 两标签陈旧保存的 409 在当前视口可见、草稿保留并转 frozen。
|
||||||
|
- [ ] 工程门禁与整改方案中的真实 UI 用例全部有证据;报告回链。
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Audit: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](../../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
- Remediation: [2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md](../../remediation/2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md)
|
||||||
|
- Test report: (Wave 5 归档至 `.doc/test-reports/`)
|
||||||
|
|
||||||
|
## 证据 / 备注
|
||||||
|
|
||||||
|
- dev 项目 143:`lifecycle_status=active`、`active_workflow_key=main`、实例 89 active;409 与三名职责人选择内容无关。
|
||||||
|
- 当前 `parseError` 已读取后端 message;前端改造重点是 typed error、冻结态切换和视口内呈现。
|
||||||
|
- 当前职责校验只检查正数/互异,数据库只外键到 users;项目成员有效性必须补服务端校验。
|
||||||
|
- 当前终止实现会取消运行任务、关闭实例并退回 planning;关闭实例访问 fail closed,因此项目 143 不能作为终止测试对象。
|
||||||
|
- 开始编码前必须先把本任务从当前误置的 `feature/remove-herdr-agent-settings` 工作区迁移至声明的 `fix/workflow-plan-409-frozen-and-optional-duties` 分支/worktree,禁止污染无关分支。
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
---
|
||||||
|
title: "workflow-plan 409 冻结冲突整改 + 文档职责可选治理"
|
||||||
|
status: planned
|
||||||
|
created: 2026-09-14
|
||||||
|
updated: 2026-09-14
|
||||||
|
---
|
||||||
|
|
||||||
|
# workflow-plan 409 冻结冲突整改 + 文档职责可选治理
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
- Audit: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
- Short-term: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](../ops/short-term/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
- gap-matrix: [WORKFLOW-PLAN-FROZEN-OPTIONAL-DUTIES](./gap-matrix.md)
|
||||||
|
|
||||||
|
项目 143 已处于 active,后端按设计返回 409 `PLAN_FROZEN`;前端仍展示可编辑表单,错误又只出现在滚动容器顶部,造成“点击无反应”。新增需求是允许部分非必需文档不设置三权,而不是允许项目配置绕过正式工作流的必需文档。
|
||||||
|
|
||||||
|
## 最终语义
|
||||||
|
|
||||||
|
每个文档任务只有以下三种合法状态:
|
||||||
|
|
||||||
|
| 状态 | 表达 | 生成与账本 | 人工审批 | 阶段门禁 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 关闭 | `enabled=false` | 不生成 | 无 | 不参与 |
|
||||||
|
| 启用、无需人工审批 | `enabled=true` 且三职全空 | 仍生成并进入版本账本 | 不创建三权指派;review/approve 不可用 | 不因批准状态阻塞 |
|
||||||
|
| 启用、纳入人工审批 | `enabled=true` 且三职全满、互异、均为在任项目成员 | 正常生成并进入版本账本 | 完整三权链路 | 按工作流定义的 `BlocksWhenEnabled()` 判定 |
|
||||||
|
|
||||||
|
禁止状态:
|
||||||
|
|
||||||
|
- 仅填写 1–2 个职责:HTTP 400,顶层 `VALIDATION_FAILED`,issue code=`DUTY_PARTIAL_ASSIGNMENT`。
|
||||||
|
- 必需、阻断或不可由创建者裁剪的文档三职全空:issue code=`DUTY_ASSIGNMENT_REQUIRED`。
|
||||||
|
- 激活时没有任何三职完整的受治理文档:issue code=`NO_GOVERNED_DOCUMENT`。
|
||||||
|
- 职责人不是在任项目成员:issue code=`DUTY_ASSIGNEE_NOT_ACTIVE_MEMBER`。
|
||||||
|
|
||||||
|
“三职全空”只能称为“无需人工审批”或“不纳入人工审批”,不得称为“非受控”。正文版本、人工修订、权限审计和来源追踪仍然存在。
|
||||||
|
|
||||||
|
## 关键设计决策
|
||||||
|
|
||||||
|
### D1:工作流定义优先,项目配置不能降低强制治理
|
||||||
|
|
||||||
|
用工作流版本中的任务定义判定是否允许全空。仅当文档任务为非必需、非阻断且允许创建者裁剪时,才允许保持启用但三职全空。至少满足 `Required || Blocking || !IsCreatorConfigurable()` 的任务必须三职完整。
|
||||||
|
|
||||||
|
这样既满足“部分文档不设置”,又避免通过清空职责绕过正式工作流门禁。
|
||||||
|
|
||||||
|
### D2:统一职责分类函数,禁止 validator/gate/access 各自推断
|
||||||
|
|
||||||
|
在 `internal/projectinstance` 建立纯函数,例如:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type DutyMode string
|
||||||
|
const (
|
||||||
|
DutyModeNone DutyMode = "none"
|
||||||
|
DutyModePartial DutyMode = "partial"
|
||||||
|
DutyModeFull DutyMode = "full"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
`classifyDocDuty`、`docTaskRequiresDuty` 和规范化函数由保存校验、激活校验、预览、门禁、阶段访问共同复用。禁止只在 `gate.go` 临时判断三个 ID 是否为零。
|
||||||
|
|
||||||
|
保存前服务端规范化:
|
||||||
|
|
||||||
|
- `enabled=false`:清空三职,避免冻结快照和 plan hash 残留旧 ID;
|
||||||
|
- 用户选择“无需人工审批”:三职一次性全部清空;
|
||||||
|
- partial 不自动补齐或自动清空,必须明确拒绝。
|
||||||
|
|
||||||
|
### D3:保存与激活分层校验
|
||||||
|
|
||||||
|
- 保存草稿:允许所有可选文档暂时全空,便于分步配置;仍拒绝 partial、重复人员、非成员及强制文档全空。
|
||||||
|
- 激活预览与最终激活:除重复执行结构校验外,至少要求一份 `DutyModeFull` 文档,并重新核对三名职责人仍为 active project member。
|
||||||
|
- 激活令牌不能替代成员复核。成员可能在预览后退出,最终事务必须 fail closed,要求重新预览。
|
||||||
|
|
||||||
|
成员校验必须在服务端完成,不能只依赖 `ProjectSettingsModal.members`。如现有 `projectinstance.Service` 无成员查询能力,注入最小只读接口,不允许让 validator 直接访问 HTTP 或前端数据。
|
||||||
|
|
||||||
|
### D4:门禁、权限与运行时采用同一模式
|
||||||
|
|
||||||
|
- `DutyModeNone`:`DocTaskReadiness.Enabled=true`,但不要求 approved;建议补充 `approval_managed=false`,避免把 `Required=false` 误读为“不生成”。
|
||||||
|
- `DutyModeFull`:保持当前 `BlocksWhenEnabled()` 逻辑;非阻断文档可以有审批链但不阻塞阶段。
|
||||||
|
- 无人工审批文档对已有项目查看权限的成员只读可见;项目创建者可触发生成和人工编辑;其他成员不能 run/edit/review/approve。
|
||||||
|
- `document_duty_assignments` 对 none 模式不写行;运行时 `TaskResolution` 的三个职责 ID 为 0 必须是显式支持的合法状态,不得触发硬编码人员回退。
|
||||||
|
- 激活预览增加“启用文档、受治理文档、无需人工审批文档、职责数”四类统计,并列出例外文档,用户确认前可见。
|
||||||
|
|
||||||
|
### D5:冻结态是独立只读页面状态
|
||||||
|
|
||||||
|
`ProjectSettingsModal` 加入 `mode=loading|planning|frozen|error`,由服务端 `can_edit_plan`、`lifecycle_status` 和 `live_instance` 决定:
|
||||||
|
|
||||||
|
- frozen 模式展示当前工作流、版本、active stage、冻结职责快照和“为什么不能修改”;
|
||||||
|
- 不渲染工作流选择、职责下拉框和“校验并预览”按钮;
|
||||||
|
- 受控终止作为独立的高风险区域,先展示影响预览,不与普通编辑混在一起;
|
||||||
|
- 所有 mutation handler 在发请求前再次检查 `can_edit_plan`,避免陈旧状态误提交。
|
||||||
|
|
||||||
|
前端 API 引入保留 `status/code/message/issues` 的 `ProjectSettingsApiError`。现有 `parseError` 已能读取后端 message,错误码映射的目的不是重复翻译,而是让 UI 能按 `PLAN_FROZEN`、`PLAN_REVISION_CONFLICT`、`ERR_CONCURRENT_SWITCH` 做定向恢复。
|
||||||
|
|
||||||
|
错误区域使用 `role=alert` / `aria-live`,放在操作按钮邻近的 sticky 区域并自动聚焦。若两标签页中一页先激活、另一页后保存,第二页收到 409 后应保留未提交草稿、重新读取服务端状态并切换 frozen,而不是静默丢弃。
|
||||||
|
|
||||||
|
### D6:项目 143 不作为破坏性测试数据
|
||||||
|
|
||||||
|
项目 143 只做页面只读回归:打开设置应直接看到冻结说明和现有快照,不出现可编辑控件,不调用保存接口。不得在验收中终止它。
|
||||||
|
|
||||||
|
业务上确需修改项目 143 时,必须先查看终止影响。现有终止实现会取消运行中任务、把旧实例标为 terminated,并将项目退回 planning;关闭实例的文档/阶段访问会 fail closed,直到重新规划并激活。不能把“终止再配置”描述成无影响的普通编辑。
|
||||||
|
|
||||||
|
## 分波次计划
|
||||||
|
|
||||||
|
| 波次 | 内容 | 完成判据 |
|
||||||
|
|---|---|---|
|
||||||
|
| Wave 0 | 冻结现状、补齐本文语义;为新错误码和 DutyMode 建立契约测试 | 文档审核通过;不改项目 143 |
|
||||||
|
| Wave 1 | 前端 typed API error、frozen 独立视图、sticky/可访问错误、两标签竞态恢复 | active 项目没有编辑控件;陈旧保存 409 可见且转 frozen,草稿不丢 |
|
||||||
|
| Wave 2 | 后端统一 DutyMode、工作流定义约束、partial/required/zero-governance 校验、服务端 active-member 复核、快照规范化 | focused Go tests、handler tests 与 race tests PASS |
|
||||||
|
| Wave 3 | 门禁、readiness、阶段/文档访问、运行时解析、激活预览统计统一采用 DutyMode | none/full/disabled 在 gate、access、snapshot、assignment rows 上一致 |
|
||||||
|
| Wave 4 | 前端逐文档“纳入人工审批”开关;切换为 none 原子清空三职;仅在存在 full 文档时提示需要至少 3 名成员 | 前端 unit/Vitest/build PASS;错误定位到具体文档和字段 |
|
||||||
|
| Wave 5 | dev 集成与真实浏览器验收;新建专用测试项目,真实页面配置、激活、生成和阶段推进;项目 143 只读回归 | test report 落盘;失败/无效运行如实留档 |
|
||||||
|
|
||||||
|
## 工程测试矩阵
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
|
||||||
|
- `classifyDocDuty`:none/partial/full 边界,0、负数、重复 ID。
|
||||||
|
- 强制任务全空拒绝;允许裁剪的非必需任务全空通过。
|
||||||
|
- partial 返回顶层 `VALIDATION_FAILED`,issue 精确到 `task_key.duties`。
|
||||||
|
- 保存时职责人非 active member 拒绝;预览后成员退出,激活再次拒绝并要求重新预览。
|
||||||
|
- disabled/none 保存后冻结快照、plan hash、`document_duty_assignments` 不含旧职责。
|
||||||
|
- 激活计划零 full 文档拒绝;至少一 full + 一 none 成功。
|
||||||
|
- readiness:none 不要求 approved;full 按定义决定是否阻塞。
|
||||||
|
- access:项目成员可 view none;仅创建者可 run/edit;任何人均不能对 none 执行 review/approve。
|
||||||
|
- termination 后旧实例仍 fail closed;重新激活后访问范围来自新快照。
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
|
||||||
|
- active + `can_edit_plan=false` 直接进入 frozen,不显示 selector/dropdown/save。
|
||||||
|
- 409 `PLAN_FROZEN`、`PLAN_REVISION_CONFLICT`、`ERR_CONCURRENT_SWITCH` 保留 status/code 并呈现不同恢复动作。
|
||||||
|
- 错误在长表单底部视口内可见、可聚焦且有 `role=alert`。
|
||||||
|
- 审批开关由 full→none 时三职原子清空;none→full 时提示完整分配。
|
||||||
|
- 少于 3 名成员但所有可选文档为 none 时可保存草稿;存在 full/强制任务时给出定向提示。
|
||||||
|
- 激活预览展示 full/none 数量和具体例外文档。
|
||||||
|
|
||||||
|
## 真实 UI 验收矩阵
|
||||||
|
|
||||||
|
最终产品验收只通过真实浏览器页面完成;不得用 API/DB 直写项目、成员、计划、助手回复或文档来替代。网络响应只作浏览器操作后的旁证。
|
||||||
|
|
||||||
|
1. 项目 143:页面打开设置即显示冻结说明、当前 main/active stage/职责快照;无可编辑表单、无 workflow-plan PUT。
|
||||||
|
2. 新项目 A:至少一份强制/full 文档 + 一份允许裁剪的 none 文档,页面保存、预览、激活成功。
|
||||||
|
3. 系统真实执行 full 文档生成与审批;none 文档真实生成、正文和版本账本可见,但不出现审核/批准动作,且不阻塞阶段推进。
|
||||||
|
4. 新项目 B:只设置编制,页面在对应文档内联拒绝;不得发出保存请求。
|
||||||
|
5. 新项目 C:尝试所有文档 none,激活预览在页面明确拒绝“至少保留一份受治理文档”。
|
||||||
|
6. 两个浏览器标签页:A 页激活,B 页保存旧草稿;B 页显示 409、保留草稿并转 frozen。
|
||||||
|
7. 非创建者项目成员能只读查看 none 文档,但看不到 run/edit/review/approve;项目外用户不可见。
|
||||||
|
8. 页面刷新、退出重登及 app restart 后,frozen 状态、职责快照、full/none 模式、阶段就绪保持一致。
|
||||||
|
|
||||||
|
项目 143 仅做只读验证。所有会改变状态的验收使用新建测试项目。
|
||||||
|
|
||||||
|
## 发布门槛
|
||||||
|
|
||||||
|
- `go vet ./...`、`go test -short ./...`、相关 `-race`、`cd web && npm test && npm run build` 全绿。
|
||||||
|
- 保存、预览、激活三条服务端路径都执行同一结构/成员策略,不能只修前端。
|
||||||
|
- 真实 UI 验收覆盖 frozen、full+none、partial、零治理、并发、权限、刷新/重启。
|
||||||
|
- 报告归档至 `.doc/test-reports/` 并回链;模块状态只依据有效证据更新。
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Audit: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
- Short-term: [2026-09-14-workflow-plan-409-frozen-and-optional-duties.md](../ops/short-term/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)
|
||||||
|
- 关联先例: [duty-etag-overwrite 审计](../audit/2026-09-13-document-revision-duty-etag-overwrite.md)
|
||||||
|
- gap-matrix 行: [WORKFLOW-PLAN-FROZEN-OPTIONAL-DUTIES](./gap-matrix.md)
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
| 文档 | 说明 |
|
| 文档 | 说明 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| [gap-matrix.md](./gap-matrix.md) | 发布缺口 SSOT |
|
| [gap-matrix.md](./gap-matrix.md) | 发布缺口 SSOT |
|
||||||
|
| [2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md](./2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md) | 项目设置 409 冻结态守卫 + 非必需文档三权全有或全无 + 成员/门禁/权限一致性 |
|
||||||
| [2026-09-14-controlled-content-canonical-hash-remediation.md](./2026-09-14-controlled-content-canonical-hash-remediation.md) | 项目 143 正文 canonical/raw 哈希漂移、错误语义与重复 hydration 整改 |
|
| [2026-09-14-controlled-content-canonical-hash-remediation.md](./2026-09-14-controlled-content-canonical-hash-remediation.md) | 项目 143 正文 canonical/raw 哈希漂移、错误语义与重复 hydration 整改 |
|
||||||
| [2026-09-13-document-revision-reviewer-duty-sync.md](./2026-09-13-document-revision-reviewer-duty-sync.md) | UI-01 已分配 B=reviewer,受控文档 reviewer_id 硬编码 2,矩阵降为 viewer |
|
| [2026-09-13-document-revision-reviewer-duty-sync.md](./2026-09-13-document-revision-reviewer-duty-sync.md) | UI-01 已分配 B=reviewer,受控文档 reviewer_id 硬编码 2,矩阵降为 viewer |
|
||||||
| [2026-09-13-document-revision-duty-cell-assertion.md](./2026-09-13-document-revision-duty-cell-assertion.md) | UI-13a 整行 option 文案误判编制负责人,须读 select value/badge |
|
| [2026-09-13-document-revision-duty-cell-assertion.md](./2026-09-13-document-revision-duty-cell-assertion.md) | UI-13a 整行 option 文案误判编制负责人,须读 select value/badge |
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
|
|
||||||
| ID | 描述 | 状态 | 证据 | 计划动作 |
|
| ID | 描述 | 状态 | 证据 | 计划动作 |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
|
| WORKFLOW-PLAN-FROZEN-OPTIONAL-DUTIES | active 项目仍展示可编辑职责表单,409 提示在长表单视口外;现有规划又不支持非必需文档“三权全空”,且后端缺少职责人在任成员复核 | **FAIL** | [audit](../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md)。项目 143 返回 `PLAN_FROZEN` 属正确后端行为;缺陷位于冻结态 UX,新增能力还涉及定义优先、零治理拦截、门禁和权限一致性 | [remediation](./2026-09-14-workflow-plan-409-frozen-and-optional-duties-remediation.md);用户批准后从最新 dev 建 fix worktree,按 Wave 1–5 实施并以真实页面验收 |
|
||||||
| DOC-CONTENT-CANONICAL-HASH-DRIFT | 旧受控文档 bootstrap 裁剪 Markdown 双空格硬换行并将 canonical SHA 写入账本/DocGov,但未同步文件;正文读取把完整性冲突误报 404,lease/list sync 又放大并制造刷新后加载死锁 | **PASS(真实 UI)** | [audit](../audit/2026-09-14-project143-controlled-content-404.md);[UI report](../test-reports/2026-09-14-controlled-content-ui-acceptance.md)。代码已合 dev、schema 32、项目 143 定向 `migration_repair`;项目 143 `clean-run3` 与新项目 `full-chain-final` 主链及 restart 全部纯页面 PASS,目标 404=0 | [remediation](./2026-09-14-controlled-content-canonical-hash-remediation.md)。隔离 `TestReconcile_LegacyRepair_PG` 未执行,按用户“本轮只允许页面测试”的边界保留为工程测试债,不作为本轮 UI PASS 的替代证据 |
|
| DOC-CONTENT-CANONICAL-HASH-DRIFT | 旧受控文档 bootstrap 裁剪 Markdown 双空格硬换行并将 canonical SHA 写入账本/DocGov,但未同步文件;正文读取把完整性冲突误报 404,lease/list sync 又放大并制造刷新后加载死锁 | **PASS(真实 UI)** | [audit](../audit/2026-09-14-project143-controlled-content-404.md);[UI report](../test-reports/2026-09-14-controlled-content-ui-acceptance.md)。代码已合 dev、schema 32、项目 143 定向 `migration_repair`;项目 143 `clean-run3` 与新项目 `full-chain-final` 主链及 restart 全部纯页面 PASS,目标 404=0 | [remediation](./2026-09-14-controlled-content-canonical-hash-remediation.md)。隔离 `TestReconcile_LegacyRepair_PG` 未执行,按用户“本轮只允许页面测试”的边界保留为工程测试债,不作为本轮 UI PASS 的替代证据 |
|
||||||
| CHAT-HISTORY-REFRESH | 工作流激活后刷新,对话正文被阶段过滤器清空,但分类数量与锚点仍按全量返回 | **PASS** | [test-report](../test-reports/2026-09-13-chat-history-refresh-test-report.md);代码:`message_scope.go`、`chat_history_auth.go`、goose 00026/00027 | 真实 PI 刷新 E2E 待 dev 容器补录(自动化/PG 已 PASS) |
|
| CHAT-HISTORY-REFRESH | 工作流激活后刷新,对话正文被阶段过滤器清空,但分类数量与锚点仍按全量返回 | **PASS** | [test-report](../test-reports/2026-09-13-chat-history-refresh-test-report.md);代码:`message_scope.go`、`chat_history_auth.go`、goose 00026/00027 | 真实 PI 刷新 E2E 待 dev 容器补录(自动化/PG 已 PASS) |
|
||||||
| CHAT-INTENT-STATE | 真实会话被诊断探针污染;只读查询误启动文档写任务;错误动作与编辑状态刷新后丢失 | **FAIL** | [audit](../audit/2026-09-13-conversation-intent-runtime-state.md);dev `sess-p143-u9` / task run / lease 现场 | [remediation](./2026-09-13-conversation-intent-runtime-state-remediation.md) |
|
| CHAT-INTENT-STATE | 真实会话被诊断探针污染;只读查询误启动文档写任务;错误动作与编辑状态刷新后丢失 | **FAIL** | [audit](../audit/2026-09-13-conversation-intent-runtime-state.md);dev `sess-p143-u9` / task run / lease 现场 | [remediation](./2026-09-13-conversation-intent-runtime-state-remediation.md) |
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
| 意图识别 L1 | 18 类 Intent + 三分支消解 | partial | [system-design/intent-recognition](../system-design/intent-recognition/);[现场专项审计](../audit/2026-09-13-conversation-intent-runtime-state.md) | 明确写动作与只读总结已分流;完整 hard-negative/eval 集仍待补齐 | `intent` |
|
| 意图识别 L1 | 18 类 Intent + 三分支消解 | partial | [system-design/intent-recognition](../system-design/intent-recognition/);[现场专项审计](../audit/2026-09-13-conversation-intent-runtime-state.md) | 明确写动作与只读总结已分流;完整 hard-negative/eval 集仍待补齐 | `intent` |
|
||||||
| 对话 / ctxmgr | 多轮上下文、记忆、摘要 | partial | [system-design/multi-turn-dialogue](../system-design/multi-turn-dialogue/);[刷新恢复专项审计](../audit/2026-09-13-chat-history-refresh-disappears.md) | `CHAT-HISTORY-REFRESH`:作用域写入、同集授权统计、历史回填;语义摘要(D5 backlog) | `chat` |
|
| 对话 / ctxmgr | 多轮上下文、记忆、摘要 | partial | [system-design/multi-turn-dialogue](../system-design/multi-turn-dialogue/);[刷新恢复专项审计](../audit/2026-09-13-chat-history-refresh-disappears.md) | `CHAT-HISTORY-REFRESH`:作用域写入、同集授权统计、历史回填;语义摘要(D5 backlog) | `chat` |
|
||||||
| 智能体小队 | 角色-任务-工作区-黑板 | done | [system-design/agent-squad](../system-design/agent-squad/);[test 2026-09-07](../test-reports/2026-09-07-dynamic-squad-planning-test-report.md) | 非关键步骤降级(A5/A6 backlog) | `squad` |
|
| 智能体小队 | 角色-任务-工作区-黑板 | done | [system-design/agent-squad](../system-design/agent-squad/);[test 2026-09-07](../test-reports/2026-09-07-dynamic-squad-planning-test-report.md) | 非关键步骤降级(A5/A6 backlog) | `squad` |
|
||||||
| 工作流治理 | 三正式工作流 + 阶段门禁 | done | [remediation/three-workflows](../remediation/2026-09-12-three-workflows-strict-governance-remediation.md);[test 2026-09-12](../test-reports/2026-09-12-three-workflows-governance-remediation-test-report.md) | Phase2 设计器 partial | `workflow` |
|
| 工作流治理 | 三正式工作流 + 阶段门禁 | partial | [remediation/three-workflows](../remediation/2026-09-12-three-workflows-strict-governance-remediation.md);[test 2026-09-12](../test-reports/2026-09-12-three-workflows-governance-remediation-test-report.md);[冻结态/职责可选审计](../audit/2026-09-14-workflow-plan-409-frozen-and-optional-duties.md) | 三正式工作流与冻结后端已完成;active 设置页冻结 UX、非必需文档职责可选及成员/门禁/权限一致性待按 Wave 1–5 实施 | `workflow` |
|
||||||
| 纯软件项目自举 | 软件项目创建、Bootstrap 基线、Delivery 迭代与受控工程交接 | planned | [详细设计](../system-design/2026-09-14-software-project-bootstrap-workflow.md);[现状调研](../ops/research/2026-09-14-software-project-bootstrap-current-state.md);[验收计划](../ops/checklists/2026-09-14-software-project-bootstrap-acceptance.md) | 当前仅完成方案;目录/模板/执行器/创建向导/真实 PI UI 均未实施。受控代码执行单列后续 Epic | `workflow-design` |
|
| 纯软件项目自举 | 软件项目创建、Bootstrap 基线、Delivery 迭代与受控工程交接 | planned | [详细设计](../system-design/2026-09-14-software-project-bootstrap-workflow.md);[现状调研](../ops/research/2026-09-14-software-project-bootstrap-current-state.md);[验收计划](../ops/checklists/2026-09-14-software-project-bootstrap-acceptance.md) | 当前仅完成方案;目录/模板/执行器/创建向导/真实 PI UI 均未实施。受控代码执行单列后续 Epic | `workflow-design` |
|
||||||
| IAM / 鉴权 | 项目级 API 鉴权 + RBAC | partial | [audit/authorization](../audit/authorization-matrix-v2.md);[刷新恢复专项审计](../audit/2026-09-13-chat-history-refresh-disappears.md) | 历史读取端点补统一会话 owner + 阶段授权;旧 API 410 标注(BUG-11 partial) | `authz` |
|
| IAM / 鉴权 | 项目级 API 鉴权 + RBAC | partial | [audit/authorization](../audit/authorization-matrix-v2.md);[刷新恢复专项审计](../audit/2026-09-13-chat-history-refresh-disappears.md) | 历史读取端点补统一会话 owner + 阶段授权;旧 API 410 标注(BUG-11 partial) | `authz` |
|
||||||
| 文档治理 | 法定文档生命周期 + 租约 + 受控正文读取 | done | [test docgov](../test-reports/2026-09-08-docgov-lifecycle-and-claiming-framework-test-report.md);[项目 143 正文 404 审计](../audit/2026-09-14-project143-controlled-content-404.md);[受控正文真实 UI 验收报告](../test-reports/2026-09-14-controlled-content-ui-acceptance.md);[UI-06 缓存竞争审计](../audit/2026-09-14-document-revision-refresh-cache-race.md);[整改](../remediation/2026-09-14-controlled-content-canonical-hash-remediation.md) | 生命周期/租约保留。W1–W6 + 审查修补 + UI hydrate 资格 + 晚到 list sync 修复均已合入 dev。项目 143 纯 UI 回归(clean-run3)与新项目全链路(main + post-restart)已全量真实页面验证通过(PASS) | `docgov` |
|
| 文档治理 | 法定文档生命周期 + 租约 + 受控正文读取 | done | [test docgov](../test-reports/2026-09-08-docgov-lifecycle-and-claiming-framework-test-report.md);[项目 143 正文 404 审计](../audit/2026-09-14-project143-controlled-content-404.md);[受控正文真实 UI 验收报告](../test-reports/2026-09-14-controlled-content-ui-acceptance.md);[UI-06 缓存竞争审计](../audit/2026-09-14-document-revision-refresh-cache-race.md);[整改](../remediation/2026-09-14-controlled-content-canonical-hash-remediation.md) | 生命周期/租约保留。W1–W6 + 审查修补 + UI hydrate 资格 + 晚到 list sync 修复均已合入 dev。项目 143 纯 UI 回归(clean-run3)与新项目全链路(main + post-restart)已全量真实页面验证通过(PASS) | `docgov` |
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
| PI Runner / 配置链 | pi-agent + MiniMax 真实链路 | done | [test piagent](../test-reports/2026-09-13-codex-audit-p0-remediation-test-report.md);[container-env](../test-reports/2026-09-13-container-env-configuration-test-report.md) | — | `pi-agent` |
|
| PI Runner / 配置链 | pi-agent + MiniMax 真实链路 | done | [test piagent](../test-reports/2026-09-13-codex-audit-p0-remediation-test-report.md);[container-env](../test-reports/2026-09-13-container-env-configuration-test-report.md) | — | `pi-agent` |
|
||||||
| 三环境运维 | dev/test/main + 共享 KB | done | [PROJECT.md](../../PROJECT.md) §4;四环境 healthy | — | `infra` |
|
| 三环境运维 | dev/test/main + 共享 KB | done | [PROJECT.md](../../PROJECT.md) §4;四环境 healthy | — | `infra` |
|
||||||
| CI / E2E | PG 集成 + 真实 PI E2E | partial | [gap-matrix BUG-08](../remediation/gap-matrix.md);[test pg-ci](../test-reports/2026-09-12-pg-ci-soak-e2e-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) | CI `TEST_DATABASE_URL` 隔离库仍缺;文档运行时真实 UI 已按 UI_PHASE 分文件且 post-restart 断言最终 A1 正文。`-race` 全包并行 TempDir flake 为基建残留 | `ci` |
|
| CI / E2E | PG 集成 + 真实 PI E2E | partial | [gap-matrix BUG-08](../remediation/gap-matrix.md);[test pg-ci](../test-reports/2026-09-12-pg-ci-soak-e2e-test-report.md);[运行时 UI Round20](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) | CI `TEST_DATABASE_URL` 隔离库仍缺;文档运行时真实 UI 已按 UI_PHASE 分文件且 post-restart 断言最终 A1 正文。`-race` 全包并行 TempDir flake 为基建残留 | `ci` |
|
||||||
|
| 模块化单体解耦 | `internal/contracts` + arch-check 门禁 | partial | [ADR](../adr/2026-09-14-modular-monolith-decoupling.md);[设计](../system-design/2026-09-14-module-decoupling-architecture.md);[Wave 0 基线](../audit/2026-09-14-arch-check-baseline.md) | Wave 0 仅骨架与 warn 基线(21 条属主/路径债)。不改业务、不占集成槽。Wave 1 起独立分支收口 | `arch` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,427 @@
|
|||||||
|
# 模块解耦架构设计与实施方案(模块化单体 + 契约通信)
|
||||||
|
|
||||||
|
> 日期:2026-09-14
|
||||||
|
> 状态:**Wave 0 已落地(契约骨架 + arch-check warn 基线);Wave 1+ 待独立分支**
|
||||||
|
> 关联 ADR:[../adr/2026-09-14-modular-monolith-decoupling.md](../adr/2026-09-14-modular-monolith-decoupling.md)
|
||||||
|
> 任务条目:[../ops/short-term/2026-09-14-module-decoupling-wave0.md](../ops/short-term/2026-09-14-module-decoupling-wave0.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. 一句话
|
||||||
|
|
||||||
|
把「server+bizdev 双组合根、隐形闭包桥、共享上帝上下文、多写者表、隐式路径契约」的现状,重构为**模块化单体**:各功能模块只通过 `internal/contracts` 中约定好的接口与 DTO 通信(含显式输入/输出/事件),编排逻辑下沉到应用层,传输与装配收敛到边界,最终用 CI 架构检查强制执行边界。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 现状诊断(证据汇总)
|
||||||
|
|
||||||
|
### 1.1 依赖图现状(go list 实测,2026-09-14)
|
||||||
|
|
||||||
|
```
|
||||||
|
server (17,007 行, 导入全部 25 个内部包) ← 唯一组合根 + 事件总线 + 编排
|
||||||
|
├─> chat (10 包) ──> coordinator (9 包) ──> bizdev (16 包) ──> squad/kb/docgov/...
|
||||||
|
│ │ │ │
|
||||||
|
│ │ └─> projectinstance ─> projectworkflow ─> auth
|
||||||
|
│ └─> intent ──> bizdev(反向依赖,仅因 MaturityStage 枚举未下沉)
|
||||||
|
└─> taskrun ──> bizdev + projectworkflow + ctxmgr + stream
|
||||||
|
```
|
||||||
|
|
||||||
|
突出问题不是「有依赖」,而是依赖的**方式**:
|
||||||
|
|
||||||
|
| # | 耦合热点 | 证据 | 后果 |
|
||||||
|
|---|---------|------|------|
|
||||||
|
| H1 | **server 是四合一**:组合根 + 跨模块事件总线 + 领域门卫 + 文件系统/SSE 细节持有者 | `server/server.go:52-177`(NewServer 装配 10+ 服务并织入运行时闭包);`server.go:32-49`(Server 16 个字段散点访问) | 任何模块的构造签名变化必改 NewServer + 字段表 + Routes |
|
||||||
|
| H2 | **bizdev 是第二组合根 + 万能门面** | `bizdev/service.go:53-190` 装配 10+ 下游;依赖 16 个内部包 | 新能力默认挂到 bizdev,职责无限膨胀 |
|
||||||
|
| H3 | **上帝上下文 `UserProjectContext`**:22 字段横跨 intent/ctxmgr/squad/bizdev 四包类型,chat 与 coordinator **双向直改** | 定义 `coordinator/context.go:78-114`;coordinator 内 `userCtx.` 出现 175 次;chat `session.go:386-410` 做字段级合并补丁 | 任何一方加字段,三方连锁改;「若为空则回填」逻辑是多方写同一结构的直接症状 |
|
||||||
|
| H4 | **工作区路径是最大隐性契约**:创建(squad.WorkspaceEngine)、命名、持久化(docgov/taskrun 各存一份)、校验(workspace.Resolver)、绕过(bizdev/coordinator 直拼)五权分立;另有 3 处硬编码 `data/dev` | `bizdev/service.go:1259-1282` 跨包覆写 squad 的 `workspace.json`/`blackboard.json`;`server/project_doc_handlers.go:16-19` 用 `DataDir/../..` 倒推仓库根;`coordinator/router.go:281-284` 注释承认容器路径映射隐式约定 | 改工作区布局会静默破坏 5 个包 |
|
||||||
|
| H5 | **squad 的文件 schema 与 DTO 是事实公共 API 但无版本化** | `bizdev/types.go:14-15,40,56-59` 大量 `type X = squad.X` 别名直透 HTTP;`coordinator/router.go:246-247` 直调 `LoadOrCreateLedger/LoadTrace`;`coordinator/engine.go:757-784` 直读写 `blackboard.json` | squad 内部文件格式任何改动静默破坏 3 个下游 |
|
||||||
|
| H6 | **共享表多写者**:`document_records` 被 docgov/docduty/docrevision/workspace/cmd 五方 UPDATE;`freeform_documents` 被 docrevision 反向写 | `docduty/store.go` 4 处、`docrevision/pg_store.go:235/432`、`workspace/resolver.go` 1 处 | 哈希/状态一致性靠约定而非单一写者 |
|
||||||
|
| H7 | **编排写在 handler / 跨 6+ 模块的巨型函数** | `chat/session.go:238 sendMessageInternal`(跨 9 模块);`bizdev/service.go:295 ExecuteTask`(鉴权+检索+编排+治理+持久化+流式六合一);`bizdev/service.go:1074 executeStageInternal`;`coordinator/engine.go:690/1020` | 修一处动全身,测试只能走全链路 |
|
||||||
|
| H8 | **server 内闭包桥替代显式事件流**:docrevision outbox 桥、审批钩子、reminder 轮询、taskrun executor/finalizer、ctxmgr approved-hook 全在 NewServer 用闭包缝合 | `server/server.go:114-175`;`workflow_approval_hook.go:14`;`notification_reminder_loop.go` | 模块间没有 import 关系却强耦合,且全部以 server 为中转 hub |
|
||||||
|
| H9 | **反向/错位依赖**:intent→bizdev(MaturityStage 未下沉);ctxmgr→docgov(case_indexing 接受 docgov 实体);领域层依赖 UI 事件格式(squad→stream);同名撞车(squad.ContextManager vs internal/ctxmgr) | `intent` 包 import bizdev;`ctxmgr/case_indexing.go:20` | 分层倒置,概念混淆 |
|
||||||
|
| H10 | **传输协议与基础设施细节内嵌 server**:SSE 帧手写、SPA 路径探测、`output.md` 哈希、`repoRoot()` 倒推;错误响应双轨(纯文本 vs JSON) | `server/sse.go:65-72,315-441`;`task_finalize.go:127-138`;`handlers.go:826/853` vs `project_doc_handlers.go:78` | 前端需双轨解析;脚本(`scripts/backfill_task_run_projection.go:44`)必须构造完整 Server 才能回填数据 |
|
||||||
|
| H11 | **PI Runner 协议 schema 三处手写**(Go / runner.mjs / pi-bridge index.mjs),无共享 IDL;CLI 通道靠「stdout 最后一行 JSON」脆弱约定 | `agent/piagent.go:141-149`;`deploy/pi-agent/runner.mjs:388`;`tools/pi-bridge/index.mjs` | 字段漂移只能靠集成测试兜底 |
|
||||||
|
|
||||||
|
### 1.2 正面样板(保持并推广)
|
||||||
|
|
||||||
|
并非全盘皆坏,以下边界是**本仓最好的实践**,目标架构将它们确立为全局标准:
|
||||||
|
|
||||||
|
- **chat_* 表族 0 越界**:全仓所有对话记录访问都经 `ctxmgr`(grep 实测 0 绕过)——这就是「对话记录模块」该有的样子;
|
||||||
|
- **kb 事实上是纯检索包**:仅 2 个调用方,接口 `Hub` 只有 Search/LookupCite/Stats/Close;唯一的越界(`MaterializeSnapshot` 写盘)是死代码,可删;
|
||||||
|
- **PI Runner 的 `Backend` 接口**(`agent/backend.go:28-31`)切换点单一、Bearer 认证明确;
|
||||||
|
- **docrevision 事件 outbox 同事务提交**——事件总线的雏形已在,只差泛化;
|
||||||
|
- **replyexport 纯函数**、**fsutil 原子写**——无状态基础设施包的正确形态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 目标架构
|
||||||
|
|
||||||
|
### 2.1 六条设计原则
|
||||||
|
|
||||||
|
1. **契约优先(Contract-first)**:模块间只允许两种通信——①调用 `internal/contracts` 中定义的接口(显式输入结构 + 显式输出结构);②发布/订阅 `contracts/events` 中的类型化事件。禁止直接 import 对方内部类型、禁止读对方文件、禁止写对方表。
|
||||||
|
2. **单一写者(Single Writer)**:每张 PG 表、每类文件/目录恰好一个属主模块;其他模块只能通过属主的接口读写。
|
||||||
|
3. **编排下沉(Orchestration down)**:跨模块流程属于应用层(use case),不允许出现在 HTTP handler、也不允许埋在某个领域模块里(bizdev 现状)。
|
||||||
|
4. **模块自带边界(Self-contained)**:每个模块的输入由调用方(系统)准备齐全(含工作区、材料、上下文),输出只落到约定位置/返回值;模块不「顺便」帮别人做事(如 kb 不写文件、squad 不发 SSE)。
|
||||||
|
5. **传输与装配收敛到边缘**:HTTP/SSE/CLI 只是适配器;组合根唯一(`cmd/server` → `internal/app`)。
|
||||||
|
6. **可执行的门禁**:边界必须能被机器检查(包依赖 allowlist 进 CI),否则会再次腐化。
|
||||||
|
|
||||||
|
### 2.2 目标分层图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ cmd/server + internal/app 唯一组合根(wiring、生命周期) │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ internal/server(瘦传输层) HTTP 路由 + SSE 帧 + 错误信封 │
|
||||||
|
│ 只做:参数解析 → 调应用服务 → DTO 序列化;禁止编排/SQL/拼路径 │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ 应用编排层(L1/L2 拆自 chat/coordinator/bizdev) │
|
||||||
|
│ chatflow 一轮对话的 use case(识别→协调→动作→落账) │
|
||||||
|
│ stageflow 阶段推进 / 任务执行的 use case │
|
||||||
|
│ docflow 文档生命周期 use case │
|
||||||
|
│ 它们 import contracts(接口) + 订阅/发布事件;不 import 对方实现 │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ internal/contracts ★ 新增:接口 + DTO + 事件 + 共享枚举(纯定义) │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ 领域能力模块(互不 import,只依赖 contracts + 自有 infra) │
|
||||||
|
│ squadrun 智能体小队执行器(沙箱内运行,产出 manifest) │
|
||||||
|
│ kb 法规/知识检索(纯查,无解释无写盘) │
|
||||||
|
│ chatlog 对话记录存储与查询(chat_* 表唯一属主) │
|
||||||
|
│ contextasm 上下文装配(记忆/摘要/检索召回,供编排层组装 prompt) │
|
||||||
|
│ docgov 文档生命周期治理(document_records 唯一属主) │
|
||||||
|
│ docrevision 版本账本(snapshots/versions/...) │
|
||||||
|
│ docquery 文档列表与正文读取(统一读模型,含自由文档) │
|
||||||
|
│ intent 意图识别(纯函数,零业务依赖) │
|
||||||
|
│ taskrun 任务运行生命周期(含两阶段占位完整协议) │
|
||||||
|
│ workflow* 工作流/实例治理 │
|
||||||
|
│ workspace 工作区路径唯一所有者(版本化 manifest) │
|
||||||
|
│ notification / replyexport / quote / template / agent(PI) │
|
||||||
|
├─────────────────────────────────────────────────────────────────┤
|
||||||
|
│ 基础设施:db / storage(CAS) / fsutil / stream(事件+传输) │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 依赖规则(arch-check 强制项)
|
||||||
|
|
||||||
|
| 规则 | 检查方式 |
|
||||||
|
|------|---------|
|
||||||
|
| R1 `contracts` 不 import 任何业务包 | allowlist:contracts 的内部 import 仅限空 |
|
||||||
|
| R2 领域模块之间禁止互相 import(跨模块只走 contracts) | 模块→模块 import 检查 |
|
||||||
|
| R3 领域模块禁止 import `server` / 应用编排层 | 方向检查 |
|
||||||
|
| R4 `server` 禁止 import 领域模块**实现**(只 import contracts + app 层服务接口) | allowlist |
|
||||||
|
| R5 每张表名只允许出现在属主包(grep SQL 字符串) | 表属主清单检查 |
|
||||||
|
| R6 工作区路径字面量(`workspaces/`、`output.md`、`data/dev` 等拼接)只允许出现在 `workspace`/`squadrun` | 字符串扫描 |
|
||||||
|
| R7 领域模块禁止 import `stream` 的传输层(只允许中性的进度事件接口) | allowlist |
|
||||||
|
|
||||||
|
实施载体:`scripts/arch-check.sh`(基于 `go list -deps` + grep 的表属主/路径字面量检查),先以 **warn** 跑两周建立基线,再转 **block** 进 CI 与集成槽位冒烟。
|
||||||
|
|
||||||
|
### 2.4 模块边界总表
|
||||||
|
|
||||||
|
| 模块(目标包) | 现状来源 | 唯一职责 | 允许依赖 | 明确禁止 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `contracts` | 新增 | 接口/DTO/事件/共享枚举定义 | 无 | 任何实现、IO |
|
||||||
|
| `squadrun` | `squad`(重组) | 在给定沙箱内编排角色-步骤,产出契约化交付物与 manifest | contracts, agent, template, quote, fsutil | 访问 DB;发 SSE;感知 docgov/chat/业务字段语义;写沙箱与 OutputDir 之外 |
|
||||||
|
| `kb` | `kb` | 法规/资产/能力三域检索 + 引用定位 | contracts, db | 解释、成文、写盘、缓存工作区 |
|
||||||
|
| `chatlog` | `ctxmgr` 拆分 | chat_* 表族读写、会话/消息/轮次/摘要/未读查询 | contracts, db, replyexport(策略) | 上下文装配、prompt 相关逻辑 |
|
||||||
|
| `contextasm` | `ctxmgr` 拆分 | 记忆/摘要/召回/上下文组装(供编排层) | contracts, chatlog(接口), kb(接口) | 直接被 HTTP 暴露 |
|
||||||
|
| `docgov` | `docgov` | 状态机、角色权限、租约、`document_records` 唯一写者 | contracts, db, fsutil | 被他人 UPDATE 属主表 |
|
||||||
|
| `docrevision` | `docrevision` | 版本/快照/变更事件账本、outbox | contracts, db, fsutil | 直写 `document_records`/`freeform_documents`(改走 docgov/freedoc 接口) |
|
||||||
|
| `docquery` | 新增(聚合读) | 文档列表/正文/版本的统一只读模型 | contracts, docgov/docrevision/freedoc 的读接口 | 写操作 |
|
||||||
|
| `intent` | `intent` | 消息→意图分类(纯规则) | contracts | import bizdev/任何业务模块 |
|
||||||
|
| `taskrun` | `taskrun` | 任务运行记录、两阶段占位、finalize 投影的完整协议 | contracts, db, stream(事件) | 在 server 里补协议缺口的现状 |
|
||||||
|
| `workspace` | `workspace`(强化) | 工作区路径解析/创建/manifest 版本化 | contracts, fsutil | 他人拼路径 |
|
||||||
|
| `notification` | `notification` | 通知投递 + outbox 轮询(事件订阅者) | contracts, db | 被闭包桥接 |
|
||||||
|
| `agent` | `agent` | PI Runner 双通道调用(协议单一 schema 源) | contracts | — |
|
||||||
|
| `chatflow`/`stageflow`/`docflow` | `chat`+`coordinator`+`bizdev` 拆解 | 跨模块 use case 编排、`UserProjectContext` 唯一属主 | contracts + 各模块接口 | 被 server/其他领域模块 import 实现细节 |
|
||||||
|
| `server` | `server`(瘦身) | HTTP/SSE 适配、错误信封、SPA | contracts, app 层服务接口 | 编排、SQL、拼路径、领域闭包 |
|
||||||
|
| `app` | 新增 | wiring、事件总线注册、生命周期 | 所有模块构造函数 | 被 server 反向 import |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 核心接口契约设计
|
||||||
|
|
||||||
|
> 以下签名是**设计稿**(Go 伪代码),落地时放 `internal/contracts/`(按域分文件)。所有 DTO 带 json tag,一旦对外发布即视为版本化契约,变更需走 ADR。
|
||||||
|
|
||||||
|
### 3.1 squadrun —— 智能体小队执行器(对应用户示例 1)
|
||||||
|
|
||||||
|
**核心思想**:系统(编排层)负责「备料 + 备工作区 + 定产出契约」,小队只管「在沙箱里干活 + 按契约交货」。小队不再感知业务字段语义、DB、SSE。
|
||||||
|
|
||||||
|
```go
|
||||||
|
// contracts/squadrun.go
|
||||||
|
type SquadRunner interface {
|
||||||
|
Plan(ctx context.Context, spec RunSpec) (*RunPlan, error) // 只规划不执行
|
||||||
|
Run(ctx context.Context, spec RunSpec, r ProgressReporter) (*RunManifest, error)
|
||||||
|
Inspect(ctx context.Context, ws WorkspaceRef) (*RunState, error) // 取代外部直读 blackboard.json
|
||||||
|
Resume(ctx context.Context, ws WorkspaceRef, m RunModification, r ProgressReporter) (*RunManifest, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RunSpec struct {
|
||||||
|
RunID string // 系统生成并已在 taskrun 落账
|
||||||
|
Workspace WorkspaceRef // 系统已创建好的沙箱(见 3.6)
|
||||||
|
Goal string // 任务目标
|
||||||
|
Inputs InputBundle // 全部输入(小队不做材料提取/检索/上下文装配)
|
||||||
|
Output OutputContract // 产出契约
|
||||||
|
Budget Budget
|
||||||
|
Backend string // pi-agent | cursor-cli | stub
|
||||||
|
}
|
||||||
|
|
||||||
|
// 系统准备的一切输入
|
||||||
|
type InputBundle struct {
|
||||||
|
Materials []FileInput // 已提取文本的材料(CAS 引用 + 文本)
|
||||||
|
Knowledge []KnowledgeHit // kb 检索结果(含 CiteKey)
|
||||||
|
PriorDocs []DocInput // 前序文档(已过阶段权限过滤)
|
||||||
|
Context []FileInput // user/project/memory 分级上下文
|
||||||
|
Facts map[string]string // 结构化业务参数(ProjectName/DeviceType… 的泛化)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 产出契约:完成后只把这些写到 OutputDir,并生成 manifest
|
||||||
|
type OutputContract struct {
|
||||||
|
OutputDir string
|
||||||
|
Artifacts []ArtifactSpec // {Name, Filename, Format, Required}
|
||||||
|
ManifestName string // 缺省 run-manifest.json
|
||||||
|
}
|
||||||
|
|
||||||
|
// 唯一对外输出事实源:下游只读 manifest,不再扫目录猜文件
|
||||||
|
type RunManifest struct {
|
||||||
|
RunID string
|
||||||
|
Status string // succeeded | failed | partial
|
||||||
|
Artifacts []ArtifactResult // {Name, RelPath, SHA256, Bytes}
|
||||||
|
Steps []StepResult // {Key, Role, Status, DurationMS, RetryCount}
|
||||||
|
Gaps []string // 质量门禁未覆盖项(黑板 gaps 的投影)
|
||||||
|
Evidence map[string]string
|
||||||
|
SchemaVer int // 文件契约版本化(H5 的解药)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**配套迁移决定**:
|
||||||
|
- bizdev 的 `type X = squad.X` 别名(`bizdev/types.go:14-15,40,56-59`)全部解绑,bizdev/server 改用 contracts DTO;
|
||||||
|
- bizdev 停止覆写 squad 状态文件(`bizdev/service.go:1259-1282` 改调 `Resume`/`Inspect`);
|
||||||
|
- coordinator 停止直读 `blackboard.json`、直调 `LoadOrCreateLedger/LoadTrace`(`coordinator/router.go:246-258`、`engine.go:757-784`),改走 `Inspect`;
|
||||||
|
- squad 不再 import `stream`:`StepNotify` 回调改为 `contracts.ProgressReporter` 接口,SSE 转换由应用层完成。
|
||||||
|
|
||||||
|
### 3.2 kb —— 法规知识库(对应用户示例 2)
|
||||||
|
|
||||||
|
现状已接近目标,只需**收紧**:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// contracts/kb.go
|
||||||
|
type KnowledgeBase interface {
|
||||||
|
Search(ctx context.Context, q SearchQuery) (*SearchResult, error)
|
||||||
|
LookupCite(ctx context.Context, citeKey string) (*CiteRef, bool, error)
|
||||||
|
Stats(ctx context.Context) (*KBStats, error)
|
||||||
|
}
|
||||||
|
type SearchQuery struct { Text string; Domain []string; TopK int } // 只承载检索语义
|
||||||
|
type KnowledgeHit struct { CiteKey, Title, Section, Snippet string; Score float64 }
|
||||||
|
```
|
||||||
|
|
||||||
|
**决定**:① 删除死代码 `kb.MaterializeSnapshot`(写盘行为,全仓无调用);② 「解释/成文」永远留在编排层(现状 coordinator 拼 Markdown 的**位置**正确,但拼装代码应移到应用层 chatflow);③ bizdev façade 退役后由编排层直接持有该接口。
|
||||||
|
|
||||||
|
### 3.3 chatlog —— 对话记录(对应用户示例 3)
|
||||||
|
|
||||||
|
把 `ctxmgr` 拆成 `chatlog`(存储/查询,chat_* 表唯一属主——现状 0 越界,只需换壳)与 `contextasm`(上下文装配,见 3.6)。
|
||||||
|
|
||||||
|
```go
|
||||||
|
// contracts/chatlog.go
|
||||||
|
type ChatLog interface {
|
||||||
|
// 全部以「项目ID + 用户ID」等显式参数进出,返回带 json tag 的 DTO
|
||||||
|
ListSessions(ctx context.Context, f SessionFilter) ([]SessionSummary, error)
|
||||||
|
ListMessages(ctx context.Context, f MessageFilter) ([]MessageRecord, error) // 窗口/日期轴/分页
|
||||||
|
GetTurn(ctx context.Context, ref TurnRef) (*TurnRecord, error)
|
||||||
|
SearchMessages(ctx context.Context, f SearchFilter) ([]MessageRecord, error)
|
||||||
|
// + ListDigests / MarkRead / Import / Recategorize / Export(沿用现有能力面)
|
||||||
|
RecordMessage(ctx context.Context, w MessageWrite) (MessageID, error) // 仅编排层使用
|
||||||
|
}
|
||||||
|
type SessionFilter struct { ProjectID, UserID string; Status string; Page, PageSize int }
|
||||||
|
```
|
||||||
|
|
||||||
|
**迁移要点**:现有 20+ 条 `/api/v1/chat/*` 路由 **URL 不变**,handler 换成「解析参数 → 调 chatlog → DTO 序列化」三行式;DTO 用 golden 文件做契约测试(`web/src/api/chatSessions.ts` 等前端类型即 golden 的镜像);`export_policy` 判定保留 replyexport 纯函数。
|
||||||
|
|
||||||
|
### 3.4 docquery —— 文档列表与读取(对应用户示例 4)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// contracts/docquery.go
|
||||||
|
type DocQuery interface {
|
||||||
|
ListDocuments(ctx context.Context, f DocFilter) ([]DocSummary, error)
|
||||||
|
// DocFilter{ProjectID, UserID, Category, Status, WorkflowID, Page...}
|
||||||
|
GetDocument(ctx context.Context, ref DocRef) (*DocContent, error)
|
||||||
|
// DocRef{DocID 或 {ProjectID, DutyKey}; Version/Revision 可选,缺省 head}
|
||||||
|
BatchGetDocuments(ctx context.Context, refs []DocRef) ([]DocContent, error)
|
||||||
|
}
|
||||||
|
type DocSummary struct { DocID, Title, Category, Status, Version, Owner string; UpdatedAt time.Time; HasPendingRevision bool }
|
||||||
|
type DocContent struct { Summary DocSummary; BodyMarkdown, ContentSHA256 string; Blocks []Block }
|
||||||
|
```
|
||||||
|
|
||||||
|
**要点**:① 受控文档(docgov/docrevision)与自由文档(freedoc)统一进一个读模型,前端不再两套 API 语义;② 授权在应用层入口统一(消灭「阶段权限过滤散在 handler」`server/handlers.go:928-930`);③ 正文来源(受控正文/工作区 output.md/CAS blob)由 docquery 内部决定——现状**路径知识 4 处重复**(docgov/project_docs.go:144、docrevision/projection.go:66、server/project_doc_handlers.go:15、bizdev/service.go:160-190)收敛为 1 处。
|
||||||
|
|
||||||
|
### 3.5 docgov / docrevision —— 治理与账本
|
||||||
|
|
||||||
|
- docgov 保持状态机/租约/角色权限,新增**端口方法**承接外部写需求:`BindWorkspace(ctx, docID, wsRef)`、`UpdateGovernanceFields(ctx, docID, patch)`——docduty/docrevision/cmd 对 `document_records` 的直写全部改走这些方法(解 H6);
|
||||||
|
- docrevision 只拥有账本表;对 `document_records` 2 处、`freeform_documents` 2 处 UPDATE(`pg_store.go:235/255/424/432`)改调 docgov/freedoc 接口;
|
||||||
|
- 正文单写路径:受控正文唯一写者 = docgov(内部委托 docrevision 账本);squad 工作区文件只经 `RunManifest` → 应用层 docflow 投影,结束「三个写者写同一个 output.md」。
|
||||||
|
|
||||||
|
### 3.6 intent / taskrun / workspace / contextasm / 事件总线
|
||||||
|
|
||||||
|
- **intent**:`MaturityStage` 等共享枚举下沉 `contracts/vocabulary.go`,删除 intent→bizdev import(解 H9);Recognizer 保持纯函数。
|
||||||
|
- **taskrun**:把散在 server 的两阶段占位协议(`task_run_handlers.go` 占位创建→执行→finalize 投影 + `task_finalize.go:127-138` 哈希 output.md)整体搬进 taskrun 包,server 只调 `taskrun.Execute(ctx, TaskRequest, ExecutorPort)`;ops 回填脚本改依赖 taskrun 服务而非 `server.NewServer`。
|
||||||
|
- **workspace**:`Resolver` 升级为唯一路径所有者:`Create(ctx, CreateSpec) (WorkspaceRef, error)` / `Resolve(id) (WorkspaceRef, error)`;产出 `WorkspaceRef{ID, Root, OutputDir, ManifestPath}` 这种**句柄**而非裸字符串路径,从类型上杜绝各包自拼路径(解 H4);3 处硬编码 `data/dev` 一并清除。
|
||||||
|
- **contextasm**:承接 ctxmgr 中「为 prompt 服务」的部分(三层记忆、滑动窗口、摘要、召回、改写缓存 C1~C5);`contextasm` 依赖 chatlog/kb 的**接口**而非实现。
|
||||||
|
- **事件总线**:泛化 docrevision 已有的 outbox 模式为 `contracts/events`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Event interface{ EventName() string; OccurredAt() time.Time }
|
||||||
|
// 首批事件(均有隐式对应物,只是从闭包改为显式类型):
|
||||||
|
// DocumentApproved / DocumentRejected / DocumentLeaseChanged
|
||||||
|
// RunFinalized{RunID, WorkspaceID, Manifest} / StageAdvanced / StageGateDenied
|
||||||
|
// MessageRecorded / NotificationRequested
|
||||||
|
type EventBus interface {
|
||||||
|
Publish(ctx context.Context, ev Event) error // 与业务同事务写 outbox_events
|
||||||
|
Subscribe(name string, h EventHandler) // app 层注册
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
server.go:114-175 的 outbox 桥、`workflow_approval_hook`、reminder 轮询、ctxmgr approved-hook 全部改写为事件订阅者(解 H8)。
|
||||||
|
|
||||||
|
### 3.7 PI Runner 协议单一来源
|
||||||
|
|
||||||
|
新增 `configs/schemas/pi-runner.run.schema.json`(JSON Schema),Go 侧(`agent/piagent.go`)与 JS 侧(`deploy/pi-agent/runner.mjs`、`tools/pi-bridge/index.mjs`)由 schema 生成/校验;CI 加 schema 一致性测试;废弃「stdout 最后一行 JSON」约定,改为桥在 workDir 写 `result.json`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 横切规范
|
||||||
|
|
||||||
|
### 4.1 包依赖门禁
|
||||||
|
|
||||||
|
`scripts/arch-check.sh`(新增):读取 `.doc/system-design/module-boundaries.yaml`(模块→允许 import 清单 + 表属主清单 + 路径字面量属主清单),执行三类检查:
|
||||||
|
|
||||||
|
1. `go list -f '{{join .Imports "\n"}}' ./internal/...` 比对 allowlist(规则 R1–R4、R7);
|
||||||
|
2. grep 表名字符串比对属主(R5);
|
||||||
|
3. grep 路径字面量(`workspaces/`、`output.md`、`data/dev`、`DataDir/../..`)比对属主(R6)。
|
||||||
|
|
||||||
|
先 `--mode warn` 输出基线报告(`.doc/audit/` 存档),两周后转 block 并纳入集成槽位冒烟与 CI。
|
||||||
|
|
||||||
|
### 4.2 统一错误信封
|
||||||
|
|
||||||
|
所有 API 错误统一 JSON:`{"error": "<CODE>", "message": "...", "detail": {...}}`;错误码表进 contracts。消灭 `http.Error` 纯文本与 JSON 双轨(`server/handlers.go:826/853` vs `project_doc_handlers.go:78`),前端单轨解析。
|
||||||
|
|
||||||
|
### 4.3 文件契约版本化
|
||||||
|
|
||||||
|
squad 工作区文件(workspace.json/blackboard.json/run-manifest.json)与 workspace manifest 全部带 `schema_ver` 字段;读取方遇到未知版本必须显式报错而非静默误读。`RunManifest.SchemaVer` 起步即 v1。
|
||||||
|
|
||||||
|
### 4.4 架构测试(防回腐化)
|
||||||
|
|
||||||
|
- `arch_test.go`(放 contracts 包):断言「server 不 import 领域实现包」「领域包互不 import」——CI 常跑;
|
||||||
|
- DTO golden 测试:chatlog/docquery 的响应结构序列化快照,前端类型与之对齐,防字段漂移。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 分阶段实施计划(Wave 0–6)
|
||||||
|
|
||||||
|
> 纪律:每个 Wave 独立 `feature/*` 分支 + 独立可回滚;遵守 AGENTS.md §6(mutex、集成槽位);合 dev 前 `go vet ./... && go test -short ./... && arch-check && env-service.sh dev rebuild && curl /health`。排期需避开 CURRENT 中 `in_progress` 的认领(chat-history-refresh、conversation-intent-state、workflow-plan Wave 5)所持 mutex。
|
||||||
|
|
||||||
|
### Wave 0:立规矩(0.5–1 天,低风险,mutex: arch)
|
||||||
|
|
||||||
|
- 建 `internal/contracts` 骨架(先放共享枚举 vocabulary.go、错误码表、事件类型定义,纯定义零实现);
|
||||||
|
- 写 `scripts/arch-check.sh` + `module-boundaries.yaml`(以现状为基线生成 allowlist,**先不收紧**);
|
||||||
|
- 产出基线违规报告存 `.doc/audit/`,作为后续各 Wave 的验收对照。
|
||||||
|
- **验收**:arch-check warn 模式可运行并产出基线;`go vet/test` 全绿;本 ADR 状态转「已接受」。
|
||||||
|
|
||||||
|
### Wave 1:止血清障(2–4 天,mutex: 多个小项,逐项独立提交)
|
||||||
|
|
||||||
|
1. 删死代码:`kb.MaterializeSnapshot`、ctxmgr 的 CAS 空壳依赖(`ctxmgr/manager.go:19` 注入未用);
|
||||||
|
2. 解反向依赖:`MaturityStage` 下沉 contracts,删除 intent→bizdev import;
|
||||||
|
3. 拆类型别名:bizdev 的 `type X = squad.X` 全部替换为独立 DTO(先复制字段,行为不变);
|
||||||
|
4. 清路径 hack:`server/project_doc_handlers.go:16-19` 的 `repoRoot()` 倒推、3 处硬编码 `data/dev`,统一改走 workspace.Resolver;
|
||||||
|
5. 统一错误信封(仅 server 层,路由 URL 不动)。
|
||||||
|
- **验收**:arch-check 中上述各项违规清零;前端 `npm test && npm run build` 通过(错误信封变更需前端配合单轨解析)。
|
||||||
|
|
||||||
|
### Wave 2:读路径 API 化——chatlog + docquery(1–2 周,mutex: chat → docgov)
|
||||||
|
|
||||||
|
- ctxmgr 拆分:chat_* 表访问与查询方法整体搬 `internal/chatlog`,prompt 装配留 `internal/contextasm`(先物理分包、行为不变,再做接口化);
|
||||||
|
- 新增 `internal/docquery` 聚合读模型,server 的文档/对话 handler 全部改为三行式;
|
||||||
|
- DTO golden 契约测试 + 前端类型对齐。
|
||||||
|
- **验收**:URL 不变前提下前端功能回归全通过;`chat_messages` 等表名在 chatlog 之外 0 出现(arch-check R5 首条 block 生效)。
|
||||||
|
|
||||||
|
### Wave 3:小队执行器契约化(1–2 周,mutex: squad)
|
||||||
|
|
||||||
|
- squad 重组为 squadrun 门面:实现 `SquadRunner` 四方法 + `RunManifest`(SchemaVer=1);`StepNotify` 改 `contracts.ProgressReporter`;
|
||||||
|
- bizdev 停止覆写 squad 状态文件、coordinator 停止直读 blackboard/ledger,全部改 `Inspect`/`Resume`;
|
||||||
|
- 编排层(此时仍可暂居 bizdev 内部的 `stageflow` 雏形)负责:创建工作区(workspace 模块)→ 备 InputBundle(材料/知识/前序文档/上下文)→ 定 OutputContract → 调 `Run` → 读 manifest 投影 docgov/taskrun。
|
||||||
|
- **验收**:`internal/squad`(squadrun)不再 import stream;`blackboard.json`/`LoadOrCreateLedger` 在 squad 包外 0 引用;真实 PI 全链路冒烟(AGENTS.md §8 铁律)。
|
||||||
|
|
||||||
|
### Wave 4:单写者与协议内聚(1 周,mutex: docgov → taskrun)
|
||||||
|
|
||||||
|
- docgov 增端口方法;docduty/docrevision/workspace/cmd 的 `document_records`/`freeform_documents` 直写改走端口;
|
||||||
|
- taskrun 吸收两阶段占位协议与 finalize 投影;`scripts/backfill_task_run_projection.go` 改依赖 taskrun 服务;
|
||||||
|
- 正文单写路径收敛(output.md 三写者 → manifest 投影唯一路径,旧路径保留一版兼容期后删除)。
|
||||||
|
- **验收**:R5 表属主检查全绿;ops 回填脚本不再 import server。
|
||||||
|
|
||||||
|
### Wave 5:编排与事件化(1–2 周,mutex: bizdev/coordinator —— 全程最大的一步,需独占排期)
|
||||||
|
|
||||||
|
- 新增 `internal/app`:wiring 从 NewServer 迁入;事件总线落地,5 个闭包桥全部改订阅者;
|
||||||
|
- bizdev 拆解:组合根职责交 app;`ExecuteTask`/`executeStageInternal` 按职责拆为 stageflow 的 use case(鉴权→备料→执行→投影→落账 分函数);
|
||||||
|
- `UserProjectContext` 所有权收归编排层:coordinator 返回 `ContextUpdate` 增量而非直改共享结构;chat 不再字段级合并。
|
||||||
|
- **验收**:server 不再出现业务闭包;`sendMessageInternal` 缩减为纯编排调用序列;arch-check R4 生效。
|
||||||
|
|
||||||
|
### Wave 6:传输与组合根收口(1 周,mutex: server/stream)
|
||||||
|
|
||||||
|
- SSE 帧协议下沉 `internal/stream/transport`;server 只剩路由+参数+序列化(目标:17k 行 → 约 6–8k);
|
||||||
|
- PI Runner JSON Schema 单一来源 + 双端校验 + `result.json` 替代 stdout 约定;
|
||||||
|
- arch-check 全面转 block 进 CI 与冒烟。
|
||||||
|
- **验收**:R1–R7 全绿;PI E2E(RUN_PI_E2E=1)通过;FEATURE-MATRIX 更新为解耦后模块结构。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 迁移与兼容策略
|
||||||
|
|
||||||
|
- **URL 兼容**:所有既有 HTTP 路由路径与响应结构在 Wave 1–5 期间保持不变(错误信封除外,属前端单轨化配合项);前端只在 Wave 2 换「同 URL 同结构」的内部实现。
|
||||||
|
- **数据零迁移**:本方案**不改任何表结构**,只改「谁来写」(写者路径收权)与「谁来读」(读模型聚合);Goose 序号不受影响。
|
||||||
|
- **双写/双读兼容期**:Wave 3 的 manifest 投影与 Wave 4 的正文单写路径,均设一版兼容期(旧路径只读、新路径写),冒烟一个发布周期后删旧路径。
|
||||||
|
- **回滚单位**:每个 Wave 一个 merge,可独立 revert;Wave 内小项(如 Wave 1 的 5 项)各自原子提交。
|
||||||
|
|
||||||
|
## 7. 验收与度量(怎么证明「解耦成功」)
|
||||||
|
|
||||||
|
| 指标 | 基线(2026-09-14 实测) | 目标 |
|
||||||
|
|---|---|---|
|
||||||
|
| server 导入内部包数 / 行数 | 25 个 / 17,007 | ≤ 10(contracts+app+少量) / 约 6–8k |
|
||||||
|
| bizdev 导入内部包数 | 16 | 拆解后各 use-case ≤ 5 |
|
||||||
|
| 领域包互相 import | 多处(含 intent→bizdev、ctxmgr→docgov) | 0(arch-check R2 block) |
|
||||||
|
| `document_records` 写者 | 5 方 | 1(docgov) |
|
||||||
|
| 工作区路径拼接点 | 4+ 处重复、3 处硬编码 | 仅 workspace 模块 |
|
||||||
|
| SSE 帧代码位置 | server/sse.go 手写 | internal/stream/transport |
|
||||||
|
| ops 脚本依赖 | 必须构造完整 Server | 直接调领域服务 |
|
||||||
|
| 「改一处波及几处」 | 以近 5 次跨模块 bug(见 .doc/audit/ 索引)触达文件数为基线抽样 | 同类改动触达文件数减半以上 |
|
||||||
|
|
||||||
|
## 8. 风险与对策
|
||||||
|
|
||||||
|
| 风险 | 对策 |
|
||||||
|
|---|---|
|
||||||
|
| 与在途认领冲突(chat/intent/workflow 三个 in_progress) | Wave 排期避开其 mutex;Wave 1 的小项与三者无交集可先行 |
|
||||||
|
| Wave 5 改动面大(bizdev/coordinator/chat 同时动) | 拆成 5a(事件总线+wiring 迁移)与 5b(bizdev 拆解+上下文收权)两批;每批独立冒烟 |
|
||||||
|
| 大规模搬包引发行为漂移 | 各 Wave 以「物理搬移、行为不变」为第一步,接口化第二步;golden 契约测试 + 既有 54 个 server 测试文件兜底 |
|
||||||
|
| 前端配合项(错误信封单轨) | Wave 1 与前端同步排期;`web/e2e` Playwright 回归纳入该 Wave 验收 |
|
||||||
|
| arch-check 误报/规则争议 | 规则文件走 PR 评审;warn 期两周允许豁免清单(带到期日) |
|
||||||
|
|
||||||
|
## 9. 与现有工作的衔接
|
||||||
|
|
||||||
|
- 本方案不替代进行中的专项整改(chat-history-refresh、conversation-intent-state、workflow-plan Wave 5 等),它们按原计划收敛后再启动对应 mutex 的 Wave;
|
||||||
|
- `软件项目自举`(planned)应**基于本方案后的新边界**实施(squadrun 契约正好覆盖其执行器需求),避免在旧耦合上叠新功能;
|
||||||
|
- 各 Wave 合 dev 后按 AGENTS.md 同步更新 FEATURE-MATRIX、gap-matrix(若引入发布 blocker)与本文件状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**附:现状→目标包名映射速查**
|
||||||
|
|
||||||
|
| 现状 | 目标 | 动作 |
|
||||||
|
|---|---|---|
|
||||||
|
| internal/server(NewServer+闭包桥+SSE+路径 hack) | internal/app + internal/server(瘦) + internal/stream/transport + internal/workspace | 拆四 |
|
||||||
|
| internal/bizdev | stageflow/chatflow/docflow(app 层)+ 各领域端口 | 拆解 |
|
||||||
|
| internal/coordinator + internal/chat | chatflow(编排)+ UserProjectContext 收权 | 收敛 |
|
||||||
|
| internal/ctxmgr | internal/chatlog + internal/contextasm | 一拆二 |
|
||||||
|
| internal/squad | internal/squadrun(+contracts 契约) | 换壳+版本化 |
|
||||||
|
| internal/kb / docgov / docrevision / docduty / freedoc | 同名保留 + 新增 internal/docquery 读模型 | 收紧+聚合读 |
|
||||||
|
| internal/intent / taskrun / workspace / notification | 同名保留(依赖修正) | 修正 |
|
||||||
|
| — | internal/contracts | 新增 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -99,6 +99,24 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 6. 模块解耦架构设计与实施方案
|
||||||
|
|
||||||
|
| 项 | 链接 |
|
||||||
|
|-----|------|
|
||||||
|
| 主报告 | [2026-09-14-module-decoupling-architecture.md](2026-09-14-module-decoupling-architecture.md) |
|
||||||
|
| ADR | [../adr/2026-09-14-modular-monolith-decoupling.md](../adr/2026-09-14-modular-monolith-decoupling.md) |
|
||||||
|
| 一句话总结 | 模块化单体 + `internal/contracts` 契约通信 + 单一写者 + 编排下沉 + arch-check 机器门禁,Wave 0–6 分阶段落地 |
|
||||||
|
|
||||||
|
**核心要点**:
|
||||||
|
|
||||||
|
- **诊断**:server/bizdev 双组合根、`UserProjectContext` 双向直改、`document_records` 五写者、工作区路径与 squad 文件 schema 为无版本化隐性契约(11 项热点均有文件:行号证据);
|
||||||
|
- **四例契约**:squadrun(系统备料备工作区、小队按 OutputContract 交货并写 RunManifest)、kb(纯检索)、chatlog(项目ID+用户ID → 结构化 DTO)、docquery(统一文档列表/正文读模型);
|
||||||
|
- **横切**:事件总线泛化 outbox、表/文件单写者、统一错误信封、DTO golden 契约测试、PI Runner 协议单一 schema 源;
|
||||||
|
- **门禁**:`scripts/arch-check.sh` + module-boundaries.yaml,warn 两周后 block 进 CI;
|
||||||
|
- **兼容**:URL 不变、零表结构迁移、每 Wave 独立分支独立回滚。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🏗️ 在整体架构中的位置
|
## 🏗️ 在整体架构中的位置
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -149,6 +167,8 @@
|
|||||||
```
|
```
|
||||||
.doc/system-design/
|
.doc/system-design/
|
||||||
├── README.md ← 本文件(顶层索引)
|
├── README.md ← 本文件(顶层索引)
|
||||||
|
├── module-boundaries.yaml ← Wave 0 arch-check allowlist
|
||||||
|
├── 2026-09-14-module-decoupling-architecture.md ← 模块化单体解耦
|
||||||
├── document-revision-management.md ← 文档版本、修订、差异、报告与通知
|
├── document-revision-management.md ← 文档版本、修订、差异、报告与通知
|
||||||
├── intent-recognition/
|
├── intent-recognition/
|
||||||
│ └── 01-意图识别框架-设计-架构-实现-难点-解决方案-亮点与优势.md
|
│ └── 01-意图识别框架-设计-架构-实现-难点-解决方案-亮点与优势.md
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
# Wave 0 baseline allowlist. Generated from 2026-09-14 go list, not tightened.
|
||||||
|
# Target owners are recorded for table/path checks; extra mentions are warnings.
|
||||||
|
|
||||||
|
imports:
|
||||||
|
agent: config
|
||||||
|
auth: config
|
||||||
|
authz: docduty
|
||||||
|
bizdev: authz,config,ctxmgr,db,docduty,docgov,docrevision,freedoc,kb,projectcontext,projectworkflow,quote,squad,storage,stream,workflowdef
|
||||||
|
chat: bizdev,coordinator,ctxmgr,docgov,intent,projectinstance,replyexport,squad,stream,taskrun
|
||||||
|
config:
|
||||||
|
coordinator: agent,bizdev,ctxmgr,docgov,intent,kb,projectinstance,squad,stream
|
||||||
|
ctxmgr: docgov,replyexport,storage
|
||||||
|
db:
|
||||||
|
docduty:
|
||||||
|
docgov: docduty,docrevision,fsutil
|
||||||
|
docrevision: fsutil
|
||||||
|
freedoc: ctxmgr,docrevision,replyexport
|
||||||
|
fsutil: ""
|
||||||
|
intent: bizdev
|
||||||
|
kb: config
|
||||||
|
notification:
|
||||||
|
observability:
|
||||||
|
projectcontext: squad
|
||||||
|
projectinstance: docgov,projectworkflow,workflowdef
|
||||||
|
projectworkflow: auth
|
||||||
|
quote:
|
||||||
|
replyexport:
|
||||||
|
server: agent,auth,authz,bizdev,chat,config,coordinator,ctxmgr,docduty,docgov,docrevision,freedoc,intent,notification,observability,projectinstance,projectworkflow,replyexport,squad,stream,taskrun,upload,workflowdef,workspace
|
||||||
|
squad: agent,config,quote,stream,template
|
||||||
|
storage:
|
||||||
|
stream:
|
||||||
|
taskrun: bizdev,ctxmgr,projectworkflow,stream
|
||||||
|
template:
|
||||||
|
upload:
|
||||||
|
workflow:
|
||||||
|
workflowdef: projectworkflow,storage,upload,workflow
|
||||||
|
workspace:
|
||||||
|
contracts:
|
||||||
|
|
||||||
|
table_owners:
|
||||||
|
document_records: docgov
|
||||||
|
chat_messages: ctxmgr
|
||||||
|
chat_turns: ctxmgr
|
||||||
|
freeform_documents: freedoc
|
||||||
|
task_runs: taskrun
|
||||||
|
|
||||||
|
path_owners:
|
||||||
|
output.md: squad,workspace
|
||||||
|
data/dev: config,workspace
|
||||||
|
workspaces/: squad,workspace
|
||||||
@@ -66,3 +66,4 @@ regulations/
|
|||||||
.doc/test-reports/assets/**/playwright-output-*/
|
.doc/test-reports/assets/**/playwright-output-*/
|
||||||
.doc/test-reports/assets/**/playwright-report/
|
.doc/test-reports/assets/**/playwright-report/
|
||||||
.doc/test-reports/assets/**/playwright-report-*/
|
.doc/test-reports/assets/**/playwright-report-*/
|
||||||
|
.doc/test-reports/assets/2026-09-13-document-revision-runtime-ui/
|
||||||
|
|||||||
@@ -164,15 +164,6 @@ curl -sf http://127.0.0.1:8080/health
|
|||||||
- **不覆盖**:发现冲突先 handoff/blocker
|
- **不覆盖**:发现冲突先 handoff/blocker
|
||||||
- **交接优先**:口头转述不可代替 handoff 文件
|
- **交接优先**:口头转述不可代替 handoff 文件
|
||||||
|
|
||||||
### 6.6 Herdr 角色、自动审批与备援
|
|
||||||
|
|
||||||
- 本仓库 Herdr 角色、任务泳道、额度保护和备援顺序以 [Herdr Agent roster](./.doc/ops/2026-09-13-herdr-agent-roster.md) 为准。
|
|
||||||
- Herdr 实例统一通过 [`scripts/herdr-agent-start.sh`](./scripts/herdr-agent-start.sh) 启动;自动派发使用 [`scripts/herdr-agent-dispatch.sh`](./scripts/herdr-agent-dispatch.sh)。
|
|
||||||
- 自动审批只消除客户端逐条确认,不扩大任务授权;分支、worktree、mutex、集成槽和破坏性操作红线继续生效。
|
|
||||||
- 自动接管仅允许发生在未开始写入且明确的额度、认证、客户端或启动失败。`timeout` / `unknown` 不证明任务未执行,必须停止重复投递并先检查 pane 与 handoff。
|
|
||||||
- 名称 `context_pi` / “Herdr PI”专指协作终端内的 PI CLI;产品容器中的执行器统一称“产品 PI Agent Runner”,两者禁止混称或互相代替验收。
|
|
||||||
- 未进入 roster 的新模型(包括尚未接入的 Devin)默认 `standby`,通过一次只读任务和一次隔离 worktree 写任务验证后再进入路由。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 文档生命周期
|
## 7. 文档生命周期
|
||||||
|
|||||||
+2
-9
@@ -35,7 +35,7 @@
|
|||||||
- **前端**:React + Vite
|
- **前端**:React + Vite
|
||||||
- **数据库**:PostgreSQL + pgvector(Goose 迁移)
|
- **数据库**:PostgreSQL + pgvector(Goose 迁移)
|
||||||
- **产品执行器**:容器内 PI Agent Runner(真实 / stub)
|
- **产品执行器**:容器内 PI Agent Runner(真实 / stub)
|
||||||
- **工程协作**:Herdr 管理的 Claude / Codex / Cursor / Antigravity / PI / Kimi / Cline;其中 Herdr PI 与产品 PI Agent Runner 无关
|
- **工程协作**:按 [AGENTS.md](./AGENTS.md) 的分支 / worktree / mutex / 集成槽位协作(Cursor、Codex、Claude Code 等);产品 PI Agent Runner 仅指容器内执行器
|
||||||
- **工作流**:main、product_manager、test_manager 三套正式定义
|
- **工作流**:main、product_manager、test_manager 三套正式定义
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -69,7 +69,6 @@
|
|||||||
| 整改方案 | [.doc/remediation/INDEX.md](./.doc/remediation/INDEX.md) |
|
| 整改方案 | [.doc/remediation/INDEX.md](./.doc/remediation/INDEX.md) |
|
||||||
| 测试报告归档 | [.doc/test-reports/](./.doc/test-reports/) |
|
| 测试报告归档 | [.doc/test-reports/](./.doc/test-reports/) |
|
||||||
| 测试报告规范 | [.cursor/rules/test-reports-standard.mdc](./.cursor/rules/test-reports-standard.mdc) |
|
| 测试报告规范 | [.cursor/rules/test-reports-standard.mdc](./.cursor/rules/test-reports-standard.mdc) |
|
||||||
| Herdr 角色与备援 | [.doc/ops/2026-09-13-herdr-agent-roster.md](./.doc/ops/2026-09-13-herdr-agent-roster.md) |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -132,13 +131,7 @@ cd web && npm test && npm run build
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Herdr 工程协作
|
## 10. 语言与提交
|
||||||
|
|
||||||
- `orchestrator_codex` 负责拆解、方案、调度、风险控制和终审;主力实现默认交给 `implementer_cursor`。
|
|
||||||
- 启动实例用 `scripts/herdr-agent-start.sh`,派发与安全降级用 `scripts/herdr-agent-dispatch.sh`。
|
|
||||||
- 角色和备援 SSOT 见 [Herdr Agent roster](./.doc/ops/2026-09-13-herdr-agent-roster.md)。
|
|
||||||
|
|
||||||
## 11. 语言与提交
|
|
||||||
|
|
||||||
- 对用户:**简体中文**
|
- 对用户:**简体中文**
|
||||||
- 提交:Conventional Commits;在 `feature/*` 或 `fix/*` 上渐进式分批提交,合回 `dev` 用 `--no-ff`(禁止在三大主干直接 commit)
|
- 提交:Conventional Commits;在 `feature/*` 或 `fix/*` 上渐进式分批提交,合回 `dev` 用 `--no-ff`(禁止在三大主干直接 commit)
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package contracts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContractsHasNoBusinessImports(t *testing.T) {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ents, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
for _, ent := range ents {
|
||||||
|
if ent.IsDir() || !strings.HasSuffix(ent.Name(), ".go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f, err := parser.ParseFile(fset, filepath.Join(dir, ent.Name()), nil, parser.ImportsOnly)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, spec := range f.Imports {
|
||||||
|
path := strings.Trim(spec.Path.Value, `"`)
|
||||||
|
if strings.HasPrefix(path, "chat-agent-system/internal/") && path != "chat-agent-system/internal/contracts" {
|
||||||
|
t.Fatalf("%s imports business package %s", ent.Name(), path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVocabularyAndErrorCodesStable(t *testing.T) {
|
||||||
|
if StageV01Triage != "V0.1_triage" || ErrorPlanFrozen != "PLAN_FROZEN" {
|
||||||
|
t.Fatal("Wave 0 vocabulary drifted")
|
||||||
|
}
|
||||||
|
if EventDocumentApproved == "" || EventRunFinalized == "" {
|
||||||
|
t.Fatal("event names required")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package contracts
|
||||||
|
|
||||||
|
// Stable API error codes. Handlers should eventually emit only these strings
|
||||||
|
// in the JSON envelope. Wave 0 records the table; it does not change HTTP.
|
||||||
|
const (
|
||||||
|
ErrorDocumentNotFound = "DOCUMENT_NOT_FOUND"
|
||||||
|
ErrorDocumentContentMissing = "DOCUMENT_CONTENT_PROJECTION_MISSING"
|
||||||
|
ErrorDocumentContentEmpty = "DOCUMENT_CONTENT_PROJECTION_EMPTY"
|
||||||
|
ErrorDocumentContentIntegrity = "DOCUMENT_CONTENT_INTEGRITY_MISMATCH"
|
||||||
|
ErrorDocumentContentDrift = "DOCUMENT_CONTENT_PROJECTION_DRIFT"
|
||||||
|
ErrorDocumentHeadConflict = "DOCUMENT_HEAD_CONFLICT"
|
||||||
|
ErrorDocumentEditForbidden = "DOCUMENT_EDIT_FORBIDDEN"
|
||||||
|
ErrorDocumentBusy = "DOCUMENT_BUSY"
|
||||||
|
ErrorPlanFrozen = "PLAN_FROZEN"
|
||||||
|
ErrorWorkflowAccessDenied = "WORKFLOW_ACCESS_DENIED"
|
||||||
|
ErrorTurnNotCompleted = "TURN_NOT_COMPLETED"
|
||||||
|
ErrorMessageNotExportable = "MESSAGE_NOT_EXPORTABLE"
|
||||||
|
ErrorMessageRevisionChanged = "MESSAGE_REVISION_CHANGED"
|
||||||
|
)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package contracts
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Event is the typed bus payload. Implementations must stay serializable
|
||||||
|
// and must not embed domain structs from other packages.
|
||||||
|
type Event interface {
|
||||||
|
EventName() string
|
||||||
|
OccurredAt() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventDocumentApproved = "document.approved"
|
||||||
|
EventDocumentRejected = "document.rejected"
|
||||||
|
EventDocumentLeaseChanged = "document.lease_changed"
|
||||||
|
EventRunFinalized = "run.finalized"
|
||||||
|
EventStageAdvanced = "stage.advanced"
|
||||||
|
EventStageGateDenied = "stage.gate_denied"
|
||||||
|
EventMessageRecorded = "message.recorded"
|
||||||
|
EventNotificationRequested = "notification.requested"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EventHandler func(ev Event) error
|
||||||
|
|
||||||
|
type EventBus interface {
|
||||||
|
Publish(ev Event) error
|
||||||
|
Subscribe(name string, h EventHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProgressReporter is the transport-neutral progress port.
|
||||||
|
// Domain modules must not import internal/stream.
|
||||||
|
type ProgressReporter interface {
|
||||||
|
Report(step string, status string, detail map[string]string)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package contracts
|
||||||
|
|
||||||
|
// MaturityStage is the shared business-maturity vocabulary.
|
||||||
|
// Wave 0 only records the values; intent/bizdev still own their local aliases
|
||||||
|
// until Wave 1 deletes the reverse import.
|
||||||
|
type MaturityStage string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StageV01Triage MaturityStage = "V0.1_triage"
|
||||||
|
StageV05Feasibility MaturityStage = "V0.5_feasibility"
|
||||||
|
StageV10Quoting MaturityStage = "V1.0_quoting"
|
||||||
|
)
|
||||||
Executable
+151
@@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Architecture boundary check. Wave 0: warn by default, never rewrite code.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
BOUNDARIES="${ROOT}/.doc/system-design/module-boundaries.yaml"
|
||||||
|
MODE="warn"
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--mode) MODE="${2:-warn}"; shift 2 ;;
|
||||||
|
--mode=*) MODE="${1#--mode=}"; shift ;;
|
||||||
|
*) echo "usage: $0 [--mode warn|block]" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${MODE}" != "warn" && "${MODE}" != "block" ]]; then
|
||||||
|
echo "mode must be warn or block" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
violations=0
|
||||||
|
warn() {
|
||||||
|
printf 'WARN %s\n' "$1"
|
||||||
|
violations=$((violations + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup_imports() {
|
||||||
|
local pkg="$1"
|
||||||
|
awk -v pkg="$pkg" '
|
||||||
|
$1=="imports:" {sec=1; next}
|
||||||
|
sec && $1 ~ /^(table_owners|path_owners):/ { exit }
|
||||||
|
sec && $1 ~ /^[A-Za-z0-9_]+:$/ || sec && $1 ~ /^[A-Za-z0-9_]+:/ {
|
||||||
|
key=$1; sub(":","",key)
|
||||||
|
$1=""
|
||||||
|
gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0)
|
||||||
|
gsub(/"/,"",$0)
|
||||||
|
if (key==pkg) { print $0; exit }
|
||||||
|
next
|
||||||
|
}
|
||||||
|
' "${BOUNDARIES}"
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup_owners() {
|
||||||
|
local section="$1" key="$2"
|
||||||
|
awk -v secname="$section" -v key="$key" '
|
||||||
|
$0 ~ "^"secname":" {sec=1; next}
|
||||||
|
sec && $1 ~ /^(imports|table_owners|path_owners):/ && $1 !~ "^"secname":" { exit }
|
||||||
|
sec && $1 ~ /^[A-Za-z0-9_./]+:/ {
|
||||||
|
k=$1; sub(":","",k)
|
||||||
|
$1=""
|
||||||
|
gsub(/^[[:space:]]+|[[:space:]]+$/,"",$0)
|
||||||
|
if (k==key) { print $0; exit }
|
||||||
|
next
|
||||||
|
}
|
||||||
|
' "${BOUNDARIES}"
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed_has() {
|
||||||
|
local needle="$1"
|
||||||
|
local list="$2"
|
||||||
|
[[ -z "${list}" ]] && return 1
|
||||||
|
IFS=',' read -r -a parts <<< "${list}"
|
||||||
|
for p in "${parts[@]}"; do
|
||||||
|
p="${p// /}"
|
||||||
|
[[ "${p}" == "${needle}" ]] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
root_pkg() {
|
||||||
|
local rel="${1#chat-agent-system/internal/}"
|
||||||
|
echo "${rel%%/*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "arch-check mode=${MODE}"
|
||||||
|
echo "boundaries=${BOUNDARIES}"
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "${line}" ]] && continue
|
||||||
|
pkg="$(echo "${line}" | awk '{print $1}')"
|
||||||
|
root="$(root_pkg "${pkg}")"
|
||||||
|
[[ "${root}" == "workflow" ]] && continue
|
||||||
|
allow="$(lookup_imports "${root}")"
|
||||||
|
deps="$(echo "${line}" | awk '{for(i=2;i<=NF;i++) print $i}')"
|
||||||
|
for dep in ${deps}; do
|
||||||
|
[[ "${dep}" != chat-agent-system/internal/* ]] && continue
|
||||||
|
droot="$(root_pkg "${dep}")"
|
||||||
|
[[ "${droot}" == "${root}" ]] && continue
|
||||||
|
if ! allowed_has "${droot}" "${allow}"; then
|
||||||
|
warn "R-import ${root} -> ${droot} not in baseline allowlist"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "${root}" == "contracts" ]]; then
|
||||||
|
for dep in ${deps}; do
|
||||||
|
if [[ "${dep}" == chat-agent-system/internal/* && "${dep}" != chat-agent-system/internal/contracts ]]; then
|
||||||
|
warn "R1 contracts imports ${dep}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done < <(go list -f '{{.ImportPath}} {{join .Imports " "}}' ./internal/...)
|
||||||
|
|
||||||
|
declare -A seen_table
|
||||||
|
while IFS= read -r table; do
|
||||||
|
owners="$(lookup_owners table_owners "${table}")"
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -z "${file}" ]] && continue
|
||||||
|
rel="${file#${ROOT}/internal/}"
|
||||||
|
pkg="${rel%%/*}"
|
||||||
|
key="${table}|${pkg}"
|
||||||
|
[[ -n "${seen_table[$key]+x}" ]] && continue
|
||||||
|
seen_table[$key]=1
|
||||||
|
if ! allowed_has "${pkg}" "${owners}"; then
|
||||||
|
warn "R5 table ${table} mentioned in ${pkg} (target owner ${owners})"
|
||||||
|
fi
|
||||||
|
done < <(rg -l --glob '*.go' --glob '!*_test.go' --fixed-strings "${table}" "${ROOT}/internal" || true)
|
||||||
|
done <<'TABLES'
|
||||||
|
document_records
|
||||||
|
chat_messages
|
||||||
|
chat_turns
|
||||||
|
freeform_documents
|
||||||
|
task_runs
|
||||||
|
TABLES
|
||||||
|
|
||||||
|
declare -A seen_path
|
||||||
|
while IFS= read -r lit; do
|
||||||
|
owners="$(lookup_owners path_owners "${lit}")"
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -z "${file}" ]] && continue
|
||||||
|
rel="${file#${ROOT}/internal/}"
|
||||||
|
pkg="${rel%%/*}"
|
||||||
|
key="${lit}|${pkg}"
|
||||||
|
[[ -n "${seen_path[$key]+x}" ]] && continue
|
||||||
|
seen_path[$key]=1
|
||||||
|
if ! allowed_has "${pkg}" "${owners}"; then
|
||||||
|
warn "R6 path ${lit} mentioned in ${pkg} (target owner ${owners})"
|
||||||
|
fi
|
||||||
|
done < <(rg -l --glob '*.go' --glob '!*_test.go' --fixed-strings "${lit}" "${ROOT}/internal" || true)
|
||||||
|
done <<'PATHS'
|
||||||
|
output.md
|
||||||
|
data/dev
|
||||||
|
workspaces/
|
||||||
|
PATHS
|
||||||
|
|
||||||
|
echo "violations=${violations}"
|
||||||
|
if [[ "${MODE}" == "block" && "${violations}" -gt 0 ]]; then
|
||||||
|
echo "arch-check block: failing" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "arch-check ${MODE}: recorded ${violations} baseline findings"
|
||||||
|
exit 0
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Dispatch to a standard live Herdr agent, with conservative failover.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'EOF'
|
|
||||||
用法:
|
|
||||||
scripts/herdr-agent-dispatch.sh --explain <lane>
|
|
||||||
scripts/herdr-agent-dispatch.sh [--timeout-ms N] <lane> <prompt>
|
|
||||||
|
|
||||||
lane: governance | critical-review | implementation | ui | test | fast-check | long-context
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
route_for() {
|
|
||||||
case "$1" in
|
|
||||||
governance) printf '%s\n' orchestrator_codex context_kimi implementer_cursor reserve_claude ;;
|
|
||||||
critical-review) printf '%s\n' orchestrator_codex reserve_claude context_kimi ;;
|
|
||||||
implementation) printf '%s\n' implementer_cursor implementer_cline verifier_antigravity orchestrator_codex ;;
|
|
||||||
ui) printf '%s\n' implementer_cursor verifier_antigravity implementer_cline ;;
|
|
||||||
test) printf '%s\n' implementer_cursor implementer_cline verifier_antigravity ;;
|
|
||||||
fast-check) printf '%s\n' verifier_antigravity implementer_cline implementer_cursor ;;
|
|
||||||
long-context) printf '%s\n' context_pi context_kimi orchestrator_codex ;;
|
|
||||||
*) return 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ "${1:-}" == "--explain" ]]; then
|
|
||||||
[[ $# -eq 2 ]] || { usage >&2; exit 2; }
|
|
||||||
if ! route_for "$2"; then
|
|
||||||
printf '[错误] 未知 lane: %s\n' "$2" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
timeout_ms=120000
|
|
||||||
if [[ "${1:-}" == "--timeout-ms" ]]; then
|
|
||||||
[[ $# -ge 4 ]] || { usage >&2; exit 2; }
|
|
||||||
timeout_ms="$2"
|
|
||||||
shift 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $# -ne 2 ]]; then
|
|
||||||
usage >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
lane="$1"
|
|
||||||
prompt="$2"
|
|
||||||
if ! routes="$(route_for "${lane}")"; then
|
|
||||||
printf '[错误] 未知 lane: %s\n' "${lane}" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! "${timeout_ms}" =~ ^[1-9][0-9]*$ ]]; then
|
|
||||||
printf '[错误] timeout 必须是正整数毫秒。\n' >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
if [[ "${HERDR_ENV:-}" != "1" ]]; then
|
|
||||||
printf '[错误] 当前进程不在 Herdr 管理的 pane 中。\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
command -v jq >/dev/null 2>&1 || { printf '[错误] 缺少 jq。\n' >&2; exit 1; }
|
|
||||||
|
|
||||||
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
||||||
if [[ -z "${repo_root}" || ! -f "${repo_root}/AGENTS.md" || ! -f "${repo_root}/PROJECT.md" ]]; then
|
|
||||||
printf '[错误] 必须从 Agent-System 任务 worktree 派发。\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
branch="$(git branch --show-current)"
|
|
||||||
if [[ "${branch}" != feature/* && "${branch}" != fix/* ]]; then
|
|
||||||
printf '[错误] 只允许从 feature/* 或 fix/* 分支派发,当前为 %s。\n' "${branch}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
current_cwd="$(realpath -m "${repo_root}")"
|
|
||||||
agent_json="$(herdr agent list)"
|
|
||||||
dispatch_id="${BASHPID:-$$}-${SECONDS}"
|
|
||||||
retry_prefix="HERDR_RETRY:${dispatch_id}"
|
|
||||||
protocol="$(printf '\n\n[Herdr failover protocol]\n只有在尚未写文件、未运行有副作用命令,且仅因额度、认证或客户端故障无法开始时,回复独占一行 %s:quota、%s:auth 或 %s:client。已经开始工作时不得返回该标记;请写 handoff 并说明当前状态。' "${retry_prefix}" "${retry_prefix}" "${retry_prefix}")"
|
|
||||||
|
|
||||||
while IFS= read -r target; do
|
|
||||||
row="$(jq -c --arg name "${target}" '.result.agents[] | select(.name == $name)' <<<"${agent_json}" | head -n 1)"
|
|
||||||
if [[ -z "${row}" ]]; then
|
|
||||||
printf '[跳过] %s 未在线。\n' "${target}" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
status="$(jq -r '.agent_status' <<<"${row}")"
|
|
||||||
agent_cwd="$(jq -r '.cwd' <<<"${row}")"
|
|
||||||
if [[ "${agent_cwd}" != "${current_cwd}" ]]; then
|
|
||||||
printf '[跳过] %s cwd=%s,不是当前任务 worktree。\n' "${target}" "${agent_cwd}" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [[ "${status}" != "idle" && "${status}" != "done" ]]; then
|
|
||||||
printf '[跳过] %s 状态为 %s。\n' "${target}" "${status}" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '[派发] lane=%s target=%s\n' "${lane}" "${target}" >&2
|
|
||||||
set +e
|
|
||||||
result="$(herdr agent prompt "${target}" "${prompt}${protocol}" --wait --timeout "${timeout_ms}" 2>&1)"
|
|
||||||
rc=$?
|
|
||||||
set -e
|
|
||||||
|
|
||||||
if [[ ${rc} -ne 0 ]]; then
|
|
||||||
if rg -q 'agent_blocked|agent_not_found|agent_not_ready' <<<"${result}"; then
|
|
||||||
printf '[降级] %s 在投递前不可用。\n' "${target}" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
printf '%s\n' "${result}" >&2
|
|
||||||
printf '[停止] 结果可能已投递;为避免重复执行,不自动切换备援。\n' >&2
|
|
||||||
exit 75
|
|
||||||
fi
|
|
||||||
|
|
||||||
transcript="$(herdr agent read "${target}" --source recent-unwrapped --lines 120 2>/dev/null || true)"
|
|
||||||
if rg -q "^${retry_prefix}:(quota|auth|client)$" <<<"${transcript}"; then
|
|
||||||
printf '[降级] %s 明确报告写入前失败。\n' "${target}" >&2
|
|
||||||
agent_json="$(herdr agent list)"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf '%s\n' "${result}"
|
|
||||||
exit 0
|
|
||||||
done <<<"${routes}"
|
|
||||||
|
|
||||||
printf '[错误] lane=%s 无可用实例;请用 herdr-agent-start.sh 启动标准实例。\n' "${lane}" >&2
|
|
||||||
exit 69
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Start a standard Herdr agent with non-interactive tool approvals.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'EOF'
|
|
||||||
用法:
|
|
||||||
scripts/herdr-agent-start.sh [--dry-run] <profile> <agent-name> <pane-id>
|
|
||||||
|
|
||||||
profile: claude | codex | cursor | antigravity | pi | kimi | cline
|
|
||||||
标准名称见 .doc/ops/2026-09-13-herdr-agent-roster.md。
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
dry_run=0
|
|
||||||
if [[ "${1:-}" == "--dry-run" ]]; then
|
|
||||||
dry_run=1
|
|
||||||
shift
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $# -ne 3 ]]; then
|
|
||||||
usage >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
profile="$1"
|
|
||||||
agent_name="$2"
|
|
||||||
pane_id="$3"
|
|
||||||
shift 3
|
|
||||||
|
|
||||||
case "${profile}" in
|
|
||||||
claude)
|
|
||||||
kind="claude"
|
|
||||||
expected_name="reserve_claude"
|
|
||||||
native_args=(--dangerously-skip-permissions)
|
|
||||||
;;
|
|
||||||
codex)
|
|
||||||
kind="codex"
|
|
||||||
expected_name="orchestrator_codex"
|
|
||||||
native_args=(--ask-for-approval never --sandbox workspace-write)
|
|
||||||
;;
|
|
||||||
cursor)
|
|
||||||
kind="cursor"
|
|
||||||
expected_name="implementer_cursor"
|
|
||||||
native_args=(--force --sandbox enabled --approve-mcps --trust)
|
|
||||||
;;
|
|
||||||
antigravity)
|
|
||||||
kind="agy"
|
|
||||||
expected_name="verifier_antigravity"
|
|
||||||
native_args=(--dangerously-skip-permissions --sandbox)
|
|
||||||
;;
|
|
||||||
pi)
|
|
||||||
kind="pi"
|
|
||||||
expected_name="context_pi"
|
|
||||||
native_args=(--approve)
|
|
||||||
;;
|
|
||||||
kimi)
|
|
||||||
kind="kimi"
|
|
||||||
expected_name="context_kimi"
|
|
||||||
native_args=(--auto)
|
|
||||||
;;
|
|
||||||
cline)
|
|
||||||
kind="cline"
|
|
||||||
expected_name="implementer_cline"
|
|
||||||
native_args=(--tui --auto-approve true)
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
printf '[错误] 未知 profile: %s\n' "${profile}" >&2
|
|
||||||
usage >&2
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ "${agent_name}" != "${expected_name}" ]]; then
|
|
||||||
printf '[错误] profile %s 的标准名称必须是 %s\n' "${profile}" "${expected_name}" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! "${pane_id}" =~ ^w[[:alnum:]]+:p[[:alnum:]]+$ ]]; then
|
|
||||||
printf '[错误] 非法 pane id: %s\n' "${pane_id}" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
command=(herdr agent start "${agent_name}" --kind "${kind}" --pane "${pane_id}" -- "${native_args[@]}")
|
|
||||||
|
|
||||||
if [[ ${dry_run} -eq 1 ]]; then
|
|
||||||
printf '%q ' "${command[@]}"
|
|
||||||
printf '\n'
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${HERDR_ENV:-}" != "1" ]]; then
|
|
||||||
printf '[错误] 当前进程不在 Herdr 管理的 pane 中。\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
||||||
if [[ -z "${repo_root}" || ! -f "${repo_root}/AGENTS.md" || ! -f "${repo_root}/PROJECT.md" ]]; then
|
|
||||||
printf '[错误] 必须从 Agent-System 任务 worktree 启动。\n' >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
branch="$(git branch --show-current)"
|
|
||||||
if [[ "${branch}" != feature/* && "${branch}" != fix/* ]]; then
|
|
||||||
printf '[错误] 自动审批实例只能在 feature/* 或 fix/* 分支启动,当前为 %s。\n' "${branch}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
command -v jq >/dev/null 2>&1 || { printf '[错误] 缺少 jq。\n' >&2; exit 1; }
|
|
||||||
workspace_id="${pane_id%%:*}"
|
|
||||||
pane_json="$(herdr pane list --workspace "${workspace_id}")"
|
|
||||||
target_cwd="$(jq -r --arg pane "${pane_id}" '.result.panes[] | select(.pane_id == $pane) | .cwd' <<<"${pane_json}" | head -n 1)"
|
|
||||||
if [[ -z "${target_cwd}" || "$(realpath -m "${target_cwd}")" != "$(realpath -m "${repo_root}")" ]]; then
|
|
||||||
printf '[错误] 目标 pane 不存在或不在当前任务 worktree: %s\n' "${pane_id}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "${command[@]}"
|
|
||||||
Reference in New Issue
Block a user