diff --git a/docs/proposals/2026-10-14-versioned-dependency-review-remediation-plan.md b/docs/proposals/2026-10-14-versioned-dependency-review-remediation-plan.md new file mode 100644 index 0000000..c4c09c9 --- /dev/null +++ b/docs/proposals/2026-10-14-versioned-dependency-review-remediation-plan.md @@ -0,0 +1,267 @@ +# 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:** 用户已批准书面设计并要求编制计划;本计划本身仍待人工评审、执行方法未选。Dev 旧候选 `6a2f7cd3e610a263b6348efe9084e386dc88f30c` 的 run3076、RDM run3077 和阶段 7 人工结论不放行新 HEAD。14 个迁移 PR 保持 WIP,#518 无活动 Dev 窗口;本计划不授权发布、部署、业务数据、历史改写、删除来源引用、改变共享 runner/分支保护或任务工作区清理。 + +## 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。 + +- [ ] **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) +``` + +- [ ] **Step 2 — 执行红灯并保存 SELF。** `CMD='PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_candidate_sandbox -v' make evidence`;当前代码缺入口或能读假标记,应非零,不伪写通过。 +- [ ] **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) +``` + +- [ ] **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”“嵌套但已隔离”“伪标记”路径。 +- [ ] **Step 5 — 跑局部绿灯。** 运行 Task 1 的两组测试、`python3 scripts/build-composition.py --help`;结果需显示绝对路径假秘密不可读、伪标记不绕过、候选入口遗漏失败。正式 `make build` 使用 committed tooling 身份,留待下一步。 +- [ ] **Step 6 — 代码 Review 后提交。** 仅提交 Task 1 相关文件;主题 `Isolate local candidate execution with Bubblewrap`,body 按 `Why:/Evidence:/Impact:/Refs:`,把红绿 SELF 收据和限制写任务卡。尚不推固定最终 Dev HEAD 作阶段 7 通过声明。 +- [ ] **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 接受已有容器包络。 + +- [ ] **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。 +- [ ] **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] +``` + +- [ ] **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 +``` + +- [ ] **Step 4 — 跑绿灯。** 临时 HTTP Git 与 synthetic token 夹具重放 `tests.test_ci_credentials`,其中 Docker 参数用假 CLI 检查;受信父 launcher 的真实 Docker 机制负例和卷/网络正例单独通过且是 CI 必需步骤。用当前 Gitea runner 同镜像执行 executor 工具链探针;若无 socket 子容器无法运行 Go/Node/PG 或必须挂父 HOME/Socket,停止并返回设计评审,不通过直接执行旧步骤取得绿色。 +- [ ] **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。 + +- [ ] **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) +``` + +- [ ] **Step 2 — 运行 RED。** `CMD='PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_workspace_cutover -v' make evidence`,单独标注新失败用例,不能把其它旧用例成功合并为本轮通过。 +- [ ] **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))` 保全;未知/脏目标保留并失败。 +- [ ] **Step 4 — 跑 GREEN 与拒绝回归。** Task 3 新例、旧规则 symlink 测试、`make test-governance`;检查暂存目录不会被误判为固定成功,锁仍由 owner 控制且重试没有覆盖唯一文件。 +- [ ] **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 回收逻辑。 + +- [ ] **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) +``` + +- [ ] **Step 2 — 运行 RED。** `CMD='PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_workspace_cutover tests.test_task -v' make evidence`;预期只新 clean-finish 用例失败,原有保存用例继续通过。 +- [ ] **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。 +- [ ] **Step 4 — 跑 GREEN 与真实保护回归。** Task 4 用例、`make test-governance`;确保已编辑或唯一文件的仓库给出可保存路径,不要求/执行自动清理。旧任务 gitlink 清理仍走原有递归保护。 +- [ ] **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、平台保护修改、服务部署或实际任务回收授权。**本计划待用户评审;没有执行方法选择和阶段 5 结论时,不开始 Task 1。** diff --git a/docs/proposals/2026-10-14-versioned-dependency-review-remediation.md b/docs/proposals/2026-10-14-versioned-dependency-review-remediation.md index 87f7ebb..cdc73ff 100644 --- a/docs/proposals/2026-10-14-versioned-dependency-review-remediation.md +++ b/docs/proposals/2026-10-14-versioned-dependency-review-remediation.md @@ -1,6 +1,6 @@ # 版本依赖迁移:独立 Review 整改设计 r1 -状态:**设计原则已获用户批准;本书面设计待人工评审**(2026-10-14 UTC)。实施计划、代码整改、新 HEAD 的阶段 7 验收及阶段 8 复审均未完成。本文是[版本化依赖与按需源码工作区方案 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 的原交付窗口已释放。 +状态:**设计原则与书面设计 `68c658a` 均已获用户批准**(2026-10-14;当前文件仅回填批准状态)。实施计划待人工评审;代码整改、新 HEAD 的阶段 7 验收及阶段 8 复审均未完成。本文是[版本化依赖与按需源码工作区方案 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 的原交付窗口已释放。 ## 决定与目标 diff --git a/docs/proposals/INDEX.md b/docs/proposals/INDEX.md index 0953cb4..89a313d 100644 --- a/docs/proposals/INDEX.md +++ b/docs/proposals/INDEX.md @@ -12,7 +12,8 @@ 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 沙箱、规范缓存重试与生成工作区安全回收边界;设计原则已获用户批准,书面设计待人工评审,旧候选不得据旧 CI 合入。 + [独立 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 沙箱、重新验收与合入停止条件;计划待人工评审,代码未按本计划实施。 - [IAM I1 权限边界与公开扩展合同 r1](2026-10-13-iam-i1-permission-contract.md): 依据已批准的[用户、职位角色与分层权限统一方案 r1](2026-10-13-identity-role-permission-plan.md)