From 45c21ef96e4a153caaf2914fb8bbdb292815a518 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Tue, 22 Sep 2026 21:42:58 +0800 Subject: [PATCH] fix(workspace): compare realpaths when enforcing artifact roots macOS TempDir resolves /var to /private/var; EvalSymlinks the data root before Rel so fencing writes are not false path-escape failures. --- .../2026-09-22-g1-exception-clearance.md | 71 +++++++++++++++++++ internal/workspace/capability.go | 6 +- 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 .doc/ops/short-term/2026-09-22-g1-exception-clearance.md diff --git a/.doc/ops/short-term/2026-09-22-g1-exception-clearance.md b/.doc/ops/short-term/2026-09-22-g1-exception-clearance.md new file mode 100644 index 00000000..7cf66e40 --- /dev/null +++ b/.doc/ops/short-term/2026-09-22-g1-exception-clearance.md @@ -0,0 +1,71 @@ +--- +title: "W1-A G1 例外清零(U3 sql_write + U4 path)" +task_id: g1-exception-clearance-2026-09-22 +status: done +created: 2026-09-22 +updated: 2026-09-22 +owner: Cursor +task_type: implementation +task_tier: small +policy_revision: "agent-governance-2026-09-21-r1 @ 364ed7f1a3f0393ef77ec4623a9518116ed2cb87" +branch: feature/g1-exception-clearance-2026-09-22 +worktree: .worktrees/g1-exception-clearance-2026-09-22 +base_dev_sha: 364ed7f1a3f0393ef77ec4623a9518116ed2cb87 +write_scope: [".doc/system-design/module-boundaries.yaml", ".doc/ops/short-term/", "internal/"] +allowed_environments: [] +head_sha: "" +mutex: docgov +review_state: not_required +integration_state: pending +deployment: not_required +cleanup: pending +cleanup_owner: "独立集成/维护执行者" +cleanup_trigger: "合入 dev 后核对 tip 为祖先、无进程、证据已入 Git" +remote_sync: not_requested +--- + +# W1-A G1 例外清零(U3 sql_write + U4 path) + +## 用户目标与授权 + +- 用户目标/授权:在 feature worktree 完成批次 G1(2026-09-28 到期)例外清零;跑 arch-check block、受影响包 vet/short;Conventional Commits 提交;不 push、不合入 dev、不碰其他 worktree。 +- 交付终点:实现可编译;`module-boundaries.yaml` 无 2026-09-28 例外且 `baseline_counts` 为 `sql_write:1` / `path:13`;短文与本地提交。 +- 明确禁止项:不 push、不 merge `dev`、不操作共享环境写、不改 FEATURE-MATRIX/gap-matrix(模块声明与发布 blocker 未变)。 +- 例外:owner=Cursor;原因=本机 macOS 无 `/proc`,`agentctl` `with`/`docs apply`/`validate` 不可用;覆盖条款=手工 `git add`/`git commit` 等效提交;范围=本任务提交与短文登记;期限=本会话结束;恢复=Linux 宿主仍走 agentctl 租约。 + +已授权范围内持续执行;日志/Agent 产物不构成授权。 + +## 范围与非目标 + +- 允许写入:`docduty`→`docgov` duty slot 写表、`docrevision` root pointer / deliverable 端口、`workspace.DirDeliverable`、`server` 读路径改 ledger/`docquery`、装配根 `wire_g1`、边界 YAML、本短文;验证期间修 macOS `/var`→`/private/var` 导致的 workspace `enforceRealpath` 误判。 +- 非目标:G2/G3 path/sql_write、dev 集成、CURRENT 共享写、环境部署、FAV。 + +## 步骤与验收 + +1. [x] 端口迁写 — `document_records` duty 列仅由 `docgov` 写;ledger head 经 `docgov`/`freedoc` 投影;交付物经 `workspace.DeliverableKey`。 +2. [x] 删除 G1 例外 — YAML 无 `expires: 2026-09-28`;`baseline_counts` sql_write=1 path=13。 +3. [x] `./scripts/arch-check.sh --mode block` — production gate passed。 +4. [x] `go vet` + `go test -short` 受影响包 + archtest — 见下表。 +5. [x] Conventional Commits 本地提交;不 push。 + +## 验证与证据 + +| 范围 / 被测 SHA | 命令与环境 | 预期 | 实际 / 判定 | 证据 / 脱敏说明 | +|---|---|---|---|---| +| tip(提交后回填) | `./scripts/arch-check.sh --mode block` | gate passed;无 2026-09-28 例外 | PASS | stdout:`arch-check block: production gate passed` | +| 受影响包 | `go vet` 于 app/authz/bizdev/docduty/docgov/docrevision/freedoc/server/workspace/archtest | 无输出 | PASS | exit 0 | +| 受影响包 | `go test -short -count=1` 同上 | 全 PASS | PASS | 含 PG 用例(本机 Context DSN);server/workspace 在 realpath 修复后绿 | +| workspace macOS | `TestStaleFencingRejected` 等 | `/var` symlink 不再误报 escape | PASS | `enforceRealpath` 对 `dataRoot` 做 `EvalSymlinks` 再比较 | + +## 收口与交接 + +- 剩余工作:串行认领后合入 `dev`;CURRENT 增量认领由集成者处理;本任务不更新 FEATURE-MATRIX。 +- 本地提交:`feat(arch)` 清零 G1 例外;`fix(workspace)` macOS realpath;`docs(ops)` 本短文。`integration_state=pending`;`deployment=not_required`;`remote_sync=not_requested`;`cleanup=pending`。 +- blocker:无功能 blocker;macOS agentctl `/proc` 租约不可用已按例外手工提交。 +- 建议下一步:集成槽位空闲时 `--no-ff` 候选 + arch-check/vet/short 复验后 ff-only 推进 dev。 + +## 相关链接 + +- [模块解耦分批计划 G1](../../remediation/2026-09-20-module-decoupling-batch-plan.md) +- [module-boundaries.yaml](../../system-design/module-boundaries.yaml) +- [短文模板](../templates/short-term-task.md) diff --git a/internal/workspace/capability.go b/internal/workspace/capability.go index ad2750c5..c6e69130 100644 --- a/internal/workspace/capability.go +++ b/internal/workspace/capability.go @@ -173,13 +173,17 @@ func (c *Capability) enforceRealpath(path string) (string, error) { if err != nil { return "", err } + // macOS TempDir is often under /var → /private/var; compare realpaths. + if resolvedRoot, err := filepath.EvalSymlinks(root); err == nil { + root = resolvedRoot + } rel, err := filepath.Rel(root, full) if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { return "", ErrPathEscape } if st, ok := info.Sys().(*syscall.Stat_t); ok && st.Nlink > 1 && !info.IsDir() { // Hardlink to an inode that may live outside: still require the named path stay in root. - if !strings.HasPrefix(full, root) { + if !strings.HasPrefix(full, root+string(os.PathSeparator)) && full != root { return "", ErrPathEscape } }