feat(governance): enforce managed worktrees and scoped agent operations

Consolidate approved policies and add local lease, document CAS, commit, candidate and environment gates. Preserve conservative recovery and cleanup limits.
This commit is contained in:
weichen
2026-09-21 14:41:40 +00:00
parent eb4dbdca5b
commit fed87ad14b
48 changed files with 4296 additions and 1013 deletions
+2 -21
View File
@@ -1,25 +1,6 @@
--- ---
description: 强制 Agent 文档同步与 ops 协作纪律;详情见 AGENTS.md description: 任务分级、授权、文档与收口入口
alwaysApply: true alwaysApply: true
--- ---
# Agent 文档同步(强制) 遵循 [AGENTS](../../AGENTS.md) 的启动、任务分级与收口规则。写入文档前读 [文档地图](../../.doc/README.md);创建、集成、环境操作、清理前读 [操作规范](../../.doc/ops/2026-09-21-agent-worktree-workflow.md)。
完整规范见 [AGENTS.md](../../AGENTS.md) 与 [PROJECT.md](../../PROJECT.md)。
## 开场必读
1. `AGENTS.md` → `PROJECT.md` → `.doc/README.md` → `.doc/ops/CURRENT.md` → `FEATURE-MATRIX` → short-term / handoff
## 红线
- 任务开始前:short-term/long-term + CURRENT 并行认领(mutex)
- 合 dev + 容器测:占用 CURRENT 集成槽位;他人 queue 等待
- 多 Agent 并行:worktree(`.worktrees/`);禁止 worktree 内 env-service start
- 禁止只改代码;done 任务 → `archive/ops/`
- 验收报告 → `.doc/test-reports/`;运行产物 → `test-reports/assets/`
- 禁止在 `.doc/` 根或 `docs/` 随意新建文档
## 状态
`planned | in_progress | blocked | done | cancelled`
+2 -20
View File
@@ -1,24 +1,6 @@
--- ---
description: Git push 凭证自主读取;禁止向用户索要 token;详情见 AGENTS.md §8 description: 凭证与推送授权入口
alwaysApply: true alwaysApply: true
--- ---
# Git Secrets 自主(禁止索要) 遵循 [AGENTS 安全与证据](../../AGENTS.md#5-安全与证据);授权推送的凭证和脚本入口见 [PROJECT](../../PROJECT.md#凭证与推送)。
完整规范见 [AGENTS.md](../../AGENTS.md) §8 与 [PROJECT.md](../../PROJECT.md) §7。
## 本仓约定
- 凭证:`${HOME}/.secrets/gitea`
- 字段:`- ACCESS_TOKEN=`、`- URL_PREFIX=`
- 推送:优先 `./scripts/git-push-dev.sh <branch>`
## 禁止
- 向用户索要或要求粘贴 token
- 在聊天/文档/commit 中展示 secret
- 写入 git config 或提交到仓库
## 缺失时
在 `.doc/ops` 记 blocker(路径 + 缺字段),提示用户本机补齐后重试;**仍不索要内容**。
@@ -1,61 +1,6 @@
--- ---
description: 项目固定3分支模型(dev/test/main)、逐步渐进式提交规范与三套独立服务(知识库共享+对话与DB隔离)准则 description: 分支、原子提交与环境操作入口
alwaysApply: true alwaysApply: true
--- ---
# Git 三分支流转与三套服务环境架构准则 分支/原子提交边界见 [AGENTS](../../AGENTS.md),环境拓扑见 [PROJECT](../../PROJECT.md)。创建、集成、环境写操作及清理按 [操作规范](../../.doc/ops/2026-09-21-agent-worktree-workflow.md) 执行。
本仓库严格遵循以下固定分支体系、提交拆分规范与多套独立服务运行准则:
## 一、 Git 固定三分支体系
1. **开发分支 (`dev`)**:
- **合入基线**,不是日常 commit 目标。
- **禁止**在 `dev` 上直接开发提交(含 docs/chore)。
- 全部新增改动(含单 Agent、文档)从明确的 `dev` SHA 创建 `.worktrees/` 内的 `feature/*` / `fix/*` 任务;主目录只作集成与运行入口。
- 创建、候选验证、串行合入、部署和安全清理统一遵守 [AGENTS.md](../../AGENTS.md) §6 与 [操作规范](../../.doc/ops/2026-09-21-agent-worktree-workflow.md);不以旧脚本的强制删除替代清理门禁。
2. **测试分支 (`test`)**:
- 当 `dev` 积累了一定的功能阶段后,合并至 `test` 分支,创建阶段候选节点 Tag(如 `rc-vX.Y.Z-beta.1`)。
- 用于较长时间的真实环境部署、全链路集成测试、运维稳定性检验。
- **严禁**在 `test` 分支进行开发或直接 commit。
3. **主分支 (`main`)**:
- 生产发布分支(Release)。
- **严禁**直接在 `main` 上开发或 commit!
- 只有 `test` 分支的代码经过实际运维和测试充分验证达到发布标准后,方可将对应验证节点合并回 `main` 分支,并打正式发布 Tag(如 `v1.0.0`)。
---
## 二、 任务拆分与渐进式提交规范 (Progressive Commits)
- **原子性拆分**:严禁把整个任务的大量改动揉合成一次庞大的提交。
- **阶段逐步提交**:一个任务必须按功能逻辑分层逐步提交:
1. `feat(db)` / `refactor(db)`: 数据表、模型、迁移脚本
2. `feat(agent)` / `feat(service)`: 业务逻辑、智能体执行流程
3. `feat(api)`: 接口路由与交互
4. `test(...)`: 单元测试与集成测试用例
5. `docs(...)` / `chore(...)`: 配置、运维脚本与文档同步
- **提交规范**:遵循 Conventional Commits (`feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`)。
---
## 三、 三套环境独立运行准则 (知识库共享 + 对话与DB隔离)
三套独立服务对应三个分支的运行态:
| 环境 | 分支 | Compose 项目 | HTTP 端口 | DB 端口 | 数据/对话路径 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **共享知识库** | 跨环境 | `agent-infra` | - | `5432` | `data/shared/kb` |
| **开发环境** | `dev` | `agent-dev` | `8080` | `5433` | `data/dev` |
| **测试环境** | `test` | `agent-test` | `8081` | `5434` | `data/test` |
| **生产环境** | `main` | `agent-main` | `8000` | `5435` | `data/main` |
### 核心约束:
1. **知识库共享**:
- 三套环境服务均通过共享网络或内网连接同一个知识库数据库(`shuimu-kb-shared:5432`)。
- 知识库法规、向量索引保持全局一致。
2. **对话数据与数据库隔离(红线)**:
- 各环境对话记录(Chat History)、Agent 思考工作区、审计事件及 SQLite/Postgres 业务库必须严格隔离!
- 严禁任何环境读取或写入其他环境的对话数据与业务库。
- 宿主机挂载目录严格区分:`data/dev/`、`data/test/`、`data/main/`。
+2 -61
View File
@@ -1,65 +1,6 @@
--- ---
description: 规定项目中所有自动化测试、功能测试、集成测试报告的存放路径与结构标准规范 description: 按变更范围选择测试及真实证据要求
globs: ["**/*test*.go", ".doc/test-reports/**/*.md"]
alwaysApply: true alwaysApply: true
--- ---
# 测试报告规范准则 (Test Reports Standard) 测试范围、精确产品 PI 入口、环境模型证据和报告格式统一见 [验证契约](../../.doc/ops/2026-09-21-validation-contract.md)。按实际变更选择适用行;报告大小不扩大测试范围。
本仓库严格遵循标准化、可追溯的质量测试管理体系。所有测试报告必须归档于指定的文档目录下,并遵循固定结构。
## 一、 测试报告归档路径规范
- **统一目录**:所有测试报告必须统一存放于 `.doc/test-reports/` 目录下。
- **命名规范**:遵循 `YYYY-MM-DD-<模块/功能/版本>-test-report.md` 命名格式。
- 例如:`.doc/test-reports/2026-09-07-three-branch-intent-framework-test-report.md`
- **证据附件**:运行产物(json/jsonl、workspace 快照、截图)放 `.doc/test-reports/assets/<report-slug>/`,**不得**与 `*-test-report.md` 混放在同一层级目录。
---
## 二、 测试报告固定结构模板
每份测试报告必须包含以下核心板块:
```markdown
# [功能/模块名称] 测试报告
## 一、 测试背景 (Background)
- 简述被测功能/特性的产生背景、业务痛点或上一轮测试中发现的缺陷与瓶颈。
## 二、 测试目标 (Test Objectives)
- 列举本次测试需要验证的核心功能点、质量指标(如通过率 100%、无流程卡点、非阻塞人机协同等)。
## 三、 测试方案与环境 (Test Plan & Environment)
- 测试环境(OS、Go 版本、后端 Stub/Real 模型);
- 测试层级分层(单元测试、协同集成测试、CLI 真实交互测试);
- 核心测试用例矩阵设计。
## 四、 预期结果 (Expected Results)
- 明确给出各用例输入下的理论判定分支、目标意图、提取槽位、参谋回复风格与状态机流转。
## 五、 实际测试结果与判定 (Actual Results & Evaluation)
- 必须包含针对每一个典型测试用例的真实记录:
- **测试输入 (Input)**
- **实际输出 (Actual Output)**:贴出真实的终端/日志输出文本
- **评价判定 (Evaluation / Verdict)**:PASS / FAIL 标志及判定依据
- **性能与耗时 (Duration & Latency)**
## 六、 自动化测试套件汇总 (Test Suite Execution)
- 附带 `go test -v ./...` 全量回归测试输出与通过率统计。
## 七、 测试结论与后续优化建议 (Conclusion & Next Steps)
- 总结测试是否达到上线/合并标准,提出后续演进或强化点。
```
---
## 三、 执行要求与真实 Agent 交付铁律
1. **真实 Agent 交付铁律(红线)**:
- **开发实现阶段**:可以快速使用 `stub` 验证状态机、协议分流、数据落盘与逻辑契约,加速反馈;
- **交付前验收与测试报告生成**:**必须切回真实环境与真实 Agent(`pi-agent` + 实际运行的 `MiniMax-M2.7-highspeed` 容器)进行全链路端到端实测**;
- **严禁**使用 stub 的测试输出作为最终交付报告的验收数据。
2. **严禁散落存放**:测试报告统一归档至 `.doc/test-reports/` 目录下,禁止散落在根目录或临时沙箱中。
3. **真实控制台取样**:测试报告中的输入、输出、耗时与判定必须取自实际环境运行的真实控制台输出与日志。
4. **Git 规范管理**:测试报告伴随对应阶段的 `docs(test)` 或 `test(...)` 规范提交入库。
+7
View File
@@ -33,6 +33,9 @@
| 我要… | 读这里 | | 我要… | 读这里 |
|--------|--------| |--------|--------|
| 规则来源/生效版本 | [规范登记](ops/2026-09-21-policy-registry.md) |
| 选择测试/报告范围 | [验证契约](ops/2026-09-21-validation-contract.md) |
| 任务模板与会话收口 | [任务模板](ops/templates/short-term-task.md) · [收口清单](ops/templates/session-close-checklist.md) |
| 创建任务、集成、部署、清理 | [worktree 工作规范](ops/2026-09-21-agent-worktree-workflow.md) | | 创建任务、集成、部署、清理 | [worktree 工作规范](ops/2026-09-21-agent-worktree-workflow.md) |
| 当前在做什么 | [ops/CURRENT.md](ops/CURRENT.md) | | 当前在做什么 | [ops/CURRENT.md](ops/CURRENT.md) |
| 模块完成度 | [status/FEATURE-MATRIX.md](status/FEATURE-MATRIX.md) | | 模块完成度 | [status/FEATURE-MATRIX.md](status/FEATURE-MATRIX.md) |
@@ -58,9 +61,13 @@
## 文档生命周期 ## 文档生命周期
按 [AGENTS 任务分级](../AGENTS.md#2-任务分级与授权) 选择最小产物。下图是可选信息流,不要求每任务创建所有文件;小任务/有价值的只读审查可由一个主文档承载范围、结论、验证与交接。主文档先明确 task_id、owner、授权来源、状态和证据;跨阶段信息确需独立时才拆分并回链。
```text ```text
audit(问题)→ remediation(方案)→ ops/short-term(任务) audit(问题)→ remediation(方案)→ ops/short-term(任务)
→ test-reports(验收)→ archive(关闭证据) → test-reports(验收)→ archive(关闭证据)
``` ```
状态字段:`planned | in_progress | blocked | done | cancelled` 状态字段:`planned | in_progress | blocked | done | cancelled`
会话结束不等于任务完成;只有 done/cancelled 任务归档。CURRENT 保留最多 3 项焦点及活跃/阻塞/待集成/待清理事项,完成认领和长证据迁入 [历史快照](archive/ops/2026-09-21-current-history.md)。移动文档同步修复相对链接,保留证据与来源。规范生命周期另用 proposed/approved/active/deprecated;工具能力另用 manual/enforced。
@@ -0,0 +1,280 @@
# CURRENT 历史快照(2026-09-21)
归档来源:实施基线 `eb4dbdca5baae8dd5f3ddefc0f5510ac2978dcd3` 的 `.doc/ops/CURRENT.md`。以下保留原始认领、环境断言、焦点和证据索引;只调整移动后的 Markdown 相对链接。历史状态不是当前锁、环境或授权依据,活跃协调见 [CURRENT](../../ops/CURRENT.md)。基线内已失效的旧链接另在本次验证记录中说明,不把历史声明改写成新结论。
---
# 当前焦点
**更新**: 2026-09-21
**production_ready**: yes(门禁 B1–B4 已签;dev schema **61**,test/main 仍为 **58**。P0 热修 done;rdev 种子热修 done,见 [报告](../../test-reports/2026-09-20-rdev-seed-hotfix-test-report.md))
## 焦点
1. **工作流作用域权重**:`WORKFLOW_SCOPE_MODE` 默认 `strict`;`weighted` 按关系权重打分,只读跨流注明归属,写动作仍拦截。见 [报告](../../test-reports/2026-09-21-workflow-scope-weighting-test-report.md)。test/main 未晋升。
2. **意图确认优先与阶段感知**:弱写「整理需求」→ intake 确认卡片。见 [报告](../../test-reports/2026-09-21-intent-confirm-stage-aware-test-report.md)。test/main 未晋升。
3. **上传解析提示 / 去重 / CoW 收口**:MinerU 解析状态轮询闭环(pending→running→done)、重复文件「已复用」提示、失败原因+重新解析、CAS 不可变 CoW 契约测试,已合入 `dev`(代码 `99905571`、证据 `74587ccf`、收口 `a0464eb5`)。见 [报告](../../test-reports/2026-09-21-upload-parse-ux-dedup-test-report.md)。test/main 未晋升。前序任务执行状态卡收口见 [报告](../../test-reports/2026-09-21-taskrun-status-card-persistence-test-report.md)(含二轮修复:completed 步骤 label 冻结为"正在…(NN%)"的收口归一化 + dev 历史回填,已合入 `dev@4b303247` 并真实 PI 验收)。
4. **MinerU 真实 PDF 二轮验收**:用户反馈「PDF 内容未被理解」由两因叠加——(A) 首轮在 worktree 内跑 `env-service dev rebuild` 令挂载漂到 worktree `data/dev`,真实历史 blob 全部读不到;(B) Agent 轻量 API 硬限 20 页,28 页真实文件失败。首轮结论作废。二轮从**主目录** rebuild 修复挂载,`internal/mineru` 在 `-30003` 时按 20 页窗分块解析并重编全局页锚;`ReadRefBytes` 失败改为 `slog.Warn`。已合入 `dev@362b68a0`。真实 `bd_intake`(项目 6532)`completed`,工作区 `materials_text` 注入 29152 字节中文正文。见 [报告](../../test-reports/2026-09-21-mineru-doc-parsing-test-report.md) 第八节。
5. **红线**:禁止整支 merge FAV;不默认升 `MULTI_INTENT_PLAN_TIER=full`;`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;FAV live 观察窗证据驱动;`FAV_DEGRADED_READ=false`;项目 143 只读;SWBOOT-10 保持 fail-closed stub;D7 对话 ADR 仍待合规签字(追踪表留存类别已定为 `record`,不授权物理删除);**不改 `production_ready`**;模块解耦批次 A–G 与回溯实施切片未单独认领前保持 `planned`;**合入 dev 后必须回主目录跑 `env-service.sh dev rebuild`,禁止任何 worktree 内运行 `env-service`**(脚本 ROOT_DIR 取自脚本所在目录树,会带走 bind mount,导致 `/app/data` 漂到 worktree 空目录)。
## 并行认领
| owner | task | branch | worktree | mutex | status |
|-------|------|--------|----------|-------|--------|
| Codex | 2026-09-21-agent-governance-implementation | feature/agent-governance-implementation | .worktrees/agent-governance-implementation | agent-governance | in_progress |
| Cursor | 2026-09-21-workflow-scope-weighting | feature/workflow-scope-weighting-2026-09-21 | .worktrees/workflow-scope-weighting-2026-09-21 | intent+web+docgov | done |
| Cursor | 2026-09-21-upload-parse-ux-dedup | feature/upload-parse-ux-2026-09-21 | .worktrees/upload-parse-ux-2026-09-21 | server+web+storage | done |
| Cursor | 2026-09-21-mineru-doc-parsing | feature/mineru-doc-parsing-2026-09-21 | .worktrees/mineru-doc-parsing-2026-09-21 | storage+bizdev+kb | done |
| Cursor | 2026-09-21-intent-confirm-stage-aware | fix/intent-confirm-stage-aware-2026-09-21 | .worktrees/intent-confirm-stage-aware-2026-09-21 | intent+web+docgov | done |
| Cursor | 2026-09-21-taskrun-status-card | feature/taskrun-status-card-2026-09-21 | .worktrees/feature-taskrun-status-card-2026-09-21 | chat+web+taskrun | done |
| Cursor | 2026-09-21-intent-workflow-scoping | fix/intent-workflow-scoping-2026-09-21 | - | intent+web+docgov | done |
| Codex | 2026-09-21-agent-workflow-governance-adoption | feature/agent-workflow-governance-plan | .worktrees/feature-agent-workflow-governance-plan | agent-workflow-governance | in_progress |
| Codex | 2026-09-21-agent-workflow-governance-plan | feature/agent-workflow-governance-plan | .worktrees/feature-agent-workflow-governance-plan | agent-workflow-governance-plan | done |
| Cursor | 2026-09-21-workflow-doc-key-unification | fix/workflow-doc-key-unification-2026-09-21 | .worktrees/fix-workflow-doc-key-unification-2026-09-21 | docgov+web | done |
| Cursor | 2026-09-21-dev-deep-test-four-issues | fix/dev-deep-test-four-issues-2026-09-21 | - | web+intent+taskrun | done |
| Cursor | 2026-09-21-trace-panel-to-chat | feature/trace-panel-to-chat-2026-09-21 | - | trace-impact+web | done |
| Cursor | 2026-09-21-taskrun-scope-persist | fix/taskrun-scope-persist-2026-09-21 | - | projectinstance+taskrun | done |
| Cursor | 2026-09-21-dev-multiuser-four-issues | fix/dev-multiuser-four-issues-2026-09-21 | .worktrees/fix-dev-multiuser-four-issues | projectinstance+docgov | done |
| Cursor | 2026-09-21-taskrun-error-transparency | fix/taskrun-error-transparency-2026-09-21 | - | chat+web | done |
| Cursor | 2026-09-21-trace-panel-null-crash | fix/trace-panel-null-crash-2026-09-21 | - | trace-index+web | done |
| Cursor-Auto | 2026-09-20-stage-backtrack-design | feature/stage-backtrack-design | - | trace-impact-design | done |
| Cursor-Auto | 2026-09-20-impact-loop-t1 至 T3/B1–B3 | feature/stage-backtrack-impl | - | trace-index | done |
| Cursor | 2026-09-20-rdev-seed-hotfix | fix/workflow-seed-bootstrap-2026-09-20 | - | workflow-def | done |
| Cursor-Auto | 2026-09-20-module-decoupling-spec-audit | docs/module-decoupling-spec-2026-09-20 | - | module-decoupling-spec | done |
| Cursor-Auto | 2026-09-20-p0-closeout | docs/p0-closeout-2026-09-20 | - | docs | done |
| Cursor-Auto | 2026-09-20-p0-hotfixes(Wave 0 文档) | docs/p0-triage-2026-09-20 | - | docs | done |
| Cursor-Auto | 2026-09-20-p0-capability-00058 | fix/capability-merge-00058-2026-09-20 | - | infra+chat | done |
| Cursor-Auto | 2026-09-20-p0-idor | fix/authz-idor-hotfixes-2026-09-20 | - | authz | done |
| Cursor-Auto | 2026-09-20-p0-runtime-stability | fix/runtime-panic-races-2026-09-20 | - | taskrun+chat+web | done |
| Cursor-Auto | 2026-09-20-p0-credentials | fix/credential-hardening-2026-09-20 | - | ops+infra | done |
| Cursor-Auto | 2026-09-20-proposed-authoritative-reports | feature/capability-enablement-impact-plan | - | docgov | done |
| Cursor-Auto | 2026-09-20-pi-narrative-clipped-diff | feature/capability-enablement-impact-plan | - | docgov | done |
| Cursor-Auto | 2026-09-20-pi-reasoning-profile | feature/capability-enablement-impact-plan | - | pi-agent | done |
| Cursor-Auto | 2026-09-20-semantic-summary-persistence | feature/capability-enablement-impact-plan | - | chat+lineage-version | done |
| Cursor-Auto | 2026-09-20-feature-readiness-and-lineage-authz | feature/capability-enablement-impact-plan | - | sysadmin+lineage | done |
| Codex | 2026-09-20-capability-enablement-impact-plan | feature/capability-enablement-impact-plan | - | capability-impact-research | done |
| Cursor-Auto | 2026-09-20-post-activation-duty-reassign-member-chat | fix/post-activation-duty-reassign-member-chat | - | projectworkflow+authz+web | done |
| Cursor-Auto | 2026-09-20-test-env-triage-fixes | fix/test-env-triage-2026-09-20 | - | projectworkflow+chat+assets | done |
| Cursor-Auto | 2026-09-20-test-env-beta6-verification | docs/test-env-beta6-verification | - | infra+docs | done |
| Cursor-Auto | 2026-09-20-bug-hunt-permissions-uploads | fix/bug-hunt-2026-09-20-permissions-and-uploads | - | authz+web+projectworkflow | done |
| Cursor | audit-b1-data-consistency | fix/audit-b1-data-consistency | - | docgov+workflow+chat | done |
| Cursor | audit-b2-authz-hardening | fix/audit-b2-authz-hardening | - | authz | done |
| Cursor | audit-b3-sse-events | fix/audit-b3-sse-events | - | chat+events | done |
| Cursor | audit-b4-frontend | fix/audit-b4-frontend | - | web | done |
| Cursor-Auto | 2026-09-19-audit-remainder-closeout | fix/audit-remainder-closeout | - | audit-remainder | done |
| Antigravity | 2026-09-20-dev-deep-test-and-promotion | feature/dev-deep-test-and-promotion | - | integration | done |
| Cursor-Auto | 2026-09-19-remove-control-center | fix/remove-control-center | - | controlcenter | done |
| Cursor-Auto | 2026-09-17-control-center-server | feature/control-center-server | - | controlcenter | cancelled |
| Kimi | 2026-09-18-user-manual | feature/user-manual | .worktrees/feature-user-manual | docs | done |
| Cursor-Auto | 2026-09-18-wave7-roadmap-orchestration | docs/wave7-roadmap-orchestration | - | roadmap-orchestration | done |
| Cursor-Auto | 2026-09-18-wave6-doc-drift | docs/wave6-doc-drift | - | doc-drift | done |
| Cursor-Auto | 2026-09-18-wave5-deadcode-decisions | fix/wave5-deadcode-decisions | - | deadcode-cleanup | done |
| Cursor-Auto | 2026-09-18-wave4-hardening-r1-r8 | fix/wave4-hardening-r1-r8 | - | hardening | done |
| Cursor-Auto | 2026-09-18-wave3-test-debt-blindspots | fix/wave3-test-debt-blindspots | - | test-debt | done |
| Cursor-Auto | 2026-09-18-wave2-config-flag-align | fix/wave2-config-flag-align | - | infra | done |
| Cursor | 2026-09-18-wave1-main-env-fix | fix/wave1-main-env-fix | - | infra | done |
| Cursor | 2026-09-18-wave0-p0-bug-fixes | fix/wave0-p0-sse-chat-cache-bugs | - | chat + web | done |
| Kimi | 2026-09-17-system-health-check(只读审计,未实施) | - | - | audit | done |
| Cursor | 2026-09-17-docgov-modification-proposal-http | feature/docgov-modification-proposal-http | - | docgov | done |
| Cursor | 2026-09-17-document-author-only-closeout | feature/document-author-only-plan | - | document-duty-policy | done |
| Cursor | 2026-09-17-rdev-template-depth-s1-continued | feature/rdev-template-depth-s1-continued | .worktrees/feature-rdev-template-depth-s1-continued | rdev-templates | done |
| Cursor | 2026-09-17-rdev-workflow-integration | feature/rdev-core | - | workflow-def | done |
| Cursor | 2026-09-17-rdev-template-depth | feature/rdev-template-depth | .worktrees/feature-rdev-template-depth | rdev-templates | done |
| Codex | 2026-09-17-document-author-only-plan | feature/document-author-only-plan | - | document-duty-policy-plan | done |
| Cursor | 2026-09-17-initial-load-perf-u2 | feature/initial-load-perf-u2 | - | web + static-delivery | done |
| Codex | 2026-09-17-initial-load-performance-plan | feature/initial-load-performance-plan | .worktrees/feature-initial-load-performance-plan | loading-performance-audit | done |
| Kimi | 2026-09-17-system-admin-console | feature/system-admin-console | - | web + sysadmin | done |
| Cursor-Auto | 2026-09-17-engineering-dashboard | feature/engineering-dashboard | .worktrees/feature-engineering-dashboard | engineering-dashboard | done |
| Cursor | 2026-09-17-promote-null-fk-to-test | feature/promote-null-fk-to-test | - | infra | done |
| Cursor | 2026-09-17-docgov-optional-duty-null-fk | fix/docgov-optional-duty-null-fk | - | docgov | done |
| Cursor | 2026-09-17-leftover-hygiene | chore/leftover-hygiene-2026-09-17 | - | ops | done |
| Cursor | 2026-09-17-w2-semantic-summary-shadow | feature/w2-semantic-summary-shadow-live | - | chat | done |
| Cursor | 2026-09-17-w3-pi-change-report-narrative | feature/w3-pi-change-report-narrative-live | - | docgov | done |
| Cursor | 2026-09-17-w4-multi-intent-u9-u12 | feature/w4-u9-segment-live | - | intent-plan | done |
| Cursor | 2026-09-17-w5a-software-bootstrap-ui | feature/w5a-software-bootstrap-ui-pi | - | workflow-design | done |
| Cursor | 2026-09-17-w6-fav-subscriber-shadow | feature/w6-fav-subscriber-shadow | - | events | done |
| Cursor | 2026-09-17-w7-d7-inventory | feature/w7-d7-data-inventory | - | chat | done |
| Cursor | 2026-09-17-promote-dev-to-test | feature/promote-dev-to-test-2026-09-17 | - | infra | done |
| Cursor | 2026-09-17-kb-host-auth-fix | fix/kb-host-auth-layered-load | - | infra | done |
| Cursor | 2026-09-17-w1-ci-e2e-green-nightly | feature/w1-ci-e2e-green-nightly | - | ci | done |
| Cline | 2026-09-16-partial-deferred-completion | docs/partial-deferred-completion-plan | - | architecture-docs | in_progress |
| Cursor | 2026-09-16-intent-plan-playwright-dual-tab | feature/intent-plan-cards-playwright | .worktrees/feature-intent-plan-cards-playwright | web | done |
| Cursor | 2026-09-16-full-backlog-override | docs/override-nogo-full-backlog | - | architecture-docs | done |
| Cursor | 2026-09-16-gitea-nightly-docker-datadir | fix/gitea-nightly-docker-datadir | - | ci | done |
| Cursor | 2026-09-16-gitea-nightly-host-tools | fix/gitea-nightly-host-tools | - | ci | done |
| Cursor | 2026-09-21-test-env-lazy-doc-verification | docs/lazy-doc-verification-2026-09-21 | - | infra+docs | done |
| Cursor | 2026-09-16-wave-leftovers | fix/wave-leftovers-n7-playwright-dod | - | ci | done |
| Cursor | 2026-09-16-wave3-pu6-pu8-go-no-go | feature/wave3-pu6-pu8-go-no-go | - | architecture-docs | done |
| Cursor | 2026-09-16-wave2c-same-image-digest | feature/wave2c-same-image-digest | - | infra | done |
| Cursor | 2026-09-16-wave2b-nightly-promotion-receipt | feature/wave2b-nightly-promotion-receipt | - | ci | done |
| Cursor | 2026-09-16-wave2-real-pi-evidence | feature/wave2-real-pi-evidence | - | intent-plan | done |
| Cursor | 2026-09-16-wave1-race-tempdir-cleanup | fix/race-tempdir-cleanup | - | ci | done |
| Cursor | 2026-09-16-wave1-iam-fail-closed | fix/iam-store-fail-closed | - | authz | done |
| Cursor | 2026-09-16-feature-matrix-wave0-calibration | feature/feature-matrix-production-readiness-plan | .worktrees/feature-matrix-production-readiness-plan | architecture-docs | done |
| Codex | 2026-09-16-feature-matrix-production-readiness-plan | feature/feature-matrix-production-readiness-plan | .worktrees/feature-matrix-production-readiness-plan | architecture-docs | done |
| Cursor-Auto | 2026-09-16-matrix-deep-test-promote-test | feature/matrix-deep-test-promote-test | - | infra | done |
| Cursor-Auto | 2026-09-16-production-ready-gates | docs/production-ready-flip | - | ci | done |
| Cursor-Auto | 2026-09-16-demo-seed-pollution | fix/demo-data-pollution-web | - | web | done |
| Cursor | 2026-09-16-multi-intent-u8-task-generate | feature/multi-intent-u8-task-generate | - | intent-plan | done |
| Cursor | 2026-09-16-multi-intent-u7-auto-read | feature/multi-intent-u7-auto-read | - | intent-plan | done |
| Cursor | 2026-09-16-multi-intent-u6-web-cards | feature/multi-intent-u6-web-cards | - | web | done |
| Cursor | 2026-09-16-multi-intent-u5-decision-api | feature/multi-intent-u5-decision-api | - | intent-plan | done |
| Cursor | 2026-09-16-multi-intent-u4-outbox-projection | feature/multi-intent-u4-outbox-projection | - | events | done |
| Cursor | 2026-09-15-multi-intent-u3-intentflow-pg | feature/multi-intent-u3-intentflow-pg | - | intent-plan | done |
| Cursor | 2026-09-15-multi-intent-u2-segmenter | feature/multi-intent-u2-segmenter | - | intent | done |
| Cursor | 2026-09-15-multi-intent-u1-contracts | feature/multi-intent-u1-contracts | - | architecture-docs | done |
| Cursor | app-composition-u11-u12 | feature/app-composition-u11-u12 | - | app | done |
| Cursor | chatlog-contextasm-u10 | feature/chatlog-contextasm-u10 | - | chat | done |
| Cursor | chat-history-refresh-remediation | fix/chat-history-refresh | - | chat | done |
| Cursor | dataset-lifecycle-u9 | feature/dataset-lifecycle-u9 | - | dataset | done |
| Cursor | taskrun-runmanifest-u8 | feature/taskrun-runmanifest | - | taskrun | done |
| Cursor | document-reliability-m1-live-and-m2 | refactor/document-reliability-pilot | - | workspace | done |
| Codex | module-decoupling-remediation-plan | feature/module-decoupling-remediation | - | architecture-docs | done |
| Codex | module-decoupling-phase2-plan-review | docs/module-decoupling-ipc-assessment | - | architecture-docs | done |
| Cursor | module-decoupling-phase2-docs | feature/module-decoupling-phase2-docs | - | architecture-docs | done |
| verifier_antigravity | controlled-content-ui-acceptance | fix/controlled-content-ui-acceptance | - | document-content-ui-acceptance | done |
| implementer_cursor | controlled-content-canonical-hash | fix/controlled-content-canonical-hash | - | docgov | done |
| Codex | project143-controlled-content-404-diagnosis | fix/project143-controlled-content-404-diagnosis | - | docgov-content-diagnosis | done |
| Codex | software-project-bootstrap-plan | feature/software-project-bootstrap-plan | - | workflow-design | done |
| Codex | conversation-intent-runtime-state | fix/conversation-intent-state | - | intent | done |
| Cursor | conversation-intent-runtime-state | docs/conversation-intent-dual-tab | - | intent | done |
| Cursor | document-revision-wave1-6-implementation | feature/document-revision-management | - | docgov | done |
| Cursor-Auto | document-revision-runtime-ui03 | fix/document-revision-runtime-ui03 | - | docgov | done |
| Cursor-Auto | document-revision-sse-idempotency | fix/document-revision-sse-idempotency | - | docgov | done |
| Cursor-Auto | document-revision-active-doc-routing | fix/document-revision-active-doc-routing | - | docgov | done |
| Cursor-Auto | document-revision-quality-context | fix/document-revision-quality-context | - | docgov | done |
| Cursor-Auto | document-revision-e2e-pending-flow | fix/document-revision-e2e-pending-flow | - | pending-report-ui | done |
| Cursor-Auto | document-revision-e2e-reject-routing | fix/document-revision-e2e-reject-routing | - | document-revision-runtime-ui | done |
| Cursor-Auto | document-revision-e2e-block-selector | fix/document-revision-e2e-block-selector | - | document-revision-block-selector | done |
| Cursor-Auto | document-revision-e2e-save-selector | fix/document-revision-e2e-save-selector | - | document-revision-save-selector | done |
| Cursor-Auto | document-revision-e2e-overlay-selector | fix/document-revision-e2e-overlay-selector | - | document-revision-overlay-selector | done |
| Cursor-Auto | document-revision-e2e-overlay-transition | fix/document-revision-e2e-overlay-transition | - | document-revision-overlay-transition | done |
| Cursor-Auto | document-revision-second-doc-routing-lease | fix/document-revision-second-doc-routing-lease | - | document-revision-second-doc-routing-lease | done |
| Cursor-Auto | document-revision-prelim-output | fix/document-revision-pi-runner-module | - | document-revision-prelim-output | done |
| Cursor-Auto | document-revision-editor-hydration-race | fix/document-revision-editor-hydration-race | - | document-revision-editor-hydration-race | done |
| Cursor-Auto | document-revision-quality-repair-workspace-contract | fix/document-revision-quality-repair-workspace-contract | - | document-revision-quality-repair-workspace-contract | done |
| Cursor-Auto | document-revision-fast-completion-observation | fix/document-revision-fast-completion-observation | - | document-revision-fast-completion-observation | done |
| Cursor-Auto | document-revision-workflow-category-navigation | fix/document-revision-workflow-category-navigation | - | document-revision-workflow-category-navigation | done |
| Cursor-Auto | document-revision-duty-cell-assertion | fix/document-revision-duty-cell-assertion | - | document-revision-duty-cell-assertion | done |
| Cursor-Auto | document-revision-reviewer-duty-sync | fix/document-revision-reviewer-duty-sync | - | document-revision-reviewer-duty-sync | done |
| Cursor-Auto | document-revision-post-restart-selector | fix/document-revision-post-restart-selector | - | document-revision-post-restart-selector | done |
| Cursor-Auto | document-revision-post-restart-final-snippet | fix/document-revision-post-restart-final-snippet | - | document-revision-post-restart-final-snippet | done |
| Cursor-Auto | document-revision-pg-id-remap | fix/document-revision-pg-id-remap | - | document-revision | done |
| Cursor-Auto | document-revision-runtime | fix/document-revision-runtime-ui03 | - | docgov | done |
| Cursor-Auto | document-revision-runtime-ui-final-report | feature/document-revision-runtime-ui-final-report | - | document-revision-runtime-ui | 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 | vite-frp-allowed-hosts | fix/vite-frp-allowed-hosts | - | web-devserver | done |
| implementer_cursor | workflow-plan-409-frozen-and-optional-duties | fix/workflow-plan-409-frozen-and-optional-duties | - | workflow | done |
| implementer_cursor | governance-action-level-authz | fix/governance-action-level-authz | - | workflow | done |
| Cursor | leftover-branch-cleanup | chore/leftover-branch-cleanup | - | ops | done |
| Cursor | event-e0a-production-scope-contract | fix/event-production-scope-contract | - | events | done |
| Cursor | event-e0b-dispatcher-pg | feature/events-dispatcher-pg | - | events | done |
| Cursor | event-e1a-notify-cutover | fix/event-notify-cutover | - | events | done |
| Cursor | event-e1b-workflow-advance | fix/event-workflow-advance | - | events | done |
| Cursor | event-e1c-projection-cutover | fix/event-projection-cutover | - | events | done |
| Cursor | event-e2-hook-guard | fix/event-e2-hook-guard | - | events | done |
| Cursor | doccontent-canonical-boundary | fix/doccontent-canonical-boundary | - | docgov | done |
| Cursor | intent-bizdev-boundary | fix/intent-bizdev-boundary | - | intent | done |
| Cursor | stageflow-type-ownership | fix/stageflow-type-ownership | - | stageflow | done |
| Cursor | bizdev-locator-removal | fix/bizdev-locator-removal | - | bizdev | done |
| Cursor | chatflow-orchestration-boundary | fix/chatflow-orchestration-boundary | - | chat | done |
| Cursor | phase2-closeout-inventories | feature/phase2-closeout-inventories | - | architecture-docs | done |
| Cursor | event-shared-live-cutover | feature/event-shared-live-cutover | - | events | done |
| Cursor | pi-typed-result | feature/pi-typed-result | - | pi-agent | done |
| Cursor | promote-dev-to-test-2026-09-15 | feature/promote-dev-to-test-2026-09-15 | - | infra | done |
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。
> workflow-plan 409 与治理鉴权 G1–G5 已收口(见 [test-report](../../test-reports/2026-09-14-governance-action-level-authz-test-report.md))。禁止终止或改写项目 143。
> `document-reliability-pilot` U11–U12 已合入 `dev@bf59cce` 并 rebuild。`CHAT-INTENT-STATE` 双标签 busy 已 PASS(项目 6275)。禁止 worktree 内 `env-service`。项目 143 只读,禁止终止。
> 2026-09-17 leftover 二次收口:[报告](../../test-reports/2026-09-17-leftover-hygiene-test-report.md)。已合入 leftover worktree/旁支再拆一轮;`feature/software-project-bootstrap` L1 已在 `dev`(残留关闭 commit 已丢弃)。`fix/clean-test-env-baseline` **不 merge**(前端 demo 清理已放弃;仅保留已挑拣的 `SEED_DEMO_USERS=false`)。extra worktree 仅余 `.worktrees/fix-dev-runtime-data-root-guard`(目录名是 guard,检出的是 `feature/final-artifact-vault`)。**故意不合入、不删**:`feature/final-artifact-vault`、`feature/remove-herdr-agent-settings`、`fix/project143-controlled-content-404-diagnosis`、`fix/dev-runtime-data-root-guard`、`dev-old-linear`。不删 `origin/*`。不整支 merge FAV;不启用 `FAV_DEGRADED_READ`。
## 集成槽位
| holder | phase | since | scope | queue |
|--------|-------|-------|-------|-------|
| - | idle | 2026-09-21 | taskrun-status-card label 修复已合入 dev@4b303247 并真实 PI 验收(round2 docs 随后合入);mineru 二轮文档已合入;schema=61 | - |
**phase**: `idle` | `merging` | `smoke` | `e2e`
> 合入 `dev` + 共享容器验证必须串行。详见 [AGENTS.md](../../../AGENTS.md) §6.3。
## 环境快照
| 环境 | 端口 | schema | 状态 |
|------|------|--------|------|
| dev | 8080 | **61** | healthy(mineru `00061`;intent-confirm 真实 PI PASS;`/health` schema_version=61;`pi-agent`;events `:8088`) |
| test | 8081 | **58** | healthy(`rc-v1.0.0-beta.7`;rdev v1 active;`published_by` 回落到用户 4;events `:8089`) |
| main | 8000 | **58** | healthy(P0 Wave3 rebuild 已追平 schema;分支树对齐仍记 backlog;口令已轮换;events `:8090`) |
## SSOT
| 类型 | 路径 |
|------|------|
| 2026-09-21 Agent worktree 工作规范 | [方案](../../remediation/2026-09-21-agent-workflow-governance-plan.md) · [调研](../../ops/research/2026-09-21-agent-workflow-governance-research.md) · [交接](../../ops/handoffs/2026-09-21-agent-workflow-governance-plan.md);规范已采纳,自动化另列 planned |
| 2026-09-20 rdev 种子 | [short-term](../../ops/short-term/2026-09-20-rdev-seed-hotfix.md) · [报告](../../test-reports/2026-09-20-rdev-seed-hotfix-test-report.md) |
| 2026-09-20 模块解耦 | [审计](../../audit/2026-09-20-module-coupling-audit.md) · [规范](../../system-design/2026-09-20-module-development-standards.md) · [批次 A–G](../../remediation/2026-09-20-module-decoupling-batch-plan.md) · [short-term](../../ops/short-term/2026-09-20-module-decoupling-spec-audit.md) |
| 文档总地图 | [../README.md](../../README.md) |
| 模块完成度 | [FEATURE-MATRIX](../../status/FEATURE-MATRIX.md)(Wave 0 已校准契约与冻结边界) |
| 发布缺口 | [gap-matrix](../../remediation/gap-matrix.md)(`production_ready=yes`;Wave 0 IAM 只读未发现旁路) |
| 2026-09-20 系统体检 | [audit](../../audit/2026-09-20-system-triage-audit.md) · [P0 整改](../../remediation/2026-09-20-p0-remediation-plan.md) · [short-term](../../ops/short-term/2026-09-20-p0-hotfixes.md) |
| 连续性审计/计划 | [audit](../../audit/2026-09-16-feature-matrix-production-readiness-audit.md) · [remediation](../../remediation/2026-09-16-feature-matrix-production-readiness-plan.md) · [Wave 0 任务](../../ops/short-term/2026-09-16-feature-matrix-wave0-calibration.md) |
| 测试报告 | [test-reports/](../../test-reports) |
| dev→test 晋升前复核 | [research](../../ops/research/2026-09-19-pre-test-promotion-review.md)(2026-09-19 只读;静态门禁过,容器未 rebuild,暂不合并) |
| D7 盘点(W7) | [audit](../../audit/2026-09-17-d7-data-inventory.md) · [报告](../../test-reports/2026-09-17-w7-d7-inventory-test-report.md) · ADR 仍 `pending-compliance-signoff` |
| 功能启用与文档影响分析 | [调研](../../ops/research/2026-09-20-capability-enablement-impact-research.md) · [整改方案](../../remediation/2026-09-20-capability-enablement-impact-plan.md) · [方案审阅](../../ops/research/2026-09-20-capability-enablement-impact-plan-review.md)(2026-09-20;未启用开关、未改业务代码) |
| 阶段回溯与影响闭环 | [设计](../../system-design/2026-09-20-stage-backtrack-and-impact-loop-design.md) · [short-term](../../ops/short-term/2026-09-20-stage-backtrack-design.md)(2026-09-20;仅设计。切片 T1/T2/B1/B2/B3/T3 均为 planned) |
| Wave 7 编排确认 | [决策记录](../../ops/short-term/2026-09-18-wave7-roadmap-orchestration.md) |
| Agent 手册 | [AGENTS.md](../../../AGENTS.md) |
| 意图双标签 busy 交接 | [./handoffs/2026-09-15-conversation-intent-dual-tab.md](../../ops/handoffs/2026-09-15-conversation-intent-dual-tab.md) |
| 受控正文 hash 漂移 W1–W6 | [../ops/handoffs/2026-09-14-controlled-content-canonical-hash.md](../../ops/handoffs/2026-09-14-controlled-content-canonical-hash.md) |
| DOCREV ADR | [../adr/2026-09-13-document-version-boundary.md](../../adr/2026-09-13-document-version-boundary.md) |
| DOCREV 管理测试 | [../test-reports/2026-09-13-document-revision-management-test-report.md](../../test-reports/2026-09-13-document-revision-management-test-report.md) |
| DOCREV 运行时 UI Round20 | [../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md](../../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) |
| 纯软件项目自举设计 | [../system-design/2026-09-14-software-project-bootstrap-workflow.md](../../system-design/2026-09-14-software-project-bootstrap-workflow.md) |
| 模块化单体解耦架构 | [../system-design/2026-09-14-module-decoupling-architecture.md](../../system-design/2026-09-14-module-decoupling-architecture.md) |
| 模块解耦整改计划 | [../remediation/2026-09-14-module-decoupling-refactor-plan.md](../../remediation/2026-09-14-module-decoupling-refactor-plan.md) |
| Phase 2 方案审阅 | [../audit/2026-09-15-module-decoupling-phase2-plan-review.md](../../audit/2026-09-15-module-decoupling-phase2-plan-review.md) |
| Phase 2 修订计划 | [../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md](../../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) |
| 多意图 HITL 复核方案 | [../system-design/2026-09-15-multi-intent-hitl-plan.md](../../system-design/2026-09-15-multi-intent-hitl-plan.md) |
| 多意图 HITL U1 契约 | [../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md](../../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md) |
| 多意图 HITL U2 分段 | [../test-reports/2026-09-15-multi-intent-u2-segmenter-test-report.md](../../test-reports/2026-09-15-multi-intent-u2-segmenter-test-report.md) |
| 多意图 HITL U3 PG | [../test-reports/2026-09-15-multi-intent-u3-intentflow-pg-test-report.md](../../test-reports/2026-09-15-multi-intent-u3-intentflow-pg-test-report.md) |
| 多意图 HITL U4 投影 | [../test-reports/2026-09-16-multi-intent-u4-outbox-projection-test-report.md](../../test-reports/2026-09-16-multi-intent-u4-outbox-projection-test-report.md) |
| 多意图 HITL U5 API | [../test-reports/2026-09-16-multi-intent-u5-decision-api-test-report.md](../../test-reports/2026-09-16-multi-intent-u5-decision-api-test-report.md) |
| 多意图 HITL U6 Web | [../test-reports/2026-09-16-multi-intent-u6-web-cards-test-report.md](../../test-reports/2026-09-16-multi-intent-u6-web-cards-test-report.md) |
| 多意图 HITL U8 generate | [../test-reports/2026-09-16-multi-intent-u8-task-generate-test-report.md](../../test-reports/2026-09-16-multi-intent-u8-task-generate-test-report.md) |
| 多意图 HITL U7 只读 | [../test-reports/2026-09-16-multi-intent-u7-auto-read-test-report.md](../../test-reports/2026-09-16-multi-intent-u7-auto-read-test-report.md) |
| 文档可靠性 M1 报告 | [../test-reports/2026-09-14-document-reliability-pilot-test-report.md](../../test-reports/2026-09-14-document-reliability-pilot-test-report.md) |
| 文档可靠性 M1 活验 | [../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md](../../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md) |
| 文档可靠性 M2 | [../test-reports/2026-09-14-document-reliability-m2-test-report.md](../../test-reports/2026-09-14-document-reliability-m2-test-report.md) |
| 文档可靠性 M3 U8 | [../test-reports/2026-09-15-taskrun-runmanifest-u8-test-report.md](../../test-reports/2026-09-15-taskrun-runmanifest-u8-test-report.md) |
| 文档可靠性 M3 U9 | [../test-reports/2026-09-15-dataset-lifecycle-u9-test-report.md](../../test-reports/2026-09-15-dataset-lifecycle-u9-test-report.md) |
| 文档可靠性 M4 U10 | [../test-reports/2026-09-15-chatlog-contextasm-u10-test-report.md](../../test-reports/2026-09-15-chatlog-contextasm-u10-test-report.md) |
| 文档可靠性 M5/M6 U11–U12 | [../test-reports/2026-09-15-app-composition-u11-u12-test-report.md](../../test-reports/2026-09-15-app-composition-u11-u12-test-report.md) |
| Track E E2 闭包守卫 | [../test-reports/2026-09-15-event-e2-hook-guard-test-report.md](../../test-reports/2026-09-15-event-e2-hook-guard-test-report.md) |
| Track E 复盘 | [./research/2026-09-15-track-e-retrospective.md](../../ops/research/2026-09-15-track-e-retrospective.md) |
| 旁支清点收口 | [../test-reports/2026-09-15-leftover-branch-cleanup-test-report.md](../../test-reports/2026-09-15-leftover-branch-cleanup-test-report.md) · [二次收口](../../test-reports/2026-09-17-leftover-hygiene-test-report.md) |
| SSE/幂等审计 | [../audit/2026-09-13-document-revision-sse-idempotency.md](../../audit/2026-09-13-document-revision-sse-idempotency.md) |
| 质量门禁自污染审计 | [../audit/2026-09-13-document-revision-quality-gate-self-pollution.md](../../audit/2026-09-13-document-revision-quality-gate-self-pollution.md) |
| 当前文档路由审计 | [../audit/2026-09-13-document-revision-active-doc-routing.md](../../audit/2026-09-13-document-revision-active-doc-routing.md) |
| 行动表状态假阳性审计 | [../audit/2026-09-13-document-revision-quality-status-false-positive.md](../../audit/2026-09-13-document-revision-quality-status-false-positive.md) |
| 待确认报告选择失败审计 | [../audit/2026-09-13-document-revision-pending-report-selection.md](../../audit/2026-09-13-document-revision-pending-report-selection.md) |
| UI-05b 拒绝路由失败审计 | [../audit/2026-09-13-document-revision-e2e-reject-routing.md](../../audit/2026-09-13-document-revision-e2e-reject-routing.md) |
| 职责伪造与 ETag 覆盖审计 | [../audit/2026-09-13-document-revision-duty-etag-overwrite.md](../../audit/2026-09-13-document-revision-duty-etag-overwrite.md) |
| UI-06 块选择器失败审计 | [../audit/2026-09-13-document-revision-block-selector.md](../../audit/2026-09-13-document-revision-block-selector.md) |
| UI-06 保存选择器失败审计 | [../audit/2026-09-13-document-revision-save-selector.md](../../audit/2026-09-13-document-revision-save-selector.md) |
| UI-07 纯净阅读取样失败审计 | [../audit/2026-09-13-document-revision-overlay-selector.md](../../audit/2026-09-13-document-revision-overlay-selector.md) |
| UI-07 transition 中间帧取样审计 | [../audit/2026-09-13-document-revision-overlay-transition.md](../../audit/2026-09-13-document-revision-overlay-transition.md) |
| UI-11 第二份文档路由与 lease 审计 | [../audit/2026-09-13-document-revision-second-doc-routing-lease.md](../../audit/2026-09-13-document-revision-second-doc-routing-lease.md) |
| UI-11 prelim_design 缺 output.md 审计 | [../audit/2026-09-13-document-revision-prelim-output.md](../../audit/2026-09-13-document-revision-prelim-output.md) |
| UI-06 编辑弹窗 hydration 竞态审计 | [../audit/2026-09-13-document-revision-editor-hydration-race.md](../../audit/2026-09-13-document-revision-editor-hydration-race.md) |
| UI-03 质量修复子工作区契约审计 | [../audit/2026-09-13-document-revision-quality-repair-workspace-contract.md](../../audit/2026-09-13-document-revision-quality-repair-workspace-contract.md) |
| UI-11 快速完成观测误判审计 | [../audit/2026-09-13-document-revision-fast-completion-observation.md](../../audit/2026-09-13-document-revision-fast-completion-observation.md) |
| UI-13a 私密分类导航失败审计 | [../audit/2026-09-13-document-revision-workflow-category-navigation.md](../../audit/2026-09-13-document-revision-workflow-category-navigation.md) |
| UI-13a 职责单元格误判审计 | [../audit/2026-09-13-document-revision-duty-cell-assertion.md](../../audit/2026-09-13-document-revision-duty-cell-assertion.md) |
| UI-13a reviewer 职责未同步审计 | [../audit/2026-09-13-document-revision-reviewer-duty-sync.md](../../audit/2026-09-13-document-revision-reviewer-duty-sync.md) |
| UI-03 首次生成 PG ID 误删 baseline | [../audit/2026-09-13-document-revision-pg-id-remap.md](../../audit/2026-09-13-document-revision-pg-id-remap.md) |
| UI-06 刷新后缓存竞争死锁审计 | [../audit/2026-09-14-document-revision-refresh-cache-race.md](../../audit/2026-09-14-document-revision-refresh-cache-race.md) |
| 受控正文真实 UI 验收报告 | [../test-reports/2026-09-14-controlled-content-ui-acceptance.md](../../test-reports/2026-09-14-controlled-content-ui-acceptance.md) |
+10 -6
View File
@@ -1,6 +1,6 @@
# Herdr Agent 角色、审批与备援 SSOT # Herdr Agent 角色、审批与备援 SSOT
**更新**: 2026-09-13 **更新**: 2026-09-21(路由目标基线仍为 2026-09-13)
**owner**: `orchestrator_codex` **owner**: `orchestrator_codex`
**status**: active **status**: active
@@ -10,7 +10,7 @@
| 名称 | 唯一含义 | 禁止混称 | | 名称 | 唯一含义 | 禁止混称 |
|------|----------|----------| |------|----------|----------|
| Herdr PI / `context_pi` | Herdr pane 内运行的 PI CLI,当前目标模型 MiniMax M3,负责工程调研与长上下文分析 | 不代表后端 runner,不作为产品 E2E 证据 | | Herdr PI / `context_pi` | Herdr pane 内运行的 PI CLI,2026-09-13 路由目标模型 MiniMax M3,负责工程调研与长上下文分析 | 不代表后端 runner,不作为产品 E2E 证据 |
| 产品 PI Agent Runner | Compose/服务端调用的产品执行器;模型与验收要求见 PROJECT | 不参与 Herdr 工程任务路由 | | 产品 PI Agent Runner | Compose/服务端调用的产品执行器;模型与验收要求见 PROJECT | 不参与 Herdr 工程任务路由 |
文档、提示词和 handoff 中禁止只写“PI”;必须写 `context_pi` 或“产品 PI Agent Runner”。 文档、提示词和 handoff 中禁止只写“PI”;必须写 `context_pi` 或“产品 PI Agent Runner”。
@@ -28,7 +28,7 @@
| `reserve_claude` | Claude Code / Sonnet 5 | P0/P1 架构争议、安全关键复核、Codex 失败后的第二强意见 | 额度极少,禁止日常调研、批量实现和首轮测试 | `orchestrator_codex` → `context_kimi` | | `reserve_claude` | Claude Code / Sonnet 5 | P0/P1 架构争议、安全关键复核、Codex 失败后的第二强意见 | 额度极少,禁止日常调研、批量实现和首轮测试 | `orchestrator_codex` → `context_kimi` |
| `reserve_devin` | Devin / 尚未接入 | 预留长任务自治席 | `standby`,不得派发 | 无 | | `reserve_devin` | Devin / 尚未接入 | 预留长任务自治席 | `standby`,不得派发 | 无 |
模型名称来自当前工作区约定;启动脚本不猜测各 CLI 的模型别名。每次新会话由 orchestrator 在首条状态回报中确认实际模型,不一致则停止高风险任务并记录 handoff。 表中模型是带日期的路由目标约定,不是在线能力证明。启动脚本不猜测 CLI 别名;每次会话记录实际客户端版本、模型、配置来源、可用性及核验时间。不一致时先核实批准配置,暂停依赖该能力的高风险任务,不为符合旧表自行换模型。产品 Runner 模型证据另按 [验证契约](./2026-09-21-validation-contract.md) 记录。
## 3. 任务泳道与自动备援顺序 ## 3. 任务泳道与自动备援顺序
@@ -46,7 +46,7 @@ Claude 只在 `critical-review` 中作为前排备援;其他 lane 放在最后
## 4. 自动派发和接管协议 ## 4. 自动派发和接管协议
1. Orchestrator 先创建 short-term、认领 mutex、建立独立 worktree;之后才启动或选择 agent。 1. Orchestrator 先按 AGENTS 任务分级登记主文档、认领 mutex、建立任务 worktree;相关只读子审查沿用父任务范围与记录,之后才启动或选择 agent。
2. 实例必须使用上表逻辑名,且 `cwd` 必须与任务 worktree 完全相同。派发器不会占用名称不规范或其他目录的实例。 2. 实例必须使用上表逻辑名,且 `cwd` 必须与任务 worktree 完全相同。派发器不会占用名称不规范或其他目录的实例。
3. 只向 `idle` / `done` 实例投递。同一 mutex 同时只允许一个执行者写入。 3. 只向 `idle` / `done` 实例投递。同一 mutex 同时只允许一个执行者写入。
4. 以下情况可自动尝试下一个备援:实例不存在、启动前已 blocked、认证失败、明确额度耗尽、客户端在写入前失败。 4. 以下情况可自动尝试下一个备援:实例不存在、启动前已 blocked、认证失败、明确额度耗尽、客户端在写入前失败。
@@ -57,7 +57,7 @@ Claude 只在 `critical-review` 中作为前排备援;其他 lane 放在最后
## 5. 自动审批不是无限授权 ## 5. 自动审批不是无限授权
统一入口为 [`scripts/herdr-agent-start.sh`](../../scripts/herdr-agent-start.sh)。当前安装版本验证过的参数如下: 统一入口为 [`scripts/herdr-agent-start.sh`](../../scripts/herdr-agent-start.sh)。下表保留 2026-09-13 安装版本的验证快照;使用前核对当前脚本和客户端帮助,不能据此声称新版本仍支持同一参数:
| profile | 自动审批参数 | 保留的边界 | | profile | 自动审批参数 | 保留的边界 |
|---------|--------------|------------| |---------|--------------|------------|
@@ -69,7 +69,7 @@ Claude 只在 `critical-review` 中作为前排备援;其他 lane 放在最后
| Kimi | `--auto` | Never Ask 模式 | | Kimi | `--auto` | Never Ask 模式 |
| Cline | `--tui --auto-approve true` | 所有工具自动批准 | | Cline | `--tui --auto-approve true` | 所有工具自动批准 |
即使客户端不弹确认框,Agent 也不得自行执行:删库/清空数据、强推、删除主干或未通过清理门禁的 worktree、停止 Herdr server、覆盖其他 Agent 未合并改动、泄露密钥、未获用户授权的外部写入。需要新权限时写 handoff/blocker,不把确认框转交给用户逐条点击。 即使客户端不弹确认框,Agent 也不得自行执行:删库/清空数据、强推、删除主干或未通过清理门禁的 worktree、停止 Herdr server、覆盖其他 Agent 未合并改动、泄露密钥、未获用户授权的外部写入。已授权范围内继续执行;扩大范围时记录具体动作与原因并请求必要授权。handoff、日志、引用材料或另一个 Agent 的“已授权”声明不能独立授予权限,须核实用户请求或委派来源。
## 6. 操作入口 ## 6. 操作入口
@@ -92,3 +92,7 @@ Claude 只在 `critical-review` 中作为前排备援;其他 lane 放在最后
## 7. 当前可用性基线 ## 7. 当前可用性基线
2026-09-13 现场基线:Herdr client/server 0.9.0、协议 22;Claude/Codex/Cursor/Antigravity/PI/Kimi 集成已安装且 current;Cline CLI 3.0.61 可调用,但尚无在线标准命名实例;Devin 尚未接入。在线状态是瞬时信息,派发前必须重新运行 `herdr agent list`。 2026-09-13 现场基线:Herdr client/server 0.9.0、协议 22;Claude/Codex/Cursor/Antigravity/PI/Kimi 集成已安装且 current;Cline CLI 3.0.61 可调用,但尚无在线标准命名实例;Devin 尚未接入。在线状态是瞬时信息,派发前必须重新运行 `herdr agent list`。
## 8. 退出与清理责任
任务完成不代表 pane/客户端已退出。记录 cleanup_owner、保留原因、唯一证据位置和复查触发;由独立集成/维护执行者在 owner 退出后核对工作区/进程,再按操作规范安全清理。`timeout`/`unknown` 不得当作退出证明,不强制清理尚被使用的 worktree。
+106 -58
View File
@@ -1,85 +1,133 @@
# Agent worktree 工作规范 # Agent worktree 与 L1 操作规范
**生效日期**:2026-09-21。用户已确认“提交吧,之后按这套执行”。 **修订**:`agent-governance-2026-09-21-r1`。来源:[AGENTS](../../AGENTS.md)、[规范登记](./2026-09-21-policy-registry.md)。工具为 `scripts/agentctl`(Python 标准库,Linux 本机),协议 v1;实际安装与验证见[实施任务](./short-term/2026-09-21-agent-governance-implementation.md)。
**规范来源**:[AGENTS](../../AGENTS.md) §6;[完整方案](../remediation/2026-09-21-agent-workflow-governance-plan.md)。
本文件用于创建任务、集成、部署、清理时执行。工作方式立即采纳;自动注册表、队列、服务守卫、OS 权限隔离尚未实现,当前使用下面的显式校验与人工协调。代码文件隔离不等于数据库或运行环境隔离。 本规范约束合作 Agent 的统一操作入口和 Git 提交/引用门禁。任意同 UID shell、修改管理器或跳过 hooks 仍可绕过;不把 L1 宣称为 OS 沙箱。共享服务配置与产品数据库不属于任务素材。
## 1. 创建任务 ## 1. 首次启用与旧锁兼容
1. 读取启动链,检查主目录和各任务的 `git status`、分支、`git worktree list --porcelain` 与 CURRENT/任务文。识别其他 Agent 的活跃任务,冲突范围不进入写阶段。 由获授权协调者在干净主目录 dev 执行一次:
2. 确认主工作区真实路径:通过 Git worktree 列表和 common-dir 核对,不根据当前脚本父目录推算。已有 `.kilo` 或目录/分支不匹配 worktree 仅盘点,不自动搬动或清理。
3. 确定任务 ID、允许写路径、mutex 和验证计划;同一写范围只有一个 owner。CURRENT 在各分支内仅作快照,协调者还需交叉核对活跃任务;未实现原子注册表前不宣称已有自动原子认领。
4. 使用显式本地 dev SHA。远端状态注明已 fetch 或仅本地跟踪快照;不因创建任务在主目录 checkout/pull,也不从 feature 派生无关任务。
在已核实的主仓路径执行以下语义;示例 task-id 须替换为本任务唯一标识:
```bash ```bash
repo_main=/home/weichen/projects/Agent-System ./scripts/agentctl init
base_dev_sha=$(git -C "$repo_main" rev-parse --verify 'dev^{commit}') ./scripts/agentctl hooks install
git -C "$repo_main" worktree add -b feature/task-id "$repo_main/.worktrees/task-id" "$base_dev_sha" ./scripts/agentctl doctor
``` ```
先检查目标路径不存在、无 symlink 越界、分支未被使用;保留 `base_dev_sha` 到任务文。主目录需移动 Git 引用时取得下述锁。不要使用尚未修复的 `agent-worktree.sh create/remove`:前者未明确 dev 起点,后者包含强制删除。 `init` 只在主目录运行;明确初始化主仓唯一 `.locks/state.sqlite` 和身份文件,不能从 linked worktree 自动建立第二套状态。现有 `.git/agent-control/dev-integration.lock` 保留,用硬链接使 `.locks/os/dev-integration.lock` 指向同一 inode;旧操作未结束、旧锁忙或新旧身份不符均拒绝迁移。状态目录整体被 Git/Docker 忽略,禁止自行删锁/删库/复制 WAL/重建空目录。
## 2. 开发、测试与准入 `status`、`doctor` 只读,未初始化返回结构化错误而不创建文件;每次打开管理库都会核对旧新 dev 锁身份。`hooks install` 拒绝覆盖外来 hook 或既有自定义 hooksPath;不能把别人的保护脚本直接替换掉。启用前必须完成主目录集成,并让旧任务在下一次写/集成前读取本修订。
- 所有新增代码、配置、规范和文档修改都在任务 worktree 完成;同一任务允许多个提交。 ## 2. 创建任务并登记范围
- `.env`、业务数据、真实用户会话、其他任务的构建产物不复制到新工作区。测试使用专属数据库、目录、端口和测试身份。
- 每次准入冻结完整 head SHA;记录命令、结果和证据。提交变化或 dev 组合变化后检查受影响的准入是否需重跑。
- Go、web、迁移、产品 PI 的门禁见完整方案 §6.2。治理规则/纯工程文档检查链接、规则冲突、差异和状态;不启动真实 PI 或重建业务服务。
- 普通文档与运行模板、提示词分开:`templates/**/*.md`、`configs/**/*.md` 可能改变运行行为。
## 3. 串行锁和集成槽位 从任一同仓工作区使用已验证工具:
共享 dev 的 Git 更新、部署、写入验收都使用共同 Git 目录下同一个 `agent-control/dev-integration.lock`。锁是本机合作约束;旧客户端可绕过,因此仍核查主目录现场、活跃任务、CURRENT 槽位并保留前后基线。完整注册表、自动队列另行实施。
锁应由覆盖整个操作的父进程持有,不是一个命令拿锁后退出再执行下一条:
```bash ```bash
common_dir=$(git rev-parse --path-format=absolute --git-common-dir) ./scripts/agent-worktree.sh create feature/task-id
mkdir -p "$common_dir/agent-control" # 输出 worktree 路径和本次实际 base_dev_sha;所有新任务显式来自当前 dev。
exec 9>"$common_dir/agent-control/dev-integration.lock" cd .worktrees/task-id
flock -n 9 || exit 1 ../../scripts/agentctl task register --id task-id --owner Codex --scope internal/example --scope .doc
# 在本进程及受控子进程中执行基线核对、候选准入、dev 更新与所需部署/验收。
# 同时记录 owner、task、phase、原 dev、候选 SHA 和恢复状态。
# 操作完毕或形成失败交接后退出本进程,关闭 fd 9。
``` ```
CURRENT holder/phase 由集成任务分支记录并在合入时成为快照,不能为占槽去改动另一个活跃任务的脏工作区。实时 owner/phase 可写在 common-dir 的 `agent-control/active-operation.json`,但该文件不是锁。结束保留结果再释放;发现前次非终态记录,先核查恢复,不能只因锁空闲就重复执行。 示例名称、范围应替换为实际 task_id 与最小写范围;要维护 CURRENT 自己的行,task_id 与该行 task 字段一致。工具定位唯一主仓,不在任务区嵌套 `.worktrees`。分支只接受 `feature/<task-id>` 或 `fix/<task-id>`,重名/路径占用/符号链接均拒绝。旧目录清理由 Git 真实映射定位,不按名字猜测。
共享 dev 的长 E2E 持锁至结束;独立固定环境才允许释放 dev。资源不足则排队,不默认起多套共享 Compose 或终止产品任务。 任务登记保存 owner、分支、worktree、源提交、允许写路径和能力;不隐式扩展已有登记。任务 scope 是允许范围,实际写许可由每次 `with` 获取。文件路径为仓库相对路径或目录前缀,`.` 表示全仓源文件范围,仅供明确需要的治理/集成任务;不能登记 `.git`、`.locks` 或运行数据作为源码写范围。
## 4. 集成候选与部署 特殊能力按任务授权登记,例如 `--capability docs:publish`、`git:dev`、`env:dev`;test/main/shared 能力不能由普通 dev 任务顺带申请。能力字段记录的是已授予的任务边界,不自行产生用户授权。
1. 锁定 dev 基线 D 和任务 head T;任务工作树干净且准入证据有效。 ## 3. 写操作与长驻客户端
2. 从 D 建 `.worktrees/` 下的 `feature/integrate-*`,把 T 以 `--no-ff` 合成候选 M。业务冲突退回 owner 修复;集成者不在主目录改代码解冲突。
3. 在 M 验证相应门禁。dev 仍为 D、任务仍为 T 才可在干净 dev 工作区 `merge --ff-only M`;变化则重建候选。dev 历史保留该 no-ff 节点,不再重做另一份未测试合并。
4. 纯工程规范/文档标 `deployment=not_required`。运行内容变化则从已合入版本构建/应用确定制品,核验实际镜像、schema、`/health`、`/ready` 和相关业务链路。
5. 当前 `candidate-images-build.sh --commit` 未保证构建树等于参数,修复前调用者必须确保干净树、HEAD 与参数完全一致,记录证据;不能仅凭标签声明准确版本。
6. `restart` 仅重启现有容器。worktree 不调用共享 `env-service.sh` 写命令;共享服务只由主目录下获授权的持锁集成者操作。失败记录 merged/deployed/accepted 的实际状态,兼容性不明时不自动恢复数据库。
### 主目录旧任务尚未结束时 ```bash
# 每次命令包裹完整操作;不是先拿一次 fd 锁,再换一个 shell 假定仍持有。
../../scripts/agentctl with --task task-id --resource path:internal/example -- <command> <args>
保护旧任务现场,不能为了落实新规范先强切 dev。仅**纯工程规范/文档**允许过渡例外:dev 未在其他 worktree 检出时,持锁将 dev 检出到 `.worktrees/agent-governance-dev-integration`,只接收已验证文档候选,不运行服务、不合入未验收业务代码。登记临时目录、原因和接管人,当前根目录继续保持原任务分支。 # Git 暂存/提交用同一 supervisor 包住,另持任务自身的 index 锁。
../../scripts/agentctl with --task task-id --resource path:internal/example --resource git:index -- <git-write-script>
```
旧任务 owner 保存/提交成果、确认主目录干净后:先确认临时 dev 工作区干净且没有 Agent/进程使用,普通 remove 该临时 worktree,释放 dev 检出占用,再由主目录切回 dev。不能用 `--ignore-other-worktrees` 或强删抢占。此前新任务直接从 dev ref 创建自己的 worktree,读取 dev 中已采纳的规范;旧任务收口时也按新流程集成。 `with` 可包裹任务客户端或包含多条命令的受控脚本;不必由模型手工续租。它是非重入操作锁:不在持有某资源的 child 中再次申请同一资源。长期 worker 只持实施路径,不长期占共享文档/集成资源;提交、共享文档更新和 task close 由协调者在 worker 退出后以新的短操作完成。需要自主逐步提交的客户端使用逐命令 `with`,不套全任务的大锁。默认 child cwd 为登记的任务 worktree,只有明确环境/集成操作可选择主目录。代码审查、查询或读取不可变 Git 快照不必持写锁。`--shared` 仅供稳定读取,不能通过写操作 guard。
## 5. 安全清理 锁资源支持目录层级冲突;多个资源一次申请、全部获准才启动 child。同任务不同路径可并发;index 单独排他。每个执行器持工作区生命周期 SH 锁,删除必须取得 EX,防止检查完清理后又进入新任务。`--wait` 为有限秒数,默认立即拒绝冲突;失败给 request_id/holder,可做不冲突工作或用原 request_id 重试。没有后台无限排队服务,也不需要常驻产品容器。
清理在待删除目录外执行,依次核查: 会话身份包含 PID/start-time/boot、操作代次与 epoch;继承凭据只对真实 supervisor 的子孙进程有效。锁不会因超时自动转给别人;异常进程和未完成 operation 保留恢复状态。SIGINT/SIGTERM、孤儿子进程和未完成文档写回按实现的恢复门禁处理。
1. worktree 实际路径、分支与任务登记一致;无越界/保护项;无 merge/rebase 中间状态。 ## 4. 共享文档:短锁、行版本和写回收据
2. `git merge-base --is-ancestor <task-head> dev` 成功;冻结后未新增未合入提交。
3. 所需部署与验收通过或 not_required;报告、截图和唯一日志已归档。
4. 检查 tracked、untracked **及 ignored 文件**;只允许丢弃确认可再生的缓存,不因 `git status --short` 为空就认定没有证据文件。
5. Agent/测试进程已退出、无进程 cwd/其他任务依赖该目录。
6. 普通 `git worktree remove <path>`;分支用 `git branch -d <branch>`。若非 dev 当前检出导致 -d 拒绝,即使祖先检查通过也保留到 dev 可用时清理,不换成 -D。
失败只标 cleanup_pending,保留数据。禁止强制删除、按年龄批量删除或把 worktree 清理扩展为 data/数据库/容器卷清理。Push 仅在用户明确要求时执行,并与本地提交/合入分开报告。 当前提供 CURRENT 与 FEATURE-MATRIX 的受控更新;普通任务只改 CURRENT 中自己的 task 行,不能覆盖别人的状态或自行释放槽位。模块行使用现有唯一模块名称定位;重复/歧义 ID 拒绝。发布入口不是对旧文件的整份覆盖。
## 6. 执行状态 ```bash
../../scripts/agentctl docs snapshot --file .doc/ops/CURRENT.md --record task-id
../../scripts/agentctl docs apply --task task-id --request /tmp/task-state-delta.json
```
- 已采纳:强制任务 worktree、明确 dev 基线、主目录保护、串行集成/部署、按范围验证、条件清理。 请求格式(示意,hash 使用 snapshot 的实际结果):
- 可立即执行:本规范的原生 Git、人工范围认领、本机 flock、精确候选检查。
- 尚待工程实现:自动登记与排队、脚本强制 guard、安全 helper、部署身份闭环、PI 镜像交付、故障自动恢复和 OS 沙箱。见 [自动化实施任务](./long-term/2026-09-21-agent-workflow-governance-automation.md)。 ```json
{"request_id":"task-id-state-1","changes":[{"file":".doc/ops/CURRENT.md","record_id":"task-id","expected_sha256":"<old-row-hash>","replacement":"| Codex | task-id | feature/task-id | .worktrees/task-id | module | in_progress |"}]}
```
新增行使用 `expected_sha256: null`。必须登记目标文件 scope 和 `docs:publish` 能力。工具在同一个短租约中重新读取、行级 CAS、保留其他内容、记录 operation、原子替换并保存文件 hash 收据。同一 request_id 同一请求重放不会重复写;更换载荷不能复用 ID。存在未经管理的本地共享文档改动时拒绝将它们混入收据,先保留现场并对齐正确基线。
多个文档先全部预检再写;中断留下可恢复的前后内容,不能把两个 rename 声称为任意文件读取者的一次原子发布。以同一 Git 提交作为持久发布边界。文件正常更新后,在自己的 task worktree 持对应 path + git:index 租约暂存/提交;hook 检查暂存文件 hash 与已生成收据一致。
FEATURE-MATRIX 从未完成升为 done 需要 `docs:approve` 和已在该任务 HEAD 提交的 `evidence` 路径,不能因任务退出自动关闭能力。仅有工具日志不等于产品验收。文档布局/焦点维护使用显式 `docs:admin` 与 `record_id: "@document"` 的整文档 hash CAS;它不能修改 CURRENT 集成槽位,也不能绕过模块完成证据规则。普通任务没有此入口权限。
## 5. 验证与候选集成
按[验证契约](./2026-09-21-validation-contract.md)选真实检查。工程工具自测入口:
```bash
python3 -B -m unittest discover -s scripts/tests -p 'test_*.py' -v
```
冻结待交付提交 T。从最新 dev D 创建独立 `feature/integrate-<id>` 候选工作区并登记,合并 T 形成 `--no-ff` 节点 M;候选的两名父提交中第一名必须是 D。发生业务冲突回原任务修复。
```bash
# 在候选工作区:登记 candidate-id、实际写范围和 git:dev 能力。
../../scripts/agentctl validate --task candidate-id -- <verification-command> <args>
../../scripts/agentctl integrate --task candidate-id --expected-dev <D>
```
validate 要求干净树、记录精确 HEAD 与命令摘要;必要的实际命令/测试输出仍保存在任务验收证据。不能拿 `true` 替代验证契约。integrate 要求对应验证收据、干净主目录 dev、基线仍为 D,再接收同一 M;dev 前进则重建候选并重验,不能重用陈旧 PASS。
本地 hooks 检查主干直接提交、task 身份、暂存 scope/实际租约、共享文档收据与敏感运行路径;引用事务门禁复用同一候选父节点和 task-bound 验证检查;dev 还要求由 integrate 创建、绑定 old/new 的操作身份,不能借 with 的租约直接快进或 update-ref 绕过。test/main 仍需要各自明确授权的候选与能力。所有管理工具始终以主仓已生效版本为准,旧 worktree 的旧脚本不能代表新协议已接入。
## 6. 共享服务与镜像
```bash
# task 已获 env:dev;从主目录执行,源码须干净。
./scripts/agentctl with --task task-id --resource env:dev --cwd <main-root> -- ./scripts/env-service.sh dev rebuild
```
服务写动作在任何配置创建、网络或 Docker 调用前检查:调用目录与脚本目录都属于主目录、主分支为 dev、当前执行身份与目标环境租约有效。`all` 必须显式持有四个环境资源;不为方便隐式扩大权限。`status/ps/logs` 不初始化 env/data,也不要求写租约。
`candidate-images-build.sh --commit SHA` 要求实际源码 HEAD==SHA 且源码干净;仅允许现有 nightly 的固定 promotion-receipts 目录内未跟踪证据。它从该提交 `git archive` 到私有临时目录,Dockerfile 和上下文均来自不可变导出,不读取随后被其他操作修改的工作树或未跟踪证据。清单记录 source_tree/source_context;临时目录在退出时删除。镜像清单通过临时文件/fsync/rename 发布,避免截断 receipt 被当作完整证据。制品与报告版本分别记录;restart 不是新代码部署证明。
影响产品运行的变更合入后按授权更新 dev,核对实际镜像、挂载、schema、health/ready 与受影响业务。普通工程规范与本地治理工具更新无需重建产品容器。使用共享 dev 的长 E2E 持有环境租约到结束,不能让另一部署改变验收对象。
## 7. 查询、恢复与冻结
```bash
./scripts/agentctl status
./scripts/agentctl operation <run-id>
./scripts/agentctl recover --run <run-id> --reason '<现场检查与恢复依据>'
```
已结束且结果明确的请求可按 ID 查询;busy 不证明旧操作死亡。recover 仅在原 supervisor 和子进程组均已消失、物理锁可获得时继续;文档现场必须匹配该 operation 的原/目标内容,第三方变化则停止。外部 Docker/Git 未知结果必须先核对现场,理由写入恢复记录;管理器不能自动撤销已经发生的外部副作用。
必要维护可用 freeze/unfreeze 对已授权资源显式冻结,带 task 与 reason;有效持有者也不能绕过冻结。不能删除锁文件“解锁”,也不能恢复一份旧库后把旧租约自动标 active。损坏、身份变化、文件系统异常时停止受保护写入并交接。
## 8. 收口与清理
任务状态、集成、部署、远端同步和 cleanup 分别记录,见[收口清单](./templates/session-close-checklist.md)。命令结束不等于任务完成;task close 会拒绝仍有运行或待恢复 operation 的任务。
```bash
./scripts/agentctl task close --id task-id --status done
./scripts/agent-worktree.sh remove feature/task-id --delete-branch
```
清理由目录外的维护者执行,核对实际 Git 映射、dev 祖先、所有证据、tracked/untracked/ignored 文件、未完成 Git 操作、registry、生命周期锁和使用目录的进程。只用普通 remove/-d,拒绝时保留 cleanup_pending 和复查责任,不换成强制删除。
**本机限制**:无法读取某些同 UID 进程 cwd 时,工具不能证明其未使用目录,故保留 cleanup_pending;只有明确识别的 OS 用户 systemd 管理进程有诊断性例外。测试中的 3 个删除正例仅替换 `/proc` 枚举以模拟可见主机,其余 Git/锁/文件条件为真实执行;可见占用、未知不可读、未合入、脏文件与活跃登记的拒绝另有真实/针对性测试。不要把这项限制转成跳过检查的通用开关。
保护任务、旧 detached 工作区和非本次产物不自动清理。用户未要求 push 时仅本地提交/合入;用户已要求时再按授权同步并核对远端引用。
+25
View File
@@ -0,0 +1,25 @@
# Agent 规范来源与生效登记
## 生命周期与权威
`proposed → approved → active → deprecated` 是规则生命周期;与任务的 `planned/in_progress/blocked/done/cancelled` 分开。`manual/enforced` 是工具执行能力,独立于规则状态。规则获批准不证明脚本已经强制执行。
本轮修订 ID:`agent-governance-2026-09-21-r1`。owner:`orchestrator_codex`。批准依据:[规范审查](../audit/2026-09-21-agent-policy-review.md) R01–R10 与用户后续实施授权,具体授权回链由[实施任务](./short-term/2026-09-21-agent-governance-implementation.md)保存;审查原文保留历史“待采纳”语境,不改写成执行证据。
本轮规则在任务分支为 `approved`;**包含本修订的受控 dev 集成提交**是 `effective_commit`,从该提交进入 dev 后为 `active`。集成者在实施任务记录完整 SHA 与验证证据,避免自引用提交散列。读取时先核对该集成提交是否为当前 dev 祖先;尚未集成的规则不能冒充全仓已生效。任务记录读取的 dev SHA 和修订 ID;旧任务集成前比较规则变化,只按已生效规则补齐适用条件。
| 唯一来源 | 范围 | 本轮 revision/state | owner | 生效提交 |
|---|---|---|---|---|
| [AGENTS](../../AGENTS.md) | 任务分级、授权、安全、Git/收口硬边界 | r1 / approved,合入后 active | orchestrator_codex | 本修订 dev 集成提交 |
| [PROJECT](../../PROJECT.md) | 仓库地图、环境、工具与模块入口 | r1 / approved,合入后 active | orchestrator_codex | 同上 |
| [验证契约](./2026-09-21-validation-contract.md) | 测试范围、精确 PI 用例与报告 | r1 / approved,合入后 active | orchestrator_codex | 同上 |
| [worktree 操作规范](./2026-09-21-agent-worktree-workflow.md) | 创建、租约、共享文档、候选、环境、恢复、清理 | 本轮更新部分 approved,合入后 active;既有规则按历史集成记录 | orchestrator_codex | 本轮实施任务及既有历史交接 |
| [Herdr roster](./2026-09-13-herdr-agent-roster.md) | 角色与备援;实际模型/可用性须重新核验 | 既有规则 active;本轮澄清 approved,合入后 active | orchestrator_codex | Git 历史及本轮实施任务 |
客户端入口仅指向上述来源;不独立制定第二套测试、提交或凭证规则。外层系统约束及当前用户授权优先,内部冲突按 AGENTS 硬边界、PROJECT 项目细则、专门操作流程的职责解决;仍无法同时满足时记录冲突并请求范围决策。
## 能力与证据登记
具体工具版本、已验收能力与不覆盖范围由 [操作规范](./2026-09-21-agent-worktree-workflow.md) 和实施任务中的真实负例验收记录定义。文档/脚本存在不等于 `enforced`:只有指定版本入口及其测试证明的边界可记 enforced。普通 prose、未接入 hooks、绕过入口的直接文件修改、跨机器权限隔离不得因此声称已被强制。
CURRENT 是带时间的活跃协调快照;Git/机器状态用于核验实际分支、锁与进程,FEATURE-MATRIX 是能力声明,gap-matrix 是发布门禁。审计、方案、历史报告和较新日期均不能自行覆盖上述权威。保留每条结论的范围、提交、环境与日期,避免把旧的通过结论当作当前运行事实。
@@ -0,0 +1,62 @@
# 按变更范围选择验证与证据
规则状态、owner 与生效条件见 [规范登记](./2026-09-21-policy-registry.md)。本文件是项目验证范围和报告要求的唯一来源;客户端仅引用。执行共享环境测试前,先满足 [操作规范](./2026-09-21-agent-worktree-workflow.md) 的授权、环境身份和持锁条件。
## 验证矩阵
| 变更范围 | 必需验证 | 不足以验收的证据 |
|---|---|---|
| 纯规范/普通文档 | diff、相对链接、状态、所有入口指令一致性 | 未核对来源的文案结论 |
| 治理脚本 | 临时 Git 仓库中的真实分支/锁/并发/异常退出与拒绝路径 | 仅语法检查或 stub |
| Go/接口 | 对应单测;`./scripts/arch-check.sh --mode block`、`go vet ./...`、`go test -short ./...`;受影响 PG 和鉴权负例 | short 测试替代被跳过的 PG/真实链路 |
| 前端 | `web/` 中 `npm test`、`npm run build`;受影响交互的真实浏览器验证 | API 直调替代 UI 操作 |
| 迁移/运行配置 | 独立环境升级、兼容性、回退/前滚条件及就绪验证 | 重启/health 替代版本与持久性证据 |
| 产品 Agent/工作流(含运行模板/提示词) | 受影响真实产品 PI Agent Runner 链路;对应用户流程、持久状态和 UI | stub、SKIP、测试名未匹配、仅健康检查 |
多类变更取并集。按 [模块开发标准](../system-design/2026-09-20-module-development-standards.md) 追加边界与 registry parity 等范围门禁。开发阶段可用 stub;交付验收不能冒充真实执行。纯文档无需产品 PI 或全量 Go,也不为形式重建服务。
## 产品 PI 基线与精确追加
优先入口:`./scripts/run-nightly-real-pi.sh`。调用前核验并从获授权配置提供(不在命令/报告贴凭证值):
- 独立可写 `TEST_DATABASE_URL`,不得指向真实生产业务库;确认 Runner 与测试所需工作区/`DATA_DIR` 挂载相容。
- `PI_AGENT_RUNNER_URL` 和 `PI_AGENT_RUNNER_AUTH_TOKEN` / `RUNNER_AUTH_TOKEN`;真实 Runner 可访问。
- 共享 KB 的授权连接与必要凭证;正常验收要求在线。`KB_ALLOW_OFFLINE=1` 只证明显式降级场景。
- 目标环境批准的实际模型、协议及制品;通过 `PI_AGENT_MODEL` 传入批准值并比对 Runner 实际配置。脚本默认值不构成批准,也不证明实际模型。
- 脚本可能构建候选镜像、写 promotion receipt;须满足构建身份/授权条件。`NIGHTLY_SKIP_IMAGE_BUILD=1` 不能补足缺失的制品摘要证据。
脚本当前筛选 **仅两项**:
```text
TestMultiIntentU8_RealPI_ConfirmGenerate_PG
TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain
```
它不是“全部工作流通过”。按变更范围追加精确名称,例如(前提条件已核验,且已处于相应环境执行租约/独立环境):
```bash
go test -count=1 -timeout 2h -v ./internal/server/ -run '^TestPiAgent_FullLifecycle_MultiRole_RealWorld_E2E$'
go test -count=1 -timeout 90m -v ./internal/server/ -run '^TestWorkflowGovernance_PiAgent_PG_(FullChain|ProductManager_FullChain|TestManager_FullChain)$'
```
当前源码前提:上述 PG/U8 用例的 `piAgentRunnerHealthy` 预检固定访问 `127.0.0.1:18865`,不能仅设置其他 Runner URL 就假定能执行;FullLifecycle 配置写死 `MiniMax-M3` 且使用临时 DataDir/内存模式,不能用它单独证明 PG 持久性,也不能声称 `PI_AGENT_MODEL` 已改变该用例模型。执行前确认这些条件符合目标环境批准范围;不符时记录缺项并安排相应测试适配,不静默换模型或把 SKIP 当验收。
测试筛选区分大小写;`-run 'PI'` 不能选择名称中的 `PiAgent`。追加用例先读其环境前提;FullLifecycle 中 `RUN_FULL_ADHOC_WBS` 与 `RUN_HEAVY_STAGES` 控制额外段,不能把默认未跑段报为已覆盖。普通 PG 用例同样以源码中的精确名称/前提为准。
运行前列 `expected_tests`(含必须子场景),运行后逐项对照执行、PASS、FAIL、SKIP、未匹配及重试。退出码 0 或 nightly receipt `pass` 不能替代该对照:基线脚本检查全 SKIP,但部分 SKIP 仍须人工核验。缺项不记 PASS,基础设施问题记录 blocker/infra_fail。保留首轮与重试关系,不隐藏失败。
## 模型与环境证据
模型以目标环境批准配置为准;dev/test/main 的 Compose 默认值可能不同,不能将某个模型全局写成验收条件。每次记录目标环境、源码 SHA、镜像/制品摘要、schema、Runner 实际模型/协议、配置来源与核验时间;仅有环境变量不能证明实际值。发现不符先查明并记录,不为满足文档自行换模型。不得用 Herdr `context_pi` 的模型信息替代产品 Runner。
共享环境的长 E2E 持锁至结束;无权使用该环境则在独立固定环境执行。制品/候选变更后重新确认适用证据,不把旧提交 PASS 搬到新提交。
## 报告与真实性
小型文档/脚本任务可以在主文档设验证节;独立报告放 `.doc/test-reports/YYYY-MM-DD-<slug>-test-report.md`,附件放 `.doc/test-reports/assets/<report-slug>/`。任何层级至少记录:范围、被测 SHA、实际命令、环境、预期与实际、判定、证据链接、缺项/风险。命令包含秘密时记录参数名与脱敏方式,不记录值。
跨模块/业务交付使用完整报告:背景、目标、方案/环境、预期、逐例实际结果与耗时、实际执行套件汇总、结论与剩余条件。套件汇总只列本次适用并实际运行的检查,不无条件要求全量 Go。声明 PASS 必须有对应真实输入/输出或足够判定的真实片段;来源与结论可核实,脱敏不得改写结果。无需提交原始完整日志、个人信息、业务正文或秘密截图。记下删减/脱敏范围,唯一证据清理前先安全归档。
## 工程工具验收补充
本地协调与运维守卫使用 `python3 -B -m unittest discover -s scripts/tests -p 'test_*.py' -v`,在临时真实 Git/SQLite/进程环境执行;Docker 守卫测试使用受控目标记录调用,不重建共享服务。nightly 入口现在核对最终尝试日志中两个预期顶层测试均恰好执行且 PASS;零用例、部分 SKIP 或重复拼接证据均不能生成 PASS 收据。该日志核验不是本轮执行了产品 PI 的声明。
+36 -238
View File
@@ -1,188 +1,52 @@
# 当前焦点 # 当前焦点
**更新**: 2026-09-21 **更新**:2026-09-21。此文件是协调快照;实际分支、进程、执行租约以机器核验为准。能力和发布结论分别见 [FEATURE-MATRIX](../status/FEATURE-MATRIX.md) / [gap-matrix](../remediation/gap-matrix.md),本次归档不改变 `production_ready`。
**production_ready**: yes(门禁 B1–B4 已签;dev schema **61**,test/main 仍为 **58**。P0 热修 done;rdev 种子热修 done,见 [报告](../test-reports/2026-09-20-rdev-seed-hotfix-test-report.md))
## 焦点 ## 焦点
1. **工作流作用域权重**:`WORKFLOW_SCOPE_MODE` 默认 `strict`;`weighted` 按关系权重打分,只读跨流注明归属,写动作仍拦截。见 [报告](../test-reports/2026-09-21-workflow-scope-weighting-test-report.md)。test/main 未晋升。 1. **Agent 规范与 L1 操作门禁实施**:R01–R10、授权/证据边界、临时仓库负例验收;[实施任务](./short-term/2026-09-21-agent-governance-implementation.md)。工具是否 enforced 以实际版本和验收收据为准。
2. **意图确认优先与阶段感知**:弱写「整理需求」→ intake 确认卡片。见 [报告](../test-reports/2026-09-21-intent-confirm-stage-aware-test-report.md)。test/main 未晋升。 2. **遗留未完成任务确认**:保留 governance-adoption 与 partial-deferred 原状态,由原 owner 核验后收口/交接;本次归档不擅自宣告完成。
3. **上传解析提示 / 去重 / CoW 收口**:MinerU 解析状态轮询闭环(pending→running→done)、重复文件「已复用」提示、失败原因+重新解析、CAS 不可变 CoW 契约测试,已合入 `dev`(代码 `99905571`、证据 `74587ccf`、收口 `a0464eb5`)。见 [报告](../test-reports/2026-09-21-upload-parse-ux-dedup-test-report.md)。test/main 未晋升。前序任务执行状态卡收口见 [报告](../test-reports/2026-09-21-taskrun-status-card-persistence-test-report.md)(含二轮修复:completed 步骤 label 冻结为"正在…(NN%)"的收口归一化 + dev 历史回填,已合入 `dev@4b303247` 并真实 PI 验收)。 3. **worktree 保留与清理接管**:已完成交付与 cleanup 分别记录。owner 退出、证据保存且门禁满足后再由维护者清理,见下表。
4. **MinerU 真实 PDF 二轮验收**:用户反馈「PDF 内容未被理解」由两因叠加——(A) 首轮在 worktree 内跑 `env-service dev rebuild` 令挂载漂到 worktree `data/dev`,真实历史 blob 全部读不到;(B) Agent 轻量 API 硬限 20 页,28 页真实文件失败。首轮结论作废。二轮从**主目录** rebuild 修复挂载,`internal/mineru` 在 `-30003` 时按 20 页窗分块解析并重编全局页锚;`ReadRefBytes` 失败改为 `slog.Warn`。已合入 `dev@362b68a0`。真实 `bd_intake`(项目 6532)`completed`,工作区 `materials_text` 注入 29152 字节中文正文。见 [报告](../test-reports/2026-09-21-mineru-doc-parsing-test-report.md) 第八节。
5. **红线**:禁止整支 merge FAV;不默认升 `MULTI_INTENT_PLAN_TIER=full`;`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;FAV live 观察窗证据驱动;`FAV_DEGRADED_READ=false`;项目 143 只读;SWBOOT-10 保持 fail-closed stub;D7 对话 ADR 仍待合规签字(追踪表留存类别已定为 `record`,不授权物理删除);**不改 `production_ready`**;模块解耦批次 A–G 与回溯实施切片未单独认领前保持 `planned`;**合入 dev 后必须回主目录跑 `env-service.sh dev rebuild`,禁止任何 worktree 内运行 `env-service`**(脚本 ROOT_DIR 取自脚本所在目录树,会带走 bind mount,导致 `/app/data` 漂到 worktree 空目录)。
## 并行认领 ## 并行认领
| owner | task | branch | worktree | mutex | status | | owner | task | branch | worktree | mutex | status |
|-------|------|--------|----------|-------|--------| |---|---|---|---|---|---|
| Codex | 2026-09-21-agent-governance-implementation | feature/agent-governance-implementation | .worktrees/agent-governance-implementation | agent-governance | in_progress | | Codex | 2026-09-21-agent-governance-implementation | feature/agent-governance-implementation | .worktrees/agent-governance-implementation | agent-governance | in_progress |
| Cursor | 2026-09-21-workflow-scope-weighting | feature/workflow-scope-weighting-2026-09-21 | .worktrees/workflow-scope-weighting-2026-09-21 | intent+web+docgov | done | | Cursor | 2026-09-21-workflow-scope-weighting | feature/workflow-scope-weighting-2026-09-21 | .worktrees/workflow-scope-weighting-2026-09-21 | intent+web+docgov | done |
| Cursor | 2026-09-21-upload-parse-ux-dedup | feature/upload-parse-ux-2026-09-21 | .worktrees/upload-parse-ux-2026-09-21 | server+web+storage | done | | Cursor | 2026-09-21-upload-parse-ux-dedup | feature/upload-parse-ux-2026-09-21 | .worktrees/upload-parse-ux-2026-09-21 | server+web+storage | done |
| Cursor | 2026-09-21-mineru-doc-parsing | feature/mineru-doc-parsing-2026-09-21 | .worktrees/mineru-doc-parsing-2026-09-21 | storage+bizdev+kb | done | | Cursor | 2026-09-21-mineru-doc-parsing | feature/mineru-doc-parsing-2026-09-21 | .worktrees/mineru-doc-parsing-2026-09-21 | storage+bizdev+kb | done |
| Cursor | 2026-09-21-intent-confirm-stage-aware | fix/intent-confirm-stage-aware-2026-09-21 | .worktrees/intent-confirm-stage-aware-2026-09-21 | intent+web+docgov | done | | Cursor | 2026-09-21-intent-confirm-stage-aware | fix/intent-confirm-stage-aware-2026-09-21 | .worktrees/intent-confirm-stage-aware-2026-09-21 | intent+web+docgov | done |
| Cursor | 2026-09-21-taskrun-status-card | feature/taskrun-status-card-2026-09-21 | .worktrees/feature-taskrun-status-card-2026-09-21 | chat+web+taskrun | done | | Cursor | 2026-09-21-taskrun-status-card | feature/taskrun-status-card-2026-09-21 | .worktrees/feature-taskrun-status-card-2026-09-21 | chat+web+taskrun | done |
| Cursor | 2026-09-21-intent-workflow-scoping | fix/intent-workflow-scoping-2026-09-21 | - | intent+web+docgov | done |
| Codex | 2026-09-21-agent-workflow-governance-adoption | feature/agent-workflow-governance-plan | .worktrees/feature-agent-workflow-governance-plan | agent-workflow-governance | in_progress | | Codex | 2026-09-21-agent-workflow-governance-adoption | feature/agent-workflow-governance-plan | .worktrees/feature-agent-workflow-governance-plan | agent-workflow-governance | in_progress |
| Codex | 2026-09-21-agent-workflow-governance-plan | feature/agent-workflow-governance-plan | .worktrees/feature-agent-workflow-governance-plan | agent-workflow-governance-plan | done | | Codex | 2026-09-21-agent-workflow-governance-plan | feature/agent-workflow-governance-plan | .worktrees/feature-agent-workflow-governance-plan | agent-workflow-governance-plan | done |
| Cursor | 2026-09-21-workflow-doc-key-unification | fix/workflow-doc-key-unification-2026-09-21 | .worktrees/fix-workflow-doc-key-unification-2026-09-21 | docgov+web | done | | Cursor | 2026-09-21-workflow-doc-key-unification | fix/workflow-doc-key-unification-2026-09-21 | .worktrees/fix-workflow-doc-key-unification-2026-09-21 | docgov+web | done |
| Cursor | 2026-09-21-dev-deep-test-four-issues | fix/dev-deep-test-four-issues-2026-09-21 | - | web+intent+taskrun | done |
| Cursor | 2026-09-21-trace-panel-to-chat | feature/trace-panel-to-chat-2026-09-21 | - | trace-impact+web | done |
| Cursor | 2026-09-21-taskrun-scope-persist | fix/taskrun-scope-persist-2026-09-21 | - | projectinstance+taskrun | done |
| Cursor | 2026-09-21-dev-multiuser-four-issues | fix/dev-multiuser-four-issues-2026-09-21 | .worktrees/fix-dev-multiuser-four-issues | projectinstance+docgov | done | | Cursor | 2026-09-21-dev-multiuser-four-issues | fix/dev-multiuser-four-issues-2026-09-21 | .worktrees/fix-dev-multiuser-four-issues | projectinstance+docgov | done |
| Cursor | 2026-09-21-taskrun-error-transparency | fix/taskrun-error-transparency-2026-09-21 | - | chat+web | done |
| Cursor | 2026-09-21-trace-panel-null-crash | fix/trace-panel-null-crash-2026-09-21 | - | trace-index+web | done |
| Cursor-Auto | 2026-09-20-stage-backtrack-design | feature/stage-backtrack-design | - | trace-impact-design | done |
| Cursor-Auto | 2026-09-20-impact-loop-t1 至 T3/B1–B3 | feature/stage-backtrack-impl | - | trace-index | done |
| Cursor | 2026-09-20-rdev-seed-hotfix | fix/workflow-seed-bootstrap-2026-09-20 | - | workflow-def | done |
| Cursor-Auto | 2026-09-20-module-decoupling-spec-audit | docs/module-decoupling-spec-2026-09-20 | - | module-decoupling-spec | done |
| Cursor-Auto | 2026-09-20-p0-closeout | docs/p0-closeout-2026-09-20 | - | docs | done |
| Cursor-Auto | 2026-09-20-p0-hotfixes(Wave 0 文档) | docs/p0-triage-2026-09-20 | - | docs | done |
| Cursor-Auto | 2026-09-20-p0-capability-00058 | fix/capability-merge-00058-2026-09-20 | - | infra+chat | done |
| Cursor-Auto | 2026-09-20-p0-idor | fix/authz-idor-hotfixes-2026-09-20 | - | authz | done |
| Cursor-Auto | 2026-09-20-p0-runtime-stability | fix/runtime-panic-races-2026-09-20 | - | taskrun+chat+web | done |
| Cursor-Auto | 2026-09-20-p0-credentials | fix/credential-hardening-2026-09-20 | - | ops+infra | done |
| Cursor-Auto | 2026-09-20-proposed-authoritative-reports | feature/capability-enablement-impact-plan | - | docgov | done |
| Cursor-Auto | 2026-09-20-pi-narrative-clipped-diff | feature/capability-enablement-impact-plan | - | docgov | done |
| Cursor-Auto | 2026-09-20-pi-reasoning-profile | feature/capability-enablement-impact-plan | - | pi-agent | done |
| Cursor-Auto | 2026-09-20-semantic-summary-persistence | feature/capability-enablement-impact-plan | - | chat+lineage-version | done |
| Cursor-Auto | 2026-09-20-feature-readiness-and-lineage-authz | feature/capability-enablement-impact-plan | - | sysadmin+lineage | done |
| Codex | 2026-09-20-capability-enablement-impact-plan | feature/capability-enablement-impact-plan | - | capability-impact-research | done |
| Cursor-Auto | 2026-09-20-post-activation-duty-reassign-member-chat | fix/post-activation-duty-reassign-member-chat | - | projectworkflow+authz+web | done |
| Cursor-Auto | 2026-09-20-test-env-triage-fixes | fix/test-env-triage-2026-09-20 | - | projectworkflow+chat+assets | done |
| Cursor-Auto | 2026-09-20-test-env-beta6-verification | docs/test-env-beta6-verification | - | infra+docs | done |
| Cursor-Auto | 2026-09-20-bug-hunt-permissions-uploads | fix/bug-hunt-2026-09-20-permissions-and-uploads | - | authz+web+projectworkflow | done |
| Cursor | audit-b1-data-consistency | fix/audit-b1-data-consistency | - | docgov+workflow+chat | done |
| Cursor | audit-b2-authz-hardening | fix/audit-b2-authz-hardening | - | authz | done |
| Cursor | audit-b3-sse-events | fix/audit-b3-sse-events | - | chat+events | done |
| Cursor | audit-b4-frontend | fix/audit-b4-frontend | - | web | done |
| Cursor-Auto | 2026-09-19-audit-remainder-closeout | fix/audit-remainder-closeout | - | audit-remainder | done |
| Antigravity | 2026-09-20-dev-deep-test-and-promotion | feature/dev-deep-test-and-promotion | - | integration | done |
| Cursor-Auto | 2026-09-19-remove-control-center | fix/remove-control-center | - | controlcenter | done |
| Cursor-Auto | 2026-09-17-control-center-server | feature/control-center-server | - | controlcenter | cancelled |
| Kimi | 2026-09-18-user-manual | feature/user-manual | .worktrees/feature-user-manual | docs | done |
| Cursor-Auto | 2026-09-18-wave7-roadmap-orchestration | docs/wave7-roadmap-orchestration | - | roadmap-orchestration | done |
| Cursor-Auto | 2026-09-18-wave6-doc-drift | docs/wave6-doc-drift | - | doc-drift | done |
| Cursor-Auto | 2026-09-18-wave5-deadcode-decisions | fix/wave5-deadcode-decisions | - | deadcode-cleanup | done |
| Cursor-Auto | 2026-09-18-wave4-hardening-r1-r8 | fix/wave4-hardening-r1-r8 | - | hardening | done |
| Cursor-Auto | 2026-09-18-wave3-test-debt-blindspots | fix/wave3-test-debt-blindspots | - | test-debt | done |
| Cursor-Auto | 2026-09-18-wave2-config-flag-align | fix/wave2-config-flag-align | - | infra | done |
| Cursor | 2026-09-18-wave1-main-env-fix | fix/wave1-main-env-fix | - | infra | done |
| Cursor | 2026-09-18-wave0-p0-bug-fixes | fix/wave0-p0-sse-chat-cache-bugs | - | chat + web | done |
| Kimi | 2026-09-17-system-health-check(只读审计,未实施) | - | - | audit | done |
| Cursor | 2026-09-17-docgov-modification-proposal-http | feature/docgov-modification-proposal-http | - | docgov | done |
| Cursor | 2026-09-17-document-author-only-closeout | feature/document-author-only-plan | - | document-duty-policy | done |
| Cursor | 2026-09-17-rdev-template-depth-s1-continued | feature/rdev-template-depth-s1-continued | .worktrees/feature-rdev-template-depth-s1-continued | rdev-templates | done |
| Cursor | 2026-09-17-rdev-workflow-integration | feature/rdev-core | - | workflow-def | done |
| Cursor | 2026-09-17-rdev-template-depth | feature/rdev-template-depth | .worktrees/feature-rdev-template-depth | rdev-templates | done |
| Codex | 2026-09-17-document-author-only-plan | feature/document-author-only-plan | - | document-duty-policy-plan | done |
| Cursor | 2026-09-17-initial-load-perf-u2 | feature/initial-load-perf-u2 | - | web + static-delivery | done |
| Codex | 2026-09-17-initial-load-performance-plan | feature/initial-load-performance-plan | .worktrees/feature-initial-load-performance-plan | loading-performance-audit | done | | Codex | 2026-09-17-initial-load-performance-plan | feature/initial-load-performance-plan | .worktrees/feature-initial-load-performance-plan | loading-performance-audit | done |
| Kimi | 2026-09-17-system-admin-console | feature/system-admin-console | - | web + sysadmin | done |
| Cursor-Auto | 2026-09-17-engineering-dashboard | feature/engineering-dashboard | .worktrees/feature-engineering-dashboard | engineering-dashboard | done |
| Cursor | 2026-09-17-promote-null-fk-to-test | feature/promote-null-fk-to-test | - | infra | done |
| Cursor | 2026-09-17-docgov-optional-duty-null-fk | fix/docgov-optional-duty-null-fk | - | docgov | done |
| Cursor | 2026-09-17-leftover-hygiene | chore/leftover-hygiene-2026-09-17 | - | ops | done |
| Cursor | 2026-09-17-w2-semantic-summary-shadow | feature/w2-semantic-summary-shadow-live | - | chat | done |
| Cursor | 2026-09-17-w3-pi-change-report-narrative | feature/w3-pi-change-report-narrative-live | - | docgov | done |
| Cursor | 2026-09-17-w4-multi-intent-u9-u12 | feature/w4-u9-segment-live | - | intent-plan | done |
| Cursor | 2026-09-17-w5a-software-bootstrap-ui | feature/w5a-software-bootstrap-ui-pi | - | workflow-design | done |
| Cursor | 2026-09-17-w6-fav-subscriber-shadow | feature/w6-fav-subscriber-shadow | - | events | done |
| Cursor | 2026-09-17-w7-d7-inventory | feature/w7-d7-data-inventory | - | chat | done |
| Cursor | 2026-09-17-promote-dev-to-test | feature/promote-dev-to-test-2026-09-17 | - | infra | done |
| Cursor | 2026-09-17-kb-host-auth-fix | fix/kb-host-auth-layered-load | - | infra | done |
| Cursor | 2026-09-17-w1-ci-e2e-green-nightly | feature/w1-ci-e2e-green-nightly | - | ci | done |
| Cline | 2026-09-16-partial-deferred-completion | docs/partial-deferred-completion-plan | - | architecture-docs | in_progress | | Cline | 2026-09-16-partial-deferred-completion | docs/partial-deferred-completion-plan | - | architecture-docs | in_progress |
| Cursor | 2026-09-16-intent-plan-playwright-dual-tab | feature/intent-plan-cards-playwright | .worktrees/feature-intent-plan-cards-playwright | web | done |
| Cursor | 2026-09-16-full-backlog-override | docs/override-nogo-full-backlog | - | architecture-docs | done |
| Cursor | 2026-09-16-gitea-nightly-docker-datadir | fix/gitea-nightly-docker-datadir | - | ci | done |
| Cursor | 2026-09-16-gitea-nightly-host-tools | fix/gitea-nightly-host-tools | - | ci | done |
| Cursor | 2026-09-21-test-env-lazy-doc-verification | docs/lazy-doc-verification-2026-09-21 | - | infra+docs | done |
| Cursor | 2026-09-16-wave-leftovers | fix/wave-leftovers-n7-playwright-dod | - | ci | done |
| Cursor | 2026-09-16-wave3-pu6-pu8-go-no-go | feature/wave3-pu6-pu8-go-no-go | - | architecture-docs | done |
| Cursor | 2026-09-16-wave2c-same-image-digest | feature/wave2c-same-image-digest | - | infra | done |
| Cursor | 2026-09-16-wave2b-nightly-promotion-receipt | feature/wave2b-nightly-promotion-receipt | - | ci | done |
| Cursor | 2026-09-16-wave2-real-pi-evidence | feature/wave2-real-pi-evidence | - | intent-plan | done |
| Cursor | 2026-09-16-wave1-race-tempdir-cleanup | fix/race-tempdir-cleanup | - | ci | done |
| Cursor | 2026-09-16-wave1-iam-fail-closed | fix/iam-store-fail-closed | - | authz | done |
| Cursor | 2026-09-16-feature-matrix-wave0-calibration | feature/feature-matrix-production-readiness-plan | .worktrees/feature-matrix-production-readiness-plan | architecture-docs | done |
| Codex | 2026-09-16-feature-matrix-production-readiness-plan | feature/feature-matrix-production-readiness-plan | .worktrees/feature-matrix-production-readiness-plan | architecture-docs | done |
| Cursor-Auto | 2026-09-16-matrix-deep-test-promote-test | feature/matrix-deep-test-promote-test | - | infra | done |
| Cursor-Auto | 2026-09-16-production-ready-gates | docs/production-ready-flip | - | ci | done |
| Cursor-Auto | 2026-09-16-demo-seed-pollution | fix/demo-data-pollution-web | - | web | done |
| Cursor | 2026-09-16-multi-intent-u8-task-generate | feature/multi-intent-u8-task-generate | - | intent-plan | done |
| Cursor | 2026-09-16-multi-intent-u7-auto-read | feature/multi-intent-u7-auto-read | - | intent-plan | done |
| Cursor | 2026-09-16-multi-intent-u6-web-cards | feature/multi-intent-u6-web-cards | - | web | done |
| Cursor | 2026-09-16-multi-intent-u5-decision-api | feature/multi-intent-u5-decision-api | - | intent-plan | done |
| Cursor | 2026-09-16-multi-intent-u4-outbox-projection | feature/multi-intent-u4-outbox-projection | - | events | done |
| Cursor | 2026-09-15-multi-intent-u3-intentflow-pg | feature/multi-intent-u3-intentflow-pg | - | intent-plan | done |
| Cursor | 2026-09-15-multi-intent-u2-segmenter | feature/multi-intent-u2-segmenter | - | intent | done |
| Cursor | 2026-09-15-multi-intent-u1-contracts | feature/multi-intent-u1-contracts | - | architecture-docs | done |
| Cursor | app-composition-u11-u12 | feature/app-composition-u11-u12 | - | app | done |
| Cursor | chatlog-contextasm-u10 | feature/chatlog-contextasm-u10 | - | chat | done |
| Cursor | chat-history-refresh-remediation | fix/chat-history-refresh | - | chat | done |
| Cursor | dataset-lifecycle-u9 | feature/dataset-lifecycle-u9 | - | dataset | done |
| Cursor | taskrun-runmanifest-u8 | feature/taskrun-runmanifest | - | taskrun | done |
| Cursor | document-reliability-m1-live-and-m2 | refactor/document-reliability-pilot | - | workspace | done |
| Codex | module-decoupling-remediation-plan | feature/module-decoupling-remediation | - | architecture-docs | done |
| Codex | module-decoupling-phase2-plan-review | docs/module-decoupling-ipc-assessment | - | architecture-docs | done |
| Cursor | module-decoupling-phase2-docs | feature/module-decoupling-phase2-docs | - | architecture-docs | done |
| verifier_antigravity | controlled-content-ui-acceptance | fix/controlled-content-ui-acceptance | - | document-content-ui-acceptance | done |
| implementer_cursor | controlled-content-canonical-hash | fix/controlled-content-canonical-hash | - | docgov | done |
| Codex | project143-controlled-content-404-diagnosis | fix/project143-controlled-content-404-diagnosis | - | docgov-content-diagnosis | done |
| Codex | software-project-bootstrap-plan | feature/software-project-bootstrap-plan | - | workflow-design | done |
| Codex | conversation-intent-runtime-state | fix/conversation-intent-state | - | intent | done |
| Cursor | conversation-intent-runtime-state | docs/conversation-intent-dual-tab | - | intent | done |
| Cursor | document-revision-wave1-6-implementation | feature/document-revision-management | - | docgov | done |
| Cursor-Auto | document-revision-runtime-ui03 | fix/document-revision-runtime-ui03 | - | docgov | done |
| Cursor-Auto | document-revision-sse-idempotency | fix/document-revision-sse-idempotency | - | docgov | done |
| Cursor-Auto | document-revision-active-doc-routing | fix/document-revision-active-doc-routing | - | docgov | done |
| Cursor-Auto | document-revision-quality-context | fix/document-revision-quality-context | - | docgov | done |
| Cursor-Auto | document-revision-e2e-pending-flow | fix/document-revision-e2e-pending-flow | - | pending-report-ui | done |
| Cursor-Auto | document-revision-e2e-reject-routing | fix/document-revision-e2e-reject-routing | - | document-revision-runtime-ui | done |
| Cursor-Auto | document-revision-e2e-block-selector | fix/document-revision-e2e-block-selector | - | document-revision-block-selector | done |
| Cursor-Auto | document-revision-e2e-save-selector | fix/document-revision-e2e-save-selector | - | document-revision-save-selector | done |
| Cursor-Auto | document-revision-e2e-overlay-selector | fix/document-revision-e2e-overlay-selector | - | document-revision-overlay-selector | done |
| Cursor-Auto | document-revision-e2e-overlay-transition | fix/document-revision-e2e-overlay-transition | - | document-revision-overlay-transition | done |
| Cursor-Auto | document-revision-second-doc-routing-lease | fix/document-revision-second-doc-routing-lease | - | document-revision-second-doc-routing-lease | done |
| Cursor-Auto | document-revision-prelim-output | fix/document-revision-pi-runner-module | - | document-revision-prelim-output | done |
| Cursor-Auto | document-revision-editor-hydration-race | fix/document-revision-editor-hydration-race | - | document-revision-editor-hydration-race | done |
| Cursor-Auto | document-revision-quality-repair-workspace-contract | fix/document-revision-quality-repair-workspace-contract | - | document-revision-quality-repair-workspace-contract | done |
| Cursor-Auto | document-revision-fast-completion-observation | fix/document-revision-fast-completion-observation | - | document-revision-fast-completion-observation | done |
| Cursor-Auto | document-revision-workflow-category-navigation | fix/document-revision-workflow-category-navigation | - | document-revision-workflow-category-navigation | done |
| Cursor-Auto | document-revision-duty-cell-assertion | fix/document-revision-duty-cell-assertion | - | document-revision-duty-cell-assertion | done |
| Cursor-Auto | document-revision-reviewer-duty-sync | fix/document-revision-reviewer-duty-sync | - | document-revision-reviewer-duty-sync | done |
| Cursor-Auto | document-revision-post-restart-selector | fix/document-revision-post-restart-selector | - | document-revision-post-restart-selector | done |
| Cursor-Auto | document-revision-post-restart-final-snippet | fix/document-revision-post-restart-final-snippet | - | document-revision-post-restart-final-snippet | done |
| Cursor-Auto | document-revision-pg-id-remap | fix/document-revision-pg-id-remap | - | document-revision | done |
| Cursor-Auto | document-revision-runtime | fix/document-revision-runtime-ui03 | - | docgov | done |
| Cursor-Auto | document-revision-runtime-ui-final-report | feature/document-revision-runtime-ui-final-report | - | document-revision-runtime-ui | 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 | vite-frp-allowed-hosts | fix/vite-frp-allowed-hosts | - | web-devserver | done |
| implementer_cursor | workflow-plan-409-frozen-and-optional-duties | fix/workflow-plan-409-frozen-and-optional-duties | - | workflow | done |
| implementer_cursor | governance-action-level-authz | fix/governance-action-level-authz | - | workflow | done |
| Cursor | leftover-branch-cleanup | chore/leftover-branch-cleanup | - | ops | done |
| Cursor | event-e0a-production-scope-contract | fix/event-production-scope-contract | - | events | done |
| Cursor | event-e0b-dispatcher-pg | feature/events-dispatcher-pg | - | events | done |
| Cursor | event-e1a-notify-cutover | fix/event-notify-cutover | - | events | done |
| Cursor | event-e1b-workflow-advance | fix/event-workflow-advance | - | events | done |
| Cursor | event-e1c-projection-cutover | fix/event-projection-cutover | - | events | done |
| Cursor | event-e2-hook-guard | fix/event-e2-hook-guard | - | events | done |
| Cursor | doccontent-canonical-boundary | fix/doccontent-canonical-boundary | - | docgov | done |
| Cursor | intent-bizdev-boundary | fix/intent-bizdev-boundary | - | intent | done |
| Cursor | stageflow-type-ownership | fix/stageflow-type-ownership | - | stageflow | done |
| Cursor | bizdev-locator-removal | fix/bizdev-locator-removal | - | bizdev | done |
| Cursor | chatflow-orchestration-boundary | fix/chatflow-orchestration-boundary | - | chat | done |
| Cursor | phase2-closeout-inventories | feature/phase2-closeout-inventories | - | architecture-docs | done |
| Cursor | event-shared-live-cutover | feature/event-shared-live-cutover | - | events | done |
| Cursor | pi-typed-result | feature/pi-typed-result | - | pi-agent | done |
| Cursor | promote-dev-to-test-2026-09-15 | feature/promote-dev-to-test-2026-09-15 | - | infra | done |
> 新任务开始前登记。`branch` 必须为 `feature/<slug>` 或 `fix/<slug>`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。 以上 done 行因工作区仍在而保留;不代表仍持有执行锁。活跃任务的写路径、base_dev_sha、授权与验证计划见主任务记录/机器登记;历史 claim 不能代替当前登记。
> workflow-plan 409 与治理鉴权 G1–G5 已收口(见 [test-report](../test-reports/2026-09-14-governance-action-level-authz-test-report.md))。禁止终止或改写项目 143。 ## 清理接管与遗留现场
> `document-reliability-pilot` U11–U12 已合入 `dev@bf59cce` 并 rebuild。`CHAT-INTENT-STATE` 双标签 busy 已 PASS(项目 6275)。禁止 worktree 内 `env-service`。项目 143 只读,禁止终止。
> 2026-09-17 leftover 二次收口:[报告](../test-reports/2026-09-17-leftover-hygiene-test-report.md)。已合入 leftover worktree/旁支再拆一轮;`feature/software-project-bootstrap` L1 已在 `dev`(残留关闭 commit 已丢弃)。`fix/clean-test-env-baseline` **不 merge**(前端 demo 清理已放弃;仅保留已挑拣的 `SEED_DEMO_USERS=false`)。extra worktree 仅余 `.worktrees/fix-dev-runtime-data-root-guard`(目录名是 guard,检出的是 `feature/final-artifact-vault`)。**故意不合入、不删**:`feature/final-artifact-vault`、`feature/remove-herdr-agent-settings`、`fix/project143-controlled-content-404-diagnosis`、`fix/dev-runtime-data-root-guard`、`dev-old-linear`。不删 `origin/*`。不整支 merge FAV;不启用 `FAV_DEGRADED_READ`。 快照来自 2026-09-21 `git worktree list --porcelain`;未检查目录内文件/进程,不据此放行删除。下列 cleanup_owner 是复查责任,不增加删除授权;实际删除须逐项满足门禁。
| 路径/事项 | 分类与保留原因 | cleanup_owner | 复查触发 |
|---|---|---|---|
| `.worktrees/agent-governance-implementation` | 活跃实施;cleanup=pending | Codex 之外的集成/维护执行者 | 验收、集成、证据归档完成且 owner 退出 |
| `.worktrees/feature-agent-workflow-governance-plan` | 原 adoption 仍 in_progress;不得替 owner 改 done | 原 Codex owner / 维护者接管 | 核对原任务交付与进程后更新真实状态 |
| Cline `2026-09-16-partial-deferred-completion` | 原 in_progress,worktree 未登记;保留待核验 | Cline owner / 维护者接管 | 查明主文档、分支与未提交现场 |
| `.worktrees/agent-governance-dev-integration` | 历史集成现场 detached;待检查唯一证据/进程 | Codex 维护者 | 所需证据持久化并确认不再使用 |
| `.worktrees/agent-lock-coordination-plan`、`.worktrees/agent-policy-review` | 审核产物原现场;待核对集成祖先与证据 | Codex 维护者 | 本次实施收口且原 owner 退出 |
| `.worktrees/feature-initial-load-performance-plan` | 研究原现场;未核验合入与唯一文件,保留 | Codex 原 owner / 维护者 | 研究交接及实际 Git/文件检查完成 |
| `.worktrees/workflow-scope-weighting-2026-09-21`、`.worktrees/integrate-workflow-scope-weighting-2026-09-21` | 已完成交付的任务/集成现场;cleanup=pending | Cursor owner / 独立维护者 | owner 退出后核对祖先、证据、文件与进程 |
| `.worktrees/mineru-doc-parsing-2026-09-21`、`.worktrees/integrate-mineru-doc-parsing-2026-09-21` | 已完成交付的任务/集成现场;cleanup=pending | Cursor owner / 独立维护者 | 同上;保留挂载事故与二轮验收证据 |
| `.worktrees/intent-confirm-stage-aware-2026-09-21`、`.worktrees/feature-taskrun-status-card-2026-09-21`、`.worktrees/fix-workflow-doc-key-unification-2026-09-21` | 原认领 done,工作区仍在;cleanup=pending | Cursor owner / 独立维护者 | owner 退出后核对全部清理门禁 |
| `.worktrees/upload-parse-ux-2026-09-21`、`.worktrees/fix-dev-multiuser-four-issues` | 目录存在但本次 Git worktree 列表未列出;原认领 done,可能有未归档遗留,保留 | Cursor 原 owner / 独立维护者 | 查明注册状态、文件来源和唯一证据后按门禁处理 |
| `.worktrees/fix-dev-runtime-data-root-guard` | 保护项:实际为 `feature/final-artifact-vault`,路径/分支不同;故意不合入、不删 | 原 owner / 维护者 | 用户调整保护边界并重新核验后 |
| `.kilo/worktrees/stealth-ice` | 非本轮任务、detached,owner/唯一内容未核验,保留 | 原 owner 待核实;Codex 维护者负责查明 | 查明来源、授权与文件/进程状态后 |
cleanup=pending 不是任务状态;普通 remove/-d 拒绝时记录原因,不升级强删。历史完成/取消认领、全部原证据索引和环境快照完整保存在 [归档](../archive/ops/2026-09-21-current-history.md)。
## 集成槽位 ## 集成槽位
@@ -192,83 +56,17 @@
**phase**: `idle` | `merging` | `smoke` | `e2e` **phase**: `idle` | `merging` | `smoke` | `e2e`
> 合入 `dev` + 共享容器验证必须串行。详见 [AGENTS.md](../../AGENTS.md) §6.3。 > 合入 `dev` + 共享容器验证必须串行。实际机器锁/进程优先;详见 [操作规范](./2026-09-21-agent-worktree-workflow.md)。
## 环境快照 ## 受控约束
| 环境 | 端口 | schema | 状态 | - 禁止整支 merge FAV;`FAV_DEGRADED_READ=false`;FAV live 观察窗以证据驱动。保留保护分支 `feature/final-artifact-vault`、`feature/remove-herdr-agent-settings`、`fix/project143-controlled-content-404-diagnosis`、`fix/dev-runtime-data-root-guard`、`dev-old-linear`;不删 `origin/*`。
|------|------|--------|------| - 不默认升 `MULTI_INTENT_PLAN_TIER=full`;`SEMANTIC_SUMMARY_REPLACE` 不随 W2 开启;项目 143 只读、禁止终止;SWBOOT-10 保持 fail-closed stub。
| dev | 8080 | **61** | healthy(mineru `00061`;intent-confirm 真实 PI PASS;`/health` schema_version=61;`pi-agent`;events `:8088`) | - D7 对话 ADR 仍待合规签字;追踪表类别 `record` 不授权物理删除。模块解耦 A–G 与回溯切片按各自最新主任务认领/证据判断,不因历史设计或本次归档自动启动。
| test | 8081 | **58** | healthy(`rc-v1.0.0-beta.7`;rdev v1 active;`published_by` 回落到用户 4;events `:8089`) | - `fix/clean-test-env-baseline` 不 merge;仅保留已挑拣的 `SEED_DEMO_USERS=false`。主运行目录与 worktree 的服务边界、重建与恢复要求见操作规范;MinerU 旧挂载事故与二轮证据见历史快照。
| main | 8000 | **58** | healthy(P0 Wave3 rebuild 已追平 schema;分支树对齐仍记 backlog;口令已轮换;events `:8090`) |
## SSOT ## 活跃入口
| 类型 | 路径 | - [操作规范](./2026-09-21-agent-worktree-workflow.md) · [规则来源/生效](./2026-09-21-policy-registry.md) · [验证契约](./2026-09-21-validation-contract.md)
|------|------| - [模块开发标准](../system-design/2026-09-20-module-development-standards.md) · [原 governance 交接](./handoffs/2026-09-21-agent-workflow-governance-plan.md)
| 2026-09-21 Agent worktree 工作规范 | [方案](../remediation/2026-09-21-agent-workflow-governance-plan.md) · [调研](./research/2026-09-21-agent-workflow-governance-research.md) · [交接](./handoffs/2026-09-21-agent-workflow-governance-plan.md);规范已采纳,自动化另列 planned | - [partial-deferred 主任务](./short-term/2026-09-16-partial-deferred-completion.md) · [历史证据与环境快照](../archive/ops/2026-09-21-current-history.md)
| 2026-09-20 rdev 种子 | [short-term](./short-term/2026-09-20-rdev-seed-hotfix.md) · [报告](../test-reports/2026-09-20-rdev-seed-hotfix-test-report.md) |
| 2026-09-20 模块解耦 | [审计](../audit/2026-09-20-module-coupling-audit.md) · [规范](../system-design/2026-09-20-module-development-standards.md) · [批次 A–G](../remediation/2026-09-20-module-decoupling-batch-plan.md) · [short-term](./short-term/2026-09-20-module-decoupling-spec-audit.md) |
| 文档总地图 | [../README.md](../README.md) |
| 模块完成度 | [FEATURE-MATRIX](../status/FEATURE-MATRIX.md)(Wave 0 已校准契约与冻结边界) |
| 发布缺口 | [gap-matrix](../remediation/gap-matrix.md)(`production_ready=yes`;Wave 0 IAM 只读未发现旁路) |
| 2026-09-20 系统体检 | [audit](../audit/2026-09-20-system-triage-audit.md) · [P0 整改](../remediation/2026-09-20-p0-remediation-plan.md) · [short-term](./short-term/2026-09-20-p0-hotfixes.md) |
| 连续性审计/计划 | [audit](../audit/2026-09-16-feature-matrix-production-readiness-audit.md) · [remediation](../remediation/2026-09-16-feature-matrix-production-readiness-plan.md) · [Wave 0 任务](./short-term/2026-09-16-feature-matrix-wave0-calibration.md) |
| 测试报告 | [test-reports/](../test-reports/) |
| dev→test 晋升前复核 | [research](./research/2026-09-19-pre-test-promotion-review.md)(2026-09-19 只读;静态门禁过,容器未 rebuild,暂不合并) |
| D7 盘点(W7) | [audit](../audit/2026-09-17-d7-data-inventory.md) · [报告](../test-reports/2026-09-17-w7-d7-inventory-test-report.md) · ADR 仍 `pending-compliance-signoff` |
| 功能启用与文档影响分析 | [调研](./research/2026-09-20-capability-enablement-impact-research.md) · [整改方案](../remediation/2026-09-20-capability-enablement-impact-plan.md) · [方案审阅](./research/2026-09-20-capability-enablement-impact-plan-review.md)(2026-09-20;未启用开关、未改业务代码) |
| 阶段回溯与影响闭环 | [设计](../system-design/2026-09-20-stage-backtrack-and-impact-loop-design.md) · [short-term](./short-term/2026-09-20-stage-backtrack-design.md)(2026-09-20;仅设计。切片 T1/T2/B1/B2/B3/T3 均为 planned) |
| Wave 7 编排确认 | [决策记录](./short-term/2026-09-18-wave7-roadmap-orchestration.md) |
| Agent 手册 | [AGENTS.md](../../AGENTS.md) |
| 意图双标签 busy 交接 | [./handoffs/2026-09-15-conversation-intent-dual-tab.md](./handoffs/2026-09-15-conversation-intent-dual-tab.md) |
| 受控正文 hash 漂移 W1–W6 | [../ops/handoffs/2026-09-14-controlled-content-canonical-hash.md](../ops/handoffs/2026-09-14-controlled-content-canonical-hash.md) |
| DOCREV ADR | [../adr/2026-09-13-document-version-boundary.md](../adr/2026-09-13-document-version-boundary.md) |
| DOCREV 管理测试 | [../test-reports/2026-09-13-document-revision-management-test-report.md](../test-reports/2026-09-13-document-revision-management-test-report.md) |
| DOCREV 运行时 UI Round20 | [../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md](../test-reports/2026-09-14-document-revision-runtime-ui-test-report.md) |
| 纯软件项目自举设计 | [../system-design/2026-09-14-software-project-bootstrap-workflow.md](../system-design/2026-09-14-software-project-bootstrap-workflow.md) |
| 模块化单体解耦架构 | [../system-design/2026-09-14-module-decoupling-architecture.md](../system-design/2026-09-14-module-decoupling-architecture.md) |
| 模块解耦整改计划 | [../remediation/2026-09-14-module-decoupling-refactor-plan.md](../remediation/2026-09-14-module-decoupling-refactor-plan.md) |
| Phase 2 方案审阅 | [../audit/2026-09-15-module-decoupling-phase2-plan-review.md](../audit/2026-09-15-module-decoupling-phase2-plan-review.md) |
| Phase 2 修订计划 | [../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md](../remediation/2026-09-15-module-decoupling-ipc-phase2-remediation-plan.md) |
| 多意图 HITL 复核方案 | [../system-design/2026-09-15-multi-intent-hitl-plan.md](../system-design/2026-09-15-multi-intent-hitl-plan.md) |
| 多意图 HITL U1 契约 | [../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md](../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md) |
| 多意图 HITL U2 分段 | [../test-reports/2026-09-15-multi-intent-u2-segmenter-test-report.md](../test-reports/2026-09-15-multi-intent-u2-segmenter-test-report.md) |
| 多意图 HITL U3 PG | [../test-reports/2026-09-15-multi-intent-u3-intentflow-pg-test-report.md](../test-reports/2026-09-15-multi-intent-u3-intentflow-pg-test-report.md) |
| 多意图 HITL U4 投影 | [../test-reports/2026-09-16-multi-intent-u4-outbox-projection-test-report.md](../test-reports/2026-09-16-multi-intent-u4-outbox-projection-test-report.md) |
| 多意图 HITL U5 API | [../test-reports/2026-09-16-multi-intent-u5-decision-api-test-report.md](../test-reports/2026-09-16-multi-intent-u5-decision-api-test-report.md) |
| 多意图 HITL U6 Web | [../test-reports/2026-09-16-multi-intent-u6-web-cards-test-report.md](../test-reports/2026-09-16-multi-intent-u6-web-cards-test-report.md) |
| 多意图 HITL U8 generate | [../test-reports/2026-09-16-multi-intent-u8-task-generate-test-report.md](../test-reports/2026-09-16-multi-intent-u8-task-generate-test-report.md) |
| 多意图 HITL U7 只读 | [../test-reports/2026-09-16-multi-intent-u7-auto-read-test-report.md](../test-reports/2026-09-16-multi-intent-u7-auto-read-test-report.md) |
| 文档可靠性 M1 报告 | [../test-reports/2026-09-14-document-reliability-pilot-test-report.md](../test-reports/2026-09-14-document-reliability-pilot-test-report.md) |
| 文档可靠性 M1 活验 | [../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md](../test-reports/2026-09-14-document-reliability-pilot-live-test-report.md) |
| 文档可靠性 M2 | [../test-reports/2026-09-14-document-reliability-m2-test-report.md](../test-reports/2026-09-14-document-reliability-m2-test-report.md) |
| 文档可靠性 M3 U8 | [../test-reports/2026-09-15-taskrun-runmanifest-u8-test-report.md](../test-reports/2026-09-15-taskrun-runmanifest-u8-test-report.md) |
| 文档可靠性 M3 U9 | [../test-reports/2026-09-15-dataset-lifecycle-u9-test-report.md](../test-reports/2026-09-15-dataset-lifecycle-u9-test-report.md) |
| 文档可靠性 M4 U10 | [../test-reports/2026-09-15-chatlog-contextasm-u10-test-report.md](../test-reports/2026-09-15-chatlog-contextasm-u10-test-report.md) |
| 文档可靠性 M5/M6 U11–U12 | [../test-reports/2026-09-15-app-composition-u11-u12-test-report.md](../test-reports/2026-09-15-app-composition-u11-u12-test-report.md) |
| Track E E2 闭包守卫 | [../test-reports/2026-09-15-event-e2-hook-guard-test-report.md](../test-reports/2026-09-15-event-e2-hook-guard-test-report.md) |
| Track E 复盘 | [./research/2026-09-15-track-e-retrospective.md](./research/2026-09-15-track-e-retrospective.md) |
| 旁支清点收口 | [../test-reports/2026-09-15-leftover-branch-cleanup-test-report.md](../test-reports/2026-09-15-leftover-branch-cleanup-test-report.md) · [二次收口](../test-reports/2026-09-17-leftover-hygiene-test-report.md) |
| SSE/幂等审计 | [../audit/2026-09-13-document-revision-sse-idempotency.md](../audit/2026-09-13-document-revision-sse-idempotency.md) |
| 质量门禁自污染审计 | [../audit/2026-09-13-document-revision-quality-gate-self-pollution.md](../audit/2026-09-13-document-revision-quality-gate-self-pollution.md) |
| 当前文档路由审计 | [../audit/2026-09-13-document-revision-active-doc-routing.md](../audit/2026-09-13-document-revision-active-doc-routing.md) |
| 行动表状态假阳性审计 | [../audit/2026-09-13-document-revision-quality-status-false-positive.md](../audit/2026-09-13-document-revision-quality-status-false-positive.md) |
| 待确认报告选择失败审计 | [../audit/2026-09-13-document-revision-pending-report-selection.md](../audit/2026-09-13-document-revision-pending-report-selection.md) |
| UI-05b 拒绝路由失败审计 | [../audit/2026-09-13-document-revision-e2e-reject-routing.md](../audit/2026-09-13-document-revision-e2e-reject-routing.md) |
| 职责伪造与 ETag 覆盖审计 | [../audit/2026-09-13-document-revision-duty-etag-overwrite.md](../audit/2026-09-13-document-revision-duty-etag-overwrite.md) |
| UI-06 块选择器失败审计 | [../audit/2026-09-13-document-revision-block-selector.md](../audit/2026-09-13-document-revision-block-selector.md) |
| UI-06 保存选择器失败审计 | [../audit/2026-09-13-document-revision-save-selector.md](../audit/2026-09-13-document-revision-save-selector.md) |
| UI-07 纯净阅读取样失败审计 | [../audit/2026-09-13-document-revision-overlay-selector.md](../audit/2026-09-13-document-revision-overlay-selector.md) |
| UI-07 transition 中间帧取样审计 | [../audit/2026-09-13-document-revision-overlay-transition.md](../audit/2026-09-13-document-revision-overlay-transition.md) |
| UI-11 第二份文档路由与 lease 审计 | [../audit/2026-09-13-document-revision-second-doc-routing-lease.md](../audit/2026-09-13-document-revision-second-doc-routing-lease.md) |
| UI-11 prelim_design 缺 output.md 审计 | [../audit/2026-09-13-document-revision-prelim-output.md](../audit/2026-09-13-document-revision-prelim-output.md) |
| UI-06 编辑弹窗 hydration 竞态审计 | [../audit/2026-09-13-document-revision-editor-hydration-race.md](../audit/2026-09-13-document-revision-editor-hydration-race.md) |
| UI-03 质量修复子工作区契约审计 | [../audit/2026-09-13-document-revision-quality-repair-workspace-contract.md](../audit/2026-09-13-document-revision-quality-repair-workspace-contract.md) |
| UI-11 快速完成观测误判审计 | [../audit/2026-09-13-document-revision-fast-completion-observation.md](../audit/2026-09-13-document-revision-fast-completion-observation.md) |
| UI-13a 私密分类导航失败审计 | [../audit/2026-09-13-document-revision-workflow-category-navigation.md](../audit/2026-09-13-document-revision-workflow-category-navigation.md) |
| UI-13a 职责单元格误判审计 | [../audit/2026-09-13-document-revision-duty-cell-assertion.md](../audit/2026-09-13-document-revision-duty-cell-assertion.md) |
| UI-13a reviewer 职责未同步审计 | [../audit/2026-09-13-document-revision-reviewer-duty-sync.md](../audit/2026-09-13-document-revision-reviewer-duty-sync.md) |
| UI-03 首次生成 PG ID 误删 baseline | [../audit/2026-09-13-document-revision-pg-id-remap.md](../audit/2026-09-13-document-revision-pg-id-remap.md) |
| UI-06 刷新后缓存竞争死锁审计 | [../audit/2026-09-14-document-revision-refresh-cache-race.md](../audit/2026-09-14-document-revision-refresh-cache-race.md) |
| 受控正文真实 UI 验收报告 | [../test-reports/2026-09-14-controlled-content-ui-acceptance.md](../test-reports/2026-09-14-controlled-content-ui-acceptance.md) |
@@ -9,7 +9,7 @@ mutex: agent-workflow-governance
# Agent worktree 治理自动化 W0–W4 # Agent worktree 治理自动化 W0–W4
工作方式已由用户采纳,本任务跟踪尚未实现的工程能力。规则生效不等于脚本或权限隔离已完成。 工作方式已由用户采纳,本任务跟踪完整工程能力及剩余范围。规则生效不等于脚本或权限隔离已完成。
## 按依赖实施 ## 按依赖实施
@@ -26,3 +26,9 @@ mutex: agent-workflow-governance
- 两个独立任务完成并行开发、串行交付、运行验收和安全清理。 - 两个独立任务完成并行开发、串行交付、运行验收和安全清理。
- 错误起点、并发抢占、危险清理、部署失败/超时/崩溃均有拒绝或恢复证据。 - 错误起点、并发抢占、危险清理、部署失败/超时/崩溃均有拒绝或恢复证据。
- 自动化机制确实落地后再将对应 FEATURE-MATRIX 能力升 done。 - 自动化机制确实落地后再将对应 FEATURE-MATRIX 能力升 done。
## 2026-09-21 L1 分批交付
[实施任务](../short-term/2026-09-21-agent-governance-implementation.md) 已验证 W1、安全门禁、W2 本机 SQLite 原子租约/恢复/有限等待、两个共享文档 CAS,以及 W3 精确候选准入与固定源码导出、W4 薄入口和证据归档。验收见[报告](../../test-reports/2026-09-21-agent-governance-test-report.md),实际主仓启用状态以收口为准。
完整自动化仍未关闭:后台持久队列与自动调度、通用文档投影、两个业务任务的部署/恢复闭环、产品 PI 源码镜像交付及本机不可读进程造成的清理限制仍需后续范围;OS/跨机隔离另立任务。不得把本次临时 Git 仓库门禁测试当作真实业务运行验收。
@@ -38,3 +38,10 @@ remote_sync: not_requested
## 交付边界 ## 交付边界
L1 对统一入口与 Git hooks 防误改,不能阻止任意同 UID shell 绕过。OS 沙箱、跨机锁、产品 PI 镜像交付等独立运行架构变更不在本次规范/最小门禁改动内。纯工程代码与文档不触发产品重建;将验证主目录运行挂载和就绪未因本轮改变。 L1 对统一入口与 Git hooks 防误改,不能阻止任意同 UID shell 绕过。OS 沙箱、跨机锁、产品 PI 镜像交付等独立运行架构变更不在本次规范/最小门禁改动内。纯工程代码与文档不触发产品重建;将验证主目录运行挂载和就绪未因本轮改变。
## 验证与集成进度
- 76 项 Python 工程测试通过;20 个真实进程/独立 Git worktree 争同一资源:1 个执行、19 个 LOCK_BUSY。
- ce-simplify-code 三视角完成;ce-code-review `20260921-133611-5e4781e6` 完成,5 个确认问题全部修复并经独立复核关闭。详见[报告](../../test-reports/2026-09-21-agent-governance-test-report.md)。
- 候选待基于最新 dev 构造;期间 Cursor 的材料切块任务推进 dev,保留其 CURRENT 增量和验收记录。
- 主机部分同 UID 进程 cwd 不可读,保守清理可能 pending;证据与接管责任随收口更新。
+16
View File
@@ -2,12 +2,24 @@
title: "<审计/分析标题>" title: "<审计/分析标题>"
status: planned status: planned
created: YYYY-MM-DD created: YYYY-MM-DD
owner: agent
task_id: "<自身或父任务 ID>"
policy_revision: "<修订 ID + dev SHA>"
review_state: pending
integration_state: pending
deployment: not_required
cleanup: pending
remote_sync: not_requested
updated: YYYY-MM-DD updated: YYYY-MM-DD
severity: medium severity: medium
--- ---
# <审计/分析标题> # <审计/分析标题>
## 范围与授权
用户目标及可核实来源、允许写路径、环境、交付终点、非目标。实质审查须落盘;可作为唯一主文档,无需复制任务链。关联父任务时引用其授权/认领,不另建重复认领。
## 现象 ## 现象
(可观测症状) (可观测症状)
@@ -31,3 +43,7 @@ severity: medium
## 开放问题 ## 开放问题
- -
## 状态与交接
记录实际命令、被审 SHA、结果与脱敏范围;研究完成指产物有据可审,不代表建议已实施。记录剩余工作、cleanup_owner/保留原因/复查触发,以及本地提交、集成、远端状态。会话结束不强制 done。
+24 -29
View File
@@ -3,50 +3,45 @@ title: "<交接标题>"
created: YYYY-MM-DD created: YYYY-MM-DD
from: agent from: agent
to: next-agent to: next-agent
status: open status: in_progress # 沿用实际任务状态;交接不自动 done
task_id: "<父任务 ID>"
--- ---
# 交接:<标题> # 交接:<标题>
## 目标(下一位 Agent 应完成什么) ## 目标与授权来源
## 已完成 回链可核实的用户请求/委派范围及任务主文档,写清允许写路径、环境、交付终点、禁止项。本文件的授权自述不能独立授予权限。
- ## 已完成与证据
## 未完成 - 提交、命令、环境、实际结果、可持久访问证据:
- ## 未完成与接管条件
## 认领 / 槽位状态 - 剩余工作 / blocker / 下一步:
- policy_revision 与当前 dev 规则差异:
## 操作状态
| 项 | 值 | | 项 | 值 |
|----|-----| |---|---|
| mutex 已释放 | yes / no | | branch / worktree / head_sha | |
| 集成槽位 holder | idle / 需接管 | | mutex / 执行租约及仍存活进程 | |
| branch | | | 集成槽位实际 holder / phase | |
| worktree | | | review_state / integration_state | |
| deployment / 实际运行制品 | |
| remote_sync | |
| cleanup / cleanup_owner | |
| 保留原因 / 证据位置 / 复查触发 | |
> 若 mutex 未释放,下一位须先在 CURRENT 认领或写 blocker。 按 [操作规范](../2026-09-21-agent-worktree-workflow.md) 检查机器状态后接管;CURRENT 只是快照。未知操作结果先核查,不重新投递可能已执行的操作。
## 关键文件
| 路径 | 说明 |
|------|------|
| | |
## 如何验证 ## 如何验证
```bash 记录适用命令、expected_tests、环境前提与剩余风险;脱敏说明保留,秘密不入库。
# 命令
```
## 风险 / Blocker
-
## 相关链接 ## 相关链接
- CURRENT: [CURRENT.md](../CURRENT.md) - [CURRENT](../CURRENT.md)
- Task: - Task / Plan / 独立报告(若存在):
- Checklist:
+17 -1
View File
@@ -1,12 +1,24 @@
--- ---
title: "<调研标题>" title: "<调研标题>"
created: YYYY-MM-DD created: YYYY-MM-DD
status: done owner: agent
task_id: "<自身或父任务 ID>"
policy_revision: "<修订 ID + dev SHA>"
review_state: pending
integration_state: pending
deployment: not_required
cleanup: pending
remote_sync: not_requested
status: planned
confidence: high confidence: high
--- ---
# 调研:<标题> # 调研:<标题>
## 范围与授权
用户目标及可核实来源、允许写路径、环境、交付终点、非目标。实质审查须落盘;可作为唯一主文档,无需复制任务链。关联父任务时引用其授权/认领,不另建重复认领。
## 结论 ## 结论
(1–3 句) (1–3 句)
@@ -24,3 +36,7 @@ confidence: high
## 建议动作 ## 建议动作
- -
## 状态与交接
记录实际命令、被审 SHA、结果与脱敏范围;研究完成指产物有据可审,不代表建议已实施。记录剩余工作、cleanup_owner/保留原因/复查触发,以及本地提交、集成、远端状态。会话结束不强制 done。
+19 -22
View File
@@ -1,29 +1,26 @@
# 会话结束清单 # 会话结束与任务收口清单
任务完成或切换 Agent 前逐项勾选: 每次结束会话或切换 Agent 使用;不要求为了结束会话完成未授权的实施、部署或推送。小任务可将检查结果并入主文档。
## 认领与集成 ## 状态与证据
- [ ] CURRENT「并行认领」行 status 已更新(done 或移除) - [ ] 主文档状态符合实际:`planned | in_progress | blocked | done | cancelled`;只有授权范围内验收满足才 done。
- [ ] 若曾占集成槽位:所需 smoke/E2E 已完成(或失败恢复交接),锁已释放、holder=idle;文档可记 deployment=not_required - [ ] 分别记录 review_state、integration_state、deployment、cleanup、remote_sync;代码已合入但部署 failed 不报交付完成。
- [ ] mutex 无遗留 in_progress(除明确 handoff 交接项) - [ ] 验证范围按 [验证契约](../2026-09-21-validation-contract.md);保存实际命令、SHA、环境、结果和缺项,敏感材料最小化/脱敏。
- [ ] 本次影响、关联文档、剩余工作/接管条件在主文档或 handoff 中可恢复。
## 代码与分支 ## 认领、集成与环境
- [ ] 确认未在 `dev` / `test` / `main` 上直接 commit(工作均在 `feature/*` 或 `fix/*`) - [ ] CURRENT 增量更新为实际状态;保留活跃/阻塞/待集成/待清理事项,只有已终态且无后续阻塞的认领转历史。
- [ ] 已持锁/占槽验证合并候选并合回 `dev`(或 handoff 说明未 merge 原因) - [ ] 所持执行租约、mutex 与槽位按实际操作结束释放;共享环境 E2E 未结束不释放。异常退出按操作规范核查进程与恢复,不仅修改 Markdown。
- [ ] 已按 [清理门禁](../2026-09-21-agent-worktree-workflow.md) 核对祖先、部署/验收、文件与进程;普通 remove/-d 已完成,或记录保留原因(不强删) - [ ] 本地提交、dev 合入、实际运行版本、远端同步分别有证据;未合入/未部署记录原因,文档可记 deployment=not_required。
- [ ] 无未 push 的关键提交(若用户要求同步远端) - [ ] 仅用户已授权同步远端时完成 push;未授权记 remote_sync=not_requested。
## 文档 ## 清理与归档
- [ ] [FEATURE-MATRIX.md](../../status/FEATURE-MATRIX.md) 已更新(若模块状态变) - [ ] 按 [操作规范](../2026-09-21-agent-worktree-workflow.md) 核对 dev 祖先、验收、文件/唯一 ignored 证据、进程、保护项;普通 remove/-d 拒绝则保留,不强删。
- [ ] [gap-matrix.md](../../remediation/gap-matrix.md) 已更新(若 blocker 变) - [ ] cleanup 未完成时有 cleanup_owner、保留原因、证据位置、可观测复查触发;owner 退出后由独立集成/维护执行者复查。
- [ ] 任务 frontmatter status=done - [ ] 可再生缓存与唯一证据分开处理;唯一日志/截图已持久保存,不能仅留下即将删除的路径。
- [ ] done 任务已 `git mv` 到 [archive/ops/](../../archive/ops/) - [ ] 只有 done/cancelled 任务归档;移动时修复相对链接并保留历史。未完成任务保留原状态,不为归档改 done。
- [ ] [CURRENT.md](../CURRENT.md) 焦点 1–3 条已刷新 - [ ] 仅模块声明变化时更新 [FEATURE-MATRIX](../../status/FEATURE-MATRIX.md),仅 blocker 变化时更新 [gap-matrix](../../remediation/gap-matrix.md);CURRENT 焦点最多 3 条。
- [ ] 若换人:已写 [handoffs/](../handoffs/) - [ ] 实现与相应证据形成可解释、可验证、可回退的逻辑提交;无三大主干直接开发提交。
## 提交
- [ ] 文档与代码同批或紧随 `docs(...)` 提交
+44 -24
View File
@@ -1,48 +1,68 @@
--- ---
title: "<任务标题>" title: "<任务标题>"
status: planned task_id: "<沿用相关追问/修正的原任务 ID>"
status: planned # planned | in_progress | blocked | done | cancelled
created: YYYY-MM-DD created: YYYY-MM-DD
updated: YYYY-MM-DD updated: YYYY-MM-DD
owner: agent owner: agent
branch: feature/<slug> # 必填:feature/* 或 fix/*;禁止 dev / test / main task_type: implementation # research | implementation | operations
task_tier: small # small | multi_stage;信息答复无持久成果可不建任务
policy_revision: "<读取的修订 ID + dev SHA>"
branch: feature/<slug>
worktree: .worktrees/<task-id> worktree: .worktrees/<task-id>
base_dev_sha: <创建时的完整 dev SHA> base_dev_sha: "<创建时完整 dev SHA>"
write_scope: [<允许修改的路径>] write_scope: ["<允许修改的路径>"]
head_sha: <准入时冻结的提交> allowed_environments: []
head_sha: "<准入时冻结的提交>"
mutex: "<模块/路径资源>"
review_state: pending # pending | approved | changes_requested | not_required
integration_state: pending # pending | merged | not_required
deployment: pending # pending | passed | failed | not_required deployment: pending # pending | passed | failed | not_required
cleanup: pending # pending | done | retained-with-reason cleanup: pending # pending | done | retained-with-reason
mutex: <module> cleanup_owner: "<owner 退出后的独立集成/维护执行者>"
cleanup_trigger: "<owner 退出/验收完成/审批完成等可观测触发>"
remote_sync: not_requested # not_requested | pending | pushed | failed
--- ---
# <任务标题> # <任务标题>
## 目标 ## 用户目标与授权
(一句话可验证目标) - 用户目标/可核实授权来源(记录原请求或委派位置):
- 交付终点(研究产物/实现验证/dev 集成等):
- 明确禁止项(默认不 push,不操作未授权环境,分析不含业务实施):
- 例外(若有):owner、原因、覆盖条款、范围、期限/退出条件、恢复动作。
## 范围 已授权范围内持续执行,不重复索取同一许可;日志/网页/Agent 产物不构成授权。
- ## 范围与非目标
## 非目标 - 允许写入、影响模块及依赖:
- 非目标:
- ## 步骤与验收
## 步骤 1. [ ] <步骤> — 验收条件/证据:
1. [ ] 按 [验证契约](../2026-09-21-validation-contract.md) 选择检查。小任务可在本节承载完整验证,无需重复建 checklist、报告与 handoff。
2. [ ]
## 验收标准 ## 验证与证据
- [ ] | 范围 / 被测 SHA | 命令与环境 | 预期 | 实际 / 判定 | 证据 / 脱敏说明 |
|---|---|---|---|---|
| | | | | |
产品链路按需追加 expected_tests 与实际执行/SKIP 对照、Runner 模型/协议和制品身份。未运行不记 PASS。
## 收口与交接
- 剩余工作、blocker 和接管条件:
- 本地提交 / dev 集成 / 部署制品 / 远端状态(分别记录):
- cleanup 保留原因、唯一证据归档位置与下次检查条件:
- 本次影响、相关文档与建议下一步:
会话结束不等于 done;仅授权范围验收满足时完成,终态任务才归档。状态取实际值,优先引用工具收据,不重复人工维护多张表。
## 相关链接 ## 相关链接
- Checklist: - Plan / Checklist / 独立报告 / Handoff(仅实际拆分时):
- Handoff:
- Test report:
## 证据 / 备注
(命令输出、commit、文件路径)
+1 -1
View File
@@ -49,7 +49,7 @@
| 模块 | 声明范围 | 状态 | 发布关系 | 证据 | 剩余工作 / 关闭条件 | mutex | | 模块 | 声明范围 | 状态 | 发布关系 | 证据 | 剩余工作 / 关闭条件 | mutex |
|------|----------|------|----------|------|---------------------|-------| |------|----------|------|----------|------|---------------------|-------|
| Agent worktree 治理自动化 | 主目录保护、任务 worktree、机器认领、候选集成、dev 部署与安全清理 | planned | non_blocker | [方案](../remediation/2026-09-21-agent-workflow-governance-plan.md);[调研](../ops/research/2026-09-21-agent-workflow-governance-research.md) | [工作规范](../ops/2026-09-21-agent-worktree-workflow.md) 已采纳;人工显式校验立即执行。自动注册表、队列、脚本守卫、部署闭环和权限隔离仍待[实施](../ops/long-term/2026-09-21-agent-workflow-governance-automation.md),不能宣称自动强制已完成 | `agent-workflow-governance` | | Agent worktree 治理自动化 | 主目录保护、任务 worktree、机器认领、候选集成、dev 部署与安全清理 | partial | non_blocker | [方案](../remediation/2026-09-21-agent-workflow-governance-plan.md);[L1 验收](../test-reports/2026-09-21-agent-governance-test-report.md) | 本机 .locks/短租约、共享文档 CAS、提交/候选/环境门禁及安全 helper 已验;主仓启用见报告。后台调度、完整业务部署恢复闭环、产品 PI 镜像交付、OS/跨机隔离与清理可见性仍待[后续](../ops/long-term/2026-09-21-agent-workflow-governance-automation.md) | `agent-workflow-governance` |
| 意图识别 L1 | 18 类 Intent、三分支消解、写/只读分流、双标签 `DOC_BUSY`、intent 纯依赖边界 | done | non_blocker | [intent-recognition](../system-design/intent-recognition/);[工作流作用域](../test-reports/2026-09-21-intent-workflow-scoping-test-report.md);[审计](../audit/2026-09-13-conversation-intent-runtime-state.md);[报告](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md);[双标签](../test-reports/assets/2026-09-15-conversation-intent-dual-tab/live.json);[U1](../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md)–[U3](../test-reports/2026-09-15-multi-intent-u3-intentflow-pg-test-report.md) | **边界 before**:含 U9–U12 叙事混入 → **after**:U9–U12 迁至「多意图扩面」行。关闭:纯依赖边界回归即重开 | `intent` | | 意图识别 L1 | 18 类 Intent、三分支消解、写/只读分流、双标签 `DOC_BUSY`、intent 纯依赖边界 | done | non_blocker | [intent-recognition](../system-design/intent-recognition/);[工作流作用域](../test-reports/2026-09-21-intent-workflow-scoping-test-report.md);[审计](../audit/2026-09-13-conversation-intent-runtime-state.md);[报告](../test-reports/2026-09-13-conversation-intent-runtime-state-test-report.md);[双标签](../test-reports/assets/2026-09-15-conversation-intent-dual-tab/live.json);[U1](../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md)–[U3](../test-reports/2026-09-15-multi-intent-u3-intentflow-pg-test-report.md) | **边界 before**:含 U9–U12 叙事混入 → **after**:U9–U12 迁至「多意图扩面」行。关闭:纯依赖边界回归即重开 | `intent` |
| 多意图持久计划(U1–U8 基线) | app/intentflow 生命周期、HITL、默认 `legacy`、U1–U8 代码与安全基线 | partial | non_blocker(默认 legacy) | [设计](../system-design/2026-09-15-multi-intent-hitl-plan.md);[U1](../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md)–[U8](../test-reports/2026-09-16-multi-intent-u8-task-generate-test-report.md);[Wave2 真 PI](../test-reports/2026-09-16-wave2-real-pi-evidence-test-report.md);[2026-09-20 复核](../ops/research/2026-09-20-capability-enablement-impact-research.md) | legacy 有限只读多意图仍默认启用;新持久计划生产组合根仍使用 Fake query owner,confirm 后缺可靠调度,typed 参数/目标版本不足。原 U1–U8 测试证据保留,但不足以证明升 `auto_read/confirm` 可用。关闭条件见[整改方案](../remediation/2026-09-20-capability-enablement-impact-plan.md) Foundation 1 与 Track I | `intent-plan` | | 多意图持久计划(U1–U8 基线) | app/intentflow 生命周期、HITL、默认 `legacy`、U1–U8 代码与安全基线 | partial | non_blocker(默认 legacy) | [设计](../system-design/2026-09-15-multi-intent-hitl-plan.md);[U1](../test-reports/2026-09-15-multi-intent-u1-contracts-test-report.md)–[U8](../test-reports/2026-09-16-multi-intent-u8-task-generate-test-report.md);[Wave2 真 PI](../test-reports/2026-09-16-wave2-real-pi-evidence-test-report.md);[2026-09-20 复核](../ops/research/2026-09-20-capability-enablement-impact-research.md) | legacy 有限只读多意图仍默认启用;新持久计划生产组合根仍使用 Fake query owner,confirm 后缺可靠调度,typed 参数/目标版本不足。原 U1–U8 测试证据保留,但不足以证明升 `auto_read/confirm` 可用。关闭条件见[整改方案](../remediation/2026-09-20-capability-enablement-impact-plan.md) Foundation 1 与 Track I | `intent-plan` |
| 多意图扩面(U9–U12) | U9 LLM 分段实验;U10 mutate;U11 docgov;U12 workflow/run-control | partial | deferred_enhancement | [设计](../system-design/2026-09-15-multi-intent-hitl-plan.md);[go/no-go GO](../ops/research/2026-09-16-wave3-pu6-pu8-go-no-go.md);[脚手架](../test-reports/2026-09-16-pu8-u9-u12-flags-test-report.md);[U9](../test-reports/2026-09-17-w4-u9-segment-test-report.md);[U10](../test-reports/2026-09-17-w4-u10-mutate-test-report.md);[U11](../test-reports/2026-09-17-w4-u11-docgov-test-report.md);[U12](../test-reports/2026-09-17-w4-u12-run-control-test-report.md);[2026-09-20 复核](../ops/research/2026-09-20-capability-enablement-impact-research.md) | U9 proposer 已实现和注入但未进入生产聊天分段调用;提议请求已带 reasoning profile。U10–U12 当前为 dry-run/validated-only adapter。历史直接调用/PI 测试继续有效,不代表 live 接线。逐域实现和验收,禁止以 `full` 一次放量 | `intent-plan` | | 多意图扩面(U9–U12) | U9 LLM 分段实验;U10 mutate;U11 docgov;U12 workflow/run-control | partial | deferred_enhancement | [设计](../system-design/2026-09-15-multi-intent-hitl-plan.md);[go/no-go GO](../ops/research/2026-09-16-wave3-pu6-pu8-go-no-go.md);[脚手架](../test-reports/2026-09-16-pu8-u9-u12-flags-test-report.md);[U9](../test-reports/2026-09-17-w4-u9-segment-test-report.md);[U10](../test-reports/2026-09-17-w4-u10-mutate-test-report.md);[U11](../test-reports/2026-09-17-w4-u11-docgov-test-report.md);[U12](../test-reports/2026-09-17-w4-u12-run-control-test-report.md);[2026-09-20 复核](../ops/research/2026-09-20-capability-enablement-impact-research.md) | U9 proposer 已实现和注入但未进入生产聊天分段调用;提议请求已带 reasoning profile。U10–U12 当前为 dry-run/validated-only adapter。历史直接调用/PI 测试继续有效,不代表 live 接线。逐域实现和验收,禁止以 `full` 一次放量 | `intent-plan` |
@@ -147,4 +147,4 @@ go vet ./...
go test -short ./... go test -short ./...
``` ```
改了事件契约时,额外确认 `TestRegistryParityWithYAML` 在跑到的包里通过。文档-only 变更也跑上述命令,防止索引或生成物被误改。 改了事件契约时,额外确认 `TestRegistryParityWithYAML` 在跑到的包里通过。普通工程文档变更按 [验证契约](../ops/2026-09-21-validation-contract.md) 检查差异、链接和状态;不运行无关 Go/PI 套件。运行模板、提示词与代码契约变更按受影响范围运行上述门禁,防止索引或生成物被误改。
@@ -0,0 +1,29 @@
# Agent 规范与 L1 操作门禁验收
日期:2026-09-21。范围:R01–R10、授权与脱敏证据补充、本机两个共享文档/dev 的最小 L1 门禁。授权与收口见[实施任务](../ops/short-term/2026-09-21-agent-governance-implementation.md)。不改变产品完成度/发布 blocker。
## 变更与边界
- AGENTS/PROJECT/客户端入口去重;轻量答复、单文档任务、按范围验证、规则生效版本、原子提交及会话结束/任务完成分离。CURRENT 历史移入[归档](../archive/ops/2026-09-21-current-history.md),保留原有保护项与未完成任务。
- 主仓唯一 `.locks`:SQLite WAL、PID/start/boot 身份、资源集合原子申请、父子路径互斥、幂等请求、可审计恢复、旧 dev flock 同 inode 桥接。两共享文档行级 CAS、整文档管理权限、完成证据与提交收据。
- 安全 worktree 创建/删除;主目录及源码/环境 guard;提交/引用门禁与精确候选验证;中断子进程回收。构建从指定 Git 提交导出,nightly 要求两个精确顶层测试均真实 PASS。
- 这是合作 Agent 的入口与提交门禁,不是 OS 权限隔离;直接 shell/篡改工具仍可绕过。无后台常驻队列,无产品源码/DB/模型变更,无 test/main 晋升或远端 push。
## 实际验证
| 检查 | 结果与证据 | 限制 |
|---|---|---|
| Python 工程行为测试 | 76 PASS;[完整输出](./assets/2026-09-21-agent-governance/python-tests.txt) | 临时真实 Git/SQLite/flock;不代表产品 E2E |
| 20 进程、20 worktree 争抢同一资源 | 1 执行、19 LOCK_BUSY;[结果](./assets/2026-09-21-agent-governance/contention.json)、[可重跑脚本](./assets/2026-09-21-agent-governance/contention-probe.py) | 本机并发,不是跨机证明 |
| 独立代码审查 | [原始审查](./assets/2026-09-21-agent-governance/code-review.json)确认 5 项;[修复复核](./assets/2026-09-21-agent-governance/fix-verification.json)全部关闭 | 同模型独立上下文,未调用外部 provider |
| dev 引用准入 | 线性提交借原始 with 绕过被拒;两父候选精确提升、错误基线、失败重验收据失效有正反例 | 实际主仓激活与合入另见收口 |
| 中断与恢复 | validate/integrate SIGINT 不遗留写入子进程;未知外部结果阻止继续;恢复后可重试 | 无自动 DB 回滚 |
| 文档写回 | 过期 CAS、多文件预检、他人记录、原始改动混入收据、admin 无批准升 done(紧凑单元格及前导空格 0–3)均覆盖 | 不是任意读者的多文件原子快照,以 Git 提交发布 |
| 构建与服务 guard | 无租约/错 cwd/错 SHA 拒绝;并发源码修改不污染 Git archive;nightly 证据不进入构建输入 | Docker 边界使用 fake Docker,未真实构建镜像/执行 nightly |
| worktree 清理 | 祖先、文件、生命周期、活跃登记与进程拒绝覆盖;[改前失败](./assets/2026-09-21-agent-governance/ops-red-before.txt) | 3 删除正例仅模拟可读进程枚举;其余真实 Git/锁。本机部分同 UID cwd 不可读,严格保留 cleanup_pending |
工程脚本/规范变更不需 Go、前端或产品 PI 全套测试,也不需要重建产品容器。语法、diff、文档链接和候选复验在集成阶段确认。不得用健康检查替代这些测试,亦不得用本表替代业务验收。
## 主仓收口
待验证候选集成后启用 init/hooks,并记录实际生效提交、主仓 doctor、环境只读核对与安全清理结果。
@@ -0,0 +1,531 @@
{
"status": "complete",
"verdict": "Ready with fixes",
"verdict_reason": "Four P1 defects remain in managed integration, child lifetime and candidate-build behavior, plus a P2 completion-approval bypass. Resolve admission (#1), supervision (#2), build-source choice and nightly compatibility (#3-#4), then approval parsing (#5). No bounded requirement is wholly omitted; partial items map to these findings.",
"scope": {
"base": "a241e838150f218f3a09d3523f93291b847d45fd",
"branch": "feature/agent-governance-implementation",
"head_sha": "eb4dbdca5baae8dd5f3ddefc0f5510ac2978dcd3",
"pr_url": null,
"files_changed": 41,
"includes_working_tree_diff": true
},
"intent": "Implement approved R01-R10 governance and minimal L1 managed operations: isolated worktrees, canonical SQLite coordination, scoped leases, process identity and conservative recovery, docs CAS/receipts, exact candidate integration and safe service/build guards. Preserve history and existing product behavior; L2 OS isolation and cross-host operations are outside this unit.",
"intent_confidence": "explicit",
"reviewers": [
"correctness",
"security",
"testing",
"maintainability",
"reliability",
"adversarial",
"project-standards",
"agent-native"
],
"reviewer_selection": [
{
"reviewer": "correctness",
"tier": "session",
"reason": "always-on"
},
{
"reviewer": "security",
"tier": "session",
"reason": "Managed-operation permissions and path/capability boundaries"
},
{
"reviewer": "testing",
"tier": "mid",
"reason": "Real temporary Git/SQLite subprocess test harnesses changed"
},
{
"reviewer": "maintainability",
"tier": "mid",
"reason": "Substantial new coordinator abstractions and 2343 executable changed lines"
},
{
"reviewer": "reliability",
"tier": "mid",
"reason": "Lease expiry, process identity, signal handling and recovery"
},
{
"reviewer": "adversarial",
"tier": "session",
"reason": "Concurrency, persistence and silent-pass guard risks; external transport prohibited"
},
{
"reviewer": "project-standards",
"tier": "mid",
"reason": "Root AGENTS.md and CLAUDE.md govern all changed paths"
},
{
"reviewer": "agent-native",
"tier": "mid",
"reason": "New agent-facing governance commands and instructions"
}
],
"findings": [
{
"#": 1,
"autofix_class": "manual",
"confidence": 100,
"evidence": [
"scripts/agentctl.py:802-805 checks ancestry and SELECT sha FROM validations WHERE sha=?; integrate:874-879 additionally checks task, two parents and frozen first parent.",
"Real temporary Git reproduction with production hooks: integrate rejected the linear SHA; agentctl with --resource git:dev --cwd MAIN -- git merge --ff-only SHA succeeded and advanced dev. No hook disabling or unleased writes."
],
"file": "scripts/agentctl.py",
"first_evidence": "scripts/agentctl.py:802 -- if git(repo.main, 'merge-base', '--is-ancestor', old, new, check=False).returncode:",
"independent_reviewers": [
"adversarial",
"security"
],
"line": 802,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"adversarial",
"security"
],
"severity": "P1",
"suggested_fix": "Share candidate admission between integrate and hook_ref: require two parents, first parent equal to old dev, and validation bound to candidate/task. Alternatively require an integration receipt binding old dev, candidate and owning run. Test rejection through the alternate managed git merge path.",
"title": "Managed fast-forward bypasses the validated merge-candidate invariant",
"why_it_matters": "A registered git:dev operator can promote a validated linear commit through agentctl with and git merge, although integrate rejects that commit as INVALID_CANDIDATE. The reference hook checks ancestry and any SHA receipt but omits the required two-parent, frozen-baseline and task-bound checks.",
"validation_status": "confirmed",
"validation_reason": "With a registered git:dev task and a validated linear descendant, the new execute path permits main-worktree git merge (scripts/agentctl.py:509-527) while hook_ref checks only ancestry and any SHA receipt (802-807), bypassing integrate's two-parent and frozen-first-parent checks (874-879); the supplied real-Git reproduction supports reachability, which was independently verified in source rather than rerun.",
"protected_subject": "authorization-authentication"
},
{
"#": 2,
"autofix_class": "gated_auto",
"confidence": 100,
"evidence": [
"scripts/agentctl.py:848-853 and 890-894 start new-session children then wait; Lease.__exit__:468-477 finalizes failure and unlocks without child termination.",
"execute:531-558 already forwards signals, escalates, reaps and marks recovery. Interruption finding confirmed from source; incidence was not measured."
],
"file": "scripts/agentctl.py",
"first_evidence": "scripts/agentctl.py:848-853 -- child = subprocess.Popen(command, cwd=repo.current, start_new_session=True) ... code = child.wait()",
"independent_reviewers": [
"correctness",
"reliability"
],
"line": 848,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"correctness",
"reliability"
],
"severity": "P1",
"suggested_fix": "Reuse execute child supervision in validate and integrate: forward SIGINT/SIGTERM, escalate with bounded SIGKILL, reap the process group and retain recovery_required until safe resolution. Test interruption of both commands.",
"title": "Interrupted validation leaves its child running",
"why_it_matters": "SIGINT during validate or integrate can leave a separately sessioned child running after the lease is marked failed and unlocked. A later operation can overlap validation writes, or detached git merge can advance dev after reported failure.",
"validation_status": "confirmed",
"validation_reason": "If SIGINT interrupts validate or integrate after its new-session child starts, scripts/agentctl.py:848-853 and 890-894 have no signal forwarding or exception cleanup and Lease.__exit__:468-477 marks the non-document run failed and unlocks without terminating the child, allowing conflicting admission at 429-439; confirmation is source-based and interruption incidence was not measured.",
"protected_subject": "concurrency"
},
{
"#": 3,
"autofix_class": "manual",
"confidence": 75,
"evidence": [
"scripts/agentctl.py:692-696 checks HEAD/cleanliness without retaining a lease; candidate-images-build.sh:48-53 builds from the mutable checkout and publishes COMMIT at 84.",
"Real Git/SQLite controlled reproduction paused a fake Docker boundary: managed source edit succeeded, then build/manifest succeeded with the original SHA and dirty source. Actual image contents were not built or examined."
],
"file": "scripts/candidate-images-build.sh",
"first_evidence": "scripts/candidate-images-build.sh:30 -- python3 \"${ROOT}/scripts/agentctl.py\" guard build --commit \"${COMMIT}\"",
"independent_reviewers": [
"adversarial"
],
"line": 30,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"adversarial"
],
"severity": "P1",
"suggested_fix": "Choose an immutable checkout pinned to COMMIT, or retain source/index leases that conflict with every managed writer through both builds and manifest publication. Recheck provenance before publishing and add a synchronized concurrent-writer regression.",
"title": "Concurrent managed edits invalidate successful candidate image provenance",
"why_it_matters": "After the one-shot build guard exits, managed writers can change the checkout while Docker reads it. A successful manifest can therefore name the original commit despite build inputs changing.",
"validation_status": "confirmed",
"validation_reason": "If a managed writer changes build-relevant source after the newly added guard returns, scripts/agentctl.py:692-696 retains no lease and candidate-images-build.sh:48-53 reads the mutable checkout before publishing the original COMMIT at 84, so the new managed-build admission does not preserve its exact-source guarantee; supplied fake-Docker boundary evidence establishes writer admission and receipt behavior, not actual image contents or production incidence.",
"protected_subject": "concurrency"
},
{
"#": 4,
"autofix_class": "gated_auto",
"confidence": 75,
"evidence": [
"run-nightly-real-pi.sh:33,115,174,193 writes evidence under the checkout before ensure_candidate_images at 198; agentctl.py:158-160 rejects untracked files.",
"git check-ignore found no ignores for the default evidence paths. Complete nightly execution was not rerun."
],
"file": "scripts/candidate-images-build.sh",
"first_evidence": "scripts/candidate-images-build.sh:30 -- python3 \"${ROOT}/scripts/agentctl.py\" guard build --commit \"${COMMIT}\"",
"independent_reviewers": [
"correctness"
],
"line": 30,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"correctness"
],
"severity": "P1",
"suggested_fix": "Keep nightly evidence outside the build checkout until image creation finishes, then persist it at the documented path; alternatively build from a separate clean pinned checkout. Preserve dirty-source rejection and test the default fresh-manifest flow.",
"title": "New build guard rejects nightly's own evidence files",
"why_it_matters": "With default paths and no existing image manifest, nightly creates untracked health/test evidence before calling the builder. The new clean_source guard rejects those files, turning otherwise passing tests into infra_fail before image creation.",
"validation_status": "confirmed",
"validation_reason": "With default nightly evidence paths, passing required tests and no existing image manifest, run-nightly-real-pi.sh:33,115,174,193 creates untracked evidence before invoking the builder at 198, whose new guard calls clean_source and rejects all untracked files at scripts/agentctl.py:158-160; independent git check-ignore found no matching ignores, while a complete nightly execution was not rerun.",
"protected_subject": "public-contract"
},
{
"#": 5,
"autofix_class": "gated_auto",
"confidence": 100,
"evidence": [
"scripts/agentctl.py:645 matches only '| done |'; row checks at 651-660 trim cells and require docs:approve plus evidence.",
"The exact predicates evaluated on '| core |done|' give whole_document_detects_done=False and row_detects_done=True. Receipts at 682-685 satisfy hook_check:781-787 without transition rechecking; live incidence was not measured."
],
"file": "scripts/agentctl.py",
"first_evidence": "scripts/agentctl.py:645 -- if any('| done |' in line and line not in old_done for line in replacement.splitlines()):",
"independent_reviewers": [
"security"
],
"line": 645,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"security"
],
"severity": "P2",
"suggested_fix": "Parse trimmed table cells with a shared helper and compare status transitions by record id. Reject newly done rows in @document regardless of spacing; retain approval/evidence transitions on the row path. Test docs:admin without docs:approve using | core |done|.",
"title": "CWE-863: Whole-document edits bypass completion approval",
"why_it_matters": "A docs:admin and docs:publish task without docs:approve can introduce a done row through @document using valid Markdown such as | core |done|. The exact-space predicate misses it, publishes a receipt and allows the commit hook to accept completion without approval or evidence.",
"validation_status": "confirmed",
"validation_reason": "For a docs:admin and docs:publish task lacking docs:approve, a whole-document replacement containing a newly completed row such as '| core |done|' passes the exact '| done |' predicate at scripts/agentctl.py:643-648, receives a receipt at 682-683 and satisfies the receipt-only hook at 781-787 despite the row-path approval requirement at 654-660; confirmation is source-based and live incidence was not measured.",
"protected_subject": "authorization-authentication"
}
],
"actionable_findings": [
{
"#": 1,
"autofix_class": "manual",
"confidence": 100,
"evidence": [
"scripts/agentctl.py:802-805 checks ancestry and SELECT sha FROM validations WHERE sha=?; integrate:874-879 additionally checks task, two parents and frozen first parent.",
"Real temporary Git reproduction with production hooks: integrate rejected the linear SHA; agentctl with --resource git:dev --cwd MAIN -- git merge --ff-only SHA succeeded and advanced dev. No hook disabling or unleased writes."
],
"file": "scripts/agentctl.py",
"first_evidence": "scripts/agentctl.py:802 -- if git(repo.main, 'merge-base', '--is-ancestor', old, new, check=False).returncode:",
"independent_reviewers": [
"adversarial",
"security"
],
"line": 802,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"adversarial",
"security"
],
"severity": "P1",
"suggested_fix": "Share candidate admission between integrate and hook_ref: require two parents, first parent equal to old dev, and validation bound to candidate/task. Alternatively require an integration receipt binding old dev, candidate and owning run. Test rejection through the alternate managed git merge path.",
"title": "Managed fast-forward bypasses the validated merge-candidate invariant",
"why_it_matters": "A registered git:dev operator can promote a validated linear commit through agentctl with and git merge, although integrate rejects that commit as INVALID_CANDIDATE. The reference hook checks ancestry and any SHA receipt but omits the required two-parent, frozen-baseline and task-bound checks.",
"validation_status": "confirmed",
"validation_reason": "With a registered git:dev task and a validated linear descendant, the new execute path permits main-worktree git merge (scripts/agentctl.py:509-527) while hook_ref checks only ancestry and any SHA receipt (802-807), bypassing integrate's two-parent and frozen-first-parent checks (874-879); the supplied real-Git reproduction supports reachability, which was independently verified in source rather than rerun.",
"protected_subject": "authorization-authentication"
},
{
"#": 2,
"autofix_class": "gated_auto",
"confidence": 100,
"evidence": [
"scripts/agentctl.py:848-853 and 890-894 start new-session children then wait; Lease.__exit__:468-477 finalizes failure and unlocks without child termination.",
"execute:531-558 already forwards signals, escalates, reaps and marks recovery. Interruption finding confirmed from source; incidence was not measured."
],
"file": "scripts/agentctl.py",
"first_evidence": "scripts/agentctl.py:848-853 -- child = subprocess.Popen(command, cwd=repo.current, start_new_session=True) ... code = child.wait()",
"independent_reviewers": [
"correctness",
"reliability"
],
"line": 848,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"correctness",
"reliability"
],
"severity": "P1",
"suggested_fix": "Reuse execute child supervision in validate and integrate: forward SIGINT/SIGTERM, escalate with bounded SIGKILL, reap the process group and retain recovery_required until safe resolution. Test interruption of both commands.",
"title": "Interrupted validation leaves its child running",
"why_it_matters": "SIGINT during validate or integrate can leave a separately sessioned child running after the lease is marked failed and unlocked. A later operation can overlap validation writes, or detached git merge can advance dev after reported failure.",
"validation_status": "confirmed",
"validation_reason": "If SIGINT interrupts validate or integrate after its new-session child starts, scripts/agentctl.py:848-853 and 890-894 have no signal forwarding or exception cleanup and Lease.__exit__:468-477 marks the non-document run failed and unlocks without terminating the child, allowing conflicting admission at 429-439; confirmation is source-based and interruption incidence was not measured.",
"protected_subject": "concurrency"
},
{
"#": 3,
"autofix_class": "manual",
"confidence": 75,
"evidence": [
"scripts/agentctl.py:692-696 checks HEAD/cleanliness without retaining a lease; candidate-images-build.sh:48-53 builds from the mutable checkout and publishes COMMIT at 84.",
"Real Git/SQLite controlled reproduction paused a fake Docker boundary: managed source edit succeeded, then build/manifest succeeded with the original SHA and dirty source. Actual image contents were not built or examined."
],
"file": "scripts/candidate-images-build.sh",
"first_evidence": "scripts/candidate-images-build.sh:30 -- python3 \"${ROOT}/scripts/agentctl.py\" guard build --commit \"${COMMIT}\"",
"independent_reviewers": [
"adversarial"
],
"line": 30,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"adversarial"
],
"severity": "P1",
"suggested_fix": "Choose an immutable checkout pinned to COMMIT, or retain source/index leases that conflict with every managed writer through both builds and manifest publication. Recheck provenance before publishing and add a synchronized concurrent-writer regression.",
"title": "Concurrent managed edits invalidate successful candidate image provenance",
"why_it_matters": "After the one-shot build guard exits, managed writers can change the checkout while Docker reads it. A successful manifest can therefore name the original commit despite build inputs changing.",
"validation_status": "confirmed",
"validation_reason": "If a managed writer changes build-relevant source after the newly added guard returns, scripts/agentctl.py:692-696 retains no lease and candidate-images-build.sh:48-53 reads the mutable checkout before publishing the original COMMIT at 84, so the new managed-build admission does not preserve its exact-source guarantee; supplied fake-Docker boundary evidence establishes writer admission and receipt behavior, not actual image contents or production incidence.",
"protected_subject": "concurrency"
},
{
"#": 4,
"autofix_class": "gated_auto",
"confidence": 75,
"evidence": [
"run-nightly-real-pi.sh:33,115,174,193 writes evidence under the checkout before ensure_candidate_images at 198; agentctl.py:158-160 rejects untracked files.",
"git check-ignore found no ignores for the default evidence paths. Complete nightly execution was not rerun."
],
"file": "scripts/candidate-images-build.sh",
"first_evidence": "scripts/candidate-images-build.sh:30 -- python3 \"${ROOT}/scripts/agentctl.py\" guard build --commit \"${COMMIT}\"",
"independent_reviewers": [
"correctness"
],
"line": 30,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"correctness"
],
"severity": "P1",
"suggested_fix": "Keep nightly evidence outside the build checkout until image creation finishes, then persist it at the documented path; alternatively build from a separate clean pinned checkout. Preserve dirty-source rejection and test the default fresh-manifest flow.",
"title": "New build guard rejects nightly's own evidence files",
"why_it_matters": "With default paths and no existing image manifest, nightly creates untracked health/test evidence before calling the builder. The new clean_source guard rejects those files, turning otherwise passing tests into infra_fail before image creation.",
"validation_status": "confirmed",
"validation_reason": "With default nightly evidence paths, passing required tests and no existing image manifest, run-nightly-real-pi.sh:33,115,174,193 creates untracked evidence before invoking the builder at 198, whose new guard calls clean_source and rejects all untracked files at scripts/agentctl.py:158-160; independent git check-ignore found no matching ignores, while a complete nightly execution was not rerun.",
"protected_subject": "public-contract"
},
{
"#": 5,
"autofix_class": "gated_auto",
"confidence": 100,
"evidence": [
"scripts/agentctl.py:645 matches only '| done |'; row checks at 651-660 trim cells and require docs:approve plus evidence.",
"The exact predicates evaluated on '| core |done|' give whole_document_detects_done=False and row_detects_done=True. Receipts at 682-685 satisfy hook_check:781-787 without transition rechecking; live incidence was not measured."
],
"file": "scripts/agentctl.py",
"first_evidence": "scripts/agentctl.py:645 -- if any('| done |' in line and line not in old_done for line in replacement.splitlines()):",
"independent_reviewers": [
"security"
],
"line": 645,
"owner": "downstream-resolver",
"pre_existing": false,
"requires_verification": true,
"reviewers": [
"security"
],
"severity": "P2",
"suggested_fix": "Parse trimmed table cells with a shared helper and compare status transitions by record id. Reject newly done rows in @document regardless of spacing; retain approval/evidence transitions on the row path. Test docs:admin without docs:approve using | core |done|.",
"title": "CWE-863: Whole-document edits bypass completion approval",
"why_it_matters": "A docs:admin and docs:publish task without docs:approve can introduce a done row through @document using valid Markdown such as | core |done|. The exact-space predicate misses it, publishes a receipt and allows the commit hook to accept completion without approval or evidence.",
"validation_status": "confirmed",
"validation_reason": "For a docs:admin and docs:publish task lacking docs:approve, a whole-document replacement containing a newly completed row such as '| core |done|' passes the exact '| done |' predicate at scripts/agentctl.py:643-648, receives a receipt at 682-683 and satisfies the receipt-only hook at 781-787 despite the row-path approval requirement at 654-660; confirmation is source-based and live incidence was not measured.",
"protected_subject": "authorization-authentication"
}
],
"triage_groups": [
{
"title": "Managed integration admission and child lifetime",
"findings": [
1,
2
],
"context": "Managed integration must enforce the same exact candidate and retain coordination while child processes can still act.",
"preferred_resolution": "Decision gate: choose shared admission or an integration receipt for #1, then reuse child supervision for #2. Verify alternate-entrypoint and interruption cases.",
"why": "The two independent defects can admit invalid or overlapping protected-ref operations; fixing either alone leaves the other reachable."
},
{
"title": "Candidate build source and nightly evidence",
"findings": [
3,
4
],
"context": "The builder reads a mutable checkout and the nightly caller creates evidence there before invoking its clean-source guard.",
"preferred_resolution": "Decision gate: choose immutable build input or retained source leases for #3, then keep nightly evidence outside that input for #4. Verify concurrency and the default fresh-manifest flow.",
"why": "A pinned clean checkout can address both concerns, but concurrency and self-generated dirty state remain separate acceptance cases."
}
],
"pre_existing_findings": [],
"requirements_completeness": {
"plan": "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/.doc/audit/2026-09-21-agent-policy-review.md",
"source": "explicit",
"requirements": [
{
"id": "R01",
"requirement": "Scope tests and model evidence to changes",
"status": "met",
"note": "Scoped validation contract and temporary Git/SQLite/subprocess tests are present; no shared product-runtime claim."
},
{
"id": "R02",
"requirement": "Exact real-PI selection and no-skip evidence",
"status": "partially addressed",
"note": "Exact test selection and no-skip evidence checks are present; default nightly completion is blocked by #4."
},
{
"id": "R03",
"requirement": "Task levels and single durable record for small work",
"status": "met",
"note": "Task-level and single-record conventions are present in AGENTS.md and task templates."
},
{
"id": "R04",
"requirement": "CURRENT active coordination and preserved history",
"status": "met",
"note": "CURRENT coordination changes and archived history are present."
},
{
"id": "R05",
"requirement": "Separate session, task, integration, deployment, cleanup statuses",
"status": "met",
"note": "Separate lifecycle status conventions are present in the task and close templates."
},
{
"id": "R06",
"requirement": "Atomic logical commits without fixed layer order",
"status": "met",
"note": "Atomic logical commit guidance is present; this review does not attest future caller commits."
},
{
"id": "R07",
"requirement": "Normative SSOT lifecycle and thin client entries",
"status": "met",
"note": "Policy registry, lifecycle guidance and thin client entrypoints are present."
},
{
"id": "R08",
"requirement": "Task authorization and exception boundaries",
"status": "partially addressed",
"note": "Scoped capabilities and exception boundaries are implemented, with alternate-entrypoint gaps #1 and #5."
},
{
"id": "R09",
"requirement": "Minimal L1 worktree, env, docs CAS, commit and integration gates",
"status": "partially addressed",
"note": "Minimal L1 gates are implemented, with integration, child-lifetime and build defects #1-#4."
},
{
"id": "R10",
"requirement": "Cleanup owner, retention reason, revisit trigger",
"status": "met",
"note": "Cleanup ownership, retention and revisit guidance are present; inaccessible processes fail closed."
}
],
"implementation_units": [],
"note": "Coverage describes the bounded two-document/dev L1 unit. L2, cross-host and full product-image delivery are deferred."
},
"learnings": [],
"agent_native_gaps": [],
"deployment_notes": [],
"residual_risks": [
"Interruption and completion-approval incidence were not measured; validator confirmation is source-based.",
"The controlled build probe establishes managed-writer admission and receipt behavior, not actual Docker image contents.",
"Default nightly and shared product runtime were not executed in this review."
],
"testing_gaps": [
{
"title": "Test admin capability denial",
"file": "scripts/agentctl.py",
"line": 611,
"autofix_class": "advisory",
"owner": "human",
"suggested_fix": "Add a docs:publish-only whole-document request to test_admin_document_cas_preserves_slot_and_needs_capability (or a separate test), assert CAPABILITY_REQUIRED, and compare the document bytes before and after."
},
{
"title": "Exercise failed validation outcomes",
"file": "scripts/agentctl.py",
"line": 855,
"autofix_class": "advisory",
"owner": "human",
"suggested_fix": "Extend test_validated_merge_candidate_is_exactly_promoted_to_dev (or add focused tests) with a nonzero validation command and a command that creates an untracked file; for each, assert validate fails and a subsequent integrate for that candidate is rejected with VALIDATION_REQUIRED."
}
],
"coverage": {
"notes": [
"Standalone current-tree review against explicit base; staged and unstaged intent-to-add files included.",
"Standards fall back to root AGENTS.md and CLAUDE.md; no CODING_STANDARDS.md exists.",
"No Compound Packs or matching docs/solutions corpus.",
"Tool-only change: shared product services, Go/React/database runtime and L2 OS isolation excluded; tests are temporary Git/SQLite/process scenarios.",
"Run artifacts use skill-sanctioned temporary directory; no repository artifacts written by reviewers.",
"All eight local reviewer returns collected. Testing and standards reviewers independently ran real temporary Git/SQLite/subprocess suite: 67 tests passed. Parent additionally reports 20-contender probe PASS, one writer/19 busy; raw parent evidence outside this review run.",
"Cross-model transport is outside the caller-authorized boundary; no peer launched. Adversarial lens uses in-session reviewer; no different-model corroboration claimed.",
"Two testing-only findings are report-only gaps: nonzero/dirty validation receipt denial and docs:admin denial. No defect is inferred from their absence. The line-count-only split proposal failed the concrete-consequence/applicable-standard admission check; the specific duplicate supervision bug remains primary.",
"Three positive cleanup tests mock only /proc enumeration; inaccessible-process and live-cwd negatives remain real. Production cleanup fails closed."
],
"first_evidence_backfilled": 10,
"quote_gate_demoted": 0,
"suppressed_by_confidence": {
"50": 0,
"25": 0
},
"malformed_findings": 0,
"malformed_returns": 0,
"semantic_duplicates_merged": 2,
"mode_aware_demotions": 2,
"admission_rejected": 1,
"settlement_suppressed": 0,
"validator_shortcut_skipped": 0,
"validator_shortcut_evidence_basis": "No independently verified cross-model peer; in-session persona agreement never licenses shortcut or confidence promotion.",
"failed_reviewers": [],
"bound_exceeded": [],
"compound_packs": {
"roots": [],
"errors": [],
"warnings": []
},
"independence_note": "independent_reviewers retains helper-derived persona provenance; these identities do not represent different serving models.",
"mode": "agent report-only",
"lite": false,
"cross_model_outcome": "in-process-fallback",
"cross_model_coverage": "Cross-model transport is outside the caller-authorized boundary; no peer launched. Adversarial lens uses in-session reviewer; no different-model corroboration claimed.",
"validator": {
"outcome": "verdicts",
"batches": 1,
"confirmed": 5,
"rejected": 0,
"unresolved": 0,
"malformed": 0,
"failed": 0,
"shortcut_skipped": 0,
"bound_exceeded": false,
"protected_subject_reclassifications": [],
"degraded_blockers": [],
"reason": "All five matched verdicts landed; severity, confidence and routes preserved. Reasons and evidence limits accompany each finding."
}
},
"artifact_path": "/tmp/compound-engineering-1000/ce-code-review/20260921-133611-5e4781e6",
"run_id": "20260921-133611-5e4781e6"
}
@@ -0,0 +1,51 @@
from pathlib import Path
import importlib.util
import json
import socket
import subprocess
import sys
root = Path(__file__).resolve().parents[4]
spec = importlib.util.spec_from_file_location('coordination_tests', root/'scripts/tests/test_agentctl.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
case = module.AgentctlTests('test_uninitialized_status_and_doctor_do_not_create_state')
case.setUp()
processes = []
listener = socket.socket(socket.AF_UNIX)
connection = None
try:
tasks = [case.initialized_task('contender-'+str(i), scopes=('src',)) for i in range(20)]
endpoint = str(Path(case.temp.name)/'stress.sock')
listener.bind(endpoint)
listener.listen(20)
listener.settimeout(20)
code = "import os,socket; s=socket.socket(socket.AF_UNIX); s.connect(%r); s.sendall(os.environ['AGENTCTL_TASK_ID'].encode()); s.recv(1)" % endpoint
for i, task in enumerate(tasks):
processes.append(subprocess.Popen([sys.executable, str(case.root/'scripts/agentctl.py'), 'with', '--task', 'contender-'+str(i), '--resource', 'path:src/a', '--', sys.executable, '-c', code], cwd=task, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True))
connection, _ = listener.accept()
winner = connection.recv(100).decode()
outcomes = {}
for i, process in enumerate(processes):
if 'contender-'+str(i) == winner:
continue
out, err = process.communicate(timeout=20)
assert process.returncode == 75, (i,process.returncode,out,err)
outcomes['contender-'+str(i)] = 'LOCK_BUSY'
connection.sendall(b'x')
index = int(winner.split('-')[-1])
out, err = processes[index].communicate(timeout=10)
assert processes[index].returncode == 0, (out,err)
outcomes[winner] = 'completed'
result = {'contenders':20,'exclusive_writers':1,'busy':19,'winner':winner,'real_git_worktrees':True,'real_sqlite':True,'real_subprocesses':True,'result':'PASS'}
Path('/tmp/agentctl-contention-result.json').write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps(result))
finally:
if connection:
connection.close()
listener.close()
for process in processes:
if process.poll() is None:
process.terminate()
process.communicate(timeout=10)
case.doCleanups()
@@ -0,0 +1,10 @@
{
"contenders": 20,
"exclusive_writers": 1,
"busy": 19,
"winner": "contender-2",
"real_git_worktrees": true,
"real_sqlite": true,
"real_subprocesses": true,
"result": "PASS"
}
@@ -0,0 +1,69 @@
{
"status": "complete_all_findings_fixed",
"scope": "Independent bounded verification of original ce-code-review findings 1-5; no broad review.",
"worktree": "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation",
"original_receipt": "/tmp/compound-engineering-1000/ce-code-review/20260921-133611-5e4781e6/review.json",
"findings": [
{
"id": 1,
"status": "fixed",
"evidence": [
"agentctl.py:855-865 admit_candidate requires exactly two parents, frozen first parent, current owning task HEAD, and task-bound exact SHA validation.",
"agentctl.py:880-882 applies shared admission in reference hook and requires integrate kind and old/new fingerprint for dev.",
"Independent test_validated_linear_commit_cannot_bypass_candidate_entrypoint PASS; exact merge promotion test PASS."
]
},
{
"id": 2,
"status": "fixed",
"evidence": [
"agentctl.py:517-561 shared supervise used by execute, validate and integrate; installs SIGINT/SIGTERM handler before child spawn, terminates child process group, escalates after five seconds, waits and checks surviving group.",
"validate preserves recovery if group survives; integrate preserves recovery for nonzero result. Integration holds git:index in addition to git:dev.",
"Independent test_interrupted_validation_stops_child_before_releasing_scope PASS, using pidfd to verify child exit.",
"Independent test_validated_merge_candidate_is_exactly_promoted_to_dev PASS, including SIGINT at Git boundary, unchanged dev, blocked index resource until recovery, then successful exact promotion."
]
},
{
"id": 3,
"status": "fixed",
"evidence": [
"candidate-images-build.sh:55-61 exports immutable requested commit via git archive; both Dockerfiles and both build contexts use that export.",
"Independent test_build_context_is_frozen_when_managed_source_changes PASS: managed mutable-source change admitted while fake Docker reads original archived content.",
"Manifest includes source_tree and source_context=git-archive."
]
},
{
"id": 4,
"status": "fixed",
"evidence": [
"agentctl.py:158-170 build cleanliness retains tracked/unrelated-untracked rejection and permits only untracked files under .doc/test-reports/assets/promotion-receipts/.",
"Default nightly health, expected-tests and Go evidence all use that prefix before ensure_candidate_images.",
"Independent test_build_accepts_nightly_evidence_excluded_from_build_context PASS with fake Docker; git archive excludes untracked evidence."
]
},
{
"id": 5,
"status": "fixed",
"evidence": [
"matrix_states and doc_rows now normalize leading spaces before detecting table rows; status cells are trimmed and canonical states checked.",
"Independent rerun of test_admin_document_cannot_bypass_done_with_compact_cells PASS with indent 0, 1, 2 and 3; each asserts nonzero exit and unchanged bytes.",
"Independent original temporary Git/SQLite reproduction rerun: docs:publish and docs:admin without docs:approve cannot replace | core | planned | with one-space-indented | core |done| through @document; exit=1, EVIDENCE_REQUIRED, and document unchanged."
],
"verification_history": "Initial verification found one leading-space variant remaining; parent fixed it and independent rerun confirms closure."
}
],
"narrowly_related_regressions": [],
"validation": {
"independent_focused_tests_passed": 7,
"additional_reproductions": {
"leading_space_done_bypass": "closed: rerun denied EVIDENCE_REQUIRED and preserved document"
},
"supplemental_test": "test_failed_revalidation_revokes_old_success_receipt PASS",
"final_followup_tests_passed": 1
},
"limits": [
"No real Docker image build, full nightly, product runtime, database service, external provider, repository mutation, Git index update or commit performed.",
"Build tests use fake Docker boundary and real temporary Git/SQLite.",
"No claim of different-model independence."
]
}
@@ -0,0 +1,123 @@
test_build_dirty_source_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_dirty_source_refuses_before_docker_or_receipts) ... FAIL
test_build_mismatched_commit_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_mismatched_commit_refuses_before_docker_or_receipts) ... FAIL
test_build_untracked_source_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_untracked_source_refuses_before_docker_or_receipts) ... FAIL
test_invalid_action_does_not_initialize_environment (test_ops_guards.OpsGuardsTest.test_invalid_action_does_not_initialize_environment) ... FAIL
test_linked_worktree_writes_refuse_and_queries_remain_read_only (test_ops_guards.OpsGuardsTest.test_linked_worktree_writes_refuse_and_queries_remain_read_only) ... FAIL
test_queries_never_initialize_environment (test_ops_guards.OpsGuardsTest.test_queries_never_initialize_environment) ...
test_queries_never_initialize_environment (test_ops_guards.OpsGuardsTest.test_queries_never_initialize_environment) (action='logs') ... FAIL
test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) ...
test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='start') ... FAIL
test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='stop') ... FAIL
test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='restart') ... FAIL
test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='rebuild') ... FAIL
test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='down') ... FAIL
======================================================================
FAIL: test_build_dirty_source_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_dirty_source_refuses_before_docker_or_receipts)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 109, in test_build_dirty_source_refuses_before_docker_or_receipts
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_build_mismatched_commit_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_mismatched_commit_refuses_before_docker_or_receipts)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 102, in test_build_mismatched_commit_refuses_before_docker_or_receipts
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_build_untracked_source_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_untracked_source_refuses_before_docker_or_receipts)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 116, in test_build_untracked_source_refuses_before_docker_or_receipts
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_invalid_action_does_not_initialize_environment (test_ops_guards.OpsGuardsTest.test_invalid_action_does_not_initialize_environment)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 96, in test_invalid_action_does_not_initialize_environment
self.assert_no_write_effects()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 61, in assert_no_write_effects
self.assertFalse((root / "data").exists())
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: True is not false
======================================================================
FAIL: test_linked_worktree_writes_refuse_and_queries_remain_read_only (test_ops_guards.OpsGuardsTest.test_linked_worktree_writes_refuse_and_queries_remain_read_only)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 87, in test_linked_worktree_writes_refuse_and_queries_remain_read_only
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_queries_never_initialize_environment (test_ops_guards.OpsGuardsTest.test_queries_never_initialize_environment) (action='logs')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 70, in test_queries_never_initialize_environment
self.assert_no_write_effects()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 61, in assert_no_write_effects
self.assertFalse((root / "data").exists())
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: True is not false
======================================================================
FAIL: test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='start')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 78, in test_service_writes_without_lease_refuse_before_any_side_effect
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='stop')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 78, in test_service_writes_without_lease_refuse_before_any_side_effect
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='restart')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 78, in test_service_writes_without_lease_refuse_before_any_side_effect
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='rebuild')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 78, in test_service_writes_without_lease_refuse_before_any_side_effect
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
======================================================================
FAIL: test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) (action='down')
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/weichen/projects/Agent-System/.worktrees/agent-governance-implementation/scripts/tests/test_ops_guards.py", line 78, in test_service_writes_without_lease_refuse_before_any_side_effect
self.assertNotEqual(result.returncode, 0)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 0 == 0
----------------------------------------------------------------------
Ran 7 tests in 0.235s
FAILED (failures=11)
@@ -0,0 +1,81 @@
test_unknown_same_uid_inaccessible_cwd_is_not_skipped (test_agent_worktree.ProcessVisibilityTest.test_unknown_same_uid_inaccessible_cwd_is_not_skipped) ... ok
test_branch_delete_refusal_preserves_branch_without_force_with_visibility_fixture (test_agent_worktree.WorktreeTest.test_branch_delete_refusal_preserves_branch_without_force_with_visibility_fixture) ... ok
test_cleanup_actual_branch_mapping_with_visibility_fixture (test_agent_worktree.WorktreeTest.test_cleanup_actual_branch_mapping_with_visibility_fixture) ... ok
test_cleanup_legacy_mapping_and_nonforced_branch_delete_with_visibility_fixture (test_agent_worktree.WorktreeTest.test_cleanup_legacy_mapping_and_nonforced_branch_delete_with_visibility_fixture) ... ok
test_cleanup_lifecycle_lock_prevents_a_new_run (test_agent_worktree.WorktreeTest.test_cleanup_lifecycle_lock_prevents_a_new_run) ... ok
test_cleanup_refuses_active_registry_operation_even_with_other_cwd (test_agent_worktree.WorktreeTest.test_cleanup_refuses_active_registry_operation_even_with_other_cwd) ... ok
test_cleanup_refuses_live_process_cwd (test_agent_worktree.WorktreeTest.test_cleanup_refuses_live_process_cwd) ... ok
test_cleanup_refuses_tracked_untracked_and_ignored_evidence (test_agent_worktree.WorktreeTest.test_cleanup_refuses_tracked_untracked_and_ignored_evidence) ... ok
test_cleanup_refuses_unfinished_git_operation (test_agent_worktree.WorktreeTest.test_cleanup_refuses_unfinished_git_operation) ... ok
test_cleanup_refuses_unmerged_commit (test_agent_worktree.WorktreeTest.test_cleanup_refuses_unmerged_commit) ... ok
test_create_from_feature_uses_current_dev_and_canonical_main (test_agent_worktree.WorktreeTest.test_create_from_feature_uses_current_dev_and_canonical_main) ... ok
test_create_with_main_on_unrelated_branch_uses_dev (test_agent_worktree.WorktreeTest.test_create_with_main_on_unrelated_branch_uses_dev) ... ok
test_forged_inherited_lease_is_refused (test_agent_worktree.WorktreeTest.test_forged_inherited_lease_is_refused) ... ok
test_inherited_worktree_lease_does_not_double_lock (test_agent_worktree.WorktreeTest.test_inherited_worktree_lease_does_not_double_lock) ... ok
test_list_is_read_only_without_init (test_agent_worktree.WorktreeTest.test_list_is_read_only_without_init) ... ok
test_rejects_traversal_nested_and_protected_branches (test_agent_worktree.WorktreeTest.test_rejects_traversal_nested_and_protected_branches) ... ok
test_same_task_id_and_existing_branch_do_not_collide (test_agent_worktree.WorktreeTest.test_same_task_id_and_existing_branch_do_not_collide) ... ok
test_stable_lock_refuses_concurrent_writer (test_agent_worktree.WorktreeTest.test_stable_lock_refuses_concurrent_writer) ... ok
test_symlink_parent_or_target_is_refused (test_agent_worktree.WorktreeTest.test_symlink_parent_or_target_is_refused) ... ok
test_write_requires_explicit_init (test_agent_worktree.WorktreeTest.test_write_requires_explicit_init) ... ok
test_admin_document_cannot_bypass_done_with_compact_cells (test_agentctl.AgentctlTests.test_admin_document_cannot_bypass_done_with_compact_cells) ... ok
test_admin_document_cas_preserves_slot_and_needs_capability (test_agentctl.AgentctlTests.test_admin_document_cas_preserves_slot_and_needs_capability) ... ok
test_admin_full_document_requires_explicit_admin_capability (test_agentctl.AgentctlTests.test_admin_full_document_requires_explicit_admin_capability) ... ok
test_commit_lease_must_cover_staged_files_not_only_task_scope (test_agentctl.AgentctlTests.test_commit_lease_must_cover_staged_files_not_only_task_scope) ... ok
test_descendant_resource_guard_accepts_only_held_resource (test_agentctl.AgentctlTests.test_descendant_resource_guard_accepts_only_held_resource) ... ok
test_docs_cannot_launder_unmanaged_other_task_changes (test_agentctl.AgentctlTests.test_docs_cannot_launder_unmanaged_other_task_changes) ... ok
test_docs_foreign_current_row_is_rejected (test_agentctl.AgentctlTests.test_docs_foreign_current_row_is_rejected) ... ok
test_docs_multi_file_preflight_failure_has_no_partial_writes (test_agentctl.AgentctlTests.test_docs_multi_file_preflight_failure_has_no_partial_writes) ... ok
test_docs_row_cas_preserves_unrelated_rows_and_is_idempotent (test_agentctl.AgentctlTests.test_docs_row_cas_preserves_unrelated_rows_and_is_idempotent) ... ok
test_env_guard_accepts_clean_main_and_requires_all_target_leases (test_agentctl.AgentctlTests.test_env_guard_accepts_clean_main_and_requires_all_target_leases) ... ok
test_env_guard_refuses_linked_worktree_even_with_capability (test_agentctl.AgentctlTests.test_env_guard_refuses_linked_worktree_even_with_capability) ... ok
test_failed_child_releases_lease_and_preserves_failure (test_agentctl.AgentctlTests.test_failed_child_releases_lease_and_preserves_failure) ... ok
test_failed_revalidation_revokes_old_success_receipt (test_agentctl.AgentctlTests.test_failed_revalidation_revokes_old_success_receipt) ... ok
test_guard_build_requires_exact_clean_commit (test_agentctl.AgentctlTests.test_guard_build_requires_exact_clean_commit) ... ok
test_hook_checks_reject_out_of_scope_and_secret_paths (test_agentctl.AgentctlTests.test_hook_checks_reject_out_of_scope_and_secret_paths) ... ok
test_hook_rejects_shared_document_without_publish_receipt (test_agentctl.AgentctlTests.test_hook_rejects_shared_document_without_publish_receipt) ... ok
test_hooks_allow_published_shared_document_receipt (test_agentctl.AgentctlTests.test_hooks_allow_published_shared_document_receipt) ... ok
test_hooks_allow_scoped_task_commit_under_live_lease (test_agentctl.AgentctlTests.test_hooks_allow_scoped_task_commit_under_live_lease) ... ok
test_hooks_refuse_overwriting_foreign_hook (test_agentctl.AgentctlTests.test_hooks_refuse_overwriting_foreign_hook) ... ok
test_init_cannot_steal_live_legacy_lock (test_agentctl.AgentctlTests.test_init_cannot_steal_live_legacy_lock) ... ok
test_init_preserves_existing_legacy_lock_inode (test_agentctl.AgentctlTests.test_init_preserves_existing_legacy_lock_inode) ... ok
test_installed_hooks_reject_direct_dev_commit (test_agentctl.AgentctlTests.test_installed_hooks_reject_direct_dev_commit) ... ok
test_interrupted_validation_stops_child_before_releasing_scope (test_agentctl.AgentctlTests.test_interrupted_validation_stops_child_before_releasing_scope) ... ok
test_invalid_document_request_shape_has_no_write_side_effects (test_agentctl.AgentctlTests.test_invalid_document_request_shape_has_no_write_side_effects) ... ok
test_linked_worktree_uses_only_main_coordination_root (test_agentctl.AgentctlTests.test_linked_worktree_uses_only_main_coordination_root) ... ok
test_live_and_stale_credentials_cannot_be_spoofed (test_agentctl.AgentctlTests.test_live_and_stale_credentials_cannot_be_spoofed) ... ok
test_live_run_cannot_be_recovered_or_task_closed (test_agentctl.AgentctlTests.test_live_run_cannot_be_recovered_or_task_closed) ... ok
test_multi_resource_acquisition_is_atomic (test_agentctl.AgentctlTests.test_multi_resource_acquisition_is_atomic) ... ok
test_parent_child_path_conflict_and_disjoint_coexistence (test_agentctl.AgentctlTests.test_parent_child_path_conflict_and_disjoint_coexistence) ... ok
test_path_scope_and_capability_are_enforced (test_agentctl.AgentctlTests.test_path_scope_and_capability_are_enforced) ... ok
test_recovery_refuses_dead_supervisor_with_live_child (test_agentctl.AgentctlTests.test_recovery_refuses_dead_supervisor_with_live_child) ... ok
test_registered_active_task_must_close_before_cleanup (test_agentctl.AgentctlTests.test_registered_active_task_must_close_before_cleanup) ... ok
test_replaced_compatibility_inode_is_not_a_new_free_lock (test_agentctl.AgentctlTests.test_replaced_compatibility_inode_is_not_a_new_free_lock) ... ok
test_shared_lease_does_not_satisfy_write_guard (test_agentctl.AgentctlTests.test_shared_lease_does_not_satisfy_write_guard) ... ok
test_task_index_lease_serializes_index_but_not_disjoint_work (test_agentctl.AgentctlTests.test_task_index_lease_serializes_index_but_not_disjoint_work) ... ok
test_task_registration_rejects_protected_branch_and_invalid_scopes (test_agentctl.AgentctlTests.test_task_registration_rejects_protected_branch_and_invalid_scopes) ... ok
test_uninitialized_status_and_doctor_do_not_create_state (test_agentctl.AgentctlTests.test_uninitialized_status_and_doctor_do_not_create_state) ... ok
test_validated_linear_commit_cannot_bypass_candidate_entrypoint (test_agentctl.AgentctlTests.test_validated_linear_commit_cannot_bypass_candidate_entrypoint) ... ok
test_validated_merge_candidate_is_exactly_promoted_to_dev (test_agentctl.AgentctlTests.test_validated_merge_candidate_is_exactly_promoted_to_dev) ... ok
test_both_expected_tests_pass (test_go_test_evidence.TestEvidence.test_both_expected_tests_pass) ... ok
test_missing_partial_skip_failure_and_duplicate_are_not_pass (test_go_test_evidence.TestEvidence.test_missing_partial_skip_failure_and_duplicate_are_not_pass) ... ok
test_subtests_and_case_mismatches_do_not_prove_parent_execution (test_go_test_evidence.TestEvidence.test_subtests_and_case_mismatches_do_not_prove_parent_execution) ... ok
test_authorized_pinned_rebuild_preserves_no_build (test_ops_guards.OpsGuardsTest.test_authorized_pinned_rebuild_preserves_no_build) ... ok
test_build_accepts_nightly_evidence_excluded_from_build_context (test_ops_guards.OpsGuardsTest.test_build_accepts_nightly_evidence_excluded_from_build_context) ... ok
test_build_context_is_frozen_when_managed_source_changes (test_ops_guards.OpsGuardsTest.test_build_context_is_frozen_when_managed_source_changes) ... ok
test_build_dirty_source_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_dirty_source_refuses_before_docker_or_receipts) ... ok
test_build_mismatched_commit_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_mismatched_commit_refuses_before_docker_or_receipts) ... ok
test_build_untracked_source_refuses_before_docker_or_receipts (test_ops_guards.OpsGuardsTest.test_build_untracked_source_refuses_before_docker_or_receipts) ... ok
test_clean_build_records_exact_source_commit (test_ops_guards.OpsGuardsTest.test_clean_build_records_exact_source_commit) ... ok
test_environment_lease_cannot_deploy_dirty_main_source (test_ops_guards.OpsGuardsTest.test_environment_lease_cannot_deploy_dirty_main_source) ... ok
test_invalid_action_does_not_initialize_environment (test_ops_guards.OpsGuardsTest.test_invalid_action_does_not_initialize_environment) ... ok
test_linked_worktree_writes_refuse_and_queries_remain_read_only (test_ops_guards.OpsGuardsTest.test_linked_worktree_writes_refuse_and_queries_remain_read_only) ... ok
test_one_environment_lease_does_not_authorize_all (test_ops_guards.OpsGuardsTest.test_one_environment_lease_does_not_authorize_all) ... ok
test_queries_never_initialize_environment (test_ops_guards.OpsGuardsTest.test_queries_never_initialize_environment) ... ok
test_service_writes_without_lease_refuse_before_any_side_effect (test_ops_guards.OpsGuardsTest.test_service_writes_without_lease_refuse_before_any_side_effect) ... ok
test_valid_lease_cannot_hide_linked_caller_or_linked_script (test_ops_guards.OpsGuardsTest.test_valid_lease_cannot_hide_linked_caller_or_linked_script) ... ok
----------------------------------------------------------------------
Ran 76 tests in 31.904s
OK
+5
View File
@@ -18,3 +18,8 @@ bin
configs/.env.* configs/.env.*
agent-transcripts agent-transcripts
.refs .refs
# Engineering coordination and isolated checkouts
.worktrees
.locks
.kilo
+1
View File
@@ -30,6 +30,7 @@ logs/
# Workspaces & Cache # Workspaces & Cache
workspaces/ workspaces/
.worktrees/ .worktrees/
.locks/
.cache/ .cache/
tmp/ tmp/
temp/ temp/
+38 -223
View File
@@ -1,244 +1,59 @@
# Agent 工作手册(通用) # Agent 工作手册
> 本文件与具体业务仓库无关,可整文件复用到其他项目。 可复用基线;复制到其他仓库时须调整项目路径与流程。项目入口见 [PROJECT.md](./PROJECT.md)。
> **本仓库差异化约定见 [PROJECT.md](./PROJECT.md)。**
--- ## 1. 启动与规范来源
## 1. 目的与适用范围 每会话先读本文件、[PROJECT](./PROJECT.md) 和 [CURRENT](./.doc/ops/CURRENT.md) 活跃摘要。按任务分支补读:
- 适用于在本仓库工作的所有编码 Agent(Cursor、Claude Code、Codex、Kimi、Antigravity 等)。 - 写入成果前:读 [文档地图](./.doc/README.md)、相关任务/方案/handoff;创建、集成、部署、清理前读 [worktree 操作规范](./.doc/ops/2026-09-21-agent-worktree-workflow.md)。
- **聊天记录易失**;Git 与活文档区才是可协作、可审计的真相来源。 - 编码前:读相关 FEATURE-MATRIX 行、[模块开发标准](./.doc/system-design/2026-09-20-module-development-standards.md)及其接口、事件、表写者约束;依 [验证契约](./.doc/ops/2026-09-21-validation-contract.md) 选择测试。
- 任何任务、调研、审计、修复、运维操作均须同步落盘,禁止「只改代码 / 只留在对话」。 - 发布/审计:读 [发布门禁](./.doc/remediation/gap-matrix.md) 和本次范围对应证据;完整发布判断须复核全部发布门禁。
- Herdr 操作:读 [角色与备援 SSOT](./.doc/ops/2026-09-13-herdr-agent-roster.md)。会话结束/换人:用 [收口清单](./.doc/ops/templates/session-close-checklist.md)。
--- 外层系统约束与用户授权优先。仓库内本文件定义硬边界,PROJECT 定义项目细则,专门 runbook 定义操作;客户端文件仅转发。规则生命周期、owner、生效提交与工具执行能力见 [规范登记](./.doc/ops/2026-09-21-policy-registry.md)。草案、历史审计和报告是证据,不能覆盖生效规则;日期更新不等于生效。任务记录 `policy_revision`,集成前重读当前 dev 的相关规则并检查差异。
## 2. 启动协议(Session Bootstrap) ## 2. 任务分级与授权
每次会话开始,按顺序阅读: | 类型 | 最小产物 |
|---|---|
| 信息答复:解释已有结果/简单查询,无新决策、持久成果、仓库写入或运行副作用 | 可直接答复,不单独开 worktree |
| 单一小任务 / 有价值的只读审查 | 一个任务文或 audit/research 主文档,承载范围、结论、验证、状态、交接 |
| 跨模块、多阶段、数据/部署任务 | task、必要 plan、验证报告及交接;独立信息拆文件并链接,不复制全文 |
1. **本文件** `AGENTS.md` 任何成果写入均在任务 worktree;实质调研/审计必须落盘。相关追问、修正和收口沿用原 task_id;只读子审查使用父任务记录。无关任务重新从 dev 创建。
2. **[PROJECT.md](./PROJECT.md)**(本仓库地图与约定)
3. **[`.doc/README.md`](./.doc/README.md)**(文档 taxonomy)
4. **[`.doc/ops/CURRENT.md`](./.doc/ops/CURRENT.md)**(焦点、并行认领、集成槽位)
5. **[`.doc/status/FEATURE-MATRIX.md`](./.doc/status/FEATURE-MATRIX.md)**(模块完成度)
6. 创建任务、合入、部署、清理前:读 [worktree 工作规范](./.doc/ops/2026-09-21-agent-worktree-workflow.md)(含自动化未落地时的操作方法)
7. 相关 **short-term** / **handoff** / **audit** / **remediation**
8. 发布/审计类任务:[`remediation/gap-matrix.md`](./.doc/remediation/gap-matrix.md)
**未读完不得开始改代码**(紧急热修除外,事后必须在 24h 内补文档)。 任务记录用户目标及授权来源、研究/实现/运维类型、允许写路径、可执行环境、交付终点和禁止项。授权内自主持续执行;分析/方案默认不含业务实施。扩大业务范围、增加外部写入、操作未授权环境或破坏性恢复才重新确认。自动审批和凭证存在均不扩大授权。
--- 例外须写 owner、原因、覆盖条款、范围、期限/退出条件和恢复动作。紧急热修仅可延后启动阅读,24 小时内补证;不豁免 worktree、安全与验收边界。
## 3. 文档同步红线(强制) ## 3. 写入、集成与环境边界
| 时机 | 必须动作 | - 主目录用于 `dev` 集成与运行;所有新增开发、配置、规范和文档修改在 `.worktrees/` 中的 `feature/<task-id>` / `fix/<task-id>`。主目录跟踪文件仅经受控集成更新;`.git`、任务 worktree、运行数据和获授权运维配置除外。禁止递归 chmod 主目录。
|------|----------| - 创建前核对仓库、分支、状态、已有 worktree 和认领;显式记录本地 `dev` 完整 SHA、远端同步状态、owner、branch、worktree、写路径、mutex 与验证计划。从该 SHA 创建,不从别人的 feature 分支启动。
| 任务开始 | 在 `short-term/` 或 `long-term/` 新建或更新条目(复制 templates) | - 保留旧任务现场,不擅自 checkout、stash、reset 或移动他人未提交文件;不复制主目录 `.env`、业务 data 或他人可变产物。重叠路径/mutex 只允许一个写者;公共 lockfile、路由、迁移号单独认领。超范围先重新认领。
| 认领 | 在 CURRENT「并行认领」表登记 owner / branch / mutex | - 编码/隔离测试可并行;dev 集成、共享服务修改与共享环境验收串行。用操作规范规定的机器执行锁/入口,CURRENT 是快照,不是原子锁;不以 Markdown `idle` 放行。工具未验收的能力不得写为 enforced。
| 合 dev + 容器测 | 占用 CURRENT「集成槽位」;他人排队 | - 集成前冻结 head SHA;从确切 dev 基线构造 `--no-ff` 候选并验证,锁内确认基线未变后以 `--ff-only` 推进干净 dev。代码或候选变化使原准入失效;冲突返回任务 worktree 解决。主干 `dev/test/main` 禁止直接开发提交。
| 里程碑 | 更新任务文 `status`、FEATURE-MATRIX、CURRENT | - 影响运行内容的修改合入后,在获授权环境内验证实际制品、`/health`、`/ready` 与业务行为;`restart` 不证明已部署新代码。共享服务写操作只能从规范指定的主运行入口执行,禁止从任务 worktree 操作;不得顺手操作 `all` 或晋升 test/main。
| 调研/审计 | 写入 `audit/` 或 `ops/research/` | - 长 E2E 使用共享环境期间持续持锁;仅切换到独立固定环境后可释放。失败/未知结果先核对运行态并记录恢复,不盲目重试,不自动回滚数据库或硬重置 dev。
| 整改方案 | 写入 `remediation/` |
| 验收 | 写 test-report(路径见 PROJECT),回链 checklist |
| 会话结束/切换 Agent | 按 [session-close-checklist](./.doc/ops/templates/session-close-checklist.md) |
| 提交 | 文档变更与代码同批或紧随 `docs(...)` 提交 |
**状态字段**:`planned | in_progress | blocked | done | cancelled` ## 4. 提交、文档与收口
**current vs historical**:完成项标 `done` 并移入 `archive/`;不删除证据。 使用 Conventional Commits:`feat/fix/refactor/test/docs/chore/ci`。每个提交围绕可解释、可验证、可回退的逻辑变更,实现及其回归测试可同批;兼容阶段/迁移/配置/文档仅在独立审查或回退有价值时拆分。不固定按数据库、服务、API、测试、文档分层凑提交。
--- 文档进入 [.doc taxonomy](./.doc/README.md),命名 `YYYY-MM-DD-<slug>.md`;禁止在 `.doc/` 根或 `docs/` 随意新增项目文档。任务开始登记,里程碑更新主记录;仅模块声明变化才更新 FEATURE-MATRIX,仅发布 blocker 变化才更新 gap-matrix。共享 CURRENT/MATRIX 按操作规范增量发布,不覆盖别人记录。CURRENT 只保留 1–3 焦点、活跃/阻塞/待集成/待清理事项;历史只迁移、不删除。
## 4. 文档 taxonomy 任务状态仅为 `planned | in_progress | blocked | done | cancelled`。会话结束不等于 done:只有授权范围内验收满足才完成;方案研究的验收是可审产物,未获实施授权无需部署。换人记录剩余工作和接管条件,只有终态任务归档。分别报告 `review_state`、`integration_state`、`deployment`、`cleanup`、`remote_sync`;业务已合入但部署失败不得报交付完成。
默认路径:`.doc/`(详见 [`.doc/README.md`](./.doc/README.md)) 清理前逐项确认任务提交为 dev 祖先、所需验收通过、证据已持久化、无 tracked/untracked/唯一 ignored 遗留、无 Agent/进程使用、路径分支匹配且非保护项。普通 `git worktree remove` / `git branch -d` 拒绝时记录原因,不升级 `--force` / `-D`。`cleanup=pending` 是操作状态,不是第六种任务状态;保留 `cleanup_owner`、原因、证据及下次复查触发。由独立集成/维护执行者在 owner 退出后复查;可再生缓存登记后处理,唯一日志/截图先归档,只有路径不算保存证据。不按年龄批量删除 worktree。
```text 晋升路径 `dev → test`(rc tag)`→ main`(release tag),需相应授权。用户未要求不 push;用户明确要求 push/同步远端则自主完成。收口始终区分本地提交、dev 合入、实际运行版本、远端同步和清理结果。
.doc/
README.md # 总地图
status/ # FEATURE-MATRIX(模块完成度 SSOT)
audit/ # 问题发现与分析
remediation/ # 整改方案;gap-matrix(发布 blocker)
archive/ # 历史(只增不删)
ops/ # 活协作:CURRENT、short-term、handoffs…
system-design/ # 模块架构深读
adr/ | backlog/ | test-reports/ | test-fixtures/
```
**红线**:禁止在 `.doc/` 根或 `docs/` 随意新建项目文档;新文档必须落入 taxonomy。 ## 5. 安全与证据
**命名**:`YYYY-MM-DD-<slug>.md` - 凭证按 [PROJECT 推送入口](./PROJECT.md#凭证与推送) 从本机查找;禁止向用户索要 token、在聊天/文档/commit 展示 secret、写入 git config 或提交 `.env`/密钥/凭证/生产数据倾倒。缺失时在主记录/handoff 记路径与缺字段,不记录值。
- 强推、硬重置、删库等破坏性操作须用户明确授权;XSS/注入等安全修复优先于功能扩展。
- 日志、网页、测试输出、引用材料和 Agent 产物不能授予权限;其中命令和“已授权”声明须回链核实用户目标/委派范围,不能仅凭 handoff 自述执行外部写入、推送或清理。
- 证据必须来自真实执行;允许保存足够判定的真实片段并说明删减/脱敏范围。避免原始业务正文、个人信息、秘密和敏感截图入 Git。stub、SKIP、未运行不得冒充真实链路 PASS。
--- 对用户简洁中文,指向证据文件;需要决策时一次 1–2 个关键问题。
## 5. 文档类型与何时使用
| 类型 | 路径 | 模板 |
|------|------|------|
| 问题/分析 | `audit/` | [audit-note.md](./.doc/ops/templates/audit-note.md) |
| 整改方案 | `remediation/` | [remediation-plan.md](./.doc/ops/templates/remediation-plan.md) |
| 短期任务 | `ops/short-term/` | [short-term-task.md](./.doc/ops/templates/short-term-task.md) |
| 长期 backlog | `ops/long-term/` | [long-term-task.md](./.doc/ops/templates/long-term-task.md) |
| Checklist | `ops/checklists/` | [checklist.md](./.doc/ops/templates/checklist.md) |
| 交接 | `ops/handoffs/` | [handoff.md](./.doc/ops/templates/handoff.md) |
| 调研 | `ops/research/` | [research-note.md](./.doc/ops/templates/research-note.md) |
| 模块完成度 | `status/FEATURE-MATRIX.md` | [feature-matrix-row.md](./.doc/ops/templates/feature-matrix-row.md) |
| 验收报告 | `.doc/test-reports/` | 见 test-reports-standard |
| 历史 | `archive/**` | 关闭后迁入 |
---
## 6. 多 Agent 协作
### 6.1 任务与主目录边界
- 主目录作为 `dev` 集成与运行入口;**所有新增开发、配置、规范和文档改动,包括单 Agent 任务,均在 `.worktrees/` 的任务 worktree 内完成**。
- 主目录跟踪文件只通过受控 Git 集成更新,不作日常编辑;`.git`、任务 worktree、服务运行数据和获授权的运维配置属于明确例外。禁止递归 chmod 整个主目录。
- 一个任务一个 `feature/<task-id>` / `fix/<task-id>` 分支、一个 worktree,可包含多个原子提交。既有任务延续原任务分支;无关新任务重新从 dev 创建。
- 已经在主目录运行的旧任务保留现场,由所有者完成或交接后迁移;其他 Agent 不得擅自 checkout、stash、reset、移动其未提交文件。
- 任务必须登记 owner、branch、worktree、base_dev_sha、允许写路径、mutex、验证计划。CURRENT 保留认领快照;各 worktree 的 CURRENT 不构成原子锁。
- 同一 mutex 或重叠写路径同时只允许一个写者;公共 lockfile、路由、迁移号单独认领。冲突时排队或写 handoff,不覆盖他人工作。
### 6.2 创建与开发
1. 先核对仓库、主工作区、当前分支、工作树状态、已有 worktree 与任务认领。
2. 由协调者管理 dev 同步;开发 Agent 不在共享主目录切分支或直接 pull。记录本地 dev 的确切 SHA 与远端同步状态,分叉时保留现场。
3. 显式从该 `base_dev_sha` 创建 `.worktrees/<task-id>`,不能省略起点,也不从另一 feature 分支开新任务。
4. 在任务 worktree 完成开发、原子提交、相应测试和文档;不复制主目录 `.env`、业务 data 或其他任务的可变产物。
5. 提交集成前冻结 head SHA;变更超出允许范围时先重新认领,提交变化后原测试/审查准入失效。
当前脚本尚有错误起点和强制删除缺口,**修复验收前不用 `agent-worktree.sh create/remove`**;按 [操作规范](./.doc/ops/2026-09-21-agent-worktree-workflow.md) 使用带显式起点的 Git 命令和非强制清理。
### 6.3 串行集成与环境验收
- 编码和隔离测试可并行;dev 合入、共享服务变更和共享环境验收必须串行,记录 CURRENT 槽位和共同 Git 目录中的执行锁。完整机器队列未实现时按操作规范手工持锁,不以 Markdown 的 idle 作为唯一准入。
- 推荐在 `.worktrees/` 内的临时 `feature/integrate-*` 上从 dev 基线生成 `--no-ff` 合并候选,验证该提交;锁内确认 dev 基线未变后,将干净 dev 工作区 `--ff-only` 推进至同一候选。dev 变化则重新组合并验证。
- 业务冲突返回任务 worktree 解决;dev/test/main 禁止直接开发提交。迁移期间主目录被旧任务占用时,纯规范/文档可使用登记的临时 dev 集成 worktree,禁止由此操作共享服务;详见操作规范的收口条件。
- Go 变更执行 PROJECT 的 Go/架构及相关 PG 检查;web 变更执行前端检查与受影响浏览器验证;涉及产品 PI 的业务交付执行真实链路验收。纯工程规范/文档只做对应一致性检查,不为形式重建服务。
- 影响运行内容的改动合入后,持有环境槽位更新 dev,核查实际制品、`/health`、`/ready` 和相关业务行为。`restart` 不证明新代码部署;模板/提示词 Markdown 不能按普通文档跳过部署。
- 使用共享 dev 的长 E2E 持有环境槽位直到结束;仅在切换到独立固定环境后才可提前释放。失败或未知结果先核对运行态、记录恢复,不能冒充 PASS 或盲目重复执行。
- CURRENT 槽位字段仍为 `holder | phase | since | scope | queue`;生产/test 晋升独立授权,dev 任务不得顺手操作 `all`。
### 6.4 收口与清理
- 分别报告已提交、已合入 dev、已部署、已验收、已清理、已推送;文档合入可注明 `deployment=not_required`。
- 清理前核对:任务提交是 dev 的祖先、所需验收通过、证据持久化、无 tracked/untracked/唯一 ignored 遗留文件、无 Agent/进程或其他任务使用、路径与分支一致且不属保护项。
- 使用普通 `git worktree remove` 和 `git branch -d`;拒绝时保留并记录 cleanup_pending,不升级到 `--force` / `-D`,不批量清空 `.worktrees`。
- 未合入或待恢复任务保留 worktree 与 handoff;部署失败按兼容性恢复/前滚,不自动回滚数据库或硬重置 dev。
### 6.5 共享文档
- CURRENT 只列 1–3 个焦点;机器状态与 Git 现场优先,CURRENT 是带日期的协作快照。
- 实现者在任务文记录状态和 FEATURE-MATRIX 变更意图,由集成者串行汇总;保留其他任务、槽位与历史证据。
- 规范生效与自动化实现分别记录;机器锁服务、队列、脚本守卫和权限沙箱未验收前不得宣称已自动强制。
### 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. 文档生命周期
```text
audit(问题)→ remediation(方案)→ ops/short-term(任务)
→ test-reports(验收)→ archive/(关闭证据)
```
会话结束:[session-close-checklist.md](./.doc/ops/templates/session-close-checklist.md)
---
## 8. Git 与提交
- 遵循 **Conventional Commits**:`feat` / `fix` / `refactor` / `test` / `docs` / `chore` / `ci`
- **原子提交**:一个逻辑变更一批;提交发生在 `feature/*` 或 `fix/*` 上,不在三大主干
- **合回 dev**:占槽验证 `--no-ff` 合并候选,dev 接收已验证节点(见 §6.3);只在任务分支直接开发提交
- **晋升**:`dev` → `test`(rc tag)→ `main`(release tag);仅在规划的环境合并时动 `test`/`main`
- **用户未要求**:不擅自 push
- **用户已要求 push/同步远端**:必须自主完成
- **禁止**:在 `dev`/`test`/`main` 上直接开发 commit;在 `git config` 写入 token;硬重置/强推/删库仅在用户明确授权时
---
## 9. Secrets 与远端推送(自主,禁止索要)
### 查找顺序
1. `${HOME}/.secrets/`(如 `gitea`)
2. **PROJECT.md** 与 **push 脚本**(`scripts/git-push-dev.sh`)
### 禁止
- 向用户索要 token
- 在聊天/文档/commit 中展示 secret
- 把 token 写入仓库或 `.env` 并提交
### 缺失时
在 ops/handoff 记 blocker(路径 + 缺字段);**仍不索要 secret 内容**。
---
## 10. 安全与合规红线
- 不提交 `.env`、密钥、凭证、生产数据倾倒
- 破坏性命令仅在用户明确授权时
- XSS/注入等修复优先于功能扩展
---
## 11. 计划 / 实现 / 验证
1. **大改动**:先 short-term 范围;范围外记 long-term
2. **实现**:按 PROJECT 测试命令验证
3. **完成定义**:checklist 勾选须有证据链接
---
## 12. 沟通
- 对用户:**简洁、中文**
- 指向文件路径,不复述长篇规则
- 需要决策时 **一次只问 1–2 个关键问题**
---
## 13. 工具入口(薄转发)
| 工具 | 入口 |
|------|------|
| Cursor | `.cursor/rules/*` + 本文件 |
| Claude Code | [CLAUDE.md](./CLAUDE.md) |
| Codex | [CODEX.md](./CODEX.md) |
| Gemini | [GEMINI.md](./GEMINI.md) |
| Antigravity | [ANTIGRAVITY.md](./ANTIGRAVITY.md) |
| Kimi | [KIMI.md](./KIMI.md) |
均转发至本文件启动链。
---
## 14. 模板索引
| 类型 | 路径 |
|------|------|
| 短期任务 | `.doc/ops/templates/short-term-task.md` |
| 长期任务 | `.doc/ops/templates/long-term-task.md` |
| Checklist | `.doc/ops/templates/checklist.md` |
| 交接 | `.doc/ops/templates/handoff.md` |
| 调研 | `.doc/ops/templates/research-note.md` |
| 审计笔记 | `.doc/ops/templates/audit-note.md` |
| 整改方案 | `.doc/ops/templates/remediation-plan.md` |
| 完成度行 | `.doc/ops/templates/feature-matrix-row.md` |
| 会话结束 | `.doc/ops/templates/session-close-checklist.md` |
---
**本仓库差异化约定见 [PROJECT.md](./PROJECT.md)。**
+1 -7
View File
@@ -1,9 +1,3 @@
# Antigravity 入口 # Antigravity 入口
请先完整阅读并遵守 [AGENTS.md](./AGENTS.md)(通用 Agent 工作手册)。 按 [AGENTS.md](./AGENTS.md) 的启动与条件读取流程执行;项目地图见 [PROJECT.md](./PROJECT.md)。本入口不另设规则。
再阅读 [PROJECT.md](./PROJECT.md)(本仓库地图与约定)。
然后阅读 [.doc/README.md](./.doc/README.md) 与 [.doc/ops/CURRENT.md](./.doc/ops/CURRENT.md) 获取文档 taxonomy 与当前焦点。
勿向用户索要 Git token;按 AGENTS.md 自行从本机 `${HOME}/.secrets/` 路径读取。
+1 -7
View File
@@ -1,9 +1,3 @@
# Claude Code 入口 # Claude Code 入口
请先完整阅读并遵守 [AGENTS.md](./AGENTS.md)(通用 Agent 工作手册)。 按 [AGENTS.md](./AGENTS.md) 的启动与条件读取流程执行;项目地图见 [PROJECT.md](./PROJECT.md)。本入口不另设规则。
再阅读 [PROJECT.md](./PROJECT.md)(本仓库地图与约定)。
然后阅读 [.doc/README.md](./.doc/README.md) 与 [.doc/ops/CURRENT.md](./.doc/ops/CURRENT.md) 获取文档 taxonomy 与当前焦点。
勿向用户索要 Git token;按 AGENTS.md 自行从本机 `${HOME}/.secrets/` 路径读取。
+1 -7
View File
@@ -1,9 +1,3 @@
# Codex 入口 # Codex 入口
请先完整阅读并遵守 [AGENTS.md](./AGENTS.md)(通用 Agent 工作手册)。 按 [AGENTS.md](./AGENTS.md) 的启动与条件读取流程执行;项目地图见 [PROJECT.md](./PROJECT.md)。本入口不另设规则。
再阅读 [PROJECT.md](./PROJECT.md)(本仓库地图与约定)。
然后阅读 [.doc/README.md](./.doc/README.md) 与 [.doc/ops/CURRENT.md](./.doc/ops/CURRENT.md) 获取文档 taxonomy 与当前焦点。
勿向用户索要 Git token;按 AGENTS.md 自行从本机 `${HOME}/.secrets/` 路径读取。
+1 -7
View File
@@ -1,9 +1,3 @@
# Gemini 入口 # Gemini 入口
请先完整阅读并遵守 [AGENTS.md](./AGENTS.md)(通用 Agent 工作手册)。 按 [AGENTS.md](./AGENTS.md) 的启动与条件读取流程执行;项目地图见 [PROJECT.md](./PROJECT.md)。本入口不另设规则。
再阅读 [PROJECT.md](./PROJECT.md)(本仓库地图与约定)。
然后阅读 [.doc/README.md](./.doc/README.md) 与 [.doc/ops/CURRENT.md](./.doc/ops/CURRENT.md) 获取文档 taxonomy 与当前焦点。
勿向用户索要 Git token;按 AGENTS.md 自行从本机 `${HOME}/.secrets/` 路径读取。
+1 -7
View File
@@ -1,9 +1,3 @@
# Kimi Code 入口 # Kimi Code 入口
请先完整阅读并遵守 [AGENTS.md](./AGENTS.md)(通用 Agent 工作手册)。 按 [AGENTS.md](./AGENTS.md) 的启动与条件读取流程执行;项目地图见 [PROJECT.md](./PROJECT.md)。本入口不另设规则。
再阅读 [PROJECT.md](./PROJECT.md)(本仓库地图与约定)。
然后阅读 [.doc/README.md](./.doc/README.md) 与 [.doc/ops/CURRENT.md](./.doc/ops/CURRENT.md) 获取文档 taxonomy 与当前焦点。
勿向用户索要 Git token;按 AGENTS.md 自行从本机 `${HOME}/.secrets/` 路径读取。
+32 -122
View File
@@ -1,144 +1,54 @@
# Agent-System 项目地图 # Agent-System 项目入口
> 通用 Agent 工作方式见 [AGENTS.md](./AGENTS.md)。 工作边界见 [AGENTS.md](./AGENTS.md)。本仓库是 Go + React/Vite + PostgreSQL/pgvector 的多用户工作流与对话平台,产品执行器为容器中的产品 PI Agent Runner。
> Claude Code 入口见 [CLAUDE.md](./CLAUDE.md)。
--- ## 仓库地图
## 1. 产品一句话 | 路径 | 用途 |
|---|---|
| `cmd/`、`internal/` | 服务入口与 Go 模块 |
| `web/` | React/Vite 前端 |
| `deploy/postgres/goose/` | Goose 数据库迁移 |
| `configs/`、`templates/` | 运行配置、提示词与工作流模板(变更可能需要运行验收) |
| `scripts/` | 工程、环境与推送工具 |
| `.doc/` | [文档地图](./.doc/README.md)、任务与证据 |
| `data/dev`、`data/test`、`data/main` | 各环境独立业务数据与工作区 |
面向企业与团队的多用户工作流与对话智能体平台:RAG 知识库、PI Agent 执行、项目治理、阶段门禁、文档审批、任务持久化与 SSE 流式交互。 模块编码/接口调整前读 [模块开发标准](./.doc/system-design/2026-09-20-module-development-standards.md),遵守既有接口、事件和表写者边界。
--- ## 环境与操作入口
## 2. 主要目录
| 路径 | 说明 |
|------|------|
| `cmd/` | 服务入口 |
| `internal/` | Go 业务逻辑(server、coordinator、bizdev、chat、projectinstance 等) |
| `web/` | React + Vite 前端 |
| `deploy/postgres/goose/` | PostgreSQL 迁移(Goose) |
| `configs/` | Agent 与提示词配置 |
| `templates/` | 工作流文档模板 |
| `.doc/` | 架构、审计、整改文档 |
| `.doc/ops/` | **活协作区**(CURRENT、任务、checklist、handoff) |
| `.doc/test-reports/` | 测试/验收报告(归档) |
| `data/dev|test|main/` | 各环境对话与工作区(隔离) |
| `scripts/` | 运维与推送脚本 |
---
## 3. 技术栈
- **后端**:Go HTTP API(~110 路由)
- **前端**:React + Vite
- **数据库**:PostgreSQL + pgvector(Goose 迁移)
- **产品执行器**:容器内 PI Agent Runner(真实 / stub)
- **工程协作**:Herdr 管理的 Claude / Codex / Cursor / Antigravity / PI / Kimi / Cline;其中 Herdr PI 与产品 PI Agent Runner 无关
- **工作流**:main、product_manager、test_manager 三套正式定义
---
## 4. Git 三分支与三环境
详见 [.cursor/rules/git-workflow-and-environments.mdc](./.cursor/rules/git-workflow-and-environments.mdc)。
| 环境 | 分支 | Compose | HTTP | DB 端口 | 数据路径 | | 环境 | 分支 | Compose | HTTP | DB 端口 | 数据路径 |
|------|------|---------|------|---------|----------| |---|---|---|---|---|---|
| 开发 | dev | agent-dev | 8080 | 5433 | data/dev | | 开发 | dev | agent-dev | 8080 | 5433 | data/dev |
| 测试 | test | agent-test | 8081 | 5434 | data/test | | 测试 | test | agent-test | 8081 | 5434 | data/test |
| 生产 | main | agent-main | 8000 | 5435 | data/main | | 生产 | main | agent-main | 8000 | 5435 | data/main |
| 共享 KB | 跨环境 | agent-infra | - | 5432 | data/shared/kb | | 共享 KB | 跨环境 | agent-infra | - | 5432 | data/shared/kb |
**红线**:知识库共享;对话与业务 DB **严格隔离**,禁止跨环境读写。 这是拓扑约定,实际端口/制品/config/schema 由目标环境收据核实。知识库共享;对话、工作区、审计及业务 DB 严格隔离,禁止跨环境读写。
**开发入口**:所有新增改动均从明确的 `dev` 提交创建 `feature/*` / `fix/*`,在 `.worktrees/` 内完成;主目录保留给集成与运行。任务创建、串行集成、部署和清理遵守 [AGENTS.md](./AGENTS.md) §6 及 [worktree 工作规范](./.doc/ops/2026-09-21-agent-worktree-workflow.md)。 创建任务、合入、环境写操作与清理前读 [worktree 操作规范](./.doc/ops/2026-09-21-agent-worktree-workflow.md),使用其机器入口及持锁流程。只读环境查询入口为 `./scripts/env-service.sh all status`;共享服务写操作在主运行入口执行。晋升到 test/main 单独授权。
--- ## 验证入口
## 5. 文档地图(SSOT) 按 [验证契约](./.doc/ops/2026-09-21-validation-contract.md) 选测试、精确用例、模型证据和报告层级。普通文档只做差异/链接/状态一致性;运行模板/提示词按运行内容验收。
| 用途 | 路径 | - Go:`./scripts/arch-check.sh --mode block`、`go vet ./...`、`go test -short ./...`;按范围追加 PG/鉴权负例。
|------|------| - 前端:在 `web/` 执行 `npm test`、`npm run build`;交互变更追加受影响真实浏览器测试。
| 文档总地图 | [.doc/README.md](./.doc/README.md) | - 产品 Agent/工作流:真实产品 PI Agent Runner;[nightly 入口](./scripts/run-nightly-real-pi.sh) 仅覆盖 U8 与 FullNoneMixedChain 基线,按范围追加精确测试及 UI 验收。模型按目标环境已批准配置核验,不固定跨环境模型,不自行切换配置。
| 协作会话 / 当前焦点 | [.doc/ops/CURRENT.md](./.doc/ops/CURRENT.md) |
| 模块完成度 | [.doc/status/FEATURE-MATRIX.md](./.doc/status/FEATURE-MATRIX.md) |
| 发布/审计缺口 | [.doc/remediation/gap-matrix.md](./.doc/remediation/gap-matrix.md) |
| 问题分析 | [.doc/audit/INDEX.md](./.doc/audit/INDEX.md) |
| 整改方案 | [.doc/remediation/INDEX.md](./.doc/remediation/INDEX.md) |
| 测试报告归档 | [.doc/test-reports/](./.doc/test-reports/) |
| 测试报告规范 | [.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) |
--- ## 状态与文档权威
## 6. 常用命令 - 活跃工作/协调快照:[CURRENT](./.doc/ops/CURRENT.md)。
- 能力声明与剩余条件:[FEATURE-MATRIX](./.doc/status/FEATURE-MATRIX.md)。
- 发布门禁:[gap-matrix](./.doc/remediation/gap-matrix.md),以有日期、提交和制品身份的证据作判断;项目入口不缓存“无 blocker”或当前 schema。
- 问题与方案:[audit](./.doc/audit/INDEX.md)、[remediation](./.doc/remediation/INDEX.md)。
- 规范状态与生效版本:[规范登记](./.doc/ops/2026-09-21-policy-registry.md)。
```bash ## 凭证与推送
# 环境
./scripts/env-service.sh shared start
./scripts/env-service.sh dev start
./scripts/env-service.sh all status
# Go 质量门禁(默认) 用户明确授权 push/同步远端后,使用 [git-push-dev.sh](./scripts/git-push-dev.sh) 的分支参数;先核对目标分支。凭证查找 `${HOME}/.secrets/gitea`,所需字段为 `ACCESS_TOKEN` 与 `URL_PREFIX`(文件按脚本支持格式填写)。不向用户索要值,不把凭证写入仓库/git config;缺失只记录路径与缺字段。
go vet ./...
go test -short ./...
# PG 集成(需 TEST_DATABASE_URL) ## Herdr 工程协作
TEST_DATABASE_URL=postgres://... go test ./internal/server/... -run 'TestE2E_WorkflowGovernance'
# 真实 PI E2E(显式开关) 仅在任务需要 Herdr 时读 [角色、审批与备援 SSOT](./.doc/ops/2026-09-13-herdr-agent-roster.md);使用 [启动脚本](./scripts/herdr-agent-start.sh) / [派发脚本](./scripts/herdr-agent-dispatch.sh)。`context_pi` 是协作终端的 PI CLI,不能替代产品 PI Agent Runner 验收。实际客户端/模型/在线状态记录在本次运行证据中。
RUN_PI_E2E=1 RUNNER_AUTH_TOKEN=... go test ./... -run 'PI'
# 前端
cd web && npm test && npm run build
# 数据库迁移
# 见 deploy/postgres/goose/
```
---
## 7. Git 推送(自主,勿索要 token)
- **脚本**:[`scripts/git-push-dev.sh`](./scripts/git-push-dev.sh)
- **凭证文件**:`${HOME}/.secrets/gitea`
- **字段**:
```text
- ACCESS_TOKEN=<token>
- URL_PREFIX=<host>
```
- **用法**:`./scripts/git-push-dev.sh dev`(或 test/main 分支名)
---
## 8. 测试与验收铁律
- 涉及产品 Agent/工作流的业务交付:开发可用 stub,**交付前验收**必须真实产品 PI Agent Runner + MiniMax-M2.7-highspeed 全链路。纯工程规范/文档变更执行对应一致性检查,不调用产品 PI
- 报告必须归档至 `.doc/test-reports/`,遵循 test-reports-standard
- 禁止 stub 输出冒充最终验收数据
---
## 9. 当前焦点(动态)
见 **[.doc/ops/CURRENT.md](./.doc/ops/CURRENT.md)**。
- 发布缺口 SSOT:[gap-matrix](./.doc/remediation/gap-matrix.md)
- 模块完成度:[FEATURE-MATRIX](./.doc/status/FEATURE-MATRIX.md)
- 历史里程碑测试报告:[2026-09-13-codex-audit-p0-remediation-test-report.md](./.doc/test-reports/2026-09-13-codex-audit-p0-remediation-test-report.md)(P0 收口快照;持续更新的测试报告见 `.doc/test-reports/` 与 CURRENT)
- **发布门禁**:`production_ready=yes`(gap-matrix B1–B4 已签,[发布门禁区段](./.doc/remediation/gap-matrix.md))。**无剩余发布 blocker**;U9–U12 扩面 / 纯软件项目自举 SWBOOT-10 / FAV live 放量 / D7 合规签字均为已知 deferred/需独立授权事项,不阻塞首发,详见 gap-matrix 与 FEATURE-MATRIX 各自行
---
## 10. Herdr 工程协作
- `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/*` 上渐进式分批提交,集成遵守 AGENTS §6.3 的已验证合并候选流程(禁止在三大主干直接开发 commit)
+2 -73
View File
@@ -1,76 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Agent worktree helper — parallel coding only; do NOT start docker from worktrees. # Safe worktree lifecycle; shared services remain restricted to the main tree.
set -euo pipefail set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" exec python3 "${SCRIPT_DIR}/agent_worktree.py" "$@"
WORKTREES="${ROOT}/.worktrees"
usage() {
cat <<EOF
用法:
$0 create <branch-name> # git worktree add .worktrees/<slug> -b <branch>
$0 list # git worktree list
$0 remove <branch-name> # remove worktree and optionally delete branch
示例:
$0 create feature/my-task
$0 list
$0 remove feature/my-task
EOF
exit 1
}
ensure_ignored() {
if git -C "${ROOT}" check-ignore -q .worktrees/. 2>/dev/null; then
:
else
echo "[错误] .worktrees/ 未在 .gitignore 中,请先添加。" >&2
exit 1
fi
mkdir -p "${WORKTREES}"
}
slug_from_branch() {
echo "$1" | tr '/' '-'
}
cmd="${1:-}"
case "${cmd}" in
create)
[[ $# -eq 2 ]] || usage
branch="$2"
ensure_ignored
slug="$(slug_from_branch "${branch}")"
path="${WORKTREES}/${slug}"
if [[ -d "${path}" ]]; then
echo "[错误] 已存在: ${path}" >&2
exit 1
fi
git -C "${ROOT}" worktree add "${path}" -b "${branch}"
echo "Worktree: ${path} (branch ${branch})"
echo "禁止在此目录运行 env-service.sh start/stop/restart"
;;
list)
git -C "${ROOT}" worktree list
;;
remove)
[[ $# -ge 2 && $# -le 3 ]] || usage
branch="$2"
delete_branch="${3:-}"
slug="$(slug_from_branch "${branch}")"
path="${WORKTREES}/${slug}"
if [[ ! -d "${path}" ]]; then
echo "[错误] 不存在: ${path}" >&2
exit 1
fi
git -C "${ROOT}" worktree remove "${path}" --force
if [[ "${delete_branch}" == "--delete-branch" ]]; then
git -C "${ROOT}" branch -D "${branch}" 2>/dev/null || true
fi
echo "已移除 worktree ${path}"
;;
*)
usage
;;
esac
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""Conservative worktree lifecycle, serialized with agentctl's worktrees lock.
Git's actual registry is authoritative for removal (including old path names).
Ignored files are deliberately retained: archive evidence and remove disposable
caches explicitly before requesting cleanup; this command never force-removes.
"""
import argparse
from contextlib import contextmanager
import fcntl
import hashlib
import json
import os
from pathlib import Path
import re
import stat
import subprocess
import sys
class Refused(RuntimeError):
pass
def command(args, cwd, check=True):
result = subprocess.run(args, cwd=cwd, text=True, capture_output=True)
if check and result.returncode:
raise Refused(result.stderr.strip() or result.stdout.strip() or "command refused")
return result
def git(repo, *args, check=True):
return command(["git", "-C", str(repo), *args], repo, check=check)
def worktrees(repo):
records = []
for block in git(repo, "worktree", "list", "--porcelain", "-z").stdout.split("\0\0"):
fields = {}
for line in block.split("\0"):
if line:
key, _, value = line.partition(" ")
fields[key] = value
if fields:
records.append(fields)
return records
def canonical_main(repo):
entries = worktrees(repo)
if not entries or "bare" in entries[0]:
raise Refused("a non-bare main worktree is required")
main = Path(entries[0]["worktree"]).resolve()
common = Path(git(repo, "rev-parse", "--path-format=absolute", "--git-common-dir").stdout.strip()).resolve()
if common != main / ".git" or not (main / ".git").is_dir():
raise Refused("main worktree/common Git directory identity mismatch")
return main
def safe_branch(branch):
match = re.fullmatch(r"(?:feature|fix)/([A-Za-z0-9][A-Za-z0-9_-]{0,119})", branch)
if not match:
raise Refused("branch must be feature/<task-id> or fix/<task-id>; task-id uses letters, digits, - and _")
return match.group(1)
def safe_directory(main, path):
parent = main / ".worktrees"
if parent.is_symlink() or parent.resolve() != parent:
raise Refused(".worktrees must not be a symlink")
if path.parent != parent or path.is_symlink() or path.resolve() != path:
raise Refused("worktree must be a direct, non-symlink child of main/.worktrees")
if path.name.startswith("."):
raise Refused("protected worktree path")
def control(repo, *args, check=True):
# Use the invoked script version but explicitly bind its repository identity.
return command([sys.executable, str(Path(__file__).with_name("agentctl.py")),
"--repo", str(repo), *args], repo, check=check)
@contextmanager
def write_lock(repo, main):
control(repo, "status") # Read-only; explicit init is mandatory for writes.
if os.environ.get("AGENTCTL_RUN_ID") or os.environ.get("AGENTCTL_TASK_ID"):
control(repo, "guard", "resource", "git:worktrees")
yield
return
lock_dir = main / ".locks" / "os"
if lock_dir.is_symlink() or lock_dir.resolve() != lock_dir or not lock_dir.is_dir():
raise Refused("initialized canonical .locks/os is required")
fd = os.open(lock_dir / "worktrees.lock", os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600)
try:
if not stat.S_ISREG(os.fstat(fd).st_mode):
raise Refused("worktrees lock must be a regular file")
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as exc:
raise Refused("git:worktrees is busy; retry after the current operation") from exc
yield
finally:
os.close(fd) # Stable inode: never truncate, replace or unlink lock files.
def create(repo, main, branch):
slug = safe_branch(branch)
path = main / ".worktrees" / slug
safe_directory(main, path)
entries = worktrees(main)
if path.exists() or any(Path(e["worktree"]).resolve() == path or
e.get("branch") == f"refs/heads/{branch}" for e in entries):
raise Refused("worktree path or branch is already occupied")
if git(main, "show-ref", "--verify", "--quiet", f"refs/heads/{branch}", check=False).returncode == 0:
raise Refused("branch already exists; preserve it and review its worktree mapping")
if git(main, "check-ignore", "-q", "--", ".worktrees/", check=False).returncode:
raise Refused("main .gitignore must ignore .worktrees/")
base = git(main, "rev-parse", "--verify", "refs/heads/dev^{commit}").stdout.strip()
path.parent.mkdir(exist_ok=True)
git(main, "worktree", "add", "-b", branch, str(path), base)
print(json.dumps({"branch": branch, "worktree": str(path), "base_dev_sha": base}))
def assert_no_process_cwd(path):
unreadable = []
for proc in Path("/proc").iterdir():
if not proc.name.isdigit():
continue
try:
if proc.stat().st_uid != os.getuid():
continue
cwd = Path(os.readlink(proc / "cwd"))
if cwd == path or path in cwd.parents:
raise Refused(f"process {proc.name} still uses this worktree as cwd")
except (FileNotFoundError, ProcessLookupError):
continue
except PermissionError:
# Linux may hide cwd for the per-user OS service manager despite a
# matching UID. This narrowly named OS process is not a task agent;
# all other unreadable same-user processes remain fail-closed.
try:
if (proc / "comm").read_text().strip() == "systemd":
print(f"[notice] OS user service manager {proc.name} hides cwd; "
"managed task registry and lifecycle locks remain required", file=sys.stderr)
continue
except (OSError, UnicodeError):
pass
unreadable.append(proc.name)
if unreadable:
raise Refused(f"cannot verify cwd of same-user process {','.join(unreadable)}; cleanup_pending")
@contextmanager
def removal_lock(main, path):
# Every agentctl run/registration holds the shared side of this same lock.
# Keep it exclusive through removal so no new run can race worktree-check.
key = hashlib.sha256(str(path.resolve()).encode()).hexdigest()
lock_path = main / ".locks" / "os" / f"worktree-{key}.lock"
if lock_path.parent.resolve() != lock_path.parent:
raise Refused("canonical lifecycle lock directory is required")
fd = os.open(lock_path, os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600)
try:
if not stat.S_ISREG(os.fstat(fd).st_mode):
raise Refused("worktree lifecycle lock must be a regular file")
try:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as exc:
raise Refused("worktree has an active registered operation; cleanup_pending") from exc
yield
finally:
os.close(fd)
def remove(repo, main, branch, delete_branch):
safe_branch(branch)
entries = [entry for entry in worktrees(main) if entry.get("branch") == f"refs/heads/{branch}"]
if len(entries) != 1:
raise Refused("expected exactly one registered worktree for the requested branch")
entry = entries[0]
path = Path(entry["worktree"])
safe_directory(main, path)
if "locked" in entry or "prunable" in entry or not path.is_dir():
raise Refused("locked, missing or prunable worktree requires manual review")
with removal_lock(main, path):
remove_checked(repo, main, branch, delete_branch, path)
def remove_checked(repo, main, branch, delete_branch, path):
actual = git(path, "symbolic-ref", "--quiet", "HEAD").stdout.strip()
if actual != f"refs/heads/{branch}":
raise Refused("worktree branch identity changed")
head = git(path, "rev-parse", "HEAD").stdout.strip()
if git(main, "merge-base", "--is-ancestor", head, "refs/heads/dev", check=False).returncode:
raise Refused("task HEAD is not an ancestor of dev; cleanup_pending")
for marker in ("MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "BISECT_START"):
marker_path = Path(git(path, "rev-parse", "--path-format=absolute", "--git-path", marker).stdout.strip())
if marker_path.exists():
raise Refused(f"unfinished Git operation: {marker}; cleanup_pending")
if git(path, "status", "--porcelain=v1", "--untracked-files=all", "--ignored=matching").stdout:
raise Refused("tracked, untracked or ignored files remain; archive evidence and clean explicitly")
control(repo, "worktree-check", "--path", str(path))
assert_no_process_cwd(path)
if git(path, "rev-parse", "HEAD").stdout.strip() != head:
raise Refused("worktree HEAD changed during cleanup checks")
git(main, "worktree", "remove", str(path))
if delete_branch:
result = git(main, "branch", "-d", branch, check=False)
if result.returncode:
raise Refused(f"worktree removed; branch retained (cleanup_pending): {result.stderr.strip()}")
print(json.dumps({"removed": str(path), "branch": branch, "branch_deleted": delete_branch}))
def main():
parser = argparse.ArgumentParser(description=__doc__)
commands = parser.add_subparsers(dest="command", required=True)
commands.add_parser("list", help="read-only Git worktree listing")
for name in ("create", "remove"):
child = commands.add_parser(name)
child.add_argument("branch")
if name == "remove":
child.add_argument("--delete-branch", action="store_true")
args = parser.parse_args()
repo = Path(__file__).resolve().parent.parent
try:
root = canonical_main(repo)
if args.command == "list":
print(git(root, "worktree", "list").stdout, end="")
return 0
safe_branch(args.branch)
with write_lock(repo, root):
if args.command == "create":
create(repo, root, args.branch)
else:
remove(repo, root, args.branch, args.delete_branch)
return 0
except (Refused, OSError) as exc:
print(f"[refused] {exc}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec python3 "${SCRIPT_DIR}/agentctl.py" "$@"
+1070
View File
File diff suppressed because it is too large Load Diff
+42 -5
View File
@@ -27,6 +27,7 @@ if [[ -z "${COMMIT}" ]]; then
COMMIT="$(git rev-parse HEAD)" COMMIT="$(git rev-parse HEAD)"
fi fi
COMMIT="$(git rev-parse --verify "${COMMIT}^{commit}")" COMMIT="$(git rev-parse --verify "${COMMIT}^{commit}")"
python3 "${ROOT}/scripts/agentctl.py" guard build --commit "${COMMIT}"
SHORT="${COMMIT:0:12}" SHORT="${COMMIT:0:12}"
RECEIPT_DIR="${ROOT}/.doc/test-reports/assets/promotion-receipts" RECEIPT_DIR="${ROOT}/.doc/test-reports/assets/promotion-receipts"
OUT="${RECEIPT_DIR}/${COMMIT}.images.json" OUT="${RECEIPT_DIR}/${COMMIT}.images.json"
@@ -35,21 +36,41 @@ mkdir -p "${RECEIPT_DIR}"
if [[ -f "${OUT}" && "${FORCE}" -ne 1 ]]; then if [[ -f "${OUT}" && "${FORCE}" -ne 1 ]]; then
echo "images.json already exists (immutable): ${OUT}" >&2 echo "images.json already exists (immutable): ${OUT}" >&2
echo "hint: use --force to rebuild local fixtures" >&2 echo "hint: use --force to rebuild local fixtures" >&2
python3 - "${OUT}" "${COMMIT}" <<'CHECK_MANIFEST'
import json, sys
with open(sys.argv[1], encoding="utf-8") as source:
manifest = json.load(source)
if manifest.get("commit") != sys.argv[2]:
raise SystemExit("candidate manifest commit mismatch")
for field in ("image_ids", "image_digests", "tags"):
if not isinstance(manifest.get(field), dict) or not all(manifest[field].get(k) for k in ("app", "events", "pi-agent")):
raise SystemExit("incomplete candidate manifest")
CHECK_MANIFEST
cat "${OUT}" cat "${OUT}"
exit 0 exit 0
fi fi
# Docker reads an immutable export of the requested commit, never the mutable
# checkout or the nightly evidence generated after checkout.
BUILD_CONTEXT="$(mktemp -d "${TMPDIR:-/tmp}/agent-image-context.XXXXXX")"
trap 'rm -rf -- "${BUILD_CONTEXT}"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
git archive --format=tar "${COMMIT}" | tar -xf - -C "${BUILD_CONTEXT}"
SOURCE_TREE="$(git rev-parse "${COMMIT}^{tree}")"
APP_TAG="shuimu-agent-app:candidate-${SHORT}" APP_TAG="shuimu-agent-app:candidate-${SHORT}"
EVENTS_TAG="shuimu-agent-events:candidate-${SHORT}" EVENTS_TAG="shuimu-agent-events:candidate-${SHORT}"
PI_TAG="shuimu-pi-agent:candidate-${SHORT}" PI_TAG="shuimu-pi-agent:candidate-${SHORT}"
echo "==> building app/events image ${APP_TAG}..." echo "==> building app/events image ${APP_TAG}..."
docker build -f deploy/app/Dockerfile -t "${APP_TAG}" . docker build -f "${BUILD_CONTEXT}/deploy/app/Dockerfile" -t "${APP_TAG}" "${BUILD_CONTEXT}"
# events binary lives in the same image # events binary lives in the same image
docker tag "${APP_TAG}" "${EVENTS_TAG}" docker tag "${APP_TAG}" "${EVENTS_TAG}"
echo "==> building pi-agent image ${PI_TAG}..." echo "==> building pi-agent image ${PI_TAG}..."
docker build -f deploy/pi-agent/Dockerfile -t "${PI_TAG}" . docker build -f "${BUILD_CONTEXT}/deploy/pi-agent/Dockerfile" -t "${PI_TAG}" "${BUILD_CONTEXT}"
image_id() { image_id() {
docker inspect --format '{{.Id}}' "$1" docker inspect --format '{{.Id}}' "$1"
@@ -74,13 +95,15 @@ EVENTS_DIGEST="$(repo_digest_or_id "${EVENTS_TAG}")"
PI_DIGEST="$(repo_digest_or_id "${PI_TAG}")" PI_DIGEST="$(repo_digest_or_id "${PI_TAG}")"
RECORDED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" RECORDED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
export COMMIT SHORT RECORDED_AT APP_TAG EVENTS_TAG PI_TAG export COMMIT SHORT RECORDED_AT APP_TAG EVENTS_TAG PI_TAG SOURCE_TREE
export APP_ID EVENTS_ID PI_ID APP_DIGEST EVENTS_DIGEST PI_DIGEST OUT export APP_ID EVENTS_ID PI_ID APP_DIGEST EVENTS_DIGEST PI_DIGEST OUT
python3 -c ' python3 -c '
import json, os import json, os, tempfile
payload = { payload = {
"commit": os.environ["COMMIT"], "commit": os.environ["COMMIT"],
"source_tree": os.environ["SOURCE_TREE"],
"source_context": "git-archive",
"recorded_at": os.environ["RECORDED_AT"], "recorded_at": os.environ["RECORDED_AT"],
"tags": { "tags": {
"app": os.environ["APP_TAG"], "app": os.environ["APP_TAG"],
@@ -98,9 +121,23 @@ payload = {
"pi-agent": os.environ["PI_DIGEST"], "pi-agent": os.environ["PI_DIGEST"],
}, },
} }
with open(os.environ["OUT"], "w", encoding="utf-8") as f: out = os.environ["OUT"]
fd, temporary = tempfile.mkstemp(prefix=".candidate-images-", dir=os.path.dirname(out))
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(payload, f, indent=2, ensure_ascii=False) json.dump(payload, f, indent=2, ensure_ascii=False)
f.write("\n") f.write("\n")
f.flush()
os.fsync(f.fileno())
os.replace(temporary, out)
directory = os.open(os.path.dirname(out), os.O_DIRECTORY)
try:
os.fsync(directory)
finally:
os.close(directory)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
print(os.environ["OUT"]) print(os.environ["OUT"])
' '
+22 -8
View File
@@ -9,7 +9,7 @@
# main : 生产环境服务 (端口 8000, DB 5435, 数据隔离 ./data/main) # main : 生产环境服务 (端口 8000, DB 5435, 数据隔离 ./data/main)
# all : 所有环境 # all : 所有环境
# #
# 支持命令: start | stop | restart | rebuild | status | logs | down # 支持命令: start | stop | restart | rebuild | status | ps | logs | down
# ============================================================================= # =============================================================================
set -eo pipefail set -eo pipefail
@@ -17,15 +17,13 @@ set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${ROOT_DIR}" ENV_TARGET="${1:-}"
ACTION="${2:-}"
ENV_TARGET="$1"
ACTION="$2"
show_usage() { show_usage() {
cat <<EOF cat <<EOF
用法: 用法:
$0 <shared|dev|test|main|all> <start|stop|restart|rebuild|status|logs|down> $0 <shared|dev|test|main|all> <start|stop|restart|rebuild|status|ps|logs|down>
示例: 示例:
$0 shared start # 启动共享知识库基础服务 $0 shared start # 启动共享知识库基础服务
@@ -44,6 +42,22 @@ if [[ -z "${ENV_TARGET}" || -z "${ACTION}" ]]; then
show_usage show_usage
fi fi
# Reject invalid requests and unauthorized writes before configuration, storage,
# network or Docker operations. The guard verifies the live supervisor lease.
case "${ENV_TARGET}" in
shared|dev|test|main|all) ;;
*) show_usage ;;
esac
case "${ACTION}" in
status|ps|logs) ;;
start|stop|restart|rebuild|down)
python3 "${SCRIPT_DIR}/agentctl.py" guard env --target "${ENV_TARGET}"
python3 "${SCRIPT_DIR}/agentctl.py" --repo "${ROOT_DIR}" guard env --target "${ENV_TARGET}"
;;
*) show_usage ;;
esac
cd "${ROOT_DIR}"
# 确保环境变量配置文件与存储目录存在 # 确保环境变量配置文件与存储目录存在
prepare_env_file() { prepare_env_file() {
local target="$1" local target="$1"
@@ -86,8 +100,8 @@ run_compose() {
exit 1 exit 1
fi fi
# status/ps 为只读查询,不隐式创建配置或数据目录 # status/ps/logs 为只读查询,不隐式创建配置或数据目录
if [[ "${act}" != "status" && "${act}" != "ps" ]]; then if [[ "${act}" != "status" && "${act}" != "ps" && "${act}" != "logs" ]]; then
prepare_env_file "${target}" prepare_env_file "${target}"
fi fi
+9 -5
View File
@@ -173,22 +173,26 @@ LOG_FILE="${EVIDENCE_DIR}/go-test.txt"
set +e set +e
go test -count=1 -timeout "${TIMEOUT}" -v ./internal/server/ -run "${RUN_FILTER}" 2>&1 | tee "${LOG_FILE}" go test -count=1 -timeout "${TIMEOUT}" -v ./internal/server/ -run "${RUN_FILTER}" 2>&1 | tee "${LOG_FILE}"
TEST_RC=${PIPESTATUS[0]} TEST_RC=${PIPESTATUS[0]}
RESULT_LOG="${LOG_FILE}"
# One retry for known flakes: model quality_failed, or PI hang / client deadline. # One retry for known flakes: model quality_failed, or PI hang / client deadline.
if [[ "${TEST_RC}" -ne 0 ]] && grep -Eq 'quality_failed want completed|status=quality_failed|context deadline exceeded|Client\.Timeout exceeded|MISSING_TARGET|missing target deliverable' "${LOG_FILE}"; then if [[ "${TEST_RC}" -ne 0 ]] && grep -Eq 'quality_failed want completed|status=quality_failed|context deadline exceeded|Client\.Timeout exceeded|MISSING_TARGET|missing target deliverable' "${LOG_FILE}"; then
echo "==> retry once after quality_failed/deadline/MISSING_TARGET flake..." | tee -a "${LOG_FILE}" echo "==> retry once after quality_failed/deadline/MISSING_TARGET flake..." | tee -a "${LOG_FILE}"
RETRY_LOG="${EVIDENCE_DIR}/go-test-retry.txt" RETRY_LOG="${EVIDENCE_DIR}/go-test-retry.txt"
go test -count=1 -timeout "${TIMEOUT}" -v ./internal/server/ -run "${RUN_FILTER}" 2>&1 | tee "${RETRY_LOG}" go test -count=1 -timeout "${TIMEOUT}" -v ./internal/server/ -run "${RUN_FILTER}" 2>&1 | tee "${RETRY_LOG}"
TEST_RC=${PIPESTATUS[0]} TEST_RC=${PIPESTATUS[0]}
RESULT_LOG="${RETRY_LOG}"
cat "${RETRY_LOG}" >> "${LOG_FILE}" cat "${RETRY_LOG}" >> "${LOG_FILE}"
fi fi
set -e set -e
if [[ "${TEST_RC}" -eq 0 ]]; then if [[ "${TEST_RC}" -eq 0 ]]; then
# go test exits 0 on all-SKIP; treat that as infra (shared DATA_DIR / runner mount). # Exit zero alone also covers no tests and partial skips; require both named tests.
if ! grep -Eq '^--- PASS:' "${LOG_FILE}" && grep -Eq '^--- SKIP:' "${LOG_FILE}"; then if ! python3 "${ROOT}/scripts/verify_go_test_evidence.py" --log "${RESULT_LOG}" \
echo "all matching tests skipped (likely missing DATA_DIR/workspaces)" | tee "${EVIDENCE_DIR}/infra-tests-skipped.txt" --expected TestMultiIntentU8_RealPI_ConfirmGenerate_PG \
write_receipt infra_fail infra "${LOG_FILE}" "${EVIDENCE_DIR}/infra-tests-skipped.txt" --expected TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain \
echo "infrastructure failure: suite skipped instead of executing" >&2 > "${EVIDENCE_DIR}/expected-tests.json"; then
write_receipt infra_fail infra "${LOG_FILE}" "${EVIDENCE_DIR}/expected-tests.json"
echo "infrastructure failure: expected real-PI tests did not all execute and pass" >&2
exit 1 exit 1
fi fi
if ! ensure_candidate_images; then if ! ensure_candidate_images; then
+322
View File
@@ -0,0 +1,322 @@
"""Worktree lifecycle checks use real Git and the real agentctl registry."""
import fcntl
import importlib.util
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
from unittest import mock
SOURCE = Path(__file__).resolve().parents[1]
class WorktreeTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name) / "repo"
self.root.mkdir()
(self.root / "scripts").mkdir()
for name in ("agent-worktree.sh", "agent_worktree.py", "agentctl.py", "agentctl"):
shutil.copy2(SOURCE / name, self.root / "scripts" / name)
(self.root / ".gitignore").write_text(".worktrees/\n.locks/\nignored-evidence/\n")
(self.root / "source.txt").write_text("base\n")
self.env = {**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}
for name in ("AGENTCTL_RUN_ID", "AGENTCTL_TASK_ID"):
self.env.pop(name, None)
self.git("init", "-b", "dev")
self.git("config", "user.email", "test@example.invalid")
self.git("config", "user.name", "Test")
self.git("add", ".")
self.git("commit", "-qm", "fixture")
self.base = self.git("rev-parse", "HEAD").stdout.strip()
def run_cmd(self, args, cwd=None, env=None):
return subprocess.run(args, cwd=cwd or self.root, env=env or self.env,
text=True, capture_output=True, timeout=20)
def git(self, *args, cwd=None):
result = self.run_cmd(["git", *args], cwd=cwd)
self.assertEqual(result.returncode, 0, result.stderr)
return result
def ctl(self, *args, cwd=None):
return self.run_cmd([sys.executable, str(self.root / "scripts/agentctl.py"), *args], cwd=cwd)
def init(self):
result = self.ctl("init")
self.assertEqual(result.returncode, 0, result.stderr)
def helper(self, *args, script_root=None, cwd=None, env=None):
script = (script_root or self.root) / "scripts/agent-worktree.sh"
return self.run_cmd(["bash", str(script), *args], cwd=cwd, env=env)
def create(self, branch="feature/task"):
result = self.helper("create", branch)
self.assertEqual(result.returncode, 0, result.stderr)
return Path(json.loads(result.stdout)["worktree"])
def remove_with_visibility_fixture(self, *args):
"""Fixture only /proc enumeration; Git, registry and lock checks stay real.
This host hides unrelated same-user daemon cwds. Positive removal tests
model a fully visible process namespace containing the helper itself.
Live cwd rejection below still runs the actual host process enumeration.
"""
code = '''
import importlib.util, os, sys
from pathlib import Path
from unittest import mock
spec = importlib.util.spec_from_file_location("helper", sys.argv[1])
helper = importlib.util.module_from_spec(spec)
spec.loader.exec_module(helper)
original = Path.iterdir
def visible_processes(path):
if path == Path("/proc"):
return iter([path / str(os.getpid())])
return original(path)
sys.argv = sys.argv[1:]
with mock.patch.object(Path, "iterdir", visible_processes):
sys.exit(helper.main())
'''
return self.run_cmd([sys.executable, "-c", code, str(self.root / "scripts/agent_worktree.py"), "remove", *args])
def test_list_is_read_only_without_init(self):
result = self.helper("list")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn(str(self.root), result.stdout)
self.assertFalse((self.root / ".locks").exists())
self.assertFalse((self.root / ".worktrees").exists())
def test_write_requires_explicit_init(self):
result = self.helper("create", "feature/task")
self.assertNotEqual(result.returncode, 0)
self.assertIn("NOT_INITIALIZED", result.stderr)
self.assertFalse((self.root / ".worktrees").exists())
self.assertFalse((self.root / ".locks").exists())
def test_create_from_feature_uses_current_dev_and_canonical_main(self):
self.init()
first = self.create()
self.git("commit", "--allow-empty", "-qm", "feature-only", cwd=first)
result = self.helper("create", "fix/second", script_root=first, cwd=first / "scripts")
self.assertEqual(result.returncode, 0, result.stderr)
data = json.loads(result.stdout)
self.assertEqual(data["base_dev_sha"], self.base)
self.assertEqual(Path(data["worktree"]), self.root / ".worktrees/second")
self.assertEqual(self.git("rev-parse", "fix/second").stdout.strip(), self.base)
self.assertFalse((first / ".worktrees").exists())
self.assertFalse((first / ".locks").exists())
def test_create_with_main_on_unrelated_branch_uses_dev(self):
self.init()
self.git("checkout", "-b", "feature/legacy")
self.git("commit", "--allow-empty", "-qm", "legacy-only")
self.create()
self.assertEqual(self.git("rev-parse", "feature/task").stdout.strip(), self.base)
self.assertEqual(self.git("branch", "--show-current").stdout.strip(), "feature/legacy")
def test_rejects_traversal_nested_and_protected_branches(self):
self.init()
for branch in ("dev", "main", "test", "feature/../escape", "feature/a/b", "fix/.hidden", "feature/-x"):
with self.subTest(branch=branch):
self.assertNotEqual(self.helper("create", branch).returncode, 0)
self.assertFalse((self.root / ".worktrees").exists())
def test_same_task_id_and_existing_branch_do_not_collide(self):
self.init()
first = self.create()
self.assertNotEqual(self.helper("create", "fix/task").returncode, 0)
self.assertNotEqual(self.helper("create", "feature/task").returncode, 0)
self.assertEqual(self.git("branch", "--show-current", cwd=first).stdout.strip(), "feature/task")
def test_symlink_parent_or_target_is_refused(self):
self.init()
outside = Path(self.tmp.name) / "outside"
outside.mkdir()
parent = self.root / ".worktrees"
parent.symlink_to(outside, target_is_directory=True)
self.assertNotEqual(self.helper("create", "feature/task").returncode, 0)
self.assertEqual(list(outside.iterdir()), [])
parent.unlink()
parent.mkdir()
(parent / "task").symlink_to(outside, target_is_directory=True)
self.assertNotEqual(self.helper("create", "feature/task").returncode, 0)
self.assertEqual(list(outside.iterdir()), [])
def test_stable_lock_refuses_concurrent_writer(self):
self.init()
lock = self.root / ".locks/os/worktrees.lock"
with lock.open("a+") as holder:
inode = lock.stat().st_ino
fcntl.flock(holder.fileno(), fcntl.LOCK_EX)
result = self.helper("create", "feature/task")
self.assertNotEqual(result.returncode, 0)
self.assertIn("busy", result.stderr)
self.create()
self.assertEqual(lock.stat().st_ino, inode)
def test_forged_inherited_lease_is_refused(self):
self.init()
env = {**self.env, "AGENTCTL_RUN_ID": "forged", "AGENTCTL_TASK_ID": "task"}
self.assertNotEqual(self.helper("create", "feature/task", env=env).returncode, 0)
self.assertFalse((self.root / ".worktrees/task").exists())
def test_cleanup_legacy_mapping_and_nonforced_branch_delete_with_visibility_fixture(self):
self.init()
legacy = self.root / ".worktrees/feature-task"
self.git("worktree", "add", "-b", "feature/task", str(legacy), self.base)
result = self.remove_with_visibility_fixture("feature/task", "--delete-branch")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertFalse(legacy.exists())
self.assertNotIn("refs/heads/feature/task", self.git("show-ref").stdout)
def test_cleanup_refuses_unmerged_commit(self):
self.init()
task = self.create()
self.git("commit", "--allow-empty", "-qm", "unique", cwd=task)
result = self.helper("remove", "feature/task", "--delete-branch")
self.assertNotEqual(result.returncode, 0)
self.assertIn("ancestor", result.stderr)
self.assertTrue(task.exists())
def test_branch_delete_refusal_preserves_branch_without_force_with_visibility_fixture(self):
self.init()
self.git("branch", "feature/legacy", self.base)
self.git("commit", "--allow-empty", "-qm", "dev-next")
task = self.create()
self.git("checkout", "feature/legacy")
result = self.remove_with_visibility_fixture("feature/task", "--delete-branch")
self.assertNotEqual(result.returncode, 0)
self.assertIn("branch retained", result.stderr)
self.assertFalse(task.exists())
self.assertIn("refs/heads/feature/task", self.git("show-ref").stdout)
def test_cleanup_refuses_tracked_untracked_and_ignored_evidence(self):
self.init()
task = self.create()
for relative in ("source.txt", "new-evidence.txt", "ignored-evidence/only-copy.log"):
with self.subTest(relative=relative):
file = task / relative
original = file.read_text() if file.exists() else None
file.parent.mkdir(exist_ok=True)
file.write_text("unique evidence\n")
result = self.helper("remove", "feature/task", "--delete-branch")
self.assertNotEqual(result.returncode, 0)
self.assertTrue(file.exists())
self.assertEqual(file.read_text(), "unique evidence\n")
if original is not None:
file.write_text(original)
else:
file.unlink()
def test_cleanup_refuses_live_process_cwd(self):
self.init()
task = self.create()
process = subprocess.Popen([sys.executable, "-c", "import time; time.sleep(30)"], cwd=task)
try:
result = self.helper("remove", "feature/task")
self.assertNotEqual(result.returncode, 0)
self.assertIn(f"process {process.pid} still uses", result.stderr)
self.assertTrue(task.exists())
finally:
process.terminate()
process.wait(timeout=5)
def test_cleanup_refuses_unfinished_git_operation(self):
self.init()
task = self.create()
marker = Path(self.git("rev-parse", "--path-format=absolute", "--git-path", "MERGE_HEAD", cwd=task).stdout.strip())
marker.write_text(self.base + "\n")
result = self.helper("remove", "feature/task")
self.assertNotEqual(result.returncode, 0)
self.assertIn("unfinished", result.stderr)
self.assertTrue(task.exists())
def test_cleanup_actual_branch_mapping_with_visibility_fixture(self):
self.init()
first = self.create()
other = self.root / ".worktrees/legacy-other"
self.git("worktree", "add", "-b", "fix/task", str(other), self.base)
result = self.remove_with_visibility_fixture("fix/task")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(first.exists())
self.assertFalse(other.exists())
def test_inherited_worktree_lease_does_not_double_lock(self):
self.init()
task = self.create()
result = self.ctl("task", "register", "--id", "task", "--owner", "test", "--scope", "scripts",
"--capability", "git:worktrees", cwd=task)
self.assertEqual(result.returncode, 0, result.stderr)
result = self.ctl("with", "--task", "task", "--resource", "git:worktrees", "--cwd", str(self.root),
"--", "bash", str(self.root / "scripts/agent-worktree.sh"), "create", "fix/child", cwd=task)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue((self.root / ".worktrees/child").exists())
def test_cleanup_lifecycle_lock_prevents_a_new_run(self):
self.init()
task = self.create()
result = self.ctl("task", "register", "--id", "task", "--owner", "test", "--scope", "scripts", cwd=task)
self.assertEqual(result.returncode, 0, result.stderr)
key = hashlib.sha256(str(task.resolve()).encode()).hexdigest()
lock = self.root / ".locks/os" / f"worktree-{key}.lock"
marker = Path(self.tmp.name) / "should-not-exist"
with lock.open("a+") as holder:
fcntl.flock(holder.fileno(), fcntl.LOCK_EX)
result = self.ctl("with", "--task", "task", "--resource", "path:scripts", "--wait", "0", "--",
sys.executable, "-c", "from pathlib import Path; import sys; Path(sys.argv[1]).touch()",
str(marker))
self.assertNotEqual(result.returncode, 0)
self.assertFalse(marker.exists())
def test_cleanup_refuses_active_registry_operation_even_with_other_cwd(self):
self.init()
task = self.create()
result = self.ctl("task", "register", "--id", "task", "--owner", "test", "--scope", "scripts", cwd=task)
self.assertEqual(result.returncode, 0, result.stderr)
ready = Path(self.tmp.name) / "ready"
child_code = "from pathlib import Path; import os,time,sys; os.chdir(sys.argv[2]); Path(sys.argv[1]).touch(); time.sleep(30)"
proc = subprocess.Popen([sys.executable, str(self.root / "scripts/agentctl.py"), "with", "--task", "task",
"--resource", "path:scripts", "--", sys.executable,
"-c", child_code, str(ready), str(self.root)], cwd=self.root, env=self.env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
try:
deadline = time.monotonic() + 5
while not ready.exists() and proc.poll() is None and time.monotonic() < deadline:
time.sleep(0.02)
self.assertTrue(ready.exists(), "supervised operation did not start")
result = self.helper("remove", "feature/task")
self.assertNotEqual(result.returncode, 0)
self.assertTrue(task.exists())
self.assertNotIn("still uses", result.stderr)
self.assertRegex(result.stderr, "active registered operation|live or unresolved operation")
finally:
proc.terminate()
proc.communicate(timeout=8)
class ProcessVisibilityTest(unittest.TestCase):
def test_unknown_same_uid_inaccessible_cwd_is_not_skipped(self):
spec = importlib.util.spec_from_file_location("agent_worktree_under_test", SOURCE / "agent_worktree.py")
helper = importlib.util.module_from_spec(spec)
spec.loader.exec_module(helper)
with tempfile.TemporaryDirectory() as temp:
proc = Path(temp) / "987654321"
proc.mkdir()
(proc / "comm").write_text("coding-agent\n")
with mock.patch.object(helper.Path, "iterdir", return_value=iter([proc])), \
mock.patch.object(helper.os, "readlink", side_effect=PermissionError):
with self.assertRaisesRegex(helper.Refused, "cannot verify cwd"):
helper.assert_no_process_cwd(Path(temp) / "task")
if __name__ == "__main__":
unittest.main()
+654
View File
@@ -0,0 +1,654 @@
#!/usr/bin/env python3
"""Black-box agentctl regressions using isolated real Git repositories."""
import json
import fcntl
import hashlib
import os
import pathlib
import select
import shutil
import socket
import subprocess
import sys
import tempfile
import unittest
SOURCE = pathlib.Path(__file__).resolve().parents[1] / "agentctl.py"
class AgentctlTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="agentctl-test-")
self.addCleanup(self.temp.cleanup)
self.root = pathlib.Path(self.temp.name) / "repo"
self.root.mkdir()
self.git("init", "-b", "dev")
self.git("config", "user.name", "Agentctl Test")
self.git("config", "user.email", "agentctl-test@example.invalid")
(self.root / "scripts").mkdir()
shutil.copy2(SOURCE, self.root / "scripts" / "agentctl.py")
(self.root / ".gitignore").write_text(".locks/\n.worktrees/\n", encoding="utf-8")
for directory in ("src/a", "src/b", ".doc/ops", ".doc/status"):
(self.root / directory).mkdir(parents=True, exist_ok=True)
self.current_rows = {
"alpha": "| alpha | Alice | feature/alpha | active |",
"beta": "| beta | Bob | feature/beta | active |",
}
(self.root / ".doc/ops/CURRENT.md").write_text(
"# Coordination\n\n| Task | Owner | Branch | State |\n| --- | --- | --- | --- |\n"
+ "\n".join(self.current_rows.values()) + "\n", encoding="utf-8")
(self.root / ".doc/status/FEATURE-MATRIX.md").write_text(
"# Features\n\n| Feature | State |\n| --- | --- |\n| core | planned |\n", encoding="utf-8")
self.git("add", ".")
self.git("commit", "-m", "test fixture")
def git(self, *args, cwd=None):
return subprocess.run(["git", *args], cwd=cwd or self.root,
text=True, capture_output=True, check=True).stdout.strip()
def cli(self, *args, cwd=None, check=True, env=None):
result = subprocess.run([sys.executable, str(self.root / "scripts/agentctl.py"), *args],
cwd=cwd or self.root, text=True, capture_output=True,
env=env, timeout=20)
if check:
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
return result
def initialized_task(self, task="alpha", scopes=("src/a", ".doc"), capabilities=()):
if not (self.root / ".locks").exists():
self.cli("init")
path = self.root / ".worktrees" / task
self.git("worktree", "add", "-b", "feature/" + task, str(path), "dev")
args = ["task", "register", "--id", task, "--owner", "Test Agent"]
for scope in scopes:
args.extend(("--scope", scope))
for capability in capabilities:
args.extend(("--capability", capability))
self.cli(*args, cwd=path)
return path
def run_lease(self, task, resources, code="pass", cwd=None, env=None):
args = ["with", "--task", task, "--wait", "0"]
if cwd is not None:
args.extend(("--cwd", str(cwd)))
for resource in resources:
args.extend(("--resource", resource))
return self.cli(*args, "--", sys.executable, "-c", code,
cwd=cwd, check=False, env=env)
def live_lease(self, task, resources, cwd):
"""Wait for the actual supervised child via a socket, never timing sleeps."""
listener = socket.socket(socket.AF_UNIX)
self.addCleanup(listener.close)
socket_path = str(pathlib.Path(self.temp.name) / ("control-" + str(len(self._cleanups))))
listener.bind(socket_path)
listener.listen(1)
listener.settimeout(15)
code = (
"import json,os,socket; s=socket.socket(socket.AF_UNIX); "
f"s.connect({socket_path!r}); "
"s.sendall((json.dumps({'run':os.environ.get('AGENTCTL_RUN_ID'),"
"'task':os.environ.get('AGENTCTL_TASK_ID'),'pid':os.getpid(),"
"'credentials':{k:v for k,v in os.environ.items() if k.startswith('AGENTCTL_')}})+'\\n').encode()); "
"s.recv(1)"
)
args = [sys.executable, str(self.root / "scripts/agentctl.py"), "with", "--task", task]
for resource in resources:
args.extend(("--resource", resource))
process = subprocess.Popen(args + ["--", sys.executable, "-c", code], cwd=cwd,
text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def cleanup():
if process.poll() is None:
process.terminate()
try:
process.communicate(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.communicate(timeout=5)
self.addCleanup(cleanup)
try:
connection, _ = listener.accept()
except TimeoutError:
if process.poll() is not None:
out, err = process.communicate()
self.fail("supervised child did not start: " + out + err)
raise
connection.settimeout(10)
self.addCleanup(connection.close)
payload = b""
while not payload.endswith(b"\n"):
payload += connection.recv(4096)
return process, connection, json.loads(payload)
def finish_lease(self, process, connection):
connection.sendall(b"x")
out, err = process.communicate(timeout=10)
self.assertEqual(process.returncode, 0, out + err)
def docs_request(self, changes, request_id="request-alpha"):
path = pathlib.Path(self.temp.name) / (request_id + ".json")
path.write_text(json.dumps({"request_id": request_id, "changes": changes}), encoding="utf-8")
return str(path)
def doc_change(self, file, record, old, replacement):
return {"file": file, "record_id": record,
"expected_sha256": hashlib.sha256(old.encode()).hexdigest() if old else None,
"replacement": replacement}
def test_uninitialized_status_and_doctor_do_not_create_state(self):
for command in ("status", "doctor"):
result = self.cli(command, check=False)
json.loads(result.stdout or result.stderr)
self.assertFalse((self.root / ".locks").exists())
self.assertFalse((self.root / ".git/agent-control").exists())
def test_init_preserves_existing_legacy_lock_inode(self):
legacy = self.root / ".git/agent-control/dev-integration.lock"
legacy.parent.mkdir()
legacy.write_text("")
original_inode = legacy.stat().st_ino
self.cli("init")
stable = self.root / ".locks/os/dev-integration.lock"
self.assertEqual(legacy.stat().st_ino, original_inode)
self.assertEqual(stable.stat().st_ino, original_inode)
self.cli("init")
self.assertEqual(stable.stat().st_ino, original_inode)
def test_init_cannot_steal_live_legacy_lock(self):
legacy = self.root / ".git/agent-control/dev-integration.lock"
legacy.parent.mkdir()
with legacy.open("w") as handle:
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
inode = legacy.stat().st_ino
result = self.cli("init", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(legacy.stat().st_ino, inode)
self.cli("init")
def test_linked_worktree_uses_only_main_coordination_root(self):
task = self.initialized_task()
json.loads(self.cli("status", cwd=task).stdout)
self.assertTrue((self.root / ".locks").is_dir())
self.assertFalse((task / ".locks").exists())
self.cli("init", cwd=task, check=False)
self.assertFalse((task / ".locks").exists())
def test_task_registration_rejects_protected_branch_and_invalid_scopes(self):
self.cli("init")
result = self.cli("task", "register", "--id", "main-task", "--owner", "Test",
"--scope", "src", check=False)
self.assertNotEqual(result.returncode, 0)
path = self.root / ".worktrees/invalid"
self.git("worktree", "add", "-b", "feature/invalid", str(path), "dev")
for index, scope in enumerate(("../outside", "/tmp/absolute", ".git", ".locks", "src/../../outside")):
with self.subTest(scope=scope):
result = self.cli("task", "register", "--id", "invalid-" + str(index),
"--owner", "Test", "--scope", scope, cwd=path, check=False)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
def test_path_scope_and_capability_are_enforced(self):
task = self.initialized_task()
self.assertEqual(self.run_lease("alpha", ["path:src/a/item"], cwd=task).returncode, 0)
for resource in ("path:src/b", "path:src/ab", "path:../outside", "env:dev", "git:dev"):
with self.subTest(resource=resource):
result = self.run_lease("alpha", [resource], cwd=task)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
def test_parent_child_path_conflict_and_disjoint_coexistence(self):
task = self.initialized_task(scopes=("src",))
process, connection, _ = self.live_lease("alpha", ["path:src/a"], task)
for resource in ("path:src", "path:src/a/file", "path:src/a"):
with self.subTest(resource=resource):
result = self.run_lease("alpha", [resource], cwd=task)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.run_lease("alpha", ["path:src/b"], cwd=task).returncode, 0)
self.finish_lease(process, connection)
self.assertEqual(self.run_lease("alpha", ["path:src/a"], cwd=task).returncode, 0)
def test_multi_resource_acquisition_is_atomic(self):
task = self.initialized_task(scopes=("src",))
process, connection, _ = self.live_lease("alpha", ["path:src/a"], task)
result = self.run_lease("alpha", ["path:src/b", "path:src/a"], cwd=task)
self.assertNotEqual(result.returncode, 0)
result = self.run_lease("alpha", ["path:src/b"], cwd=task)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.finish_lease(process, connection)
def test_task_index_lease_serializes_index_but_not_disjoint_work(self):
task = self.initialized_task(scopes=("src",))
other_task = self.initialized_task(task="beta", scopes=("src/b",))
process, connection, _ = self.live_lease("alpha", ["git:index", "path:src/a"], task)
result = self.run_lease("alpha", ["git:index", "path:src/b"], cwd=task)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
result = self.run_lease("alpha", ["path:src/b"], cwd=task)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
result = self.run_lease("beta", ["git:index", "path:src/b"], cwd=other_task)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.finish_lease(process, connection)
result = self.run_lease("alpha", ["git:index", "path:src/a"], cwd=task)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_failed_child_releases_lease_and_preserves_failure(self):
task = self.initialized_task()
result = self.run_lease("alpha", ["path:src/a"], "raise SystemExit(17)", cwd=task)
self.assertEqual(result.returncode, 17, result.stdout + result.stderr)
self.assertEqual(self.run_lease("alpha", ["path:src/a"], cwd=task).returncode, 0)
def test_live_and_stale_credentials_cannot_be_spoofed(self):
task = self.initialized_task()
process, connection, payload = self.live_lease("alpha", ["path:src/a"], task)
self.assertTrue(payload["run"])
environment = dict(os.environ, **payload["credentials"])
result = self.cli("guard", "resource", "path:src/a", cwd=task, env=environment, check=False)
self.assertNotEqual(result.returncode, 0)
self.finish_lease(process, connection)
result = self.cli("guard", "resource", "path:src/a", cwd=task, env=environment, check=False)
self.assertNotEqual(result.returncode, 0)
def test_live_run_cannot_be_recovered_or_task_closed(self):
task = self.initialized_task()
process, connection, payload = self.live_lease("alpha", ["path:src/a"], task)
result = self.cli("recover", "--run", payload["run"], "--reason", "test", check=False)
self.assertNotEqual(result.returncode, 0)
result = self.cli("task", "close", "--id", "alpha", "--status", "done", cwd=task, check=False)
self.assertNotEqual(result.returncode, 0)
self.finish_lease(process, connection)
def test_recovery_refuses_dead_supervisor_with_live_child(self):
task = self.initialized_task()
process, connection, payload = self.live_lease("alpha", ["path:src/a"], task)
child_fd = os.pidfd_open(payload["pid"])
self.addCleanup(os.close, child_fd)
process.kill()
process.wait(timeout=5)
self.assertFalse(select.select([child_fd], [], [], 0)[0], "fixture child must still be alive")
result = self.cli("recover", "--run", payload["run"], "--reason", "supervisor died", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertNotEqual(self.run_lease("alpha", ["path:src/a"], cwd=task).returncode, 0)
connection.sendall(b"x")
self.assertTrue(select.select([child_fd], [], [], 10)[0], "fixture child must finish")
process.communicate(timeout=5)
self.cli("recover", "--run", payload["run"], "--reason", "all processes stopped")
self.assertEqual(self.run_lease("alpha", ["path:src/a"], cwd=task).returncode, 0)
def test_descendant_resource_guard_accepts_only_held_resource(self):
task = self.initialized_task()
code = (
"import subprocess,sys; "
f"p=subprocess.run([sys.executable,{str(self.root / 'scripts/agentctl.py')!r},"
"'guard','resource','path:src/a/item']);sys.exit(p.returncode)"
)
result = self.run_lease("alpha", ["path:src/a"], code, cwd=task)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
result = self.run_lease("alpha", ["path:src/a"], code.replace("path:src/a/item", "path:src/b"), cwd=task)
self.assertNotEqual(result.returncode, 0)
def test_shared_lease_does_not_satisfy_write_guard(self):
task = self.initialized_task()
result = self.cli("with", "--task", "alpha", "--resource", "path:src/a", "--shared",
"--", sys.executable, str(self.root / "scripts/agentctl.py"),
"guard", "resource", "path:src/a", cwd=task, check=False)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.run_lease("alpha", ["path:src/a"], cwd=task).returncode, 0)
def test_env_guard_refuses_linked_worktree_even_with_capability(self):
task = self.initialized_task(capabilities=("env:dev",))
code = (
"import subprocess,sys; "
f"p=subprocess.run([sys.executable,{str(self.root / 'scripts/agentctl.py')!r},"
"'guard','env','--target','dev']);sys.exit(p.returncode)"
)
result = self.run_lease("alpha", ["env:dev"], code, cwd=task)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
def test_env_guard_accepts_clean_main_and_requires_all_target_leases(self):
self.initialized_task(capabilities=("env:dev", "env:test", "env:main", "env:shared"))
code = (
"import subprocess,sys; "
f"p=subprocess.run([sys.executable,{str(self.root / 'scripts/agentctl.py')!r},"
"'guard','env','--target','dev']);sys.exit(p.returncode)"
)
result = self.run_lease("alpha", ["env:dev"], code, cwd=self.root)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
all_code = code.replace("'dev'", "'all'")
result = self.run_lease("alpha", ["env:dev", "env:test", "env:main", "env:shared"], all_code, cwd=self.root)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
result = self.run_lease("alpha", ["env:dev"], all_code, cwd=self.root)
self.assertNotEqual(result.returncode, 0)
def test_docs_row_cas_preserves_unrelated_rows_and_is_idempotent(self):
task = self.initialized_task(capabilities=("docs:publish",))
filename = ".doc/ops/CURRENT.md"
snapshot = json.loads(self.cli("docs", "snapshot", "--file", filename, "--record", "alpha", cwd=task).stdout)
self.assertEqual(snapshot["sha256"], hashlib.sha256(self.current_rows["alpha"].encode()).hexdigest())
document = task / filename
other = self.current_rows["beta"].replace("active", "reviewing")
document.write_text(document.read_text().replace(self.current_rows["beta"], other))
# A separate accepted update advanced the source baseline; row alpha is unchanged.
self.git("add", filename, cwd=task)
self.git("commit", "-m", "independent beta update", cwd=task)
replacement = self.current_rows["alpha"].replace("active", "done")
request = self.docs_request([self.doc_change(filename, "alpha", self.current_rows["alpha"], replacement)])
self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task)
first = document.read_bytes()
self.assertIn(other, first.decode())
self.assertIn(replacement, first.decode())
self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task)
self.assertEqual(document.read_bytes(), first)
self.assertIn(self.current_rows["alpha"], (self.root / filename).read_text())
def test_docs_foreign_current_row_is_rejected(self):
task = self.initialized_task(capabilities=("docs:publish",))
filename = ".doc/ops/CURRENT.md"
before = (task / filename).read_bytes()
request = self.docs_request([self.doc_change(filename, "beta", self.current_rows["beta"],
self.current_rows["beta"].replace("active", "done"))])
result = self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task, check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((task / filename).read_bytes(), before)
def test_docs_multi_file_preflight_failure_has_no_partial_writes(self):
task = self.initialized_task(capabilities=("docs:publish",))
current = ".doc/ops/CURRENT.md"
features = ".doc/status/FEATURE-MATRIX.md"
before = {name: (task / name).read_bytes() for name in (current, features)}
request = self.docs_request([
self.doc_change(current, "alpha", self.current_rows["alpha"], self.current_rows["alpha"].replace("active", "done")),
self.doc_change(features, "core", "| core | stale |", "| core | done |"),
])
result = self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task, check=False)
self.assertNotEqual(result.returncode, 0)
for filename, contents in before.items():
self.assertEqual((task / filename).read_bytes(), contents)
def test_docs_cannot_launder_unmanaged_other_task_changes(self):
task = self.initialized_task(capabilities=("docs:publish",))
filename = ".doc/ops/CURRENT.md"
document = task / filename
document.write_text(document.read_text().replace(self.current_rows["beta"], self.current_rows["beta"].replace("active", "done")))
before = document.read_bytes()
request = self.docs_request([self.doc_change(filename, "alpha", self.current_rows["alpha"], self.current_rows["alpha"].replace("active", "done"))])
result = self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task, check=False)
self.assertNotEqual(result.returncode, 0)
self.assertIn("UNMANAGED_DOCUMENT_CHANGE", result.stderr)
self.assertEqual(document.read_bytes(), before)
def test_commit_lease_must_cover_staged_files_not_only_task_scope(self):
task = self.initialized_task(scopes=("src",))
self.cli("hooks", "install")
file = task / "src/b/other.py"
file.parent.mkdir(parents=True, exist_ok=True)
file.write_text("value = 2\n")
self.git("add", "src/b/other.py", cwd=task)
code = "import subprocess,sys;sys.exit(subprocess.run(['git','commit','-m','missing file lease']).returncode)"
result = self.run_lease("alpha", ["git:index", "path:src/a"], code, cwd=task)
self.assertNotEqual(result.returncode, 0)
self.assertIn("LEASE_REQUIRED", result.stderr)
def test_guard_build_requires_exact_clean_commit(self):
task = self.initialized_task()
head = self.git("rev-parse", "HEAD", cwd=task)
self.cli("guard", "build", "--commit", head, cwd=task)
self.assertNotEqual(self.cli("guard", "build", "--commit", "0" * 40, cwd=task, check=False).returncode, 0)
(task / "new-source.py").write_text("print('dirty')\n")
self.assertNotEqual(self.cli("guard", "build", "--commit", head, cwd=task, check=False).returncode, 0)
def test_installed_hooks_reject_direct_dev_commit(self):
self.cli("init")
self.cli("hooks", "install")
original = self.git("rev-parse", "HEAD")
(self.root / "source.txt").write_text("change\n")
self.git("add", "source.txt")
result = subprocess.run(["git", "commit", "-m", "unauthorized direct dev commit"],
cwd=self.root, text=True, capture_output=True, timeout=20)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.git("rev-parse", "HEAD"), original)
def test_hooks_refuse_overwriting_foreign_hook(self):
self.cli("init")
hook = self.root / ".git/hooks/pre-commit"
original = "#!/bin/sh\n# existing local protection\nexit 0\n"
hook.write_text(original)
hook.chmod(0o755)
result = self.cli("hooks", "install", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(hook.read_text(), original)
def test_hook_checks_reject_out_of_scope_and_secret_paths(self):
task = self.initialized_task()
self.cli("hooks", "install")
for name in ("outside.txt", "src/a/.env"):
with self.subTest(name=name):
target = task / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text("test fixture, no actual secret\n")
self.git("add", "--", name, cwd=task)
code = (
"import subprocess,sys; "
f"p=subprocess.run([sys.executable,{str(self.root / 'scripts/agentctl.py')!r},"
"'hooks','check']);sys.exit(p.returncode)"
)
result = self.run_lease("alpha", ["git:index", "path:src/a"], code, cwd=task)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
self.git("reset", "--", name, cwd=task)
target.unlink()
def test_hook_rejects_shared_document_without_publish_receipt(self):
task = self.initialized_task(capabilities=("docs:publish",))
self.cli("hooks", "install")
current = task / ".doc/ops/CURRENT.md"
current.write_text(current.read_text().replace(self.current_rows["alpha"],
self.current_rows["alpha"].replace("active", "done")))
self.git("add", ".doc/ops/CURRENT.md", cwd=task)
code = (
"import subprocess,sys; "
f"p=subprocess.run([sys.executable,{str(self.root / 'scripts/agentctl.py')!r},"
"'hooks','check']);sys.exit(p.returncode)"
)
result = self.run_lease("alpha", ["git:index", "path:.doc", "docs:publish"], code, cwd=task)
self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr)
def test_hooks_allow_scoped_task_commit_under_live_lease(self):
task = self.initialized_task()
self.cli("hooks", "install")
source = task / "src/a/example.py"
source.parent.mkdir(parents=True, exist_ok=True)
source.write_text("value = 1\n")
self.git("add", "src/a/example.py", cwd=task)
code = "import subprocess,sys; sys.exit(subprocess.run(['git','commit','-m','scoped task change']).returncode)"
result = self.run_lease("alpha", ["git:index", "path:src/a"], code, cwd=task)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.git("status", "--porcelain", cwd=task), "")
def test_admin_full_document_requires_explicit_admin_capability(self):
task = self.initialized_task(capabilities=("docs:publish",))
filename = ".doc/ops/CURRENT.md"
before = (task/filename).read_text()
request = self.docs_request([self.doc_change(filename,"@document",before,before+"\n")], "no-admin")
result = self.cli("docs","apply","--task","alpha","--request",request,cwd=task,check=False)
self.assertNotEqual(result.returncode,0)
self.assertIn("CAPABILITY_REQUIRED",result.stderr)
self.assertEqual((task/filename).read_text(),before)
def test_registered_active_task_must_close_before_cleanup(self):
task = self.initialized_task()
result = self.cli("worktree-check","--path",str(task),check=False)
self.assertNotEqual(result.returncode,0)
self.assertIn("TASK_ACTIVE",result.stderr)
self.cli("task","close","--id","alpha")
self.cli("worktree-check","--path",str(task))
def test_failed_revalidation_revokes_old_success_receipt(self):
task = self.initialized_task()
self.cli("validate","--task","alpha","--",sys.executable,"-c","pass",cwd=task)
result = self.cli("validate","--task","alpha","--",sys.executable,"-c","raise SystemExit(3)",cwd=task,check=False)
self.assertEqual(result.returncode,3)
import sqlite3
with sqlite3.connect(self.root/'.locks/state.sqlite') as db:
self.assertEqual(db.execute('SELECT count(*) FROM validations').fetchone()[0],0)
result=self.cli('validate','--task','alpha','--',sys.executable,'-c',"from pathlib import Path;Path('dirty-generated').write_text('x')",cwd=task,check=False)
self.assertNotEqual(result.returncode,0)
with sqlite3.connect(self.root/'.locks/state.sqlite') as db:
self.assertEqual(db.execute('SELECT count(*) FROM validations').fetchone()[0],0)
def test_invalid_document_request_shape_has_no_write_side_effects(self):
task = self.initialized_task(capabilities=("docs:publish",))
original = (task/'.doc/ops/CURRENT.md').read_bytes()
for changes in [[None],[{'file':[]}]]:
request = self.docs_request(changes,'bad-shape-'+str(len(str(changes))))
result = self.cli('docs','apply','--task','alpha','--request',request,cwd=task,check=False)
self.assertNotEqual(result.returncode,0)
json.loads(result.stderr)
self.assertEqual((task/'.doc/ops/CURRENT.md').read_bytes(),original)
def test_admin_document_cannot_bypass_done_with_compact_cells(self):
task = self.initialized_task(capabilities=("docs:publish", "docs:admin"))
filename = ".doc/status/FEATURE-MATRIX.md"
snapshot = json.loads(self.cli("docs", "snapshot", "--file", filename, "--record", "@document", cwd=task).stdout)
original = (task / filename).read_text()
for indent in range(4):
with self.subTest(indent=indent):
request = self.docs_request([{"file":filename,"record_id":"@document","expected_sha256":snapshot["sha256"],
"replacement":original.replace("| core | planned |", " "*indent+"| core |done|")}], "compact-done-"+str(indent))
result = self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task, check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual((task / filename).read_text(), original)
def test_validated_linear_commit_cannot_bypass_candidate_entrypoint(self):
task = self.initialized_task(scopes=("src",), capabilities=("git:dev",))
self.cli("hooks", "install")
(task / "src/a/new.py").parent.mkdir(parents=True, exist_ok=True)
(task / "src/a/new.py").write_text("ok = 1\n")
self.git("add", "src/a/new.py", cwd=task)
result = self.run_lease("alpha", ["git:index", "path:src"], "import subprocess,sys;sys.exit(subprocess.run(['git','commit','-m','linear']).returncode)", cwd=task)
self.assertEqual(result.returncode, 0, result.stderr)
self.cli("validate", "--task", "alpha", "--", "git", "diff", "--check", cwd=task)
original = self.git("rev-parse", "dev")
result = self.cli("with", "--task", "alpha", "--resource", "git:dev", "--cwd", str(self.root),
"--", "git", "merge", "--ff-only", "feature/alpha", check=False)
self.assertNotEqual(result.returncode, 0)
self.assertEqual(self.git("rev-parse", "dev"), original)
def test_interrupted_validation_stops_child_before_releasing_scope(self):
task = self.initialized_task()
listener = socket.socket(socket.AF_UNIX)
endpoint = str(pathlib.Path(self.temp.name) / "validate.sock")
listener.bind(endpoint)
listener.listen(1)
listener.settimeout(10)
code = "import os,socket,time;s=socket.socket(socket.AF_UNIX);s.connect(%r);s.sendall(str(os.getpid()).encode());time.sleep(60)" % endpoint
process = subprocess.Popen([sys.executable,str(self.root/'scripts/agentctl.py'),'validate','--task','alpha','--',sys.executable,'-c',code],cwd=task,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
child_pid = None
try:
connection,_ = listener.accept()
with connection:
child_pid = int(connection.recv(100))
pidfd = os.pidfd_open(child_pid)
try:
process.send_signal(__import__('signal').SIGINT)
process.wait(timeout=10)
self.assertNotEqual(process.returncode, 0)
self.assertTrue(select.select([pidfd],[],[],2)[0], 'validator released with live child')
finally:
os.close(pidfd)
finally:
listener.close()
if child_pid:
try: os.killpg(child_pid,__import__('signal').SIGKILL)
except ProcessLookupError: pass
if process.poll() is None:
process.kill()
process.communicate(timeout=5)
def test_validated_merge_candidate_is_exactly_promoted_to_dev(self):
task = self.initialized_task()
self.cli("hooks", "install")
(task / "src/a/feature.py").parent.mkdir(parents=True, exist_ok=True)
(task / "src/a/feature.py").write_text("feature = True\n")
self.git("add", "src/a/feature.py", cwd=task)
change = self.run_lease("alpha", ["git:index", "path:src/a"],
"import subprocess,sys;sys.exit(subprocess.run(['git','commit','-m','feature']).returncode)", cwd=task)
self.assertEqual(change.returncode, 0, change.stderr)
baseline = self.git("rev-parse", "dev")
candidate = self.initialized_task("candidate", scopes=(".",), capabilities=("git:dev",))
merged = self.run_lease("candidate", ["git:index", "git:dev", "path:."],
"import subprocess,sys;sys.exit(subprocess.run(['git','merge','--no-ff','-m','candidate','feature/alpha']).returncode)", cwd=candidate)
self.assertEqual(merged.returncode, 0, merged.stderr)
head = self.git("rev-parse", "HEAD", cwd=candidate)
denied = self.cli("integrate", "--task", "candidate", "--expected-dev", baseline, cwd=candidate, check=False)
self.assertNotEqual(denied.returncode, 0)
self.assertIn("VALIDATION_REQUIRED", denied.stderr)
self.cli("validate", "--task", "candidate", "--", "git", "diff", "--check", baseline, cwd=candidate)
# Interruption at the Git boundary must reap its child and retain recovery.
listener = socket.socket(socket.AF_UNIX)
endpoint = str(pathlib.Path(self.temp.name)/"integrate.sock")
listener.bind(endpoint); listener.listen(1); listener.settimeout(10)
proxy_dir = pathlib.Path(self.temp.name)/"proxy-bin"; proxy_dir.mkdir()
real_git = shutil.which("git")
proxy = proxy_dir/"git"
proxy.write_text("#!/usr/bin/env python3\nimport os,sys,socket\n"
+ "if 'merge' in sys.argv and '--ff-only' in sys.argv:\n"
+ f" s=socket.socket(socket.AF_UNIX);s.connect({endpoint!r});s.sendall(os.environ['AGENTCTL_RUN_ID'].encode());s.recv(1)\n"
+ f"os.execv({real_git!r},[{real_git!r},*sys.argv[1:]])\n")
proxy.chmod(0o755)
process = subprocess.Popen([sys.executable,str(self.root/'scripts/agentctl.py'),'integrate','--task','candidate','--expected-dev',baseline],cwd=candidate,
env={**os.environ,'PATH':str(proxy_dir)+os.pathsep+os.environ['PATH']},stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True)
try:
connection,_=listener.accept()
with connection:
run_id=connection.recv(100).decode()
process.send_signal(__import__('signal').SIGINT)
process.communicate(timeout=10)
self.assertNotEqual(process.returncode,0)
self.assertEqual(self.git('rev-parse','dev'),baseline)
blocked=self.run_lease('candidate',['git:index'],cwd=candidate)
self.assertNotEqual(blocked.returncode,0)
self.cli('recover','--run',run_id,'--reason','test confirmed Git proxy stopped before ref update')
finally:
listener.close()
if process.poll() is None:
process.terminate();process.communicate(timeout=10)
self.cli("integrate", "--task", "candidate", "--expected-dev", baseline, cwd=candidate)
self.assertEqual(self.git("rev-parse", "dev"), head)
self.assertEqual(self.git("status", "--porcelain"), "")
# Reusing the old expected base cannot replay a second ref update.
stale = self.cli("integrate", "--task", "candidate", "--expected-dev", baseline, cwd=candidate, check=False)
self.assertNotEqual(stale.returncode, 0)
self.assertEqual(self.git("rev-parse", "dev"), head)
def test_admin_document_cas_preserves_slot_and_needs_capability(self):
task = self.initialized_task(capabilities=("docs:publish", "docs:admin"))
filename = ".doc/ops/CURRENT.md"
snapshot = json.loads(self.cli("docs", "snapshot", "--file", filename, "--record", "@document", cwd=task).stdout)
original = (task / filename).read_text()
request = self.docs_request([{ "file":filename, "record_id":"@document", "expected_sha256":snapshot['sha256'],
"replacement":original.replace("# Coordination", "# Active coordination") }], "admin-layout")
self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task)
self.assertIn("# Active coordination", (task / filename).read_text())
def test_replaced_compatibility_inode_is_not_a_new_free_lock(self):
self.cli("init")
bridge = self.root / ".locks/os/dev-integration.lock"
bridge.unlink()
bridge.write_text("")
self.assertNotEqual(self.cli("status", check=False).returncode, 0)
def test_hooks_allow_published_shared_document_receipt(self):
task = self.initialized_task(capabilities=("docs:publish",))
self.cli("hooks", "install")
filename = ".doc/ops/CURRENT.md"
request = self.docs_request([self.doc_change(filename, "alpha", self.current_rows["alpha"],
self.current_rows["alpha"].replace("active", "done"))])
self.cli("docs", "apply", "--task", "alpha", "--request", request, cwd=task)
self.git("add", filename, cwd=task)
code = "import subprocess,sys; sys.exit(subprocess.run(['git','commit','-m','published task state']).returncode)"
result = self.run_lease("alpha", ["git:index", "path:.doc", "docs:publish"], code, cwd=task)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertEqual(self.git("status", "--porcelain", cwd=task), "")
if __name__ == "__main__":
unittest.main()
+25
View File
@@ -0,0 +1,25 @@
"""Expected top-level tests must actually run and pass, not merely exit zero."""
import importlib.util
from pathlib import Path
import unittest
class TestEvidence(unittest.TestCase):
def checker(self):
path = Path(__file__).resolve().parents[1] / 'verify_go_test_evidence.py'
spec = importlib.util.spec_from_file_location('test_evidence_checker', path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.verify
def test_both_expected_tests_pass(self):
self.assertTrue(self.checker()('--- PASS: TestA (0.10s)\n--- PASS: TestB (2.00s)\n', ['TestA', 'TestB'])['passed'])
def test_missing_partial_skip_failure_and_duplicate_are_not_pass(self):
for text in ['', '--- PASS: TestA (1s)\n', '--- PASS: TestA (1s)\n--- SKIP: TestB (0s)\n',
'--- PASS: TestA (1s)\n--- FAIL: TestB (1s)\n',
'--- PASS: TestA (1s)\n--- PASS: TestB (1s)\n--- PASS: TestB (1s)\n']:
with self.subTest(text=text):
self.assertFalse(self.checker()(text, ['TestA', 'TestB'])['passed'])
def test_subtests_and_case_mismatches_do_not_prove_parent_execution(self):
self.assertFalse(self.checker()(' --- PASS: TestPiAgent/child (1s)\n--- PASS: TestPIAgent (1s)\n', ['TestPiAgent'])['passed'])
+228
View File
@@ -0,0 +1,228 @@
"""Real guard integration in disposable Git repositories; Docker is never real."""
import os
import json
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import unittest
SOURCE = Path(__file__).resolve().parents[1]
class OpsGuardsTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name) / "repo"
self.root.mkdir()
(self.root / "scripts").mkdir()
for name in ("env-service.sh", "candidate-images-build.sh", "agentctl.py", "agentctl"):
shutil.copy2(SOURCE / name, self.root / "scripts" / name)
(self.root / ".gitignore").write_text(".worktrees/\n.locks/\nconfigs/.env.*\n!configs/.env.*.example\ndata/\n")
(self.root / ".dockerignore").write_text(".doc\n.locks\n.worktrees\n")
(self.root / "configs").mkdir()
for target in ("shared", "dev", "test", "main"):
(self.root / f"docker-compose.{target}.yml").write_text("services: {}\n")
(self.root / "configs" / f".env.{target}.example").write_text("EXAMPLE=1\n")
self.git("init", "-b", "dev")
self.git("config", "user.email", "test@example.invalid")
self.git("config", "user.name", "Test")
self.git("add", ".")
self.git("commit", "-qm", "fixture")
self.base = self.git("rev-parse", "HEAD").stdout.strip()
self.bin = Path(self.tmp.name) / "bin"
self.bin.mkdir()
self.docker_log = Path(self.tmp.name) / "docker-calls"
docker = self.bin / "docker"
docker.write_text('#!/bin/sh\nprintf "%s\\n" "$*" >> "$DOCKER_LOG"\n'
'case "$1" in network) echo chat-agent-shared-net;; inspect) echo sha256:fixture;; esac\n')
docker.chmod(0o755)
self.env = {**os.environ, "PATH": f"{self.bin}:{os.environ['PATH']}",
"DOCKER_LOG": str(self.docker_log), "PYTHONDONTWRITEBYTECODE": "1"}
for name in ("AGENTCTL_RUN_ID", "AGENTCTL_TASK_ID"):
self.env.pop(name, None)
def run_cmd(self, args, cwd=None, env=None):
return subprocess.run(args, cwd=cwd or self.root, env=env or getattr(self, "env", None),
text=True, capture_output=True, timeout=15)
def git(self, *args, cwd=None):
result = self.run_cmd(["git", *args], cwd=cwd)
self.assertEqual(result.returncode, 0, result.stderr)
return result
def service(self, target, action, root=None):
root = root or self.root
return self.run_cmd(["bash", str(root / "scripts/env-service.sh"), target, action], cwd=root)
def ctl(self, *args, cwd=None):
return self.run_cmd([sys.executable, str(self.root / "scripts/agentctl.py"), *args], cwd=cwd)
def init(self):
result = self.ctl("init")
self.assertEqual(result.returncode, 0, result.stderr)
def register_operator(self, *capabilities):
self.init()
linked = self.root / ".worktrees/operator"
self.git("worktree", "add", "-b", "feature/operator", str(linked), self.base)
args = ["task", "register", "--id", "operator", "--owner", "test", "--scope", "scripts"]
for capability in capabilities:
args += ["--capability", capability]
result = self.ctl(*args, cwd=linked)
self.assertEqual(result.returncode, 0, result.stderr)
return linked
def supervised_service(self, resource, target, action, cwd=None, script_root=None):
return self.ctl("with", "--task", "operator", "--resource", resource,
"--cwd", str(cwd or self.root), "--", "bash",
str((script_root or self.root) / "scripts/env-service.sh"), target, action)
def assert_no_write_effects(self, root=None):
root = root or self.root
self.assertFalse((root / "data").exists())
for target in ("shared", "dev", "test", "main"):
self.assertFalse((root / "configs" / f".env.{target}").exists())
def test_queries_never_initialize_environment(self):
for action in ("status", "ps", "logs"):
with self.subTest(action=action):
result = self.service("dev", action)
self.assertEqual(result.returncode, 0, result.stderr)
self.assert_no_write_effects()
self.assertFalse((self.root / ".locks").exists())
self.assertIn("logs -f --tail=100", self.docker_log.read_text())
def test_service_writes_without_lease_refuse_before_any_side_effect(self):
for action in ("start", "stop", "restart", "rebuild", "down"):
with self.subTest(action=action):
result = self.service("dev", action)
self.assertNotEqual(result.returncode, 0)
self.assert_no_write_effects()
self.assertFalse(self.docker_log.exists())
def test_linked_worktree_writes_refuse_and_queries_remain_read_only(self):
linked = self.root / ".worktrees/task"
self.git("worktree", "add", "-b", "feature/task", str(linked), self.base)
for action in ("start", "stop", "restart", "rebuild", "down"):
result = self.service("dev", action, linked)
self.assertNotEqual(result.returncode, 0)
self.assert_no_write_effects(linked)
self.assertFalse(self.docker_log.exists())
result = self.service("dev", "logs", linked)
self.assertEqual(result.returncode, 0, result.stderr)
self.assert_no_write_effects(linked)
def test_invalid_action_does_not_initialize_environment(self):
self.assertNotEqual(self.service("dev", "bogus").returncode, 0)
self.assert_no_write_effects()
self.assertFalse(self.docker_log.exists())
def test_build_accepts_nightly_evidence_excluded_from_build_context(self):
self.init()
evidence = self.root / ".doc/test-reports/assets/promotion-receipts/evidence-fixture/go-test.txt"
evidence.parent.mkdir(parents=True)
evidence.write_text("--- PASS: TestFixture (1s)\n")
result = self.run_cmd(["bash","scripts/candidate-images-build.sh","--commit",self.base])
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(self.docker_log.exists())
def test_build_context_is_frozen_when_managed_source_changes(self):
self.init()
(self.root / "build_marker.txt").write_text("original")
self.git("add", "build_marker.txt")
self.git("commit", "-qm", "source marker")
source = self.root / ".worktrees/source"
self.git("worktree","add","-b","feature/source",str(source),"dev")
result = self.ctl("task","register","--id","source","--owner","test","--scope","build_marker.txt",cwd=source)
self.assertEqual(result.returncode,0,result.stderr)
observed = Path(self.tmp.name) / "observed-content"
docker = self.bin / "docker"
docker.write_text('#!/bin/sh\n'
'if [ "$1" = build ]; then\n'
' for context in "$@"; do :; done\n'
' python3 "$PWD/scripts/agentctl.py" with --task source --resource path:build_marker.txt -- python3 -c '
+ '\'from pathlib import Path; Path("build_marker.txt").write_text("changed")\'' + '\n'
' cat "$context/build_marker.txt" > "$OBSERVED_CONTENT"\n'
'elif [ "$1" = inspect ]; then echo sha256:fixture; fi\n')
docker.chmod(0o755)
result = self.run_cmd(["bash",str(source/"scripts/candidate-images-build.sh")],cwd=source,
env={**self.env,"OBSERVED_CONTENT":str(observed)})
self.assertEqual(result.returncode,0,result.stderr)
self.assertEqual((source/"build_marker.txt").read_text(),"changed")
self.assertEqual(observed.read_text(),"original")
def test_build_mismatched_commit_refuses_before_docker_or_receipts(self):
self.init()
self.git("commit", "--allow-empty", "-qm", "second")
result = self.run_cmd(["bash", "scripts/candidate-images-build.sh", "--commit", self.base])
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.docker_log.exists())
self.assertFalse((self.root / ".doc").exists())
def test_build_dirty_source_refuses_before_docker_or_receipts(self):
self.init()
(self.root / "docker-compose.dev.yml").write_text("dirty\n")
result = self.run_cmd(["bash", "scripts/candidate-images-build.sh"])
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.docker_log.exists())
self.assertFalse((self.root / ".doc").exists())
def test_build_untracked_source_refuses_before_docker_or_receipts(self):
self.init()
(self.root / "surprise.txt").write_text("untracked\n")
result = self.run_cmd(["bash", "scripts/candidate-images-build.sh"])
self.assertNotEqual(result.returncode, 0)
self.assertFalse(self.docker_log.exists())
self.assertFalse((self.root / ".doc").exists())
def test_clean_build_records_exact_source_commit(self):
self.init()
result = self.run_cmd(["bash", "scripts/candidate-images-build.sh", "--commit", self.base])
self.assertEqual(result.returncode, 0, result.stderr)
manifest = self.root / ".doc/test-reports/assets/promotion-receipts" / f"{self.base}.images.json"
receipt = json.loads(manifest.read_text())
self.assertEqual(receipt["commit"], self.base)
self.assertEqual(receipt["tags"]["app"], f"shuimu-agent-app:candidate-{self.base[:12]}")
self.assertEqual(self.docker_log.read_text().count("build -f "), 2)
def test_authorized_pinned_rebuild_preserves_no_build(self):
self.register_operator("env:test")
(self.root / "configs/.env.test.images").write_text("PINNED_IMAGE_MODE=1\n")
result = self.supervised_service("env:test", "test", "rebuild")
self.assertEqual(result.returncode, 0, result.stderr)
calls = self.docker_log.read_text()
self.assertIn("up -d --no-build --force-recreate", calls)
self.assertNotIn("up -d --build", calls)
def test_one_environment_lease_does_not_authorize_all(self):
self.register_operator("env:dev")
result = self.supervised_service("env:dev", "all", "start")
self.assertNotEqual(result.returncode, 0)
self.assert_no_write_effects()
self.assertFalse(self.docker_log.exists())
def test_environment_lease_cannot_deploy_dirty_main_source(self):
self.register_operator("env:dev")
(self.root / "docker-compose.dev.yml").write_text("dirty source\n")
result = self.supervised_service("env:dev", "dev", "rebuild")
self.assertNotEqual(result.returncode, 0)
self.assert_no_write_effects()
self.assertFalse(self.docker_log.exists())
def test_valid_lease_cannot_hide_linked_caller_or_linked_script(self):
linked = self.register_operator("env:dev")
for cwd, script_root in ((linked, self.root), (self.root, linked)):
with self.subTest(cwd=cwd, script_root=script_root):
result = self.supervised_service("env:dev", "dev", "start", cwd=cwd, script_root=script_root)
self.assertNotEqual(result.returncode, 0)
self.assert_no_write_effects()
self.assert_no_write_effects(linked)
self.assertFalse(self.docker_log.exists())
if __name__ == "__main__":
unittest.main()
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Validate exact expected top-level go test -v results from one final attempt."""
import argparse
import json
from pathlib import Path
import re
def verify(text, expected):
observed = {name: [] for name in expected}
for status, name in re.findall(r'^--- (PASS|FAIL|SKIP): (Test[A-Za-z0-9_]+) \(', text, re.M):
if name in observed:
observed[name].append(status)
return {'passed': bool(expected) and all(states == ['PASS'] for states in observed.values()),
'expected': expected, 'observed': observed}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--log', required=True)
parser.add_argument('--expected', action='append', required=True)
args = parser.parse_args()
result = verify(Path(args.log).read_text(), args.expected)
print(json.dumps(result, sort_keys=True))
return 0 if result['passed'] else 1
if __name__ == '__main__':
raise SystemExit(main())