[WIP] Record approved version-dependency and active-branch transition requirements #100

Closed
weichen wants to merge 15 commits from feature/dependency-workspace-plan into main
9 changed files with 928 additions and 3 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# Agent 工作说明(Agent-System-Documents)
本仓开发遵循 Development-Guidelines(Dev 仓 `deps/development-guidelines`,gitlink 固定版本):
本仓开发遵循 Development-Guidelines(Dev 仓 components.lock.json 固定来源,新布局读取 .workspace/Development-Guidelines;历史提交仍用原 gitlink):
核心规则见其 `core/`,按任务触发阅读 `standards/` 专题;日常交付走 `workflows/delivery.md`。
## 任务启动门禁
@@ -15,7 +15,7 @@
## 仓内规则
- 模块边界、公开契约与隔离档位见 `policy/modules/`(如有)与 PROJECT.md。
- 跨仓 gitlink 变更须经组合验证后在 Dev 仓推进;受保护分支经 PR 合入。
- 跨仓依赖/源码锁变更须经固定组合验证后在 Dev 仓推进;受保护分支经 PR 合入。
- 验证命令见 PROJECT.md「验证」节;完成声明须附实际命令输出证据。
## Gitea 与 Git 访问事实(2026-09-30 实测)
+1 -1
View File
@@ -20,6 +20,6 @@ docs/
0.1.0(2026-09-29):[使用报告](docs/guides/usage.md) · [测试报告](docs/reports/2026-09-29-test-report.md) ·
[实施记录](docs/records/2026-09-29-implementation.md) · [设计方案](docs/proposals/2026-09-29-system-skeleton-design-and-plan.md)
规范基线:Development-Guidelines(Dev 仓 `deps/development-guidelines`)。
规范基线:Development-Guidelines(Dev 固定 Development-Guidelines 来源)。
相关方案:[RDM 功能、实现与计划矩阵及整改方案 r1(2026-10-12)](docs/proposals/2026-10-12-rdm-function-implementation-plan.md)——RDM 全系统缺口矩阵与 U1–U9 工作包。
@@ -0,0 +1,201 @@
# 版本化依赖与按需源码工作区改造方案 r1
状态:approved(方案 r1,批准对象 commit `4a7b9293e9d74804833f98cebd52e235c9375d3c`);2026-10-13 用户批准重构,并要求当前及后续工作分支平稳过渡。实施计划细化、实施与自检连续推进;正式验收与发布边界保留。批准不等于机制已经切换。
负责人:weichen;编制:Codex;更新时间:2026-10-13(北京时间)。
需求来源:本次会话确认保留独立功能仓、替换 submodule 依赖方式,并保留从 Dev 按需修改功能仓的体验;同时提交通用规范与项目细则。
实施授权来源:用户「可以,帮我实现吧……之后的项目也都可以按照此思路来进行开发和管理」。该指示允许启动改造;本仓 `policy/delivery.md` 的具体方案、实施计划与验收评审,以及发布双钥匙边界仍须满足。本文不是对用户的代签批准。
主记录:本文;Dev 任务卡 `.task/feature-dependency-workspace.md` 仅记录现场、证据及阶段状态。
## 1. 目标、边界与当前事实
目标是保留独立仓库、独立 PR 和代码归属,以版本依赖代替重复源码指针,并用 Dev 的统一入口按需准备可编辑仓库。日常开发不要求所有组件一起升级;正式交付仍固定并验证精确组合。仓库拆分不等于拆进程,单二进制交付继续保留。
本次源码核查属于 SELF,不是正式迁移验收。Dev 的权威起点为 `ff3fb4a7ebe432ab21c6b9668abc9823aca2fdd1`;Documents 草案分支从本次获取的 `5a06acf7cded07797ec4ebfea42c89331359b0bb` 起步。版本清单与命令完整输出由 Dev 的 evidence 写入任务卡,本文不转写命令输出。
| 当前触点 | 已观察行为 | 迁移影响 |
|---|---|---|
| Dev `.gitmodules`、`Makefile`、`scripts/check-pins.sh` | 12 个顶层组件;另核对 Backend 等容器的嵌套指针 | 依赖权威、物化方式和组合验证一起调整 |
| Backend、Native、Intent、Squad 的 `go.mod` | 短模块名、`v0.0.0` 与本地 `replace` | 必须支持脱离源码目录的真实版本消费 |
| Frontend `vite.config.ts`、`tsconfig.json` | `@ui` 直接指向相邻 UI 源码 | 改为包出口;开发链接只进入本地工作区 |
| Dev `Makefile:contracts/frontend/build` | 跨仓复制 TS 契约,复制 frontend dist 后 embed | 契约包与前端制品均须固定来源和摘要 |
| Dev `task.sh`、`check-worktree.py`、`evidence.sh` | 同步、取证和回收识别递归 submodule | 改为显式工作区登记,保护普通 clone/worktree 的现场 |
| Dev CI 与 `svc.sh` | 有凭据阶段获取固定子模块;源码部署递归初始化 | 替换获取方式,保留凭据边界和历史候选重建 |
| RDM `go.mod`、`.gitmodules`、`Makefile` | `pins/` 及 9 条本地 replace;正式产物含前端 | RDM 是实际消费方,不能留到切换后才发现不可构建 |
| Guidelines `standards/multi-repo.md`、`workflows/adoption.md` | 默认将 gitlink 写成版本权威 | 改为按依赖种类选择权威,不把 submodule 写成唯一方式 |
业务权限、数据身份、数据库迁移、模型行为和运行服务不在本轮改造范围。源码部署入口的候选构建可以在隔离环境验证,本轮不部署或重启共享实例。
## 2. 需求及验收条件
| ID | 需求 | 可判定验收条件 |
|---|---|---|
| R1 | 独立功能仓和代码归属保持清晰 | Native 的修改在其独立分支和 PR 中;Dev 不提交复制的 Native 源码 |
| R2 | 使用者只需手动 clone Dev | 干净 `git clone` 不带递归选项,按记录的入口完成初始化、构建及相关测试 |
| R3 | 按需编辑功能仓 | `make component-edit NAME=Agent-Native` 准备指定仓及任务分支,返回准确路径和基线 |
| R4 | 修改后直接联调 | 本地 Native 修改被 Backend 使用;关闭工作区覆盖后恢复正式依赖;无需先发布才能联调 |
| R5 | Go 依赖可独立消费 | 干净消费方以 `GOWORK=off` 下载真实版本、通过测试和构建,无本地路径 replace |
| R6 | UI 与 TS 契约可独立消费 | Frontend 从版本包导入,CSS/类型出口完整;不存在相邻源码目录的必要依赖 |
| R7 | 最终组合固定且可追溯 | 记录应用来源、Go 实际构建列表、npm 锁与完整性值、契约及 dist 摘要、工具链和二进制摘要 |
| R8 | 正式构建不受开发覆盖影响 | 本地 go.work、npm 链接、未提交组件代码进入发布候选时被拒绝或从隔离构建中排除 |
| R9 | 保存他人现场 | 脏仓、活动 Git 操作、路径逃逸、远端不符和已有编辑分支均不被覆盖;失败后可重试 |
| R10 | 任务门禁适配新工作区 | precheck、task-start/sync、evidence、finish 识别被测组件和写者;真实临时仓覆盖正反例 |
| R11 | RDM 可消费迁移后基座 | 移除对应 pins/replace 后,完整产品构建及真实二进制烟测可重放 |
| R12 | 旧版本可重建和回退 | 新源码部署入口能物化一个旧 submodule 候选及一个新版本依赖候选;不改业务数据库 |
| R13 | 通用规则供后续项目采纳 | Guidelines 正文无本项目仓名、主机、端口、秘密路径;模板声明依赖权威、开发覆盖与正式门禁 |
| R14 | 规则与能力一致 | 项目规则、脚本、CI 与实际检查一致;通用合入不自动修改其他项目的固定规则 |
| R15 | 在途及后续分支平稳过渡 | 不改动其他活动工作区;旧版本构建保留;显式迁移保留分支/提交/未保存文件;新任务默认新布局;真实仓覆盖旧任务同步、组件修改、冲突和回退 |
R1–R14 随 r1 获批;R15 来自本次批准消息的明确约束,纳入同一改造。没有实测的能力保持 NOT_RUN。
## 3. 依赖权威和目标目录
每类对象只设一个版本权威:
- Go 库:消费方 `go.mod` 的模块图决定实际版本,`go.sum` 校验内容;构建时导出实际选择结果,不把 go.sum 宣传成 lockfile。
- npm 库:消费方声明依赖,lockfile 固定实际解析版本及完整性信息。
- 应用源码、规范和过程资料:组合锁固定来源提交;不再用库的额外源码 SHA 覆盖语言包管理器的选择。
- 最终制品:构建自动导出 resolved manifest,记录上述解析结果及资产摘要;不另手填第二份可竞争的依赖版本。
Dev 拟新增 `components.json` 保存仓库逻辑 ID、批准的上游、目标分支、职责、模块/包映射;`components.lock.json` 固定需要物化的应用及文档版本。两个文件使用标准 JSON,避免 bootstrap 为读配置先安装依赖。
```text
Agent-System-Dev/
components.json # 身份、角色和批准来源
components.lock.json # 应用、规范和资料的固定版本
policy/dependencies.md # 本项目细则与入口
scripts/workspace.py # 命令编排
scripts/workspace/ # 清单、Git、Go、npm 与状态职责
.workspace/ # ignored;普通 clone/worktree,不是 gitlink
Agent-System-Backend/
Agent-System-Frontend/
Agent-Native/ # 需要编辑时准备
Development-Guidelines/
Agent-System-Documents/
state.json # 仅本机开发覆盖与写者登记
go.work # 本机生成且 ignored
```
应用源码检出与库下载是不同操作:普通构建仅准备必需应用源码,库由包管理器获取。组件编辑先解析当前组合实际使用的版本,再检出对应源码;不能默认拉最新 main 改变复现基线。
首次 clone 的规则获取属于启动准备:precheck 使用固定清单把规范物化到仓外缓存并报告可阅读路径,再创建任务工作区。不能因为 `.workspace` 尚不存在而跳过必读或伪造 READY。
## 4. 统一命令与状态转换
以下保留已批准的工具契约。实施能力以 Dev 当前入口和任务卡中的命令证据为准,方案条目不代表能力已就位。
| 入口 | 输入与行为 | 成功输出/失败出口 |
|---|---|---|
| `make workspace-prepare` | 根据固定组合准备应用、规范和资料;库按版本下载 | 返回固定 SHA/版本;来源缺失即非零,不取 latest 兜底 |
| `make component-edit NAME=Agent-Native` | 验证登记身份;解析实际版本;普通 clone 或复用匹配仓;创建任务分支并登记 | 返回路径、origin、基线、分支;已有脏/活动现场只报告,不 reset/stash |
| `make component-unlink NAME=Agent-Native` | 核对归属,解除该仓的本地联调覆盖 | 恢复版本依赖;不删除仓库、分支或用户文件 |
| `make workspace-status` | 只读列出正式版本、本地覆盖、分支、脏状态和登记写者 | 可区分正常消费、开发覆盖、候选待审和异常 |
| 局部检查入口 | 在已登记开发覆盖上运行相关检查;具体命令由实施后的 PROJECT.md/Makefile 给出 | 结果明确标记开发组合,不能用于正式发布放行;未实现入口不对外宣称可用 |
| `make test` / `make build` | 在隔离、固定来源的正式组合执行;Go 关闭工作区覆盖 | 本地覆盖不改变候选;测试/构建缺项或失败传回非零 |
状态为 `versioned → editable → linked → submitted → adopted`。submitted 表示功能仓有提交或 PR,不表示消费方已经采用。退出 linked 仅取消本地覆盖;采用新版本由消费方依赖 PR 决定,不要求所有仓一起推进。
仓库名称、分支、目标目录和清单字段使用白名单/schema 校验,不经 shell 拼接执行。拒绝绝对目录、`..`、软链接逃逸、错误 origin、并行写同一工作区、现存 Git 锁或半完成 merge。Git 失败保留现场,不以删除锁或强制 checkout 恢复。
## 5. Go、前端及传输决策
### 5.1 Go 模块
推荐本轮不引入新域名服务,以带 VCS 后缀的稳定模块身份作为直接 Git 获取方案:`git-ssh.u1680832.nyat.app/weichen/<Repo>.git`;子模块在其后附相对目录。SSH 下载地址仍取项目清单中的现有带端口 URL。模块身份不包含端口或凭证;传输映射以进程级 Git 配置注入,不修改全局配置。
该命名属于公开 import 契约变更,须在批准本文后实施。U1 必须在隔离消费方实测 Go 的仓库发现、SSH 映射、私有前缀与子模块解析,才推广到全部组件;不能从普通 `git ls-remote` 成功推断 `go mod download` 成功。
Go 依赖使用经审核、远端可获取的提交,由 Go 生成规范 pseudo-version;不手拼版本号,也不把 feature 分支名或 `v0.0.0` 当正式锁定。带正式语义版本的 tag、包上传与版本发布保留所有者双钥匙操作。这样,完成 Git PR 与真实版本消费不依赖本轮自动发布 tag。
一次性改动清单包括 `module`、`require`、所有相关 import、示例、边界棘轮与测试工具;Squad adapters/cmd 等嵌套 Go 模块分别处理。禁止为迁移规避 Go internal 限制,禁止变更外部协议标识、数据库记录和历史事件类型。
本地 `go.work` 仅纳入需要编辑的模块及相应消费方;必要替换只留本机。正式消费检查 `GOWORK=off` 和 `-mod=readonly`;导出模块图并核验实际版本。现有 `golang.org/x` 的差异必须评估并验证,不能因联调成功就改写发布依赖。
备选是 Go proxy/Registry:模块身份可以不带 VCS 后缀,但需要验证当前服务启用、权限、包发布以及 GONOPROXY/GONOSUMDB 行为,不能把官方产品支持视为当前实例已就绪。本轮优先直接 Git 获取,Registry 后续按独立批准采用。
### 5.2 npm、契约与单二进制
UI 提供可消费的包出口(拟 `@agent-system/ui`),包含组件、类型、tokens.css、motion.css;React/ReactDOM 保持 peerDependency,验证单副本。Core 的 TS 契约使用单一源生成独立契约包(拟 `@agent-system/contracts`),Frontend 与 UI 声明依赖,停止构建时改写消费仓源码。
首批可使用固定 Git 提交的包依赖及 lockfile,避免未批准的 Registry 发布成为启动前置。npm prepare/install 生命周期不能在持有 checkout 凭据时执行;包生成在无凭据阶段完成。后续 Registry 发布仍需单独核对发布授权。
`@ui` 现有调用可通过包出口适配;禁止继续依赖相邻仓源码路径。不修改界面外观,但包出口和构建组合仍须遵守 Dev DESIGN 的令牌、动效及 Collapsible 约束,并运行设计守护。
Frontend dist 是应用制品:固定源码、契约包和 npm 锁构建后,在隔离 Backend 构建目录装配、核验摘要,再编译单二进制。不要改写开发者的 Backend web/dist,也不宣称单独升级 UI 包已经更新部署中的 bundle。
## 6. 通用规范与项目细则
通用条文进入 Development-Guidelines;项目地址、路径、组件映射、具体命令、CI 白名单和部署适配进入 Dev,过程记录进入 Documents。其他项目通过自己的采纳 PR 选择版本,不由本轮自动批量迁移。
| 通用规范文件 | 本轮拟修订的唯一权威内容 |
|---|---|
| `standards/multi-repo.md` | 分离仓库边界与依赖方式;语言依赖/源码清单/制品分别选择权威;开发覆盖与正式组合分开 |
| `standards/collaboration.md` | 共同起点包括项目提交和解析后组件版本;同步不覆盖已登记组件分支;兼容顺序按实际依赖决定 |
| `workflows/adoption.md` | 固定内容快照/清单成为默认可选方式;submodule 保留为兼容选项;升级、回退和规则生效保持显式 |
| `templates/PROJECT.md.template`、`templates/adoption.md` | 填写依赖权威、编辑入口、组件来源、开发覆盖和正式验证方式 |
| `README.md`、`CHANGELOG.md`、`VERSION` | 路由与草稿内容版本更新;不把 VERSION 修改说成正式 tag 已发布 |
候选通用规则的强度:
1. MUST:正式依赖来源可获取、版本固定、内容可校验;拒绝凭据入清单及正式候选混入本地覆盖。
2. MUST:只编辑/提交所属仓库;保护现有工作区、写者与 Git 操作;不得用复制源码隐藏归属。
3. SHOULD:可复用代码优先使用语言包管理器,应用/资料使用固定源码或制品清单;不默认要求 submodule。
4. SHOULD:一个项目入口提供按需检出和联调,库的开发不要求无关消费者一起升级。
5. MAY:特殊场景保留 submodule;该项目明确其版本权威和保护方式,不混合两套可竞争的版本来源。
Dev 新增 `policy/dependencies.md`,同步 AGENTS/PROJECT、任务与取证/回收、Makefile、CI、边界和部署构建入口。保留人工阶段评审、当前 HEAD 的 CI、来源三分和发布双钥匙;依赖方式调整不自动放宽这些边界。
## 7. 实施顺序、责任与文件范围
下表为已批准的实施顺序,实施进展另以任务卡和可复核命令记录。每个增量有可重放出口,成功替代后才退役对应旧入口。Owner 由 weichen 指定/确认,Agent 在批准范围内实施和自检;独立审阅身份另记,不由作者代签。
| 增量 | 文件/仓库范围 | 依赖及完成条件 |
|---|---|---|
| U1 基础库与传输试点 | Logging-System、Error-Code-System 的 go.mod/import;Dev 隔离消费样例与传输获取检查 | 验证两个库的真实远端版本消费、直接 Git 与私有下载;不开标签、不部署;不成功即停止推广 |
| U2 按需编辑工作区 | Dev components 清单、scripts/workspace.py 与分职责实现、Makefile、tests/test_workspace.py、.gitignore | 固定 U1 模块映射;原生临时 Git 仓覆盖 clone、复用、分支、链接/解除、脏状态和重试 |
| U3 Go 栈与实际消费方 | Core、Native、Intent、Command、Squad 根及 adapters/cmd、Backend、RDM 的模块/import/边界 | 依赖 U1/U2;组件独立构建,Native 本地改动可联调;RDM 无旧 pins/replace 的真实产品构建成功 |
| U4 UI 与契约包 | Core ts 契约包、UI package 出口、Frontend package/lock/Vite/TS 配置、Dev 资产装配 | 可与 U3 中无共享契约的工作并行;固定包来源,设计守护、单 React 副本、真实 bundle 与 embed 烟测 |
| U5 正式构建与治理适配 | Dev task.sh、check-worktree.py、evidence.sh、hooks、check-pins/边界、CI、svc.sh 和现有相关测试 | 依赖 U2/U3/U4;旧新候选均可隔离重建,实际版本和资产写入 evidence;秘密不进入候选执行环境 |
| U6 通用规则与项目采纳 | Guidelines 第 6 节文件;Dev policy/AGENTS/PROJECT;Documents 入口/进展 | 通用候选可先并行编写;以 U1–U5 实测修订与显式采纳;检查其他宿主固定版本未被暗改 |
| U7 切换与验收 | Dev/组件移除已被替代的 gitlinks/.gitmodules/嵌套 deps;组合锁与旧模式兼容入口 | 全新 clone 与 RDM 两条实际消费链重放通过,正式阶段批准与当前 HEAD CI 齐备后按 PR 合入 |
| U8 合入核验与收口 | 真实组件/Dev PR 与来源、任务卡、规划进展、写者与回收清单 | 核对实际合入组合;清理另按授权和现场保护执行,不删除未保存的 .workspace 文件 |
U1 未通过不执行 U3 的公开路径推广;U5 未通过不移除旧 submodule 机制。不得把仅完成 U2 的编辑工具宣称为完整迁移完成。
## 8. 验证、失败恢复与交付
计划验证必须区分 SELF、REPLAY、GATE;下面列的是未来命令和预期检查,不是通过记录。
| 验证对象 | 方法/命令 | 覆盖条件 |
|---|---|---|
| 工作区机制 | `python3 -m unittest discover -s tests -p 'test_workspace.py' -v`、`make test-governance` | 正常、多组件、错误名称/路径/origin、Git 锁、脏/ignored 唯一文件、断连、部分初始化、并行写者 |
| Go 正式消费 | 各模块 `GOWORK=off go test ./...`;消费方 `GOWORK=off go build -mod=readonly` | 干净缓存实际下载,版本冲突/不可获取 SHA 拒绝,所有 Squad 子模块;明确工具链版本 |
| 本地覆盖 | 真实样例改变 Native 导出行为,Backend 联调,再 unlink 并关闭 GOWORK 重建 | 修改确实生效,正式候选仍采用声明版本;不只检查 go.work 文件存在 |
| 前端包 | UI 类型检查/测试、Frontend build/test、两仓 design-guard、bundle 副本检查 | 类型与 CSS 出口,契约一致,包锁固定,离开相邻源码目录仍可构建 |
| 产品组合 | Dev `make test`、`make build`;RDM `make product`、`make product-smoke` | 前端进入真实二进制;API/插件入口与授权烟测;不使用 Vite 预览替代产品 |
| CI 凭据 | 现有 `tests/test_ci_credentials.py` 与新包/源码获取负例 | 不执行持有秘密的候选脚本、不追随清单中的任意 URL、不把临时配置留在 checkout/cache |
| 历史构建 | `tests/test_svc.py`、`tests/test_svc_realcombo.py` 与旧/新两份固定 fixture | 仅隔离候选构建,不操作生产进程;缺 dist/来源失败,旧候选可重建 |
| 规范 | 差异、相对链接、术语/强度/入口及版本检查 | 通用正文无项目细节;项目采纳显式;只有真实已验证的执行端才写 enforced |
| 交付 | evidence 保存 precheck/task-sync/相关验证;独立重放;平台当前 HEAD CI | 对象变化按影响复验,未执行明确 NOT_RUN,不靠作者 SELF 替代合入依据 |
恢复策略:初始化失败保留日志与可复用对象,重试不得覆盖脏仓;unlink 不删除源码;组合回退通过普通 PR 恢复上一份已批准依赖/源码锁及受影响构建规则;代码组合回退不推断数据库可以反向迁移。旧配置阅读与旧候选构建入口保留明确兼容范围,退役另有依据。
停止条件:固定来源不可获取、Go 传输试点失败、实际消费者不兼容、工作区唯一成果无法保护、CI 凭据边界未成立、公开路径决策变更或触及未授权共享环境。先保存现场并报告,不用 latest、全局配置、发布 tag、关门禁或强制清理补救。
最终交付分别报告已实现、SELF/REPLAY/GATE 验证、已合入、已发布和已部署。自动 Review 与合入仅在前序人工结论齐备、冲突解决且当前 HEAD CI 通过时执行。发布版本/改保护/改历史/删除归档引用仍由用户确认并亲手执行;本轮不自动扩展到其他项目的部署或规范采纳。
## 9. 评审对象与首批出口
本次需评审的具体新增决策为:第 3 节版本权威与工作区布局、第 4 节命令契约、第 5 节 Go 模块命名和下载方式、第 6 节规范归属、第 7–8 节实施顺序及验证边界。
推荐评审本文后批准 U1–U7 的实施顺序;U1 的真实下载与编辑准备验证是后续推广的硬前置,失败即停止受影响工作。若用户明确授权按本文连续完成实施与自检,记录授权范围,仍保留正式验收、平台准入与双钥匙边界;未取得连续推进授权时按项目阶段和增量逐项评审。
批准记录:用户「批准,可以进行重构,但要注意的是,当前还有其他工作中的分支,要保证这些和后续的工作分支都能够平稳过渡」。实施进展及命令证据放 Dev 任务卡,不能从批准推断任何测试、合入、发布或部署结果。
## 10. 官方依据
- [Go 模块、版本、replace 与模块图](https://go.dev/ref/mod):区分模块身份、传输、版本选择和校验。
- [Go 工作区教程](https://go.dev/doc/tutorial/workspaces):本地多模块联调与正式版本消费分开。
- [Gitea Go Registry](https://docs.gitea.com/usage/packages/go/) 和 [npm Registry](https://docs.gitea.com/usage/packages/npm/):作为可选传输,未实测当前实例就不宣称可用。
- [npm package.json](https://docs.npmjs.com/cli/v11/configuring-npm/package-json) 与 [package-lock.json](https://docs.npmjs.com/cli/v11/configuring-npm/package-lock-json):版本包、Git 依赖、生命周期与锁定信息。
外部产品文档仅支持机制说明;不证明本项目迁移已实施或当前服务权限已配置。
@@ -0,0 +1,110 @@
# Versioned Dependencies and Component Workspaces Implementation Plan
> **For agentic workers:** Use ce-work native subagents for independent units and host-owned verification/commits. Steps use checkbox syntax; progress is recorded in the Dev task card and commits, not by rewriting this plan.
**Goal:** 保留独立仓库及 Dev 单入口,以真实版本依赖和按需编辑源码工作区替换重复 submodule,并让在途分支安全过渡。
**Architecture:** 项目清单提供批准来源和模块映射;应用/文档锁固定源码,语言依赖由消费方解析。工作区工具处理普通 clone、开发覆盖和过渡;正式构建隔离开发覆盖,旧提交继续使用旧物化方式。
**Tech Stack:** Python 3 标准库、Git、Go 1.27、npm/TypeScript/Vite、现有 Make/evidence/Gitea CI。
**Spec:** [已批准方案 r1 与 R15 补充](2026-10-13-versioned-dependencies-and-workspaces.md)。
**Execution authority:** 2026-10-13 用户批准上轮请求的具体方案与实施顺序,允许计划细化、重构及自检连续推进;正式验收、共享部署和发布不被该授权替代。
## Global Constraints
- Go 模块身份采用 `git-ssh.u1680832.nyat.app/weichen/<Repo>.git`,子模块追加目录;端口只属于传输。
- 正式构建 `GOWORK=off`、无本地路径 replace;私有获取的 Git 配置限进程,禁止全局改写。
- 不修改其他活动工作区;不重写历史、不发布 tag、不操作运行服务和业务数据。
- 原生临时 Git 仓覆盖正常、拒绝及失败恢复;命令输出由 evidence 保存,来源 SELF 不提升。
- 原旧 submodule 提交可重建;新功能分支默认新布局;迁移是显式操作,不能静默 stash/reset/提交他人现场。
## Review Focus
1. 删除 gitlinks 后遗留的 `.git` 管理目录可能仍被其他 worktree 使用:保留原目录,拒绝回收外部依赖。
2. 老组件分支含未提交和未推送代码:先报告并复制到独立新工作区,原现场保持不动;冲突交给 owner。
3. 发布构建误消费 go.work/npm 链接:构建单独物化固定源码,导出实际模块图和资产摘要。
4. 私有 Go/Git npm 获取期间运行候选代码:认证获取与无凭据执行分阶段,并验证来源白名单。
5. 新主干合入时旧任务仍使用旧脚本:提供版本识别、显式迁移入口及分支过渡操作说明,不强制修改所有检出。
## U1. Prove remote module consumption
**Files:** Logging-System/Error-Code-System 的 go.mod、Go import、README;Dev 传输检查及源码记录。
**Consumes:** 批准模块映射、现有 SSH 访问;**Produces:** 可获取的两个候选提交与 Go 自动生成的 pseudo-version。
- [ ] 从各仓本次获取的 origin/main 建独立任务分支;保留其他 worktree。
- [ ] 先在隔离消费方证明旧短模块无法按新路径直接消费;写清被测 SHA 和失败类型。
- [ ] 机械改模块声明和 import,在组件内运行 `GOWORK=off go test ./...`;此机械配置改动以编译/独立消费替代镜像测试。
- [ ] 保存并推送候选分支(非 tag/版本发布),以进程级 SSH 映射运行干净缓存的 Go 下载和真实调用。
- [ ] 成功才推广;失败保留现场、修复已批准传输机制,不以本地 replace 通过代替远端消费。
## U2. Prepare editable component workspaces
**Files:** Dev `scripts/workspace.py`、`scripts/workspace/`、`tests/test_workspace.py`;清单与 Make 接线由 host 集成。
**Consumes:** `components.json` 的 schema=1、components 列表(id/repo/path/legacy_path/role/modules/target);应用/资料锁的固定 commit;
**Produces:** prepare/edit/unlink/status/verify/transition CLI,`.workspace/state.json` 及本地 go.work。
Public CLI: `python3 scripts/workspace.py [--root ROOT] prepare|edit|unlink|status|verify|transition [--name ID] [--branch feature/SLUG]`;transition 默认只报告,显式 apply 才准备新现场。
- [ ] 先建真实临时远端测试:prepare 固定 commit;edit 建独立分支;错误名称、路径逃逸和不匹配 origin 被拒。
- [ ] 运行新测试并记录预期失败,再实现最小清单/Git/状态模块。
- [ ] 补充分支、脏/ignored 文件、Git 锁、部分 clone、幂等重试、开发链接/解除测试;通过实际 Go 调用验证覆盖。
- [ ] 库编辑基线优先解析消费方实际 Go 模块版本及 Origin;旧布局只在过渡时从原 gitlink 获取,不能默认最新 main。
- [ ] transition 报告和准备均保留原 legacy 仓、refs、未保存文件;不会修改其他登记工作区;不能把冲突称为成功。
- [ ] 测试通过后 host 检查并按本单元实际文件提交。
## U3. Migrate Go providers and consumers
**Files:** Core/Native/Intent/Command/Squad 及 adapters/cmd、Backend、RDM 的 Go 源码、go.mod/go.sum、README/边界检查。
**Consumes:** U1 可用传输/候选版本;**Produces:** 无源码目录 replace 的组件和两个实际消费方。
- [ ] 每个组件从本次权威 main 起步,记录仓库与基线;批量改 import 前核对标准 import 块及 Go internal 范围。
- [ ] 按 Error/Logging → Core/Native/Intent/Command → Squad 子模块 → Backend → RDM 的实际依赖顺序提交候选。
- [ ] `go get MODULE@COMMIT` 生成版本;保留实际第三方版本,核对 MVS 差异,不抬高无关依赖。
- [ ] 各模块跑 `GOWORK=off go test ./...`,Backend/RDM 干净构建;新旧版本路径明确,不遗留短模块本地 replace。
- [ ] 更新边界规则的模块前缀,不重置业务分层基线;在途分支的新代码用显式前缀迁移辅助检查。
## U4. Package UI and contracts
**Files:** Core TS 契约包、UI package/出口、Frontend package/lock/Vite/TS 配置与包消费测试。
**Consumes:** 固定 Core 类型及 UI 源码;**Produces:** `@agent-system/contracts`、`@agent-system/ui` 包出口及无需相邻源码的前端构建。
- [ ] 读取 DESIGN 和三仓指引;先验证 npm pack 成员、类型/CSS 出口和独立 consumer 缺包时失败。
- [ ] 契约由 Core 单一源码生成;UI/Frontend 声明包依赖,保留 @ui 调用的包出口适配。
- [ ] 包版本/来源固定于提交及 npm lock,不依赖未批准 Registry 发布;所有包生成/生命周期在无 checkout 凭据环境运行。
- [ ] 运行 UI typecheck/test、Frontend build/test、两份 design-guard;检查 React 单副本和实际包消费。
- [ ] 新包提交 SHA 由 host 回填消费方锁;作者不自行提交共享代码或推送。
## U5. Adapt build, governance and historical materialization
**Files:** Dev Makefile、task.sh、check-worktree.py、evidence.py、check-evidence.py、边界脚本、CI、svc.sh、相关真实仓测试。
**Consumes:** U2 CLI 和 U3/U4 固定候选;**Produces:** 新旧布局均可运行、当前对象可取证的工具入口。
- [ ] 扩充现有 test_task/test_evidence/test_ci_credentials/test_svc 用例,先观察旧实现失败。
- [ ] 新布局 task-start 不递归初始化;task-sync 识别旧任务并报告迁移;prepare 不覆盖编辑分支。
- [ ] evidence 记录源码清单、组件实际 HEAD/dirty 与包图,保持旧收据可读;finish 保守核对独立 worktree 和 ignored 唯一文件。
- [ ] 凭据阶段只允许固定身份获取;候选代码执行环境移除凭据;CI 验证固定来源和当前 HEAD。
- [ ] Make 构建在独立固定目录装配 frontend dist/backend embed,输出 resolved manifest;源码部署按候选版本选择物化器。
- [ ] `make test-governance`、`make test`、`make build` 和历史候选 fixture 通过后提交。
## U6. Capture reusable standards and project adoption
**Files:** Guidelines multi-repo/collaboration/adoption/README/templates/CHANGELOG/VERSION;Dev policy/dependencies、AGENTS/PROJECT;Documents。
**Consumes:** 批准原则和实测接口;**Produces:** 项目无关通用条文、显式项目采纳及旧分支过渡说明。
- [ ] 通用正文不包含本项目名称/端口/凭证;固定快照和语言依赖作为可选默认,保留 submodule 兼容方式。
- [ ] 写明旧任务/新任务/组件 owner 的过渡职责、覆盖/回退和凭据边界;其他宿主不自动采纳。
- [ ] 文档检查差异、相对链接、义务强度和术语;VERSION 的草稿递增不等于发布 tag。
- [ ] 依据 U2/U5 已验证行为填项目命令和执行能力;未验证 platform 能力不标 enforced。
## U7. Cut over without damaging active branches
**Files:** Dev manifests、.gitmodules/gitlinks、.gitignore、workspace-transition 接线与分支过渡指南;组件 legacy deps/pins。
**Consumes:** U1–U6 实测出口;**Produces:** 新 clone 无 submodule 构建,旧任务和版本仍可复现。
- [ ] 保存活动工作区只读清单及其元数据;全过程不 checkout/reset/stash/clean 他人现场。
- [ ] 只移除本任务索引中已替代 gitlinks;保留旧物理目录和 Git 管理数据,不通过目录删除来完成迁移。
- [ ] 临时真实仓模拟旧任务独有组件 commit、未提交/ignored 文件、旧 PR 合入、主干前进和文本/接口冲突;原内容可复核且拒绝路径诚实。
- [ ] 新 clone prepare/build/test 和本地 Native 编辑/解除链路,RDM product/product-smoke 均验证。
- [ ] 最终候选固定后提交正式验收材料;未获正式验收结论不声称可合入。
## U8. Review, merge and close
独立重放与代码 Review、最新 main 同步、精确 HEAD 的 GATE CI、实际 PR 合入和 owner 授权的回收分别记录。禁止以 SELF 或计划文件代替正式放行;本实施终点先到可审实现与自检,后续人工验收和发布边界保留。
@@ -0,0 +1,221 @@
# Versioned Dependency Nested-Sandbox Amendment Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 让版本化依赖迁移的最终候选同时具备隔离组合门禁 A 与无凭据原生沙箱重放门禁 B,覆盖 Dev、两个 Backend 来源及 RDM,并把两门禁的缺项作为验收与合入阻塞。
**Architecture:** A 保留本地 Bubblewrap 和 Dev CI 无 socket 子容器,对确需再建 namespace 的用例按精确名称排除、核对并公开 `NOT_RUN_IN_A`;RDM CI 候选步骤也进入无 socket 子容器。B 由受信获取器导出精确源码及 vendor 包,交给无凭据、无 socket 的短生命周期 QEMU/KVM VM,在原生 Bubblewrap 下离线逐用例重放;协调器核对两份证据的源码与用例映射。
**Tech Stack:** Python 3 标准库、Go 1.27.1 的 `go test -json`/`-skip`、Git、Bubblewrap、Docker、QEMU/KVM、Ubuntu 24.04 cloud image、Gitea Actions、现有 `make evidence`。
**Spec:** [用户批准的书面修订设计 r1](2026-10-14-versioned-dependency-nested-sandbox-amendment.md),Documents `1790cf8f28cd2309dc542b63960379fd6a0405c6`;[原整改设计](2026-10-14-versioned-dependency-review-remediation.md)与[原实施计划](2026-10-14-versioned-dependency-review-remediation-plan.md)继续约束 Task 1–4 和最终 Task 6。
**Authority and current state:** 用户 2026-10-14 仅批准书面修订设计并要求编制本计划,尚未批准本计划或实施。Dev `1ea93a5` 的 `make test` 为 FAIL,`make build` 在直接 npm 注册表环境通过;RDM `d8a7098` 的产品构建和旧 run3130 成功,但无 RDM CI 候选 socket 负例。B 为 `NOT_RUN`。Dev、Docs、RDM 现有迁移 PR 保持 WIP;本计划不得把旧运行升级为最终来源图的证据。
## Global Constraints
- 不关闭 Dev 本地 Bubblewrap、不让 Dev/RDM 候选命令在有 Docker socket/凭据的 CI 父步骤执行;无 `--privileged`、host AppArmor/共享 runner/保护规则修改或凭据扩权。
- A 的精确排除只适用于实际会再创建 namespace 的用例;在报告中写完整来源 SHA、包、命令和 `NOT_RUN_IN_A` 清单。未知/新增用例、非预期 Skip、其他失败均不换绿。
- B 的 VM 不继承宿主 HOME、SSH agent、token、Docker socket 或父 CI 文件系统;正向证明 `bwrap` 启动、负向证明假秘密不可见;命名用例必须全部实际 PASS、零 Skip。
- Dev 应用 Backend 与 RDM Go 图 Backend 分别按最终精确 SHA 重放,即使两者测试名相同也不互借结果。新提供方 SHA 使旧 A/B 证据失效。
- 源码获取、模块预取、镜像创建属受信控制面;候选代码只在 A 或 B 的执行面运行。控制面仍须 Review,本计划不声称平台阻止恶意 PR 修改工作流。
- 继续使用已有登记 worktree 和任务卡;Dev 任务中途不重复 precheck。新仓/新登记任务首次写操作前按该仓门禁启动。证据经 `CMD="..." make evidence` 写入,固定后用源码外 `RECEIPT`,来源等级不自升。
- Backend 的 `python-docx` 失败须单独修复并重跑;A/B 不能遮盖它。阶段 7 人工验收、独立 Stage 8 Review、当前 HEAD GATE、#518 队列窗口和原计划 Task 6 仍为后续必经步骤。
## Review Focus
| 最容易漏掉的输入或故障 | 所属任务的钉住测试 |
| --- | --- |
| `bwrap` 可找到但立即退出,秘密不可读测试却误判成功 | Task 1 用假 `bwrap` 非零退出重放负例;Task 5 VM 正向探针 |
| A 排除正则误伤普通 `TestSandbox*` 或新用例自然 Skip | Task 2 的精确清单、`go test -list/-json` 与新增/缺失/Skip 负例 |
| Python 模块只在宿主 `~/.local`,本地外层沙箱无法导入 | Task 3 的空 HOME、隔离 wheelhouse 及 Backend `TestLintErrorContinues` |
| RDM 获取步骤结束后 `make test` 仍在有 socket 的父容器执行 | Task 4 的工作流扫描、真实父子卷/socket 探针与失败关闭 |
| B 的源码包夹带 token、Go 图漂移或某个用例被跳过 | Task 5 的 bundle 污染/篡改/错 SHA/Skip 负例;Task 6 一一映射 |
## File map and interfaces
| 单元 | 文件 | 责任 |
| --- | --- | --- |
| 提供方真实断言 | Backend `internal/tools/exec_backend_test.go`;RDM `internal/rdmapp/rdm_exec_isolation_test.go` | 对必须启动沙箱的负例加入启动成功信号;不改产品执行后端。 |
| A 清单及汇总 | Dev `scripts/nested_sandbox_gate.py`、`policy/nested-sandbox-tests.json`、`scripts/build-composition.py`、`scripts/component-check.py`、`tests/test_nested_sandbox_gate.py`、`tests/test_composition.py`、`tests/test_component_checks.py` | `run_a(source: Path, component: str, commit: str, mode: str, executor) -> dict` 发现精确测试、运行其余 Go 测试并返回可核验报告;`compose` 与 `test-backend-pg` 均公开报告。 |
| 本地 Python 测试工具 | Dev `scripts/python-test-tools.lock`、`scripts/python_test_tools.py`、`scripts/build-composition.py`、`tests/test_python_test_tools.py`;Backend `internal/runtime/llm_verify_test.go` | `prepare_wheels(root: Path, env: dict) -> Path` 在受信阶段下载固定 wheel;`install_for_candidate(executor, wheel_dir: Path, work_root: Path, env: dict) -> dict` 在已隔离环境离线安装,Backend 测试保留已有 `PYTHONPATH`。 |
| RDM CI 子容器 | RDM `scripts/ci_candidate_executor.py`、`scripts/nested_sandbox_gate.py`、`.gitea/workflows/ci.yml`、`Makefile`、`tests/test_ci_candidate_executor.py` | `run_ci_job("build-and-test")` 验证固定 HEAD/卷与子容器负例后运行全部候选命令;RDM A 包装器执行原 `make test` 的 build/web/go/governance 实质检查并公开排除。 |
| B 来源包与 VM | Dev `scripts/prepare_sandbox_replay.py`、`scripts/replay_vm.py`、`scripts/replay-vm.lock.json`、`tests/test_sandbox_replay.py`、`tests/test_replay_vm.py` | `prepare_bundle(dev: Path, rdm: Path, output: Path) -> Path` 输出无凭据固定来源包;`probe_vm(lock: Path) -> dict` 验证 VM;`run_vm(bundle: Path, lock: Path, output: Path) -> dict` 输出逐用例报告。 |
| 双门禁核对和说明 | Dev `scripts/reconcile_sandbox_gates.py`、`tests/test_reconcile_sandbox_gates.py`、`PROJECT.md`、`policy/dependencies.md`、任务卡;Docs 本计划及索引 | `reconcile(a_reports: list[dict], b_report: dict, sources: dict, gates: list[dict]) -> dict` 拒绝缺口;只在全部门禁通过后整理阶段 7 材料。 |
接口冻结:A/B 报告 schema 1 均含 `lane`、`source_sha`、`package`、`command`、`tests`(每项 `name/status`)、`exit_code` 和输出摘要;A 的排除项状态为 `NOT_RUN_IN_A`,B 只接受 `PASS`。环境探针另含 `vm_image_sha256`、`helper_image_id`、`no_socket`、`no_credentials`、`bwrap_started`。字段缺失/重复/未知值即失败。计划执行时先核对最终来源图;这里的旧 SHA 只作为诊断基线。
---
### Task 1: Make false-pass sandbox tests fail when the inner sandbox never starts
**Files:** Backend 修改 `internal/tools/exec_backend_test.go`;RDM 修改 `internal/rdmapp/rdm_exec_isolation_test.go`。分别在现有迁移 feature 分支的登记 worktree 实施,不编辑 Dev `.workspace/` 的 detached 固定检出。
**Interfaces:** 消费现有 `sandboxRun`、`sandboxBackend.Start` 和 `requireBwrap`;不改公开产品 API。产出可在 B 上可靠判定的测试,Backend 新 SHA 由后续 Dev 来源锁采纳;RDM 新 SHA 由后续组合采纳。
- [ ] **Step 1 — 红灯:假 `bwrap` 失败不能使负例通过。** 受信获取阶段把固定 Backend/RDM 源码和 Go 模块缓存放进本次 `CandidateExecutor` 工作根;在该根的 `fake/bin/bwrap` 写入 `#!/bin/sh`、`exit 1`。候选命令仍经外层 Bubblewrap,进入后才把 `fake/bin` 前置 PATH,分别运行 Backend `go test -run '^(TestSandboxHomeHidden|TestSandboxSiblingSessionUnreadable)$' -count=1 ./internal/tools` 和 RDM `go test -run '^(TestRDMExecIsolation_HomeSecretHidden|TestRDMExecIsolation_SiblingSessionUnreadable|TestRDMExecIsolation_CrossProjectUnreadable|TestRDMExecIsolation_CancelKillsSandbox)$' -count=1 ./internal/rdmapp`。先保存真实 FAIL/PASS 输出;现有可能误通过的项须在修改后变为失败。不得在未隔离的宿主上直接运行提供方 `go test`;清理只删除本次临时假二进制。
- [ ] **Step 2 — 最小修正。** 在隔离脚本尾端输出不可由 `bwrap` 启动失败产生的固定 `SANDBOX_SCRIPT_RAN`;对读不到秘密的测试同时断言该信号和预期脚本退出码。Backend sibling 测试须确认内层脚本运行后才接受 `cat` 拒绝;RDM cancel 测试在 `cancel()` 前确认进程仍存活并已写启动信号,立即退出 1 不能冒充成功。示例断言:
```go
if state != "exit=7" || !strings.Contains(out, "SANDBOX_SCRIPT_RAN") {
t.Fatalf("sandbox script did not run: state=%s out=%q", state, out)
}
```
- [ ] **Step 3 — 重跑真假两组。** 假 `bwrap` 必须使上述测试非零;能运行原生 `bwrap` 的环境中,相同用例必须真实 PASS。若本机环境无法满足正例,记 `NOT_RUN` 并留给 Task 5 的独立 VM;不能把仅通过假二进制负例当完成。分别提交 Backend、RDM 变更,任务卡写 SELF 收据和新的完整 SHA。
### Task 2: Account for exact nested tests in gate A
**Files:** Dev 新建 `scripts/nested_sandbox_gate.py`、`policy/nested-sandbox-tests.json`、`tests/test_nested_sandbox_gate.py`;修改 `scripts/build-composition.py`、`scripts/component-check.py`、`tests/test_composition.py`、`tests/test_component_checks.py`。RDM 新建 `scripts/nested_sandbox_gate.py`,在 Task 4 的 CI 子容器中调用。
**Interfaces:** Dev `run_a(source: Path, component: str, commit: str, mode: str, executor) -> dict`;RDM 包装器复用同一报告字段,但只处理 RDM 自身 `internal/rdmapp`。`policy/nested-sandbox-tests.json` 按完整来源 SHA 锁定 Backend `internal/tools` 的十项和 RDM `internal/rdmapp` 的五项;完整 SHA 由最终组合现场填入并核验。
- [ ] **Step 1 — 红灯单测。** 用假 Go `-list`/`-json` 输出覆盖“十项恰好存在”“`TestSandboxOrphanProbeScopesScript` 仍运行”“缺一项”“多一个运行时 `SKIP`”“返回码非零”“来源 SHA 改变”和“空 JSON 流”。预期前两项 PASS,后五项拒绝;RDM 五项也做同样边界。`tests/test_component_checks.py` 增加 `test-backend-pg` 从普通 `go test` 改走 A 报告的断言,确保 CI `test-pg` 不再依赖自然 Skip。先经 `CMD='PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_nested_sandbox_gate tests.test_component_checks -v' make evidence` 留 SELF 红灯。
- [ ] **Step 2 — 实现固定清单解析与精确 `-skip`。** 先用 `go test -list '^Test'` 对受影响包发现用例,再用下方构造的 `command` 运行。`go test` 与发现命令都通过 `CandidateExecutor.run` 执行;解析 JSON 时拒绝命名嵌套项被当 PASS/SKIP、清单项缺失、未清点的运行时 Skip、非零退出。正则和命令由固定清单生成,不接受调用者传入的宽泛前缀:
```python
pattern = "^(?:" + "|".join(re.escape(name) for name in sorted(expected_names)) + ")$"
command = ["go", "test", "-json", "-count=1", "-skip", pattern, "-mod=" + mode, "./..."]
```
- [ ] **Step 3 — 接入组合器和 PG 门禁。** `run_provider_tests` 对 Backend 和可选 RDM 来源调用 `run_a`,其他 Go 模块仍执行原完整 `go test`/`go vet`。将 A 报告写入最终 `resolved-manifest.json` 并在标准输出逐项打印;`--rdm-source` 存在时 RDM Backend 行和 RDM 行均须绑定实际 Go 图版本。`component-check.py` 的**固定来源** `test-backend-pg` 路径也通过同一 `run_a` 执行真实 PG/Redis 环境下的 Backend 全套其余测试:受信获取用原 `process_environment`,候选执行改用与 `execution_environment` 等价的无凭据环境;本地在 `check_sources` 的临时快照根进入 `CandidateExecutor`,CI 子容器核验只读 `ci_child` 身份。可编辑/`LOCAL_OVERLAY=1` 的开发者检查不冒充正式 A,原工作区语义不改。`svc-realcombo` 只构建/跑生命周期,没有嵌套 Go 测试,仍保留原 job 及 socket 负例。缺报告时对应命令非零,不发布成功清单。
- [ ] **Step 4 — 回归、提交。** 跑目标 Python 单测、`make test-governance`、组合 fixture,检查固定 Backend 真源的 `-list` 输出与清单一致;`make test` 可在 Task 3 完成后再作为完整门禁。Dev 提交后仍不推送为阶段 7 候选;RDM 包装器与其独立单测随 Task 4 的 RDM 提交。
### Task 3: Provide Python test tools inside the local sandbox
**Files:** Dev 新建 `scripts/python-test-tools.lock`、`scripts/python_test_tools.py`、`tests/test_python_test_tools.py`;修改 `scripts/build-composition.py`、`tests/test_composition.py`、`PROJECT.md`;Backend 修改 `internal/runtime/llm_verify_test.go`。
**Interfaces:** `prepare_wheels(root: Path, env: dict[str,str]) -> Path` 仅在受信获取阶段下载 wheel;`install_for_candidate(executor: CandidateExecutor, wheel_dir: Path, work_root: Path, env: dict[str,str]) -> dict[str,str]` 在外层沙箱内用 `--no-index --no-deps --only-binary=:all:` 安装到本次临时目录并返回只指向该目录的 `PYTHONPATH`。不得读取宿主 `~/.local`。
- [ ] **Step 1 — 写红灯。** 在现有 `CandidateExecutor` 外层隔离中使用空 `HOME`、遮蔽宿主 `~/.local`,验证 `python3 -c 'import docx,pptx,openpyxl'` 初始失败;伪造 wheel 缺失/散列变动时 `prepare_wheels` 必须非零。再通过隔离组合器运行固定 Backend `TestLintErrorContinues`,记录当前缺 `docx` 的失败 SELF。该 Go 测试现以 `t.Setenv("PYTHONPATH", abs)` 覆盖执行环境,所以仅为组合器加 `PYTHONPATH` 不够;测试修复也在本任务中。
- [ ] **Step 2 — 固定工具图。** 锁文件写入 `python-docx==1.2.0`、`python-pptx==1.0.2`、`openpyxl==3.1.5`、`lxml==6.1.3`、`Pillow==12.3.0`、`et-xmlfile==2.0.0`、`typing-extensions==4.16.0`、`XlsxWriter==3.2.9`;受信阶段以 `["python3","-m","pip","download","--only-binary=:all:","--dest",str(wheel_dir),"-r",str(root/"scripts/python-test-tools.lock")]` 获取,记录每个 wheel SHA256 与 Python ABI,并拒绝源码发行包或额外分发物。若任一目标 ABI 无这些版本的 wheel,停下修订锁,不从宿主 site-packages 偷渡。
- [ ] **Step 3 — 隔离安装与测试环境。** 候选阶段按以下 argv 安装固定 wheel,`executor` 保证实际 pip 在外层沙箱中执行;仅把 `pydeps` 加进候选 `PYTHONPATH`。Backend `TestLintErrorContinues` 设置 house-style 路径时改为拼接已有 `PYTHONPATH`,保持两个路径可见:
```python
fixed_root = work_root / "Dev-test-root"
argv = ["python3", "-m", "pip", "install", "--no-index", "--no-deps",
"--only-binary=:all:", "--target", str(work_root / "pydeps"),
"--find-links", str(wheel_dir), "-r", str(fixed_root / "scripts/python-test-tools.lock")]
executor.run(argv, work_root, env)
```
```go
original := os.Getenv("PYTHONPATH")
if original != "" {
abs += string(os.PathListSeparator) + original
}
t.Setenv("PYTHONPATH", abs)
```
在外层 Bubblewrap 内探测 `import docx,pptx,openpyxl` 和 `TestLintErrorContinues`;CI 子容器也用相同锁和摘要,不能因为镜像偶然预装就跳过固定工具图。
- [ ] **Step 4 — 记录通过或失败。** 跑目标测试、组合 fixture 和完整 `make test`;后者除 Task 2 明确排除外仍失败则按真实原因修复,不把 Python 工具正例扩大成全部门禁通过。提交 Dev 变更。
### Task 4: Isolate RDM's own CI candidate step
**Files:** RDM 新建 `scripts/ci_candidate_executor.py`、`scripts/nested_sandbox_gate.py`、`tests/test_ci_candidate_executor.py`;修改 `.gitea/workflows/ci.yml`、`Makefile`。Dev `scripts/ci_candidate_executor.py` 和 `tests/test_ci_credentials.py` 作为已验证模式的参考,不从 RDM 的工作流直接执行 Dev PR 的未固定脚本。
**Interfaces:** `run_ci_job("build-and-test") -> int` 为 RDM CI 获取步骤后唯一候选入口;`inspect_workspace_volume(parent_id: str) -> str`、`build_executor_image() -> str`、`create_identity(volume: str) -> Path` 分别验证命名卷、固定镜像及只读身份标记;`nested_sandbox_gate.run_rdm_a(source: Path, commit: str, executor) -> dict` 返回 Task 2 schema。子容器的 `COMPOSITION_ROOT` 指向受信预取的固定 Dev 源;原 `make test` 的产品构建、`test-web`、`test-governance`、Go 包测试均在子容器执行。
- [ ] **Step 1 — 红灯负例。** 测试扫描 RDM 工作流:最后候选步骤若含直接 `GOWORK=off make test` 即失败;假 Docker inspect 返回非命名卷、父子 SHA 不一致、子挂载 socket/HOME、只读标记可改写、缺 Go/Node 工具时均失败。先跑 `python3 -B -m unittest tests.test_ci_candidate_executor -v` 记录红灯。
- [ ] **Step 2 — 受信启动器。** 验证 `GITHUB_REPOSITORY=weichen/Agent-System-RDM`、平台 SHA=检出 HEAD、父容器卷和固定工具镜像 ID;以 `docker run --rm --cap-drop ALL --security-opt no-new-privileges --read-only --tmpfs /tmp:rw,exec,nosuid,nodev` 启动只挂工作卷与只读身份标记的子容器,不挂 socket/`/var/run/act`/父 HOME。启动前用假私有文件、socket、工作卷写入和网络探针做正反例;探针失败不得回落父进程执行。镜像在空上下文安装固定工具,不执行候选源码。
```python
repo = os.environ.get("GITHUB_REPOSITORY", "")
platform_sha = os.environ.get("GITHUB_SHA", "")
actual_head = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()
parent_id = os.environ.get("HOSTNAME", "")
if actual_head != platform_sha or repo != "weichen/Agent-System-RDM":
raise CIExecutorError("RDM CI source identity differs from platform")
volume = inspect_workspace_volume(parent_id)
image_id = build_executor_image()
identity_path = create_identity(volume)
workspace_volume_mount = (
f"type=volume,source={volume},target=/workspace/weichen/Agent-System-RDM")
readonly_identity_mount = (
f"type=bind,source={identity_path},target=/run/agent-system-candidate/identity,readonly")
candidate_job_script = JOB_SCRIPTS["build-and-test"]
argv = ["docker", "run", "--rm", "--cap-drop", "ALL",
"--security-opt", "no-new-privileges", "--read-only",
"--tmpfs", "/tmp:rw,exec,nosuid,nodev",
"--mount", workspace_volume_mount,
"--mount", readonly_identity_mount, image_id,
"bash", "-lc", candidate_job_script]
probe_child(argv)
subprocess.run(argv, check=True)
```
- [ ] **Step 3 — A 的 RDM 用例报告。** 子容器先运行产品构建、web 和治理检查,再由 `run_rdm_a` 对五项精确 `NOT_RUN_IN_A`,其余 Go 测试和 `go vet` 完整执行;报告绑定 RDM 最终 SHA 并输出。若 Dev 产品组合器需要 `ci_child` 身份,同一子容器只读标记须通过其内核/挂载校验。加入 RDM `make test-governance` 对本启动器单测的调用。
- [ ] **Step 4 — 真实机制和平台验证。** 用一次性命名 Docker 卷重放 RDM 父子挂载/socket 正反例,随后在 RDM PR 的**新 HEAD** 运行 `build-and-test` GATE;保存工作流 run URL、HEAD、作业身份与完整输出。CI 未在真实 runner 通过则 RDM 仍 WIP,不能引用 run3130 放行。提交并推送现有 RDM 迁移 PR,按该仓首次/恢复门禁操作。
### Task 5: Export credential-free fixed sources and replay them in a VM
**Files:** Dev 新建 `scripts/prepare_sandbox_replay.py`、`scripts/replay_vm.py`、`scripts/replay-vm.lock.json`、`tests/test_sandbox_replay.py`、`tests/test_replay_vm.py`;修改 `PROJECT.md`。Task 1 的原生正例最终由此任务证明。
**Interfaces:** `prepare_bundle(dev: Path, rdm: Path, output: Path) -> Path` 读取 Dev 应用锁与 RDM Go 图,输出每个来源的完整 SHA、`go.mod/go.sum` 摘要、vendor 树摘要和无 `.git` 的只读 tar;`probe_vm(lock: Path) -> dict` 输出实际 VM/`bwrap` 正例;`run_vm(bundle: Path, lock: Path, output: Path) -> dict` 产生 B 报告。
- [ ] **Step 1 — 红灯安全测试。** 假 bundle 含 `.git/config`、形如 `ACCESS_TOKEN=` 后接 40 位十六进制的真实值、SSH 私钥头、符号链接越界、错来源 SHA、缺 vendor 或篡改 SHA256 时拒绝;普通文档中的文字 `ACCESS_TOKEN=PLACEHOLDER` 不应误报。假 VM 控制台报告缺 `bwrap_started`、含 socket、命名用例 `SKIP`、缺一个用例或把两个 Backend SHA 混成一个时拒绝。先跑目标 Python 单测保存红灯。
- [ ] **Step 2 — 固定且无凭据的来源包。** 复用已审阅的 Dev `transport.environment`/`export_source`/`go_dependencies`/`provider_revision` 获取 Dev Backend 与 RDM Go 图 Backend、RDM 自身;每一来源分别 `go mod vendor`,在目录外核验 `go list -m -json all` 与清单。归档仅白名单源码、vendor、`go.mod`/`go.sum`、manifest;逐 tar entry 拒绝绝对路径、`..`、符号链接和 Git/config/凭据文件;最终受信步骤关闭 token 环境后再交 VM。两个 Backend SHA 即使一样也保留两个逻辑角色记录。
- [ ] **Step 3 — 建立任务专用 VM 基础镜像。** 在没有候选代码和凭据的受信 helper 中,以固定 `ubuntu@sha256:534baea6a22c03a63003dbc8dbe78fe34bc0d7e595d9a9dc9834884ff530eb55` 构建含 QEMU、`cloud-localds`、`gpgv` 的工具镜像;下载 Ubuntu Noble 官方 cloud image、`SHA256SUMS` 与签名,按 [Ubuntu 官方镜像校验方法](https://documentation.ubuntu.com/security/software-integrity/image-verification/) 验签与逐文件 SHA256,将最终镜像散列、helper image ID 和 Go 1.27.1 工具散列写入 `replay-vm.lock.json`。首次联网预置 VM 仅装公开工具 `bwrap`、Go、bash、Git;Go tar 包须匹配 [Go 官方发布 SHA256](https://go.dev/dl/) `63d339f0da5ab53635a56f2490a7984dfe12dfcff22ad749f63edaf590168445`。受信 helper 用 `cloud-localds` 生成 NoCloud seed,预置 user-data 只建无密码 `replay` 用户、安装公开工具、验证 `go version go1.27.1 linux/amd64`、输出 `REPLAY_BASE_READY` 并关机;准备好后锁定只读基础磁盘,每次测试从独立 qcow2 overlay 启动。预置阶段没有仓库源码或 token。若签名、版本或工具链散列不符,失败并保留诊断,不改宿主策略。
```bash
curl -fsSLo go1.27.1.linux-amd64.tar.gz https://go.dev/dl/go1.27.1.linux-amd64.tar.gz
echo '63d339f0da5ab53635a56f2490a7984dfe12dfcff22ad749f63edaf590168445 go1.27.1.linux-amd64.tar.gz' | sha256sum -c -
cloud-localds prep-seed.img prep-user-data prep-meta-data
```
- [ ] **Step 4 — 实际 VM 正负探针。** 用受信 Docker CLI 启动**不挂 socket/HOME**的 QEMU helper,仅传 `--device=/dev/kvm`、限定内存/CPU 和任务临时磁盘;B 测试启动时 `--network=none`。[Docker 官方文档](https://docs.docker.com/engine/containers/run/)允许用 `--device` 单独传设备,无须 `--privileged`。QEMU 仅以 `-virtfs local,path=/replay/source,mount_tag=source,security_model=none,readonly=on` 把来源包只读给 guest,按 [QEMU 9p 只读参数](https://www.qemu.org/docs/master/system/invocation.html)核对。guest 以非 root `/home/replay` 用户运行,先验证 socket/凭据不存在、假秘密在内层 `bwrap --unshare-user --unshare-pid` 中不可见、正向脚本确实输出 `BWRAP_OK`;任何设备、namespace、安装或启动失败都停在 B=`NOT_RUN/FAIL`,不试 `--privileged`、改 AppArmor 或把测试移回主机。
```python
source_bundle_readonly_mount = (
f"type=bind,source={bundle.parent.resolve()},target=/replay/source,readonly")
task_scratch_mount = (
f"type=bind,source={scratch.resolve()},target=/replay/scratch")
readonly_9p_arg = "local,path=/replay/source,mount_tag=source,security_model=none,readonly=on"
ephemeral_guest_disk_arg = "file=/replay/scratch/overlay.qcow2,if=virtio,format=qcow2"
serial_output_arg = "file:/replay/scratch/serial.log"
helper_image_id = lock["helper_image_id"]
docker_argv = ["docker", "run", "--rm", "--device=/dev/kvm",
"--cap-drop=ALL", "--security-opt=no-new-privileges",
"--network=none", "--memory=6g", "--cpus=4",
"--mount", source_bundle_readonly_mount,
"--mount", task_scratch_mount, helper_image_id,
"qemu-system-x86_64", "-machine", "q35,accel=kvm",
"-m", "4G", "-smp", "2", "-display", "none",
"-nic", "none", "-virtfs", readonly_9p_arg,
"-drive", ephemeral_guest_disk_arg,
"-drive", "file=/replay/scratch/replay-seed.img,if=virtio,format=raw,readonly=on",
"-serial", serial_output_arg]
subprocess.run(docker_argv, check=True)
```
- [ ] **Step 5 — 离线逐用例重放。** guest 从只读 9p 包复制到自己的临时磁盘,以 `GOWORK=off GOPROXY=off GOSUMDB=off` 对两份 Backend 角色分别运行 `go test -mod=vendor -json -count=1 -run` 加十项名字组成的锚定正则、包路径 `./internal/tools`;再以五项名字组成的锚定正则对 RDM `./internal/rdmapp` 运行。正则由 Task 2 的固定清单使用 `re.escape`、`join` 生成,不接受自由文本。输出完整 JSONL、每项 PASS/FAIL/SKIP、包退出码和三个来源完整 SHA。受信宿主解析控制台结果形成 B schema,零 Skip/零缺项才返回 0;VM 退出即销毁本次临时磁盘,原始脱敏输出留在源码外收据。
- [ ] **Step 6 — 独立重放。** 作者可先以 SELF 调试,随后交由独立审阅者在其受控环境按同一工具和固定 bundle/lock 重跑,记 `REPLAY`(身份、环境摘要、命令、完整 SHA、输出);若将来有可验证身份的独立 CI,可记 `GATE`。没有独立结果时 B 仍 `NOT_RUN`,此任务不能标交付完成。提交 Dev 工具与测试,但不得在作者提交中伪填 REPLAY。
### Task 6: Reconcile both gates and resume the original final-delivery plan
**Files:** Dev 新建 `scripts/reconcile_sandbox_gates.py`、`tests/test_reconcile_sandbox_gates.py`;修改 `PROJECT.md`、`policy/dependencies.md`、`.task/feature-dependency-workspace.md`、`components.lock.json`;Docs 更新本计划/索引状态。RDM 任务卡与 PR 引用最终证据。
**Interfaces:** `reconcile(a_reports: list[dict], b_report: dict, sources: dict, gates: list[dict]) -> dict` 只在每个 A 的 `NOT_RUN_IN_A` 对应同来源 B `PASS`、三个 Dev CI context 与 RDM CI 均为最终 SHA GATE、B 为独立 REPLAY/GATE、其余正式检查成功时返回 `{"result":"PASS"}`;否则返回明确非零和差异表。`gates` 的运行身份、HEAD、状态和链接须从 Gitea API 的受信读取得到,不能从候选报告自填。
- [ ] **Step 1 — 红灯矩阵。** 输入旧 run3130、旧 Dev run3076、B=`NOT_RUN`、错 Backend SHA、同名用例跨 SHA 借用、RDM CI 无 socket 证据、任何多余 Skip、缺 `python-docx` 修复或 A 失败,分别断言拒绝;两份精确对象、环境探针和逐用例均正确时才 PASS。先留 SELF 红灯。
- [ ] **Step 2 — 实现核对与任务卡。** 从 Dev `resolved-manifest.json`、Gitea API 的最终 Dev/RDM CI 运行及 B 独立收据读取固定对象,不接受人工手抄通过值;按 `(source_sha, package, test_name)` 做一一映射并打印可读差异。受信 API 读取步骤只输出作业名、SHA、状态、runner 身份和 URL,不把 token 传入候选或报告。`make evidence` 写完整脱敏输出;文档说明普通 clone 的 A 门禁、VM 的 B 门禁、现有限制和故障恢复,不把测试原生 Skip 写成通过。
```python
missing = {(r["source_sha"], r["package"], t["name"])
for r in a_reports for t in r["tests"]
if t["status"] == "NOT_RUN_IN_A"}
passed = {(r["source_sha"], r["package"], t["name"])
for r in b_report["runs"] for t in r["tests"]
if t["status"] == "PASS"}
if missing - passed or any(r["exit_code"] != 0 for r in a_reports):
raise GateMismatch("A exclusions lack exact-source B passes")
required_jobs = {"test", "test-pg", "svc-realcombo", "rdm/build-and-test"}
verified_jobs = {g["job"] for g in gates
if g["status"] == "success" and
g["source_sha"] == sources[g["repository"]]["commit"] and
g["runner_identity_verified"]}
if verified_jobs != required_jobs:
raise GateMismatch("Final-source CI gates are incomplete")
```
- [ ] **Step 3 — 完成原计划 Task 5/6 的新候选。** Docs 书面设计与本计划批准版本锁进 Dev;Guidelines 仅在通用规则确实有缺口时变更。按 `policy/merge-queue.md` 核对 #518 窗口后,逐仓同步主干、处理在途分支冲突并更新来源图;以最终 HEAD 跑 Dev `make test`/`make build`、RDM 产品与 CI、三个 Dev 必需 CI、普通 clone/Native 按需编辑、受影响浏览器和 B 重放。每个失败保持 FAIL/NOT_RUN,绝不靠旧 SHA 结果补位。
- [ ] **Step 4 — 重新走评审和合入。** A/B 与所有原有门禁都通过后才提交新的阶段 7 材料给用户评审;通过后由独立审阅者执行 Stage 8 重放/Review,修复问题再以新 HEAD 重验。依既有队列和 PR 规则逐仓合入、闭卡;实际工作区回收、发布/部署及宿主/共享 runner 改动仍受原授权边界限制,不因本计划自动实施。
## Execution handoff
本计划只提供实施步骤和停止条件。先由用户审阅本计划;用户已为原整改计划选择“本会话逐项实施”,若本修订计划获批且未改选方法,继续以当前会话逐项实施。Task 5 的独立 VM 可用性须先有真实正例;若无法实现,停止并回到设计评审,保留全部 WIP 和失败证据。
@@ -0,0 +1,54 @@
# 版本依赖迁移:嵌套沙箱测试的双必需门禁修订设计 r1
状态:**书面设计 `1790cf8` 已获用户批准,实施计划待评审;尚未批准实施**(2026-10-14)。用户批准以“双必需门禁”编制[修订实施计划](2026-10-14-versioned-dependency-nested-sandbox-amendment-plan.md),该回复不批准独立环境配置或代码改动。本文件补充已批准的[独立 Review 整改设计](2026-10-14-versioned-dependency-review-remediation.md)及其[实施计划](2026-10-14-versioned-dependency-review-remediation-plan.md)。Dev PR #537 等迁移 PR 保持 WIP。
## 触发事实与目标
Dev 候选 `1ea93a5f7254f5a4bac8fc65cd47dad9f9e51ea9` 的本地正式 `make test` **失败**(SELF 收据 `/tmp/dependency-task5-dev-test-20261014-01a1345f.json`):外层 Bubblewrap 已遮蔽宿主凭据,但 Backend 的真实沙箱集成测试再启动内层 `bwrap` 时返回 `No permissions to create a new namespace`。外层进程的 AppArmor 标识为 `bwrap//&unpriv_bwrap (enforce)`;最小嵌套探针复现失败。无 Docker socket、丢弃 capabilities 的独立 Docker 子容器探针也未能运行 `bwrap`;放宽该探针的 seccomp 或 AppArmor 配置仍失败。这些只说明**已试环境**不具备嵌套能力,不证明所有容器或独立主机都不可用。
同一次 `make test` 还出现 Backend runtime 测试缺少 `python-docx` 的失败;它是另一个未解决问题,不能因本修订设计而计作通过。本地 `make build` 在绕过本机 npm 代理故障后于同一 Dev SHA 通过,RDM `d8a7098` 的固定产品构建及其自身 CI run3130 通过;这些结果均不替代失败的 Dev 测试或下述独立重放。RDM 当前工作流在获取步骤后直接执行 `GOWORK=off make test`,没有该作业候选进程的无 socket 负例,因此 run3130 也不能证明 RDM CI 的执行隔离。
目标是在不暴露本机凭据、不把 Docker socket 交给候选代码、不改主机 AppArmor 或共享 runner 的前提下,同时证明普通组合功能及真实执行沙箱的产品语义。所有门禁绑定实际 Dev、Backend、RDM 固定提交;旧 SHA 的成功记录不迁移给新候选。
## 方案选择
| 方案 | 结论 |
| --- | --- |
| 在现有本地/CI 候选沙箱中强行嵌套 `bwrap` | 已试环境失败。若提升当前候选容器权限或改主机策略,会改变已批准的凭据与执行边界;本轮不采用。 |
| 在主门禁隐藏 `bwrap` 或让测试自然 Skip,并把主门禁绿色当成完整通过 | 会漏掉沙箱逃逸、进程限制、overlay 等真实行为;不采用。 |
| **双必需门禁**:现有隔离主门禁 + 独立、无凭据的原生沙箱重放 | 用户同意作为本轮书面设计基线。两份证据齐全、同源且通过,才可请求阶段 7 验收与后续合入。 |
### 门禁 A:隔离的组合验证
Dev 本地 `make test/build` 仍在现有 Bubblewrap 内运行;Dev CI 的 `test`、`test-pg`、`svc-realcombo` 仍由受信父步骤启动无 Docker socket 的候选子容器。RDM 自身的 `make test`/CI 也计入 A,须给出相同的用例状态报告及候选进程的 socket/凭据负例;在受信获取后直接于原作业容器执行候选 Makefile 的现状不能作为隔离证据。实施计划须为 RDM 候选步骤采用无 socket 子容器,或在同等受信边界下证明其执行环境天然无 socket、无凭据。其环境若能实际运行单层 `bwrap`,可记录真实 PASS,但不能免除 B 的独立重放。不得关闭外层隔离、向子容器挂载父 HOME/密钥/socket、增加特权,或让候选命令在受信父步骤直接运行。
主门禁只对**需要再次创建用户/挂载/PID namespace 的真实沙箱集成用例**实行显式的、有界的 `NOT_RUN_IN_A` 选择。Go 测试报告必须列出固定源码 SHA、包、每个排除的测试名及原因,核对源码中的实际测试清单,并证明其余测试实际运行且通过。嵌套用例清单出现缺失、新增、重命名、意外跳过或排除规则扩大,或主门禁出现其他失败时,A 失败。不能用 `bwrap` 缺席后 Go 的普通 Skip 作为“全部通过”,也不能在汇总中隐去这些用例;现有源测试若无法表达此状态,实施时增设可机器检查的清单/报告,而不是修改测试来伪造绿灯。
当前最小清单以**源码复核**为准:Dev 固定 Backend `48649a37ee92373c639a384a2c5a9dfaa2ec4bbb` 的 `internal/tools` 含 `TestSandboxHomeHidden`、`TestSandboxSiblingSessionUnreadable`、`TestSandboxNoNetwork`、`TestSandboxForkBombContained`、`TestSandboxOrphansDieWithNamespace`、`TestSandboxTimeoutKills`、`TestSandboxSessionWriteVisible`,以及 `TestSandboxWorkspaceOverlayRoundtrip`、`TestSandboxWorkspaceOverlayTimeoutDiscards`、`TestOverlayCleansShellDir`。RDM `d8a70988492ad0aad02768badc85502cc8c77820` 的 `internal/rdmapp` 含 `TestRDMExecIsolation_HomeSecretHidden`、`TestRDMExecIsolation_SiblingSessionUnreadable`、`TestRDMExecIsolation_CrossProjectUnreadable`、`TestRDMExecIsolation_ProcLimit`、`TestRDMExecIsolation_CancelKillsSandbox`。参数组装、策略选择、硬关闭及不启动 `bwrap` 的 `TestSandboxOrphanProbeScopesScript` 仍在 A 中运行,不能按名称前缀整组排除。若最终 RDM 组合或其他固定提供方实际包含更多启动 `bwrap` 的用例,须先更新可核验清单并经 Review,不能让它们自然 Skip。
### 门禁 B:原生沙箱独立重放
另在**独立、无凭据、无 Docker socket** 的短生命周期主机或 VM 上运行上述真实集成用例。该环境不能继承开发者 HOME、SSH agent、Gitea/npm token、父 CI 文件系统或 Docker socket;由受信获取步骤先固定并验证源码和所需模块,再以不含凭据的包交给重放环境。环境可访问所需本地测试依赖,但不得靠给现有 A 容器提权来实现。当前尚无可用环境的正例,因此 B 状态是 `NOT_RUN`,也没有“预计会通过”的承诺。实施计划须先安排最小环境探针并决定可重复执行的交付方式;若无法建立该环境,本轮迁移保持 WIP。
B 开始测试前须验证:`bwrap` 能实际启动目标 namespace、内部 `/home` 与外部假标记隔离、宿主/父凭据及 Docker socket 不存在;只看 `command -v bwrap` 不够。运行固定 SHA 的真实测试时,采用 `go test -json -count=1` 或等价逐用例记录,核对预期测试均存在、实际 `PASS` 且没有 `SKIP`;测试包退出码和正向沙箱启动探针都必须为零。现有若干“秘密不可读”断言在 `bwrap` 根本没启动时仍可能误判,因此 B 的正向启动证据是这些负例成立的前提,必要时加强测试本身。任何命名用例失败、跳过、缺失或探针失败,B 失败。
B 至少覆盖两个不同的 Backend 对象:Dev 应用清单的 `48649a37...`,以及 RDM 产品所用 Go 模块图的 Backend 固定提交(当前产品构建清单为 `f1302ed...`);还须覆盖 RDM 自身提交 `d8a7098...` 的隔离用例。最终 SHA 由候选清单和 Go 图现场解析,不能只按这里的前缀代替核验。不同 Backend 提交分别运行、分别记录,即使测试名相同也不互借结果。提供方提交或测试清单改变时,A 与 B 的旧结果一并失效。
重放由独立审阅者操作并以 `REPLAY` 记录其身份、环境身份/配置摘要、源码完整 SHA、清单/模块图、命令、退出码、逐用例结果和完整脱敏输出;若以后由可验证身份的受信 CI 运行,可记为 `GATE`。作者自测只能记 `SELF`,不能把本地收据改标签充当独立证据。无凭据状态用假标记与环境/挂载检查证明,不读取或输出真实密钥值。
## 汇总、恢复与授权边界
现有三个 Dev CI context 必须在**最终 Dev HEAD** 通过,并在产出物中显式声明 A 的 `NOT_RUN_IN_A` 清单;RDM 当前 HEAD 的产品构建与 CI 也须重新核验其候选进程边界和用例报告。协调者对最终 Dev/Backend/RDM 固定 SHA 建立一份门禁对照:A 的 GATE 身份与链接、B 的独立 REPLAY/GATE 身份与完整收据、每个排除用例到 B 通过用例的一一映射,以及其余 `make test/build`、浏览器和交付检查的真实状态。B 不是把 A 的失败改名为通过;A 中除约定清单外的任何失败(包括当前 `python-docx` 问题)都须正常修复并重跑。
只有 A、B 和其他原有必需检查在同一最终来源图上全部通过,才整理新的阶段 7 正式验收材料、请求用户审阅;之后仍须独立 Stage 8 Review、最新 `task-sync`、当前 HEAD CI 和队列窗口核验,再按现有规则合入。B 为 `NOT_RUN`、身份不可核、结果含 Skip、源码不匹配、环境不满足边界或 A 未绿时,明确阻塞验收与合入,不用旧 run3130、旧 Dev run3076 或口头结果补位。PR 保持 WIP,保留失败收据和现场以便重试。
本文件不授权创建/变更共享 runner、改主机 AppArmor、扩大 CI token 权限、改保护规则、发布、部署、删除来源引用、回收实际工作区或更改迁移的语言版本权威。独立 VM/主机的提供方式、成本、来源交付及身份校验须在本文件获批后的实施计划中写清并经用户评审;若方案需要上述更高权限操作,先停在设计阶段另行审议。
## 书面设计的验收线
1. 普通 Dev clone 的正式组合仍须证明本地 Bubblewrap 隔离;Dev CI 三个必需 job 仍须证明无 socket 子容器且候选命令不在父容器执行。RDM CI 的候选测试同样须证明无 socket、无凭据执行边界。
2. A 的清单精确区分真实嵌套用例与可在外层运行的测试,并公开 `NOT_RUN_IN_A`;未知/新增测试不能默认排除。当前 `make test` 失败保持失败记录,直到非嵌套问题被修复并获新证据。
3. B 在无凭据、无 socket 的独立环境中先证明原生 `bwrap` 真正启动,再在最终两个 Backend 来源及 RDM 来源上逐用例零 Skip 通过;新 SHA 必须重放。
4. 阶段 7/8 和合入材料同时给出 A 与 B 的命令、对象 SHA、输出及来源等级;任一缺失即停止。
本文件是设计,不是实施计划;具体脚本入口、测试选择器、无凭据来源包和环境探针的实现步骤,待书面设计评审通过后另编计划。
@@ -0,0 +1,281 @@
# Versioned Dependency Review Remediation Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 关闭 Dev PR #537 独立 Review 的三项 Important 阻塞:本地/CI 候选执行访问宿主凭据、固定规范缓存失败后不能重试、固定 `.workspace/` 使普通任务无法安全回收。
**Architecture:** 受信获取阶段先固定、预取并验证源码;本地候选命令进入 Bubblewrap,CI 候选命令进入由受信父步骤启动且不挂 Docker socket 的只读子容器。缓存使用唯一暂存目录和同目录原子发布;`task-finish` 只认可经登记并逐项核验的可再生固定工作区。三个修复可独立测试,最终在同一 Dev 候选和三项必需 CI 上组合验证。
**Tech Stack:** Python 3 标准库、Git、Bubblewrap、Docker/Gitea Actions、Go 1.27.1、Node 22、npm、现有 Make/evidence/真实临时仓测试。
**Spec:** [用户已批准的整改设计 r1](2026-10-14-versioned-dependency-review-remediation.md),Documents `68c658a0917ee308bb69d6f46156bc165c713d6c`;原[依赖迁移方案 r1](2026-10-13-versioned-dependencies-and-workspaces.md)及[实施计划](2026-10-13-versioned-dependencies-implementation-plan.md)继续约束仓库/版本边界。
**Authority and current state:** 用户已批准书面设计和本计划 `20ada84`,选择本会话逐项实施。Task 1–4 的本地代码、回归与 Dev 提交已完成;Task 5 的文档/来源锁对齐进行中,Task 6 的新 HEAD 平台 GATE、阶段 7 人工验收、Stage 8 独立复审与合入尚未完成。Dev 旧候选 `6a2f7cd3e610a263b6348efe9084e386dc88f30c` 的 run3076、RDM run3077 和阶段 7 人工结论不放行新 HEAD。14 个迁移 PR 保持 WIP,#518 无活动 Dev 窗口;本计划不授权发布、部署、业务数据、历史改写、删除来源引用、改变共享 runner/分支保护或任务工作区清理。
**执行修订(2026-10-14):** 新候选 `1ea93a5` 的正式 `make test` 遇到嵌套 Bubblewrap 阻塞,另有 `python-docx` 缺件。用户已批准[双必需门禁书面设计 `1790cf8`](2026-10-14-versioned-dependency-nested-sandbox-amendment.md),其[实施计划](2026-10-14-versioned-dependency-nested-sandbox-amendment-plan.md)待评审。Task 5/6 的完成条件现须同时满足修订计划;在计划未获批准、A/B 缺证据时,不按旧步骤推进阶段 7 或合入。
## Global Constraints
- 正式 Go 构建仍是 `GOWORK=off`、`-mod=readonly`,Go 实际版本以消费方图为准;npm 锁、应用/规范/资料来源锁分别保留唯一权威,不用本地 replace 或 latest 兜底。
- 本地正式候选执行强制 Bubblewrap;不可用或负例失败立即非零退出。CI `test`、`test-pg`、`svc-realcombo` 保留原必需 job/context,全候选命令经无 Docker socket 子容器;无直接执行回退。
- 受信编排器与工作流仍须经代码 Review。本轮不宣称恶意 PR 无法修改受信工作流;平台级工作流来源/runner/token/fencing 保持 `NOT_RUN` 或 manual,不能把环境变量清理当 OS 隔离。
- 组件旧目录、`.git`、refs、reflog、未保存文件及其他活动 worktree 不清理;规则缓存异常与 editable 源码不强删。实际 task-finish 仍需 owner 在阶段 10 确认写者退出和相应清理授权。
- 现有 Dev 任务 worktree 已登记并做过首次 precheck;实施中不重复启动检查或新建同任务 worktree。需要最终交付时先在 #518 重新领取窗口,`make task-sync` 后以新 HEAD 重验。
- 所有进入任务卡/PR 的命令输出由 `make evidence` 原样记录,固定后用源码外唯一 `RECEIPT`;SELF、REPLAY、GATE 和 SKIP/NOT_RUN 分开标注。治理脚本改动必须跑 `make test-governance`。
## Review Focus
以下五类输入即使原方案未逐字枚举,也应符合合理使用者的预期;每项都由下方所属任务的真实测试钉住:
1. **绝对路径和伪造沙箱标记**:候选脚本知道 `宿主开发者 HOME 下的私钥绝对路径` 或自行设置 `CI_SANDBOX=1`,仍不能绕过本地隔离;Task 1 的假秘密文件/伪标记负例。
2. **父 CI 挂载和工具缺失**:runner 父容器有 Docker socket,子容器缺 Go/Node 或挂错卷时必须失败,不能在父容器直接跑候选;Task 2 的真实 Docker 卷/Socket 正反例及工作流扫描。
3. **中途断开的 Git 获取**:规则 clone 建了目录但 fetch/checkout 失败,下一次 precheck 能从新暂存目录重试且保留旧现场;Task 3 的临时远端中断/重试与异常目标拒绝用例。
4. **干净生成目录与唯一文件混居**:`.workspace` 只有已登记固定检出时可完成普通任务;多出一个 ignored 文件或活动编辑分支时必须保留整个现场;Task 4 的真实 worktree 正反例。
5. **跨仓或主干在等待期间推进**:旧 `6a2f7cd` 的绿灯、Docs 锁祖先或某提供方旧 CI 不可自动升级成新组合准入;Task 6 逐仓远端/完整 SHA 检查及最终同步失败出口。
## File map and interfaces
| 单元 | 精确文件 | 职责与交界 |
|---|---|---|
| 本地执行包络 | Dev 新建 `scripts/candidate_sandbox.py`;修改 `scripts/build-composition.py`、`Makefile`、`PROJECT.md`;测试 `tests/test_candidate_sandbox.py`、`tests/test_composition.py` | `CandidateExecutor.run(command: list[str], cwd: Path, env: dict[str,str]) -> subprocess.CompletedProcess[str]` 执行候选子进程;`verify_candidate_envelope() -> Literal["local_bwrap", "ci_child"]` 核实当前内核/挂载身份;原 `run()` 保持受信获取用途。`compose()` 在获取阶段建立本次独立缓存,再把固定根传给执行包络。 |
| CI 无 socket 子容器 | Dev 新建 `scripts/ci_candidate_executor.py`;修改 `.gitea/workflows/ci.yaml`、`tests/test_ci_credentials.py` | `run_ci_job(job: Literal['test','test-pg','svc-realcombo']) -> int` 是受信父步骤唯一候选入口;固定 executor 镜像先用空上下文构建,子容器只挂本次工作区卷。三个 job/context 名称不变。 |
| 规则缓存 | Dev 修改 `scripts/check-worktree.py:126-171`;测试 `tests/test_workspace_cutover.py` | `prepare_rules(repo: Path) -> None` 在缓存身份锁内创建唯一暂存、验完整 SHA 后原子发布;旧异常目标只保留/拒绝或隔离空目录。 |
| 任务回收 | Dev 修改 `scripts/check-worktree.py:27-48,176-250,315-333`;测试 `tests/test_workspace_cutover.py`、`tests/test_task.py` | `check_generated_workspace(repo: Path, manifest: Manifest) -> bool` 对 `.workspace` 顶层、state、每个固定 Git 检出及唯一工作做只读核验;`check_clean(repo, ignored=True, generated_workspace_ok=True)` 仅过滤确切 `!! .workspace/`。 |
| 规则与版本采纳 | Dev `policy/dependencies.md`、`policy/governance.md`、`PROJECT.md`、`.task/feature-dependency-workspace.md`、`components.lock.json`;Docs 当前 PR #100 计划/索引;RDM `scripts/build-product.sh` 注释 | 只陈述实际受信控制面与执行面、必要工具、明确 NOT_RUN;Root 采用实施计划的完整 Docs SHA。RDM 注释只纠正旧 pins 描述,不改构建分支。 |
接口冻结条件:Task 2 依赖 Task 1 的“已在候选容器内”核验语义;禁止只靠可伪造环境变量跳过 Bubblewrap。Task 3/4 只依赖已有 Manifest/State/Git 检查,不依赖沙箱实现。若实现需要改公开 CLI、依赖权威或 runner 平台配置,停止并回到批准设计。
---
### Task 1: Fail-closed local candidate sandbox
**Files:** Create `scripts/candidate_sandbox.py`, `tests/test_candidate_sandbox.py`; modify `scripts/build-composition.py`, `tests/test_composition.py`, `Makefile`, `PROJECT.md`.
**Interfaces:** Consumes `compose()` 的隔离 `temporary` 目录、`execution_environment()` 结果和已完成预取;produces `CandidateExecutor(work_root: Path).run(command: list[str], cwd: Path, env: dict[str,str]) -> subprocess.CompletedProcess[str]` 与 `verify_candidate_envelope() -> Literal["local_bwrap", "ci_child"]`。候选执行不再调用原 `run()`,受信 clone/fetch/npm `ci --ignore-scripts` 仍用原入口。CI 内部调用只有经 Task 2 验证的只读身份标记才能跳过本地 Bubblewrap。
- [x] **Step 1 — 写红灯负例。** 真实临时目录创建假 `home/.ssh/probe-key` 与假 `/run/docker.sock`;让候选 Python 脚本同时尝试 HOME 相对和预先传入的宿主绝对路径。测试预期两者不可读、候选目录可写、进程环境无 token;另以伪造 `CI_SANDBOX=1` 运行,预期不绕过检查。至少写入:
```python
import os
from candidate_sandbox import CandidateExecutor
marker = self.base / "host-home/.ssh/probe-key"
marker.parent.mkdir(parents=True)
marker.write_text("synthetic-only")
work = self.base / "composition"
work.mkdir()
probe = work / "probe.py"
probe.write_text("import pathlib,sys\n"
"print('marker_denied' if not pathlib.Path(sys.argv[1]).exists() else 'LEAK')\n"
"print('docker_socket_denied' if not pathlib.Path('/var/run/docker.sock').exists() else 'SOCKET')\n")
result = CandidateExecutor(work).run(
["python3", str(probe), str(marker)], cwd=work,
env={"PATH": os.environ["PATH"], "CI_SANDBOX": "1"})
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("marker_denied", result.stdout)
self.assertIn("docker_socket_denied", result.stdout)
self.assertNotIn("synthetic-only", result.stdout)
```
- [x] **Step 2 — 执行红灯并保存 SELF。** `CMD='PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_candidate_sandbox -v' make evidence`;当前代码缺入口或能读假标记,应非零,不伪写通过。
- [x] **Step 3 — 实现最小包络。** `CandidateExecutor` 预检 `bwrap`,构造 `--unshare-user --unshare-pid --die-with-parent --ro-bind / /` 后覆盖 `/home`、`/root`、`/run`、`/tmp`,仅把本次隔离 work_root 和必要工具目录 bind 回来;新 HOME/XDG/GOCACHE/GOMODCACHE/npm cache 均在 work_root 内。用新 mount/PID namespace 的内核身份加只读包络标记识别已在本地沙箱内的嵌套 composer 测试;缺标记、缺 namespace、HOME/Socket 可见均失败,不能凭 `CI_SANDBOX` 单一变量放行。示例核心命令结构(本机工具位于 `/home/linuxbrew/.linuxbrew`;其它机器只允许已校验的 `/usr`、`/opt` 或显式受信工具根,未知工具路径失败):
```python
class SandboxError(RuntimeError):
pass
if any(word in key.upper() for key in env for word in ("TOKEN", "SECRET", "PASSWORD", "CREDENTIAL")):
raise SandboxError("candidate environment contains acquisition credentials")
clean_env = dict(env, HOME=str(work_root / "home"), GOCACHE=str(work_root / "gocache"),
GOMODCACHE=str(work_root / "gomodcache"), npm_config_cache=str(work_root / "npm-cache"))
argv = ["bwrap", "--unshare-user", "--unshare-pid", "--die-with-parent",
"--ro-bind", "/", "/", "--tmpfs", "/home", "--tmpfs", "/root",
"--dir", "/home/linuxbrew", "--ro-bind", "/home/linuxbrew/.linuxbrew",
"/home/linuxbrew/.linuxbrew", "--tmpfs", "/run", "--tmpfs", "/tmp",
"--bind", str(work_root), str(work_root), "--dev", "/dev", "--proc", "/proc",
"--chdir", str(cwd), "--", *command]
result = subprocess.run(argv, env=clean_env, text=True, capture_output=True)
```
- [x] **Step 4 — 接入全部候选调用点。** `frontend_assets()` 的 `npm run build`,`run_provider_tests()` 中 Go test/vet、npm typecheck/test 与 Dev gates,以及最终 Go/RDM build 均走 `CandidateExecutor.run`;`tooling_identity()` 把新 helper 的已提交字节纳入身份。获取阶段先把 Go/npm 缓存定向到 work_root,再传入沙箱,避免候选联网补包。`tests/test_composition.py` 的独立工具 fixture 同步复制新 helper;测试“无 bwrap”“脏 helper”“嵌套但已隔离”“伪标记”路径。
- [x] **Step 5 — 跑局部绿灯。** 运行 Task 1 的两组测试、`python3 scripts/build-composition.py --help`;结果需显示绝对路径假秘密不可读、伪标记不绕过、候选入口遗漏失败。正式 `make build` 使用 committed tooling 身份,留待下一步。
- [x] **Step 6 — 代码 Review 后提交。** 仅提交 Task 1 相关文件;主题 `Isolate local candidate execution with Bubblewrap`,body 按 `Why:/Evidence:/Impact:/Refs:`,把红绿 SELF 收据和限制写任务卡。尚不推固定最终 Dev HEAD 作阶段 7 通过声明。
- [x] **Step 7 — 提交后验证真实组合。** 在刚提交的精确 SHA 运行 `RECEIPT=/tmp/dependency-local-sandbox-build-20261014-01a1345f.json CMD='make build' make evidence`;核对 Frontend/Backend 摘要和沙箱日志。若失败先整改并产生新提交,不把失败对象写成通过。
### Task 2: Trusted CI executor image and socket-free child
**Files:** Create `scripts/ci_candidate_executor.py`; modify `.gitea/workflows/ci.yaml`, `tests/test_ci_credentials.py`.
**Interfaces:** Consumes Task 1 的 `CandidateExecutor` 已隔离身份判断、当前 workflow 固定来源 mirror 与三个已有 job 名;produces `inspect_workspace_volume(parent_id: str) -> str`、`build_executor_image() -> str`、`JOB_SCRIPTS: dict[str,str]` 和 `run_ci_job(job: Literal["test", "test-pg", "svc-realcombo"]) -> int`。父步骤可以用 Docker socket**创建**子容器,不能直接执行候选源码命令;子容器通过只读标记和实际 Socket/挂载检查使 Task 1 接受已有容器包络。
- [x] **Step 1 — 写红灯机制测试。** `tests/test_ci_credentials.py` 用假 `docker inspect`/`docker run` 记录断言:绑定来源不是唯一命名工作区卷、子容器参数包含 Socket/父 HOME/`/var/run/act`、镜像/工具版本缺失时一律拒绝;扫描 workflow,确保 `make test/build`、RDM pin 构建、PG/Redis 与生命周期候选命令不在父步骤直接执行。**真实 Docker 正反例由受信父 launcher 在执行候选前强制运行**:一次性卷放合成标记,父容器有 Socket,子容器以同卷/共享网络、`--cap-drop ALL --read-only --tmpfs /tmp` 启动,证实子容器读不到 Socket/父 HOME/`/var/run/act`,能读写唯一工作区卷并连受控端口;缺 Docker/卷/负例失败则原 CI job 失败。候选子容器里的普通 `make test` 不再试图调用宿主 Docker。以当前 workflow 运行静态单测应 RED。
- [x] **Step 2 — 建立受信 executor 镜像。** 受信父步骤在调用前以 `git show "$GITHUB_SHA:scripts/ci_candidate_executor.py"` 核对 launcher 的已提交字节;`ci_candidate_executor.py` 验证 `$HOSTNAME` 与 `docker inspect` 身份,且只有一个目标为当前 `/workspace/weichen/Agent-System-Dev` 的 Docker volume;拒绝 bind/多候选/软链路径。用空构建上下文及固定安装配方构建本作业镜像,禁止 `--build-arg` token 和候选代码进入 context;核验 Go `go1.27.1`、Node `v22.*`、Python/rg/sqlite3/ss/PG/Redis 客户端后记录镜像 digest。镜像安装配方须固定在受信代码中,构建上下文只含此文件;例如当前 amd64 runner 的可执行基线(完整 tarball 来源/工具输出进入 CI 收据):
```dockerfile
FROM gitea/runner-images@sha256:fd911d7417bfbf0f454530e447da95b58001e1df41bbc5e1a8dd35d432575aae
ENV PATH=/usr/local/go/bin:/usr/local/bin:${PATH}
RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl xz-utils ripgrep sqlite3 iproute2 postgresql-client redis-tools python3-pip && \
rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://mirrors.aliyun.com/golang/go1.27.1.linux-amd64.tar.gz \
| tar -C /usr/local -xz && ln -s /usr/local/go/bin/go /usr/local/bin/go
RUN curl -fsSL https://npmmirror.com/mirrors/node/v22.23.3/node-v22.23.3-linux-x64.tar.xz \
| tar -C /usr/local --strip-components=1 -xJ
RUN python3 -m pip install --break-system-packages -i https://pypi.tuna.tsinghua.edu.cn/simple \
python-docx python-pptx openpyxl
RUN test "$(go version)" = "go version go1.27.1 linux/amd64" && \
test "$(node --version)" = v22.23.3 && command -v rg && command -v sqlite3 && command -v ss
```
构建时由受信 launcher 将上方固定配方写在工作区卷外的临时文件,构建上下文目录保持空;命令与结果镜像 ID 写入 job 日志:
```python
with tempfile.TemporaryDirectory(prefix="ci-executor-context-") as context:
recipe = Path(context).parent / (Path(context).name + ".Dockerfile")
recipe.write_text(EXECUTOR_DOCKERFILE)
try:
subprocess.run(["docker", "build", "--pull=false", "-f", str(recipe),
"-t", image_tag, context], check=True)
image_digest = subprocess.check_output(
["docker", "image", "inspect", "--format", "{{.Id}}", image_tag], text=True).strip()
finally:
recipe.unlink(missing_ok=True)
```
`inspect_workspace_volume` 只接受当前 Docker parent 的单一命名 volume,`build_executor_image` 不传 token/build secret,`JOB_SCRIPTS` 是三个完整的受信 job 命令块。Docker 子容器运行参数核心必须包含:
```python
child = ["docker", "run", "--rm", "--network", f"container:{parent_id}",
"--cap-drop", "ALL", "--security-opt", "no-new-privileges", "--read-only",
"--tmpfs", "/tmp", "--workdir", "/workspace/weichen/Agent-System-Dev",
"--env", "HOME=/tmp/home", "--env", "GOCACHE=/tmp/gocache",
"--env", "GOPATH=/tmp/gopath", "--env", "npm_config_cache=/tmp/npm-cache",
"--mount", f"type=volume,source={workspace_volume},target=/workspace/weichen/Agent-System-Dev",
image_digest, "bash", "-lc", job_script]
```
- [x] **Step 3 — 逐作业移动候选命令。** `test` 子容器执行 `make test && make build`;`test-pg` 子容器依次执行 RDM pin 合同、三个固定 RDM 构建、Backend PG/Redis、双进程 HTTP、`SVC_TEST_REAL=1 SVC_TEST_PG=1 make test-lifecycle`;`svc-realcombo` 子容器执行单体构建、固定 RDM 构建和真实 SQLite/U3 用例。`.ci-mirrors.env` 只从已校验本地卷读取,不传 `TOKEN`/`SUBMODULE_TOKEN`;子容器内固定 `HOME=/tmp/home`、Go/npm cache 与当前 job 所需非长期本地 PG DSN。父容器安装/预取可继续,但不得调用候选 Makefile/test/build。保留 `test`、`test-pg`、`svc-realcombo` 三个 job/context;每个阶段非零即原 job 失败。子容器完成后仅删除本 job 创建的临时镜像 tag,不执行 `docker system prune` 或动其它作业对象。
三个 `JOB_SCRIPTS` 在子容器中按以下命令顺序执行,所有 `set -euo pipefail`;`AGENT_PG_TEST_DSN`、`AGENT_REDIS_TEST_ADDR` 仅在对应作业由受信工作流显式传入:
```bash
# test
source .ci-mirrors.env
make test
make build
# test-pg(另一个独立子容器/作业)
source .ci-mirrors.env
python3 -m unittest tests.test_lifecycle_pin -q
bash scripts/fetch-rdm-pin.sh tests/integration/lifecycle/rdm.pin >/dev/null
: > rdm/pin-build-digests.txt
while IFS= read -r rev; do
git -C rdm checkout --detach "$rev"
make -C rdm build
sha256sum rdm/bin/agent-system-rdm | awk -v rev="$rev" '{print rev, $1}' >> rdm/pin-build-digests.txt
done < .ci-rdm-pins
make build
make test-backend-pg
make test-multi-instance
export SVC_TEST_REAL=1 SVC_TEST_PG=1
export SVC_REAL_BIN="$PWD/bin/agent-system" SVC_REAL_RDM_BIN="$PWD/rdm/bin/agent-system-rdm" SVC_RDM_SRC_DIR="$PWD/rdm"
make test-lifecycle
# svc-realcombo(第三个独立子容器/作业)
source .ci-mirrors.env
make build
: > rdm/pin-build-digests.txt
while IFS= read -r rev; do
git -C rdm checkout --detach "$rev"
make -C rdm build
sha256sum rdm/bin/agent-system-rdm | awk -v rev="$rev" '{print rev, $1}' >> rdm/pin-build-digests.txt
done < .ci-rdm-pins
export SVC_TEST_REAL=1 SVC_REAL_BIN="$PWD/bin/agent-system" SVC_REAL_RDM_BIN="$PWD/rdm/bin/agent-system-rdm" SVC_RDM_ORIGIN="$PWD/rdm"
python3 -m unittest tests.test_svc_realcombo -v
```
- [x] **Step 4 — 跑绿灯。** 临时 HTTP Git 与 synthetic token 夹具重放 `tests.test_ci_credentials`,其中 Docker 参数用假 CLI 检查;受信父 launcher 的真实 Docker 机制负例和卷/网络正例单独通过且是 CI 必需步骤。用当前 Gitea runner 同镜像执行 executor 工具链探针;若无 socket 子容器无法运行 Go/Node/PG 或必须挂父 HOME/Socket,停止并返回设计评审,不通过直接执行旧步骤取得绿色。
- [x] **Step 5 — 代码 Review 后提交。** 主题 `Run CI candidate gates without the Docker socket`;记录 executor 镜像 digest、父/子挂载清单和负例,不记录 token 值。新 CI 的真正结果只能由新 HEAD 平台 run 确认。
### Task 3: Atomic fixed-rules acquisition and safe retry
**Files:** Modify `scripts/check-worktree.py:126-171`; test `tests/test_workspace_cutover.py`.
**Interfaces:** 保持 `prepare_rules(repo: Path) -> None` 和 precheck 的 `[rules]` 输出契约;不修改 Dev 清单和已批准 Guidelines SHA。
- [x] **Step 1 — 写真实 Git 红灯。** fixture 使第一次固定 SHA fetch/checkout 在 clone 已创建目录后失败;断言失败暂存目录原字节、无 `[verdict] READY`,随后恢复来源并重跑 precheck,断言固定身份路径完整且 READY。另测旧空目标目录被改名留存后重试;含 `unique.txt`、symlink、Git 锁或有效但 dirty 仓的目标均拒绝且 SHA/字节不变。关键断言:
```python
first = self.task(self.repo, "precheck", check=False)
self.assertNotEqual(first.returncode, 0)
self.assertFalse((self.repo / ".workspace").exists())
second = self.task(self.repo, "precheck")
self.assertIn("[verdict] READY", second.stdout)
published = next(path for path in (self.base / "cache/agent-system/rules").iterdir()
if path.is_dir() and not path.name.startswith("."))
self.assertEqual(self.git(published, "rev-parse", "HEAD").stdout.strip(), self.rules_sha)
```
- [x] **Step 2 — 运行 RED。** `CMD='PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_workspace_cutover -v' make evidence`,单独标注新失败用例,不能把其它旧用例成功合并为本轮通过。
- [x] **Step 3 — 实现原子路径。** 在 `identity.lock` 独占期间构造 `cache / ("." + identity + ".partial-" + uuid.uuid4().hex)`,`git clone --no-checkout` 到该路径、fetch 固定 commit、detached checkout、`Git.safe` 及两份必读检查全部通过后 `os.rename(stage, source)`;公布前重读目标是否被其他来源占用。失败保留 stage 并在错误中回报绝对路径,不删除锁以外的 Git 文件。旧空源目录先 `os.rename(source, cache / (identity + ".incomplete-" + uuid.uuid4().hex))` 保全;未知/脏目标保留并失败。
- [x] **Step 4 — 跑 GREEN 与拒绝回归。** Task 3 新例、旧规则 symlink 测试、`make test-governance`;检查暂存目录不会被误判为固定成功,锁仍由 owner 控制且重试没有覆盖唯一文件。
- [x] **Step 5 — 提交。** 主题 `Publish fixed rules atomically after verification`,body 引用真实临时仓红绿和保留现场结果。
### Task 4: Permit safe finish of generated fixed workspaces
**Files:** Modify `scripts/check-worktree.py:27-48,176-250,315-333`; test `tests/test_workspace_cutover.py`, `tests/test_task.py`.
**Interfaces:** `check_generated_workspace(repo, manifest) -> bool` 在 `ordinary_components()` 的递归 Git 守卫后返回允许忽略生成根的布尔值;`check_clean(repo, ignored=True, generated_workspace_ok=True)` 只能过滤一个准确的 `b"!! .workspace/"` 状态行。不得更改 `task.sh` 的 owner 确认、remote lease、非 force 回收逻辑。
- [x] **Step 1 — 写红灯。** 在真实临时仓 `adopt → task-start/prepare → 提交并合入任务分支 → TASK_WRITERS_STOPPED=1 task-finish`,要求固定 `.workspace` 的工作树成功回收且父 main/组件远端不被改写。逐项加 `extra.bin`、symlink state、dirty/ignored 文件、editable Native、独有 branch/reflog、stash、额外外部 worktree,均应拒绝且保留 inode/字节。核心检查:
```python
main_before = self.git(self.repo, "rev-parse", "HEAD").stdout.strip()
finish = self.task(wt, "finish", env=self.env | {"TASK_WRITERS_STOPPED": "1"})
self.assertEqual(finish.returncode, 0, finish.stdout)
self.assertFalse(wt.exists())
self.assertEqual(self.git(self.repo, "rev-parse", "HEAD").stdout.strip(), main_before)
```
- [x] **Step 2 — 运行 RED。** `CMD='PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_workspace_cutover tests.test_task -v' make evidence`;预期只新 clean-finish 用例失败,原有保存用例继续通过。
- [x] **Step 3 — 实现精确允许集。** 检查 `.workspace` 本身/`state.json`/`resolved.json` 均非 symlink;state owner/path/origin/baseline/mode 与 Manifest 和实际 detached HEAD 一致且未 linked、无 go.work;顶层条目只允许登记组件目录和两份元数据。`ordinary_components()` 继续先验证每个 Git 仓的锁、dirty/ignored、refs/reflog/stash、外部 worktree。仅当全部通过,根 `check_clean` 丢弃确切 `!! .workspace/`,其余 ignored/untracked 一律报错。让 Git 原生 `worktree remove` 回收经过核验的生成目录,不新增 `rm -rf` 或额外 force。
- [x] **Step 4 — 跑 GREEN 与真实保护回归。** Task 4 用例、`make test-governance`;确保已编辑或唯一文件的仓库给出可保存路径,不要求/执行自动清理。旧任务 gitlink 清理仍走原有递归保护。
- [x] **Step 5 — 提交。** 主题 `Allow clean fixed workspace disposal at task finish`,body 记录 owner/唯一文件保护及正常成功路径。
### Task 5: Keep contracts and documentation aligned
**Files:** Dev `policy/dependencies.md`, `policy/governance.md`, `PROJECT.md`, `.task/feature-dependency-workspace.md`, `components.lock.json`; RDM `scripts/build-product.sh` header; Documents PR #100 的本文、设计与索引;按影响检查 Guidelines PR #6。
**Interfaces:** Consumes Tasks 1–4 的真实命令和失败模式;produces 项目可执行说明、任务证据和 Docs 固定来源。RDM 注释改动不改变 `composition.dev.json`、Go 图或构建命令。
- [ ] **Step 1 — 文档差异红灯。** 用真实入口比对 `make setup`/`make test`/`make build` 与 PROJECT、policy 的前置和失败出口;检查受信控制面不能被误表述为平台强制准入;`rg -n '凭据|Bubblewrap|Docker socket|\.workspace|NOT_RUN'` 列出旧陈述和缺项。
- [ ] **Step 2 — 更新说明。** 写清 Linux Bubblewrap 必需、CI 子容器的挂载/无 socket 正反例、规则缓存保留路径、固定生成工作区可回收与 editable 仍需 owner 保存;RDM `scripts/build-product.sh` 首段改为新版本组合优先,历史 pins 只在旧候选使用。Dev `components.lock.json` 选择包含本计划与设计的 Documents 完整提交;不为 Go/npm 图加第二锁。Guidelines 通用条文只有在当前设计与通用能力确实冲突时才改,项目路径/端口不得写进通用正文。
- [ ] **Step 3 — 验证与提交。** `git diff --check`、Docs 相对链接、Guidelines 文档规则、Dev 受影响测试;RDM 注释改动后跑当前 HEAD 的 Go/product 影响检查和 RDM GATE。各仓各自提交/推送功能分支;body 使用 `Why:/Evidence:/Impact:/Refs:`。Docs/Guidelines 无 CI 工作流时标 N/A,不伪造绿色。
### Task 6: Integrate, reaccept, rereview, then merge
**Files:** Dev 任务卡与 PR #537 外部最终报告;14 个现有 PR 和 #518 调度记录。除按 Review 修复外不新增业务源码。
**Interfaces:** Consumes Tasks 1–5 经 Review 的提交;produces 新精确 HEAD 的 SELF/REPLAY/GATE、人工阶段 7 结论、独立 Stage 8 复审及阶段 9 逐仓真实合入核验。任何证据失效返回所属 Task 修复,不跳门禁。
- [ ] **Step 1 — 固定前重新核对现场。** 逐仓查询 14 个 PR 的实际远端 main/head、分支包含、CI workflow/必需上下文及组件来源。旧 `6a2f7cd`、旧 Docs `68c658a` 和任何旧跑次只留历史;先在 #518 无窗口时准备,不提前同步/推送 Dev 最终 HEAD。确认其它工作树、旧 `.git` 和唯一文件未被动过。
- [ ] **Step 2 — 领取新的 Dev 窗口并同步。** 由 #518 唯一协调者记录 attempt/base/head 后,在**已登记 Dev 任务 worktree** 运行 `RECEIPT=/tmp/dependency-review-remediation-final-sync-20261014-01a1345f-01.json CMD='make task-sync' make evidence`;有新主干时解决真实冲突,保留上游行为和旧物理 Git 数据。输出 `changed=0` 的后续节点不得制造提交;异常或超预算安全释放。
- [ ] **Step 3 — 本地正式重放。** 当前完整 SHA 上按受影响面跑 `make test-governance`、`make test`、`make build`;核对 resolved manifest/两个二进制摘要、12 仓 14 项来源可达、无 gitlink;用模拟 main 的普通 clone 跑 `setup → task-start → Native edit → go.work 联调 → unlink`;旧 ee9 候选仍可物化/构建;最终 Dev+RDM 临时产品跑真实浏览器 5+2 且端口关闭。任何 SKIP 和本地失败原样保留,不用 GATE 盖掉。
- [ ] **Step 4 — 真实平台 GATE。** 推送冻结的 Dev/RDM/受影响组件与 Docs/Guidelines PR,核对完整 head/base、actor/runner/每步结论:Dev `test`、`test-pg`、`svc-realcombo` 全部成功,并核对三 job 的候选步骤确在无 socket 子容器;RDM/组件新 HEAD 跑其现有必需 CI。普通构建负例能读宿主假密钥或 Docker socket、CI 镜像身份/挂载不明或所需检查缺席,一律 FAIL,不重复使用旧绿。
- [ ] **Step 5 — 人工与独立出口。** 将 R1–R15→正常/异常/边界/回归、SELF/REPLAY/GATE/SKIP/NOT_RUN 映射和 14 PR 当前 SHA 放同一阶段 7 报告供用户重新审阅;人工通过前不合入。之后由独立审阅者先在隔离环境重放最小门禁,再复审三项 Important 及全部受影响 diff;Critical/Important 未闭合或新 HEAD 无有效 Review 时不合入。
- [ ] **Step 6 — 逐仓合入和真实核验。** 先按 Go 包/核心契约/Native/Intent/Command/Squad、UI→Frontend、Backend、Guidelines/Docs、RDM、Dev 的依赖关系处理 14 个现有 PR;每个 PR 先解除 WIP、再核对最新 main 仍为 head 祖先、当前 head GATE/Review/平台必需状态成功,以 `merge` 策略和 `force_merge=false` 合入,不 squash、不删唯一来源分支。每次读回 merged SHA、完整树与候选一致、被消费提交是 main 祖先;Dev 最后合入前再次 `task-sync`。基础变化使候选或 CI/Review 失效时停止,建立新 attempt。
- [ ] **Step 7 — 阶段 10 单独边界。** 合入后只核对内容、组件与 PR 状态并释放 #518 窗口;发布/共享部署/数据库操作不执行。任务 worktree、远端分支和任务卡闭合清理在 owner 人工批准且所有写者退出后另执行,editable 或唯一内容仍拒绝强删。
## Plan self-review and handoff
本计划各任务都有红灯、最小实现、绿灯/Review 和提交出口;Task 1/2 对本地与 CI 隔离分别负责,Task 3/4 不共享可变状态,Task 5/6 只在真实结果后采纳及交付。已覆盖设计中的本地凭据绝对路径、CI Docker socket、旧缓存部分失败、生成固定工作区与异常现场、RDM/旧候选/在途分支。没有发布 tag、平台保护修改、服务部署或实际任务回收授权。**计划已获用户批准并按本会话逐项执行;下列步骤勾选与最终结果以 Dev 任务卡、固定 SHA 收据和阶段 7/8 门禁为准。**
### 实施进度(2026-10-14)
| 单元 | 已提交的本地候选 | 当前证据与待办 |
|---|---|---|
| Task 1 | Dev `e0c8686` | Bubblewrap 假密钥/缺工具负例与真实固定构建 SELF 通过;首次冷 Go 获取失败和同 SHA 重试通过均保留 |
| Task 2 | Dev `aafcb0b`、修订 `378506f` | 三 job 已转无 socket 子容器;真实 Docker 卷/网络/工具/Go 临时执行探针和隔离治理回归 SELF 通过;平台三个 job GATE 待新 HEAD |
| Task 3 | Dev `f119949` | 部分获取重试、旧空目录隔离、唯一文件/锁拒绝通过;同机端口竞争的失败记录保留 |
| Task 4 | Dev `76ad40c` | 固定生成目录正常回收及编辑/唯一内容拒绝通过;隔离 `make test-governance` 503 项通过、28 项条件跳过 |
| Task 5–6 | 进行中 / NOT_RUN | 先对齐文档和 Docs 来源锁,再固定新候选、重验、申请新阶段 7、独立复审及逐仓合入 |
上表中的 SELF 是本地候选证据;仓库 PR 的最新 HEAD、平台 GATE、阶段 7/8 结论不得由它推定。
@@ -0,0 +1,48 @@
# 版本依赖迁移:独立 Review 整改设计 r1
状态:**设计原则、书面设计 `68c658a` 与[实施计划 `20ada84`](2026-10-14-versioned-dependency-review-remediation-plan.md)均已获用户批准**(2026-10-14),用户选择本会话逐项实施。Dev 的本地隔离、CI 子容器、规范缓存和生成工作区回收已形成任务提交;新 HEAD 的阶段 7 验收、阶段 8 复审及平台 GATE 尚未完成。本文是[版本化依赖与按需源码工作区方案 r1](2026-10-13-versioned-dependencies-and-workspaces.md)的有界修订,关联[原实施计划](2026-10-13-versioned-dependencies-implementation-plan.md)、Dev PR #537 与 [独立 Stage 8 Review](https://git-home.u1680832.nyat.app:30319/weichen/Agent-System-Dev/pulls/537#issuecomment-5989)。旧 Dev `6a2f7cd3e610a263b6348efe9084e386dc88f30c` 的阶段 7 人工通过、run3076 和 RDM run3077 只覆盖旧候选;三项 Important 仍待新候选复审关闭,14 个 PR 继续 WIP,Dev #518 的原交付窗口已释放。
## 决定与目标
用户在本会话先选择“本地执行强制 Bubblewrap 沙箱”,随后选择“本轮同时隔离 CI:由受信步骤启动无 Docker socket 的子容器执行候选代码”。目标是关闭三项独立 Review 阻塞:候选脚本可通过同 UID 文件系统读取本地凭据、固定规范缓存部分克隆后无法重试、生成固定 `.workspace/` 后普通任务无法安全 `task-finish`。继续保留独立仓库、语言版本权威、旧候选重建、在途分支和唯一文件保护。
信任边界分为控制面和候选执行面。已审阅的 Dev 编排器与 CI 工作流负责校验固定提交、预取私有来源和建立沙箱;Go/npm 提供方脚本、测试、构建及 Dev 正式验证命令进入候选执行面。本轮阻断候选执行面直接读取本机 HOME、私有 SSH 文件及 Docker socket。**本轮不声称防止恶意 PR 改写其自身工作流/编排器**:平台是否强制采用不受候选控制的工作流、runner 是否隔离、token 最小权限与自动 fencing 继续按实际状态标 `NOT_RUN`/manual;若要对恶意控制面改写提供平台强制保证,需独立的受信外部准入设计与所有者操作。此限制必须写入项目规则、测试报告和 PR 审阅材料,不把“环境变量已清理”表述为 OS 级隔离。
## 本地候选执行
正式 `make test/build` 先由受信编排器从 `components.lock.json`、`go.mod/go.sum`、npm lock 获取并验证固定源码;获取完成后才调用候选命令。所有候选 Go/npm/项目测试和构建子进程经同一 Bubblewrap 入口执行,不能从 `run()` 的某条旁路直接执行。入口创建每次独立的 HOME、XDG/Go/npm 缓存及临时目录,隐藏主机 `/home`、`/root`、其他 `/tmp`、`/run`/Docker socket,只挂载必要系统工具与本次隔离组合目录;候选进程有独立 PID 与 `/proc` 视图,输出目录限于本次组合。需要本机 HTTP/数据库验收的测试保留明确的网络能力,不能由此获得主机凭据文件。传输凭据、Git extraheader、SSH agent、宿主 HOME 与 npm/Git 用户配置均不传入候选环境。
启动时验证 Bubblewrap 可用且实际阻断绝对路径访问;不可用、挂载不符合允许集或负例能读到受保护标记时,正式命令**失败关闭**,没有同 UID 直接执行的降级分支。普通 clone 的 `make setup`/PROJECT 指出该 Linux 前置和诊断命令;缺前置不能标 R2 正式构建通过。受信控制面本身仍是代码 Review 对象,不把 Bubblewrap 包装误写成恶意 Dev `Makefile` 无法执行的强制机制。
## CI 候选执行
三个必需作业 `test`、`test-pg`、`svc-realcombo` 的受信父步骤继续按白名单获取固定来源,只在获取步骤环境中读取 checkout token;父步骤校验镜像、工作区卷身份、固定 SHA 与无凭据的本地 mirror 配置。受信父步骤经 Docker socket 启动一个子容器运行该作业**全部候选源码命令**,包括 Dev `make test/build`、RDM 固定 pin 构建、Backend PG/Redis、双进程 HTTP 与真实生命周期。父容器不得在子容器启动前运行候选 Makefile、Go/npm 脚本或测试。
受信父步骤先以**空构建上下文、无 token/build secret** 的固定安装配方构建并记录本作业的 executor 镜像;镜像内含/核对 Go 1.27.1、Node 22、Python、`rg`、`sqlite3`、`ss` 及 PG/Redis 作业所需客户端,构建阶段不得执行候选仓代码。随后启动的候选子容器只挂载本次作业的工作区卷和只读身份文件,**不挂** Docker socket、`/var/run/act`、父容器 HOME 或凭据文件;其根文件系统只读,丢弃 Linux capabilities,启用 `no-new-privileges`、独立 PID 与临时 `/tmp`,仅工作区和明确缓存路径可写。Go 测试和临时钩子必须执行临时文件,故子容器的独立 `/tmp` 使用 `rw,exec,nosuid,nodev`;根文件系统仍只读。工具安装在镜像构建阶段完成,不能在只读候选容器中临时安装或以缺工具为由跳过测试。PG/Redis 作业可共享父作业的网络命名空间以访问本机测试服务,但不能共享其文件系统或 Docker socket。镜像构建/身份检查、子容器创建、工具版本、挂载/负例检查或任何候选命令失败时,原作业失败;三个必需 job/context 名称保持不变,禁止跳过作业换绿。
已做的**一次性机制探针**:本机 Bubblewrap 可隐藏 SSH 文件;当前 `gitea/runner-images:ubuntu-latest` 容器内安装 Bubblewrap 后因命名空间权限被拒;受信父容器可启动共享工作区卷与网络、无 Docker socket、cap-drop、只读根文件系统的子容器,SELF `/tmp/dependency-ci-child-sandbox-probe-20261014-01a1345f.json`。探针仅证明容器机制,不证明完整 Go/npm/PG/浏览器工作流或平台工作流来源;完整 CI 必须在新 HEAD 重跑。共享 runner 配置和分支保护不在本设计中直接修改。
## 固定规范缓存的失败恢复
`precheck` 继续读取清单中的 Guidelines 精确 SHA。持有缓存身份锁时,先在仓外缓存创建唯一暂存目录,完成 clone、fetch、detached checkout、独立 Git 身份、干净状态与 `core/baseline.md`/`workflows/delivery.md` 校验,再以同目录原子 rename 公布为固定身份路径。失败或中断留下的暂存目录原样保留并报告路径;下一次重试创建新暂存目录,不因前次临时目录阻塞。身份锁是活动写者保护,不能删除锁强行重试。
已存在的正式身份路径只在 Git 身份、origin、SHA、干净状态和必读文件完全匹配时复用。旧实现留下的空目录可在锁下保留到命名的隔离路径后重试;含未知文件、借用 Git 身份、脏状态、活动锁或独有引用的异常目录拒绝自动迁移并报告明确的人工保留/处置入口,**不自动删除或覆盖**。这一区分使新失败可直接重试,也给旧部分现场留安全出口。
## 普通任务的安全回收
`task-finish` 仍要求任务分支已合入、远端/HEAD 核验、工作区 owner 明确 `TASK_WRITERS_STOPPED=1`、Git 锁及递归子仓检查通过。仅对 `.workspace` 中已登记、固定且 detached 的独立源码检出放行:登记的 owner/path/origin/baseline 与实际 checkout 一致;没有 staged、unstaged、untracked、ignored 唯一文件、独有 refs/reflog/stash、外部 worktree、活动操作或 overlay;顶层只允许 `state.json`、`resolved.json` 与登记的固定组件目录,且元数据为普通文件。验证后,仅将根仓 `git status` 中的**确切** `!! .workspace/` 视为可再生生成内容,再由现有 `git worktree remove` 正常回收;任何其他 ignored/untracked 项仍拒绝,不能加 force 或按目录名一概清理。
处于 editable/linked 的功能仓、未知目录、坏 state、唯一内容或额外 Git 工作区继续拒绝回收,并输出需先保留独立源码的路径;本轮不自动移动或删除可编辑仓。后续阶段 10 对这些现场逐项由 owner 指定保存/处置方式,未经授权不执行实际工作区回收。正常仅准备固定源码的新任务须能顺利走完现有 `task-finish`。
## 验证、影响与停止条件
- 临时真实仓覆盖:规则 clone/fetch 中断后留存现场并重试、旧空目录隔离、未知内容/锁拒绝;固定 `.workspace` 的干净成功回收,以及脏/ignored/编辑分支/独有引用/外部 worktree/坏 state 拒绝且原字节保留。治理脚本必须跑 `make test-governance`。
- 本地沙箱负例用**假标记**验证候选脚本不能通过 HOME 或绝对路径读取宿主文件、Docker socket;正例验证固定 Go/npm/Frontend/RDM 构建和普通 Dev 克隆,不读取真实密钥值。CI 子容器负例验证 socket、父 HOME、`/var/run/act` 不可见,正例验证工作区卷、固定工具链与 PG/Redis/生命周期;不把容器机制探针当完整 CI。
- 新 Dev HEAD 需重新 `make task-sync`、`make test/build`、来源/摘要、普通克隆/Native 编辑、受影响产品浏览器重放,且当前 SHA 的 `test`、`test-pg`、`svc-realcombo` 三个 GATE 全部成功;Docs/Guidelines 的相关文档链接和规则一致性复核。正式结果重新提交阶段 7 人工审阅,之后对变更范围进行独立 Stage 8 REPLAY/Review,再逐仓核准合入。旧 `6a2f7cd` GATE/验收保留为旧对象历史,不移用。
- 若 Bubblewrap 或 CI 子容器在普通 clone/runner 上不可用、沙箱需要挂载宿主凭据/Socket 才能通过、固定缓存恢复会移动未知文件、或回收会删除唯一内容,则停止受影响交付并修订设计。发布、部署、业务数据、历史改写、删除来源引用、共享 runner/保护设置及未授权任务回收仍不实施。
备选取舍:仅清理环境变量/改 HOME 无法阻止同 UID 绝对路径读密钥;只给 CI 容器安装 Bubblewrap 在当前权限下实测失败;仅把原作业容器当沙箱仍暴露 Docker socket。因此本轮采用本地 Bubblewrap + CI 无 socket 子容器;更强的恶意工作流平台强制准入另立边界,不用此方案冒称完成。
## 实施核对(2026-10-14)
Dev `e0c8686`、`aafcb0b`/`378506f`、`f119949`、`76ad40c` 分别覆盖本地包络、CI 子容器及可执行独立 `/tmp`、固定规则原子发布、生成工作区安全回收。SELF 临时真实仓回归与隔离 `make test-governance`(503 项通过、28 项条件跳过)记录于 Dev 任务卡和源码外收据;同机固定端口并发导致的两次本地失败、CI 默认 noexec 导致的一次失败及修复均保留。此处只说明本地整改候选,**不升级旧 run 为新 HEAD 的 GATE**。RDM/Docs 组合、普通 clone、真实平台三个必需 job、阶段 7 人工验收及 Stage 8 独立复审依[实施计划](2026-10-14-versioned-dependency-review-remediation-plan.md)继续。
+10
View File
@@ -6,6 +6,16 @@
## 先看哪一份
- [版本化依赖与按需源码工作区改造方案 r1](2026-10-13-versioned-dependencies-and-workspaces.md):
保留独立功能仓和 Dev 单入口,以版本依赖、按需编辑工作区和固定制品组合替换 submodule;
覆盖 Go、UI/TS 契约、RDM、CI/源码部署和 Guidelines 通用规则。2026-10-13 用户要求实施;
2026-10-13 已批准具体方案并授权重构、自检连续推进,增补在途分支保护 R15。
[实施计划](2026-10-13-versioned-dependencies-implementation-plan.md)记录 U1–U8;实施进展见 Dev 任务卡,
正式验收、共享部署和发布保留原审批边界。
[独立 Review 整改设计 r1](2026-10-14-versioned-dependency-review-remediation.md)记录三项 Stage 8 阻塞的本地/CI 沙箱、规范缓存重试与生成工作区安全回收边界;书面设计 `68c658a` 已获用户批准,旧候选不得据旧 CI 合入。
[整改实施计划](2026-10-14-versioned-dependency-review-remediation-plan.md)细化每单元红绿回归、CI 沙箱、重新验收与合入停止条件;计划 `20ada84` 已获用户批准并由本会话逐项实施。Dev Task 1–4 本地候选已提交,Task 5 文档/来源锁对齐进行中;新 HEAD 的平台 CI、阶段 7 人工验收与 Stage 8 独立复审尚未完成。
[嵌套沙箱双必需门禁修订设计 r1](2026-10-14-versioned-dependency-nested-sandbox-amendment.md)针对新候选 `make test` 的嵌套 `bwrap` 阻塞;书面设计 `1790cf8` 已获用户批准。[修订实施计划](2026-10-14-versioned-dependency-nested-sandbox-amendment-plan.md)待评审,独立重放环境及最终门禁均未就位。
- [IAM I1 权限边界与公开扩展合同 r1](2026-10-13-iam-i1-permission-contract.md):
依据已批准的[用户、职位角色与分层权限统一方案 r1](2026-10-13-identity-role-permission-plan.md)
(Documents `a088809`)冻结系统/项目/资源边界、管理员无业务 bypass、创建者 cap、独立 `audit.read` 与公开扩展四要素。