Files
ZY-Agent/docs/multi-tenant-redesign/03-impl/pr7-ci-boundary-scan.md
T
1445043649 b0bf033f15 docs(impl): PR7 implementation note + STATUS update
Adds `pr7-ci-boundary-scan.md`:
- Scope summary (allowlist + AST scanner + TYPE_CHECKING exemption + self-tests + CLAUDE.md docs)
- Per-task commit table (T7.1-T7.5; T7.4 is the revert-after-prove injection drill — no commit by design)
- Acceptance: RED->GREEN cycle, 9 self-tests, T7.4 inject/revert, 3241 passed + 31 skipped + 18 caplog flake
- Architecture decisions (TYPE_CHECKING exemption vs allowlist inclusion, toml path-list shape, pytest-not-separate-CI-step, scope kept narrow, plan-draft vs ground-truth allowlist)
- File structure index (new vs modified)

Updates STATUS.md:
- One-line status moves to PR7 merged; Stage 0 now only PR8 left
- 8-PR status table: PR7 row marked merged with commit range and impl note link; commit count notes T7.4 has no commit by design
- Test baseline line: PR7 末 3241 + 31 + 18 (PR6 末 3214 + 30 + 17, +27/+1/+1 explained)
- Next-step suggestion switches to PR8 (parallel-eligible since PR4); PR7 inline-mode retro added

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:59:22 +08:00

7.1 KiB
Raw Blame History

PR7 · CI boundary 静态扫描(langgraph.checkpoint.* 直接 import 围栏)

实现笔记。对应 docs/superpowers/plans/2026-05-10-stage-0-multi-tenant-foundation.md PR7T7.1-T7.5)。

状态:已落地4 个 commits 提交到 docs/multi-tenant-redesignPR7 起始 1a6ccc9a.. 结束 d8b13afc)。

范围

tests/test_harness_boundary.py 的模式从"harness 不能 import app"扩到第二条 workspace-isolation 围栏:只有显式 allowlist 里的文件才能直接 import langgraph.checkpoint.*。其他任何位置必须穿 app.gateway.deps.get_checkpointer DI 或 deerflow.runtime.checkpointer 工厂——避免业务代码自己 new 个 saver 绕过 workspace 边界。

  1. tests/boundary_allowlist.toml — 4 个当前合法 importer,每行配注释解释为什么允许:
    • app/gateway/routers/threads.pythread 初始化时 empty_checkpoint
    • packages/harness/deerflow/runtime/checkpointer/async_provider.py / provider.py(唯一 saver 工厂处)
    • packages/harness/deerflow/runtime/runs/worker.pyresumed run seed empty_checkpoint
  2. tests/test_workspace_boundary.py — AST 扫描 backend 所有 *.py(排除 tests/docs/、build artefacts),命中 langgraph.checkpoint.* / langgraph_checkpoint_postgres / langgraph_checkpoint_sqlite 且不在 allowlist 即 failerror message 直接列 <path>:<line> imports <module> + 修复指引。
  3. TYPE_CHECKING 豁免 — 扫描器通过 parent-walk 检测 if TYPE_CHECKING: / if typing.TYPE_CHECKING: 块(含嵌套),块内 import 不算违规。因此 agents/factory.pyBaseCheckpointSaver 作为类型注解的 type-only import 不进 allowlist,更准确地反映 runtime boundary 语义。
  4. tests/test_workspace_boundary_self.py — 9 个 self-test 防止扫描器静默空跑:用 tmp_path 合成 .py 片段喂给 collect_runtime_checkpoint_imports,覆盖 from-import / bare import / 第三方包 / TYPE_CHECKINGName 形 + Attribute 形 + 嵌套)/ 字符串字面量 / 语法错误 / 不相关 import。
  5. backend/CLAUDE.md Boundary check 段 加两行说明新增的两个 test 文件 + allowlist 维护契约("新 importer 同 PR 加 allowlist")。

不在范围

  • 运行时 import-hook 拦截(plan 明确不做——静态 AST 扫描足够 + 不引入 runtime 开销)。
  • 把 plan 草稿里的 thread_runs.py / gateway/app.py 列入 allowlist——grep 实际证实它们走 get_checkpointer DI不直接 import,列入会假阳放水。
  • app.* → deerflow.runtime.* 的反向间接环检查(plan "一句话状态" 提到的扩展,留 Stage 1 follow-up)。

Tasks 完成清单

