From f1c3a1fdaa508bf5de27a47097229f467ea82312 Mon Sep 17 00:00:00 2001 From: huangruiteng Date: Sat, 3 Oct 2026 08:02:55 +0800 Subject: [PATCH] test(coordination): exercise settlement after policy migration Signed-off-by: huangruiteng --- .../2026-09-28-retirement-cadence.md | 17 +++-- .../2026-09-28-retirement-cadence.zh-CN.md | 17 +++-- ...test_quota_authority_settlement_journey.py | 73 ++++++++++++++++++- 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md index 7deda7c86d..d7ff47d7e0 100644 --- a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md +++ b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.md @@ -74,7 +74,7 @@ contract readback. This does not certify every installed Host or D2. ## Current closeout: validation, migration and deletion (2026-10-02) -Rechecked against main `9b0486dc1` and the linked PR heads. This is the current +Rechecked against main `8b5335a72` and the linked PR heads. This is the current execution plan for **R5 / D1–D3 / T0–T4**, replacing the previous A–D schedule; older measurements remain source-specific evidence. R6 is a separate successor. Storage format, authority selection and ownership policy are three distinct @@ -88,13 +88,14 @@ retirement of the `legacy` handoff policy. | Merged: #4931, #5251 | SQLite replay/proof and allocation improvements. Reuse these implementations and retain their matched evidence; D2 is not certified by their merge. | | Merged: #5395, #5417 | Unused Python lease/handoff crossings and duplicate settlement admission/recovery decisions retired. Continue deletion at actual last callers; do not count these again. | | Merged: #5436 | Original delegated Host lease renewal. Final Todo validation and stop acknowledgement remain distinct boundaries. | -| Review: [#5413](https://github.com/loopx-project/loopx/pull/5413), `2c99505c7` | Separate provider promotion from backed-up policy migration; reject fresh legacy configuration but recover historical operations. CLI recovery repair: 99 affected tests and actual old-to-new CLI experiments on File/SQLite pass; final-head independent review remains. Existing legacy Goals are not automatically migrated. | -| Review: [#5466](https://github.com/loopx-project/loopx/pull/5466), `60a052383` | Preserve the original lease through final acceptance. Merge after independent review, then validate the installed execution path. | -| Review: [#5283](https://github.com/loopx-project/loopx/pull/5283), `73d1fe663` | Reduce preflight projection cost without reducing decision inputs; final capture reports provider unavailability explicitly. Author reports 96 unchanged-source File/SQLite inspections and full projection parity; independent review and installed readback remain. Do not declare the historical transient open failure explained by a synthetic failure. | +| Merged: [#5413](https://github.com/loopx-project/loopx/pull/5413), head `2c99505c7` | Separate provider promotion from backed-up policy migration; reject fresh legacy configuration but recover historical operations. Retain the original CLI recovery evidence. Existing legacy Goals are not automatically migrated; a successful plan does not qualify their execution consumers. | +| Merged: [#5466](https://github.com/loopx-project/loopx/pull/5466), merge `066b5bf26` | Preserve the original lease through final acceptance. Installed consumer qualification remains distinct from merge. | +| Review: [#5283](https://github.com/loopx-project/loopx/pull/5283), `1012d37f3` | Preflight optimization remains under review. Retain failed cold-CLI qualification rows; functional projection parity alone does not establish a performance pass. Do not declare the historical transient open failure explained by a synthetic failure. | +| Review: [#5500](https://github.com/loopx-project/loopx/pull/5500), `b367a37f2` | Recover the original canonical Goal creation operation through App retries. This creation/default-adoption prerequisite does not retire existing ownership policies. | | Affected-lane dependencies | [#5308](https://github.com/loopx-project/loopx/pull/5308) must prove child stop before settled acknowledgement; [#5398](https://github.com/loopx-project/loopx/pull/5398) preserves complete UI history/inspector facts. Scope these to consumers actually included in the trial. They are not SQLite-engine prerequisites or permission to ship a known broken journey. | -There are **three prioritized open closeout PRs**, not three PRs to universal -completion. The remaining implementation packages are creation/default adoption, +The remaining open heads above concern preflight and creation recovery, not a +fixed number of PRs to universal completion. The implementation packages remain creation/default adoption, policy migration plus legacy-policy retirement, and old-writer/capture retirement. They may combine only when caller ownership and rollback are coherent. Validation can expose concrete repairs; do not manufacture a fixed remaining-PR total or @@ -104,7 +105,7 @@ restart completed work to maintain one. | Package / existing owner | Work and decisive exit | Dependency / deletion / schedule | | --- | --- | --- | -| Close current heads; R3/R5 | Resolve exact-head findings in the three PRs above, inspect affected failures/conflicts, and present reviewed heads for maintainer merge. Record what is merged versus installed. | First target: 1–2 working days, subject to actual review/fix results. No unrelated optimization PR before closing these outcomes. | +| Close current heads; R3/R5 | Resolve exact-head findings in the open PRs above, inspect affected failures/conflicts, and present reviewed heads for maintainer merge. Record what is merged versus installed. | First target: 1–2 working days, subject to actual review/fix results. No unrelated optimization PR before closing these outcomes. | | Installed recovery candidate; D1/D3, existing whole-Goal promotion task | Pin one merged source and actual CLI/App/Effect Node/SQLite identity. Independently restore a verified backup, run the matrix below on detached real data plus synthetic negatives, and complete File→SQLite→new writes→File. Then perform authorized per-Goal adoption and ordinary readback. | Begin immediately after relevant merges; target 1–2 working days for the bounded matrix. Keep the compatible recovery binary and archives. No live corruption/crash injection. | | Bounded opt-in cohort; D2/D3 | When installed recovery and relevant execution controls pass, offer a reversible trial to at most 20 core developers. Publish workload/platform limits, backup/migration/disable instructions, known gaps, stop conditions and reporting route. Collect real daily use and failed cases. | Does not wait for every formal D2 axis or a new ten-day certificate. No invitation until rollback retains new writes. Does not certify a release default. | | Canonical creation/default adoption; D3/T3 | Reuse `machine_configuration/goal_storage.py` and `local_authority_defaults.ts`. Current setting only chooses the **post-promotion target** (`promotion_performed: false`). Complete new-Goal initialization, retry and upgrade, settings plus packaged App/CLI/Lark readback; explicit existing selectors stay pinned. | Implement after the bounded candidate is useful; activate the release default only at the decision below. Remove replaced creation/selection decisions in this package. Changing `file` to `sqlite` in one setting is insufficient. | @@ -129,7 +130,7 @@ Effect processes. Do not truncate metadata, history or decision inputs to win. | --- | --- | --- | | Backup and complete data | Verify online SQLite snapshot and logical archive restore. Compare full Todo JSON, absent/null/false, unknown metadata, role/task class, archived dependencies, validation contracts/revisions, claims/lease generations, original events/receipts/cursors and the supported Goal/source state. Enumerate every stored family; counts or a final-head hash alone are insufficient. | `test_authority_archive.py`, `authority_archive_audit.test.ts`, archive crash/restore and migration suites | | Forward and reverse migration | File→SQLite; add/update/complete and replay a real new operation; restart; export to File; assert all old facts **and the new writes** survive. Lost responses and identical retries return original outcomes; a different intent with the same operation ID rejects. | `local_authority_migration.test.ts`, archive and reviewed-cutover CLI suites | -| Mutation and ownership | Create/claim/update/complete/supersede/archive; quota selection→refresh→spend; same-Todo contention, stale revision/epoch, lease renew/release and applicable policy migration. One commit/effect/settlement, no ownership invention. | Real File/SQLite command suites; #5413/#5436/#5466; shared changes also use isolated real PostgreSQL | +| Mutation and ownership | Create/claim/update/complete/supersede/archive; quota selection→refresh→spend; same-Todo contention, stale revision/epoch, lease renew/release and applicable policy migration. One commit/effect/settlement, no ownership invention. | Real File/SQLite command suites; `test_quota_authority_settlement_journey.py` joins legacy→hard migration, rejected unleased edit, leased write, returned settlement retry, migration replay after work and next-Turn admission with the Markdown source absent. #5413/#5436/#5466 cover adjacent migration/lifetime boundaries; shared changes also use isolated real PostgreSQL. | | Interruption and recovery | Process death before/after durable commit and selector publication; provider unavailable/busy, disk-full injection, stalled projection and lagged consumer. Reopen/retry settles once and permits legitimate subsequent work. A still-running child cannot be called stopped/settled. | Existing crash/migration/process suites; #5308's affected Host lane | | Installed consumers | CLI `status`, quota, Todo list/detail; packaged App list/inspector and ordinary mutation; Lark when included. Counts, metadata, freshness, error/recovery feedback and original-route results agree with canonical facts. Test restart and old page resource loading. | Existing projection/consumer tasks and packaged frontend smokes; #5398 where affected | | Cost and endurance | Same data, history, durability and commands: cold full CLI versus warm store, p50/p95/p99/sample count, RSS, database/WAL and write growth, lock contention and consumer lag. Preserve failed formal macOS cold-CLI and missing axes; disclose absolute and relative current-release regressions. | #4224, SQLite comparison/rehearsal runner and existing performance-diagnosis capability | diff --git a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md index 2be8a3f5a9..1a9acc3d6a 100644 --- a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md +++ b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-28-retirement-cadence.zh-CN.md @@ -65,7 +65,7 @@ owner、持久兼容义务、正反例证据及回退方式,和不可变基线 ## 当前收尾:验证、迁移与删除(2026-10-02) -按 main `9b0486dc1` 和所列 PR head 重新核对。本节是 **R5 / D1–D3 / T0–T4** +按 main `8b5335a72` 和所列 PR head 重新核对。本节是 **R5 / D1–D3 / T0–T4** 的当前执行计划,替代旧 A–D 排期;历史测量仍只适用于原源码和负载。R6 单独推进。 存储格式、权威选择、所有权策略是三种不同迁移:有 SQLite 数据库,不代表新 Goal 已经默认使用 canonical authority,也不代表 `legacy` handoff 策略已经退役。 @@ -77,13 +77,14 @@ owner、持久兼容义务、正反例证据及回退方式,和不可变基线 | 已合并:#4931、#5251 | SQLite 重放/证明和分配优化;复用实现及匹配证据,合并不等于 D2 已验收。 | | 已合并:#5395、#5417 | 无调用方的 Python lease/handoff 跨界、重复结算准入/恢复决策已退役。继续按最后调用方删除,不重复计账。 | | 已合并:#5436 | 委派 Host 原租约续期;最终 Todo 验收和停止确认仍是不同边界。 | -| 复审中:[#5413](https://github.com/loopx-project/loopx/pull/5413),`2c99505c7` | provider 晋升与带备份的策略迁移解耦;禁止新 legacy 配置,允许恢复历史操作。CLI 恢复修复的 99 项相关测试、File/SQLite 真实旧 CLI→新 CLI 演练通过,最终 head 独立复审待完成。没有自动迁移存量 legacy Goal。 | -| 待审:[#5466](https://github.com/loopx-project/loopx/pull/5466),`60a052383` | 原租约保持到最终验收;独立评审、维护者合并后验证安装态执行路径。 | -| 待审:[#5283](https://github.com/loopx-project/loopx/pull/5283),`73d1fe663` | 不缩减决策输入地降低 preflight 投影成本,末次 capture 显式报告 provider 不可用。作者报告固定源码下 96 次 File/SQLite 检查及完整投影等价;仍需独立复审和安装后读回。合成故障不证明历史瞬态打开失败的根因。 | +| 已合并:[#5413](https://github.com/loopx-project/loopx/pull/5413),head `2c99505c7` | provider 晋升与带备份的策略迁移解耦;禁止新 legacy 配置,允许恢复历史操作。保留原 CLI 恢复证据。没有自动迁移存量 legacy Goal,成功生成计划也不代表执行消费者已验收。 | +| 已合并:[#5466](https://github.com/loopx-project/loopx/pull/5466),merge `066b5bf26` | 原租约保持到最终验收;安装态消费者验收与合并分开记录。 | +| 待审:[#5283](https://github.com/loopx-project/loopx/pull/5283),`1012d37f3` | preflight 优化仍在评审;冷 CLI 资格失败行保留,功能投影等价不等于性能通过。合成故障不证明历史瞬态打开失败的根因。 | +| 待审:[#5500](https://github.com/loopx-project/loopx/pull/5500),`b367a37f2` | App 重试恢复原 canonical Goal 创建操作;这是创建/默认接入的前置修复,不退役存量所有权策略。 | | 按实际路径建立依赖 | [#5308](https://github.com/loopx-project/loopx/pull/5308) 要证明子进程停止后才报告已结算;[#5398](https://github.com/loopx-project/loopx/pull/5398) 保留 UI 历史和 inspector 完整事实。只对纳入试用的相关消费者建依赖,不将其说成 SQLite 引擎前置,也不能发布已知损坏的用户路径。 | -当前优先收尾的是 **3 个已存在的开放 PR**,不等于再合 3 个就全部结束。 -剩余实现包是 canonical 创建/默认接入、策略迁移与 legacy 策略删除、旧 writer/ +上述开放 head 分别解决 preflight 与创建恢复,不代表固定“剩余 PR 数”。 +剩余实现包仍是 canonical 创建/默认接入、策略迁移与 legacy 策略删除、旧 writer/ 捕获退役。仅当调用方归属和回退边界一致时才合并成同一个 PR。验证可能暴露具体修复, 不再制造固定“剩余 PR 数”,也不为维持这个数字重做已完成的工作。 @@ -91,7 +92,7 @@ owner、持久兼容义务、正反例证据及回退方式,和不可变基线 | 交付包/既有 owner | 要做什么、凭什么完成 | 依赖/删除机会/节奏 | | --- | --- | --- | -| 现有 head 收尾;R3/R5 | 修完上述 3 个 PR 的 exact-head finding,处理相关失败与冲突,提交已评审 head 给维护者合并;区分已合并和已安装。 | 第一目标为 1–2 个工作日,取决于真实评审/修复结果;收尾前不另开无关优化。 | +| 现有 head 收尾;R3/R5 | 修完上述开放 PR 的 exact-head finding,处理相关失败与冲突,提交已评审 head 给维护者合并;区分已合并和已安装。 | 第一目标为 1–2 个工作日,取决于真实评审/修复结果;收尾前不另开无关优化。 | | 安装态恢复候选;D1/D3、整 Goal 晋升任务 | 固定合并源码和 CLI/App/Effect 实际 Node/SQLite 身份;独立恢复并验证备份,用隔离真实快照及合成负例执行下表,完成 File→SQLite→新增写入→File。之后按授权逐 Goal 采用并日常回读。 | 相关 PR 合并后立即开始,有界矩阵目标 1–2 个工作日;保留兼容的恢复版本和 archive,不对活跃 Goal 注入崩溃/损坏。 | | 有界自愿试用;D2/D3 | 安装态恢复及相关执行控制通过后,邀请不超过 20 位核心开发者。公开负载/平台范围、备份迁移关闭步骤、已知缺口、停止条件与反馈入口;观察真实日常使用和失败。 | 不必等待全部正式 D2 轴或一份新的十天证书;携带新写入回退未通过前不邀请。试用不认证发布默认值。 | | Canonical 创建/默认接入;D3/T3 | 复用 `machine_configuration/goal_storage.py` 和 `local_authority_defaults.ts`。当前设置只选择**晋升后的目标**,返回 `promotion_performed: false`。补齐新建初始化/重试、升级、设置及打包 App/CLI/Lark 读回,已有显式 selector 保持固定。 | 有界候选可用后实现,发布默认启用仍服从下方决策;同包删除被替代的创建/选择决策。只把设置里的 file 改成 sqlite 不够。 | @@ -114,7 +115,7 @@ owner、持久兼容义务、正反例证据及回退方式,和不可变基线 | --- | --- | --- | | 备份与完整数据 | 验证 SQLite 在线快照及逻辑 archive 恢复;比较完整 Todo JSON、缺省/null/false、未知 metadata、role/task class、归档依赖、验收合同/版本、claim/lease generation、原 events/receipt/cursor,以及受支持 Goal/source 状态。枚举全部持久状态家族,不能只比数量或最后 head hash。 | `test_authority_archive.py`、`authority_archive_audit.test.ts`、archive crash/restore 和迁移套件 | | 正反向迁移 | File→SQLite,真正新增/修改/完成并重放一笔新操作,重启后导回 File;全部旧事实和**新增写入**都保留。丢响应与相同重试回原结果,同 operation ID 不同意图拒绝。 | `local_authority_migration.test.ts`、archive 与 reviewed-cutover CLI 套件 | -| 写入与所有权 | create/claim/update/complete/supersede/archive,quota 选择→refresh→spend,同 Todo 竞争、旧 revision/epoch、lease 续期/释放及适用策略迁移;一笔 commit/effect/settlement,不凭空造所有权。 | 真实 File/SQLite 命令套件;#5413/#5436/#5466;共享修改还须隔离真实 PostgreSQL | +| 写入与所有权 | create/claim/update/complete/supersede/archive,quota 选择→refresh→spend,同 Todo 竞争、旧 revision/epoch、lease 续期/释放及适用策略迁移;一笔 commit/effect/settlement,不凭空造所有权。 | 真实 File/SQLite 命令套件;`test_quota_authority_settlement_journey.py` 串起 legacy→hard 迁移、无租约修改拒绝、带租约写入、返回的结算命令重试、新写入后的迁移重放和下一轮准入,且 Markdown 源已移除。#5413/#5436/#5466 覆盖相邻迁移/生命周期边界;共享修改还须隔离真实 PostgreSQL。 | | 中断与恢复 | durable commit/selector 发布前后进程中断、provider unavailable/busy、空间不足注入、投影卡住和 consumer 滞后;重启/重试只结算一次且后续合法工作可继续。子进程还活着不能报告已停止/已结算。 | 既有 crash/migration/process 套件;#5308 相关 Host 路径 | | 安装态消费者 | CLI status/quota/Todo list/detail;打包 App 列表/inspector 和普通修改;纳入范围时验证 Lark。数量、metadata、新鲜度、错误/恢复反馈、原路返回与 canonical 事实一致,覆盖重启和旧标签页资源。 | 既有投影/消费者任务、打包前端 smoke;受影响处采用 #5398 | | 成本与持续运行 | 相同数据/历史/durability/命令,分别测完整冷 CLI 和 warm store,报告 p50/p95/p99/样本数、RSS、DB/WAL/写增长、锁竞争及 consumer lag。正式 macOS 冷 CLI 失败及缺项保持可见,披露相对当前 release 的绝对值与相对变化。 | #4224、SQLite comparison/rehearsal runner、既有 performance-diagnosis capability | diff --git a/tests/control_plane/test_quota_authority_settlement_journey.py b/tests/control_plane/test_quota_authority_settlement_journey.py index 42c8d1d374..f2911530ba 100644 --- a/tests/control_plane/test_quota_authority_settlement_journey.py +++ b/tests/control_plane/test_quota_authority_settlement_journey.py @@ -26,7 +26,8 @@ def _row(todo_id: str, *, status: str = "open", extra: str = "") -> str: ) -def _source(root: Path, *, provider: str, status: str = "open", extra: str = "", empty: bool = False): +def _source(root: Path, *, provider: str, status: str = "open", extra: str = "", empty: bool = False, + handoff_mode: str = "soft_claim"): project, runtime, registry = cli._write_fixture(root) goal = json.loads(registry.read_text())["goals"][0] path = project / goal["state_file"] @@ -38,7 +39,7 @@ def _source(root: Path, *, provider: str, status: str = "open", extra: str = "", initialize_canonical_authority( runtime, cli.GOAL_ID, build_todo_runtime_shadow_projection( - goal_id=cli.GOAL_ID, todos=fields["agent_todos"]["items"], handoff_mode="soft_claim", + goal_id=cli.GOAL_ID, todos=fields["agent_todos"]["items"], handoff_mode=handoff_mode, ), state_path=path, provider=provider, ) @@ -133,6 +134,74 @@ def _execute(command: str, project: Path, runtime: Path, registry: Path): return cli._run_cli(registry, runtime, *shlex.split(command)[1:], cwd=project) +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_migrated_hard_lease_can_write_settle_retry_and_continue(tmp_path, provider): + project, runtime, registry, path, _ = _source( + tmp_path, provider=provider, handoff_mode="legacy", + extra=f"claimed_by={cli.AGENT_ID}", + ) + + def run(*args): + return cli._run_cli(registry, runtime, *args, "--goal-id", cli.GOAL_ID, cwd=project) + + plan = tmp_path / "handoff-plan.json" + code, planned = run("handoff-mode", "plan-migration", "--mode", "hard_lease", "--plan", str(plan)) + assert code == 0 and planned["status"] == "planned", planned + assert planned["preserved_claim_count"] == 1 + migration = ("handoff-mode", "migrate", "--plan", str(plan), + "--plan-sha256", planned["plan_sha256"], "--execute") + code, migrated = run(*migration) + assert code == 0 and migrated["status"] == "applied", migrated + assert Path(migrated["backup_path"]).is_file() + assert migrated["execution_authority_granted"] is False + path.unlink() # The migrated journey cannot use the old Markdown writer. + + code, guard = _guard(project, runtime, registry) + assert code == 0 and guard["decision"] == "run", guard + assert guard["heartbeat_receipt"]["settlement_identity"]["todo_id"] == cli.TODO_ID + update = ("todo", "update", "--todo-id", cli.TODO_ID, "--agent-id", cli.AGENT_ID, + "--text", "Continue after policy migration") + code, rejected = run(*update) + assert code == 1 and rejected["error_code"] == "handoff_mode_requires_lease", rejected + + lease_key = "migrated-delivery" + code, acquired = run("task-lease", "acquire", "--todo-id", cli.TODO_ID, "--owner", cli.AGENT_ID, + "--idempotency-key", lease_key, "--expected-version", "0", "--ttl-seconds", "3600", + "--write-scope", "tests/**") + assert code == 0 and acquired["acquired"] is True, acquired + version = str(acquired["lease"]["version"]) + code, updated = run(*update, "--task-lease-idempotency-key", lease_key, + "--task-lease-expected-version", version) + assert code == 0 and updated["ok"] is True, updated + + code, refreshed = _refresh(project, runtime, registry) + assert code == 0, refreshed + command = refreshed["settlement_owed"]["command"] + code, spent = _execute(command, project, runtime, registry) + assert code == 0 and spent["appended"] is True, spent + assert spent["settlement_progress"]["state"] == "settled" + code, retried = _execute(command, project, runtime, registry) + assert code == 0 and retried["appended"] is False, retried + assert cli._spend_run_count(runtime) == 1 + + # A migration retry after actual work cannot reset the current projection. + before = list_goal_todos(registry_path=registry, goal_id=cli.GOAL_ID, + runtime_root_arg=str(runtime), todo_id=cli.TODO_ID)["todo"] + assert before["text"] == "[P1] Continue after policy migration" + code, replayed = run(*migration) + assert code == 0 and replayed["status"] == "replayed", replayed + assert list_goal_todos(registry_path=registry, goal_id=cli.GOAL_ID, + runtime_root_arg=str(runtime), todo_id=cli.TODO_ID)["todo"] == before + code, released = run("task-lease", "release", "--todo-id", cli.TODO_ID, "--owner", cli.AGENT_ID, + "--idempotency-key", lease_key, "--expected-version", version) + assert code == 0 and released["released"] is True, released + code, next_wake = _guard(project, runtime, registry, turn_id="turn-after-policy-migration") + assert code == 0 and next_wake["decision"] == "run", next_wake + assert next_wake["selected_todo"]["todo_id"] == cli.TODO_ID + assert next_wake["effective_action"] != "unsettled_host_turn_recovery" + assert cli._spend_run_count(runtime) == 1 + + def test_returned_command_settles_and_repairs_receipts_without_another_debit(tmp_path): project, runtime, registry = cli._write_fixture(tmp_path) code, guard = _guard(project, runtime, registry)