Task Commit 关键改动
T7.1 1a6ccc9a tests/boundary_allowlist.toml 4 entry + 每行注释解释合法性
T7.2 ba30d140 tests/test_workspace_boundary.py AST scanner + TYPE_CHECKING parent-walk + allowlist 加载
T7.3 d0f18770 tests/test_workspace_boundary_self.py 9 个 self-test
T7.4 no commit 临时在 app/gateway/routers/feedback.py:13from langgraph.checkpoint.postgres import AsyncPostgresSaver → scanner 红灯且 line 号正确 → revert。无测试改动,按 plan "仅 commit 测试自身完善" 原则不留 empty commit
T7.5 d8b13afc backend/CLAUDE.md Boundary check 段加 workspace boundary + self-test 两行

验收

  • scanner 红→绿循环empty allowlist → 14 violations across 4 filesthreads / async_provider / provider / workerTYPE_CHECKING-only 的 factory.py 正确不在内);填入 4 entry → PASS
  • scanner self-test 9 个全过(防静默空跑)
  • T7.4 反注入实验:往 feedback.py:13 加一行违规 import → pytest tests/test_workspace_boundary.py 单条 failerror 精准指 app/gateway/routers/feedback.py:13 imports langgraph.checkpoint.postgresrevert 后立即返绿
  • 全套 make test 3241 passed + 31 skipped + 18 caplog flakePR6 末 3214 + 30 + 17+27 passed / +1 skip / +1 flake — passed delta 包含 PR7 新增 10 个测试以及环境差异导致的 17 个之前 flake 这次稳过,flake 列表形态与 STATUS.md 既有 17 项 + PR6 引入的 test_path_migration_pending_warning 一致,与 PR7 改动无关)
  • CI workflow 接入:扫描器是普通 pytest,已被 .github/workflows/backend-unit-tests.yml 全套 run 覆盖;无需新 workflow

文件结构

新增

  • backend/tests/test_workspace_boundary.py — AST 扫描器(127 行)
  • backend/tests/test_workspace_boundary_self.py — 扫描器 self-test93 行)
  • backend/tests/boundary_allowlist.toml — 4 个合法 importer + 每行注释(28 行)
  • docs/multi-tenant-redesign/03-impl/pr7-ci-boundary-scan.md — 本文件

修改

  • backend/CLAUDE.md — Boundary check 段 +2 行

未改动(验证后无需触碰):

  • app/gateway/routers/threads.pyruntime/checkpointer/async_provider.pyruntime/checkpointer/provider.pyruntime/runs/worker.py — 当前合法 importer,已被 allowlist 显式覆盖
  • agents/factory.py — TYPE_CHECKING-only import,扫描器自动豁免

关键设计决策

  1. TYPE_CHECKING 豁免 vs allowlist 收纳agents/factory.pyBaseCheckpointSaver 当类型注解用。两种实现方式都能让扫描通过——加 allowlist / 加 TYPE_CHECKING 检测。选后者:boundary 的真实语义是 "runtime path 不要构造 saver"type-only import 不进 runtime,本就不构成违规,把它列 allowlist 是给后人一个错误信号("看,这文件可以直接 import")。parent-walk 实现 _inside_type_checking 多 15 行代码,换 1 条更准的语义边界。
  2. Allowlist 形态:toml list of paths,而非正则 / 模块通配:4 个 entry,未来增长慢,精确路径列表最易审计 / diff。toml 配 frontmatter 注释每条合法性来源;任何 PR 加新 entry 都被 review 看到。
  3. 扫描器作为 pytest 而非独立 CI step:复用现有 backend-unit-tests.yml,零 workflow 改动,本地 make test 也覆盖。如果未来想跑成独立 step(更快失败),切出来代价低。
  4. 不引入 deerflow / app 边界以外的更细规则plan 顶部"一句话状态"提到 app.* 反向再 import deerflow.runtime.* 的间接环检查——这条作为 Stage 1 follow-up 保留,PR7 范围里只做 langgraph.checkpoint.* 这一条单点围栏,保持每个 PR 单一关注。
  5. Plan 草稿 allowlist 修正plan 列了 thread_runs.py / gateway/app.py,但 grep 实际状态两者都通过 get_checkpointer DI 拿 saver,不直接 import;同时漏掉了 provider.py(同步版 checkpointer)和 worker.pyrun resume seed)。impl 按 ground truth 取 4 个真实 importer——这是 plan 设计阶段无法预知的代码事实,应该以代码为准。

Live smoke 命令(用户跟进)

PR7 是纯静态检查,落地即生效,无 runtime 行为变化,无需 live smokemake test 全套绿就完成。