From a092a8ab5915315e6ceb67976a2cdf9ba2202038 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Sat, 26 Sep 2026 21:24:39 +0800 Subject: [PATCH] refactor(authority): retire Python prototype and qualify native providers Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- ...26-09-24-default-cutover-reconciliation.md | 20 + ...24-default-cutover-reconciliation.zh-CN.md | 20 +- ...hority-state-provider-v0-evidence.zh-CN.md | 5 + ...shared-goal-authority-state-provider-v0.md | 7 + ...-goal-authority-state-provider-v0.zh-CN.md | 7 + .../typescript-control-plane-migration-v0.md | 10 + examples/nokv-shadow-provider/README.md | 211 --- examples/nokv-shadow-provider/live_e2e.py | 471 ------- examples/nokv-shadow-provider/probes.py | 1196 ---------------- examples/nokv-shadow-provider/provider.py | 243 ---- examples/shared-goal-authority-e2e/README.md | 58 +- examples/visible-governance-slice-smoke.py | 20 +- loopx/control_plane/coordination/executor.py | 1207 ----------------- .../coordination/file_provider.py | 309 ----- .../coordination/goal_state_shadow.py | 99 -- loopx/control_plane/coordination/head.py | 831 ------------ .../testing/authority_e2e_ladder.py | 157 +-- loopx/visible_governance.py | 137 +- pyproject.toml | 3 - .../test_coordination_executor.py | 541 -------- .../test_coordination_file_provider.py | 491 ------- tests/control_plane/test_coordination_head.py | 517 ------- .../test_coordination_provider_parity.py | 361 ----- ...test_coordination_recoverable_execution.py | 1046 -------------- .../test_local_coordination_authority.py | 11 +- .../test_shared_goal_authority_e2e.py | 90 +- .../coordination_head_stage2_v0_claimed.json | 64 - tests/test_nokv_shadow_provider_probes.py | 121 -- 28 files changed, 292 insertions(+), 7961 deletions(-) delete mode 100644 examples/nokv-shadow-provider/README.md delete mode 100644 examples/nokv-shadow-provider/live_e2e.py delete mode 100644 examples/nokv-shadow-provider/probes.py delete mode 100644 examples/nokv-shadow-provider/provider.py delete mode 100644 loopx/control_plane/coordination/executor.py delete mode 100644 loopx/control_plane/coordination/file_provider.py delete mode 100644 loopx/control_plane/coordination/goal_state_shadow.py delete mode 100644 loopx/control_plane/coordination/head.py delete mode 100644 tests/control_plane/test_coordination_executor.py delete mode 100644 tests/control_plane/test_coordination_file_provider.py delete mode 100644 tests/control_plane/test_coordination_head.py delete mode 100644 tests/control_plane/test_coordination_provider_parity.py delete mode 100644 tests/control_plane/test_coordination_recoverable_execution.py delete mode 100644 tests/fixtures/coordination_head_stage2_v0_claimed.json delete mode 100644 tests/test_nokv_shadow_provider_probes.py diff --git a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.md b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.md index ae0465067a..01ce45911e 100644 --- a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.md +++ b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.md @@ -53,6 +53,26 @@ D1–D3 and an exact total PR count remain unqualified. This storage repair reti no Python business owner; bounded Python deletion belongs to actual caller migration in the packages below. +## 2026-09-26: early prototype retirement + +Reconciled against main `8cfc0dd4c`: #5102 is merged; File history encoding and +automatic upgrade are delivered, not remaining work. This slice removes the +Python executor, head, File provider and bootstrap bridge after the production +caller audit. Native TS commands remain the owners; `authority_core.py` still +has real adapter callers. Stage 0 now qualifies actual File/SQLite conformance +instead of the obsolete Python provider. Historical row ids are not reused. +[Coverage and format boundary](../../../../../examples/shared-goal-authority-e2e/README.md#native-qualification-and-prototype-retirement). + +This independently deliverable deletion advances package 3 below; it does not +close any whole default-cutover package. **After this PR, plan the same three +named implementation PRs below**, with prototype retirement removed from their +scope. Counting this early deletion slice makes four named deliveries starting +with this PR, not a guaranteed total. #5054/#4931 remain existing dependencies; +#4224 and D1–D3 are evidence gates. The Chinese package-2 row is corrected to the +accepted retirement direction rather than requesting a new writer for retired +Todo events. Future accounting must distinguish a completed sub-slice from a +closed package and record the concrete residual gap. + ## Three concrete next code boundaries This delivery repairs integrated migration admission: stale registry snapshots diff --git a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.zh-CN.md b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.zh-CN.md index 1f61f18d29..2f44233136 100644 --- a/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.zh-CN.md +++ b/docs/architecture/rfcs/ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.zh-CN.md @@ -18,7 +18,7 @@ SQLite 已有 TS checkpoint/delta 编码,保留原始版本、回执和完整 当前增量前是四个具名开发包:本次有实际证据的 File 成本/升级修复,加下文三个 业务边界;完成本次后仍剩三个规划包,不是继续复述“5–8 PR”。#5063、#5054、 #4931 是已有 PR,不能重复计为新任务。D1–D3 的未通过证据另列,不能保证总 PR 数。 -本次没有删除 Python 业务 owner,只有升级命令的薄适配。 +该 File 格式升级本身没有删除 Python 业务 owner,只有升级命令的薄适配。 整 Goal 来源闭环须按 #5054 当前方向核对:它退役旧 Todo event 路径并分离 supervisor 日志,不应为已经退役的来源重建捕获 writer。剩余支持来源、consumer、回退与 cohort @@ -46,6 +46,22 @@ SQLite 已有 TS checkpoint/delta 编码,保留原始版本、回执和完整 scan 100 p95 801.81 ms / 250 ms),#4931 尚未提供精确 head 的正式复测。 十日 soak 到了计划结束日期,不等于已有通过结果。 +## 2026-09-26:提前完成的原型退役切片 + +核对 main `8cfc0dd4c`:#5102 已合入,不能继续把 File 历史编码/升级列为缺口。 +当前切片删除没有正式 caller 的 Python executor、head、File provider 和 bootstrap +桥接;正式命令继续使用既有 TS owner,`authority_core.py` 仍有真实适配调用者。 +Stage 0 原来测试旧 Python provider,如今接到实际 File/SQLite 完整 conformance; +旧原型的历史行不被重新解释为当前资格。 +[测试覆盖与格式边界](../../../../../examples/shared-goal-authority-e2e/README.md#native-qualification-and-prototype-retirement)。 + +这是下表第 3 包中可以独立提前交付的 Python 退役部分。**本次 PR 之后仍计划下表 +三个实现 PR**:执行区间防护、迁移回退集成、默认入口;第 3 个不再包含已经删除 +的原型。不因为删除量大就把默认切换标成完成,也不再把“包”冒充保证的 PR 总数。 +把这次独立退役也计入,从本次开始是四个具名交付切片;其中只有本次已实施,其余 +是有明确退出条件的计划。#5054、#4931 是已有依赖,#4224/D1–D3 的缺证据另算。 +修正本页中文第 2 行与英文已接受方向的矛盾,避免重新建设已决定退役的 writer。 + ## 三个明确的后续代码边界 本次补的是整合后的真实晋升准入缺口:旧 registry 快照可初始化 shadow,以及保存 @@ -55,7 +71,7 @@ scan 100 p95 801.81 ms / 250 ms),#4931 尚未提供精确 head 的正式复 | 拟议 PR | 可观察结果与 owner | 退出条件 | | --- | --- | --- | | 1. 外部动作执行区间保护 | lease/effect owner 将执行身份验证覆盖到实际外部动作、接管、超时、退出及不确定完成。复用已合入 #4994/#4995。 | 过期 executor 不能继续执行/结算;真实执行器及 receipt 恢复矩阵通过。执行前查一次 proof 不够。 | -| 2. 事件 writer 绑定与整 Goal 迁移/回退闭环 | 将 event writer 锁和原子发布接入现有 outbox;组合 Markdown/event/lease writer、drain、saved cutover、消费者和 fenced export/rollback,删除被 TS 替代的 Python 决策。 | 复用 #5003,绑定通过前保留 `event_log_writer_not_bound`;闭合 D1、命令清单与 D3 cohort。单个无 event overlay 的 Goal 晋升不证明本项。 | +| 2. 整 Goal 迁移/回退与保留来源闭环 | 结合在途 #5054 的旧 Todo event 路径退役及 supervisor 日志拆分,组合仍受支持的来源、drain、saved cutover、消费者及 fenced export/rollback;删除被 TS 替代的 Python 决策。 | 按 #5054 合入后的清单证明 D1 与 D3 cohort;显式拒绝退役来源,不再为其重建捕获 writer。单个无 event overlay 的 Goal 晋升不证明本项。 | | 3. 默认入口与有界 Python 退役 | 新 Goal、settings、安装及 packaged frontend/Lark/CLI 一致选择合格 profile;存量有显式迁移与停用流程。 | 1/2 及适用 D1–D3 通过,验证用户入口,删除最后 caller 已转走的业务 writer;保留 renderer、host IO、合法导入导出。 | **计划是三个可命名的后续实现 PR,加已有 #4931 和未闭合证据;不是保证总计四个 diff --git a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0-evidence.zh-CN.md b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0-evidence.zh-CN.md index 8146a99668..ce79b23f31 100644 --- a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0-evidence.zh-CN.md +++ b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0-evidence.zh-CN.md @@ -1,5 +1,10 @@ # 验证说明:NoKV canonical coordination provider(v0) +> 历史证据存档。此处 `nokv-shadow-provider` 和 Python head/executor 原型已于 +> 2026-09-26 退役;命令和路径仅描述当时基线,不再是当前运行指南。当前 File/SQLite +> 资格及 NoKV Stage 2A 入口见 [E2E ladder](../../../examples/shared-goal-authority-e2e/README.md)。 + + - 配套 RFC:[LoopX 共享控制面权威与可插拔状态 Provider (v0)](./shared-goal-authority-state-provider-v0.zh-CN.md) - 参考实现与探针:`examples/nokv-shadow-provider/` - 证据范围:canonical coordination aggregate、target-scoped conflict、内部 CAS diff --git a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md index 56a05268d8..be9436b3a4 100644 --- a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md +++ b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md @@ -2011,6 +2011,11 @@ flips to provider-first without changing the typed outcome contract below. #### Stage 2 slice status (2026-08-23) +> Historical prototype record. The Python head/executor/File provider and its +> probes are now retired; current Stage 0 qualifies native TS File/SQLite. +> See the [coverage and compatibility boundary](../../../examples/shared-goal-authority-e2e/README.md#native-qualification-and-prototype-retirement). + + The first Stage 2 slice is merged on `main` through #3529, additively: - `loopx.control_plane.coordination.head`: the `loopx_coordination_head_v0` @@ -2195,6 +2200,8 @@ entry point `examples/shared-goal-authority-e2e/ladder.py`. Per stage, this increment implements: +Historical Stage 0 rows below were retired on 2026-09-26. Use the current ladder for native provider qualification. + - Stage 0: `s0.file_matrix_twelve_rows` runs the retained live matrix script and requires exactly the twelve shared scenario rows to be true on the file provider; `s0.nokv_live_matrix` requires the same rows plus diff --git a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md index 59544d4abd..3fa3dcb78c 100644 --- a/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md +++ b/docs/architecture/rfcs/shared-goal-authority-state-provider-v0.zh-CN.md @@ -1594,6 +1594,11 @@ provider-first,且不改变下述 typed outcome 合同。 #### Stage 2 切片状态(2026-08-23) +> 历史原型记录:Python head/executor/File provider 及其探针已退役。当前 +> Stage 0 验证正式 TS File/SQLite;下文历史通过记录不构成当前 provider 资格。 +> [覆盖映射与兼容边界](../../../examples/shared-goal-authority-e2e/README.md#native-qualification-and-prototype-retirement)。 + + 第一个 Stage 2 切片已通过 #3529 以增量方式合入 `main`: - `loopx.control_plane.coordination.head`:`loopx_coordination_head_v0` @@ -1732,6 +1737,8 @@ CLI runner、observation-lock 窗口、候选回读)、只读 TypeScript 探 按阶段,本增量实现: +下列 Stage 0 历史行已于 2026-09-26 退役;当前资格使用 native provider ladder。 + - Stage 0:`s0.file_matrix_twelve_rows` 运行保留的 live matrix 脚本,要求 file provider 上恰好十二个共享场景行全为 true;`s0.nokv_live_matrix` 要求 live NoKV 栈上同样的行加 `restored_lineage_fails_closed` 全为 true,且 file/NoKV diff --git a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md index e0d1d9fecf..26c7d5e205 100644 --- a/docs/architecture/rfcs/typescript-control-plane-migration-v0.md +++ b/docs/architecture/rfcs/typescript-control-plane-migration-v0.md @@ -25,6 +25,16 @@ bounded Python retirement. #4931 and outstanding D2 evidence are tracked separately. Three is a delivery plan, not a guaranteed total PR count. [Current inventory and exits](ledger/shared-goal-authority-state-provider-v0/2026-09-24-default-cutover-reconciliation.md). +## Native authority qualification and prototype retirement (2026-09-26) + +The coverage-only Python coordination executor, head codec, File provider and +bootstrap bridge are retired with their last example/test callers. Stage 0 now +runs complete native File/SQLite conformance rather than the obsolete Python +provider. The existing TS domain owners and shared fixtures are reused; Python +`authority_core.py` remains a live adapter and is not removed. This is an early +bounded T4 deletion slice, not default cutover or all Python retirement. +[Coverage, behavior differences and format boundary](../../../examples/shared-goal-authority-e2e/README.md#native-qualification-and-prototype-retirement). + ## Observation writer retirement (2026-09-24) The obsolete Python post-commit observer and TS observation commit path are diff --git a/examples/nokv-shadow-provider/README.md b/examples/nokv-shadow-provider/README.md deleted file mode 100644 index 509331ca83..0000000000 --- a/examples/nokv-shadow-provider/README.md +++ /dev/null @@ -1,211 +0,0 @@ -# NoKV canonical-coordination provider reference - -This directory contains the small, reviewable NoKV reference for -[RFC: LoopX shared control-plane authority and pluggable state providers v0](../../docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md). -It is a contract example, not a shipped LoopX runtime integration or a -production deployment claim. - -## Scope - -The reference stores one per-goal **canonical coordination aggregate** and -exercises the Stage 3 lifecycle: `claim_work`, `renew_work`, `release_work`, -expired-lease `reclaim_work`, stale-fence rejection, and atomic completion with -continuation/successors. The aggregate carries the current authority revision, -claim/lease/fence state, store-lineage binding, and replayable receipt index. -It remains a coverage-only reference and does not migrate the current LoopX -runtime or promote this provider to the product source of truth. - -The following remain outside this head: - -- run artifacts and run-history ledgers; -- status and attention projections or caches; -- quota policy, accounting, and enforcement ledgers; -- host-local routes, scheduler state, locks, and runtime bindings; -- raw evidence, transcripts, credentials, and local absolute paths; and -- Agent IM delivery, wake-up, presence, and offline queues. - -Those surfaces have separate ownership and synchronization strategies in the -RFC persistence matrix. A provider does not acquire authority over them merely -because they may be visible from a shared goal. - -## Atomic aggregate and receipt replay - -The current coordination state and the receipt mapping for a newly accepted -operation are published in the **same head CAS**. The reference does not use a -last-envelope shortcut or a separate `pending -> head -> finalize` receipt -protocol. - -The one-head CAS is a physical serialization point, not a goal-wide domain -conflict boundary. Commands name the target todo revision and the compact -authorization, dependency, and gate preconditions they actually observed. -After a CAS miss, the authority reloads and checks those facts again. If an -independent todo advanced the head, it rebases and retries internally; if the -target todo or a named precondition changed, it returns a domain conflict. -`authority_revision` remains a goal-wide commit and audit sequence, not a -required client precondition. In this reference, independent claims use -`write_scopes=[]`; non-empty cross-todo scope overlap is not qualified here. -Internal rebase also assumes the publisher never reuses a target-scoped token -for a different authorization, dependency, or gate snapshot. The deterministic -probe uses static bootstrap inputs and does not qualify that dynamic publisher. - -This matters for a historical retry: - -1. operation A commits and returns receipt A; -2. operation B advances the goal head; -3. the caller retries A after losing its first response; and -4. the provider returns the original receipt A, field for field, without - applying A again or moving the current head. - -Each receipt-index entry binds the stable operation identity to a digest of the -immutable request. Reusing an operation identity with different immutable -inputs fails closed; it is never classified as an idempotent replay. - -The provider contract remains storage-only: - -```text -load() -> (aggregate | none, provider_generation) -compare_and_put(expected_provider_generation, aggregate) - -> applied(provider_generation) - | conflict(current_provider_generation) - | ambiguous - | failed -``` - -`provider_generation` is the opaque storage CAS token. It is distinct from the -aggregate's `authority_revision` and from each todo's lease epoch; none of these -three version domains is derived from another. - -The storage provider deterministically serializes and stores the opaque -aggregate and returns CAS outcomes. The production -`loopx.control_plane.coordination` modules (`head` codec plus the -`CoordinationAuthorityExecutor`) are responsible for request validation, -authority revision, claim/lease transitions, request-digest binding, and the -original domain receipt; this directory no longer carries a second reference -authority. The provider must not inspect an operation receipt or invent -lease, gate, quota, or scheduling decisions. - -## Files - -- `provider.py`: `NoKVCoordinationProvider`, which maps an opaque per-goal - aggregate to NoKV path generation CAS. It serializes through the - production canonical head codec, so the adapter cannot fork the - digest/parity basis. -- `probes.py`: deterministic contract regressions driven by the production - executor and head codec; every claim/CAS probe round-trips its persisted - head through the production `validated_head`. Only the checks in the - [evidence note](../../docs/architecture/rfcs/shared-goal-authority-state-provider-v0-evidence.zh-CN.md) - are merge evidence for the revised receipt contract. - -## Validation boundary - -The provider mapping is pinned to NoKV -[`3d75d96965`](https://github.com/NoKV-Lab/NoKV/commit/3d75d96965) (the -0.11.0 line). At that baseline, the Python `publish_bytes` surface accepts -`expected_generation` for create-only or replacement CAS and exposes optional -publication `operation_id` and `artifact_revision_id` inputs; `read` and -`stat` return the path `generation` the adapter treats as -`provider_generation`. Since NoKV 0.11.0 the SDK raises `FileNotFoundError` -for a missing path and `FileExistsError` for a create-only collision; every -other client failure is a `RuntimeError`. The adapter classifies by -exception class only: `FileNotFoundError` is the one missing signal, and any -other client failure on a read path raises the typed -`ProviderUnavailableError` instead of masquerading as an uninitialized goal -or escaping as a bare `RuntimeError`. Error prose is never a channel - real -non-missing failures carry messages such as `invalid root route: root -placement does not exist` - and pre-0.11 SDKs, which signalled missing with -such prose, cannot route the post-#465 control plane and are outside the -pinned baseline. `contract.nokv_adapter_exception_mapping` pins the -classification offline with fake clients that raise the 0.11.0 exception -classes and the real outage message shapes. - -A fresh coordination-provider handle must be admitted with -`open_nokv_coordination_provider(...)`. NoKV performs route admission while -constructing `Client`, before an ordinary provider constructor could classify -the failure. The adapter-owned helper maps that eager failure to the same -`ProviderUnavailableError`, performs no coordination write, and never falls -back to the file provider. The live matrix uses this path for every fresh -provider handle. Separate clients used only to provision test workspaces and -snapshots are outside this provider contract and cannot execute authority -commands. The -`contract.nokv_fresh_client_failure_is_typed` guards both construction-time and -post-construction outages. - -The mapping was exercised once by hand against a live NoKV stack at that -pin (etcd, an S3-compatible object store, `nokv serve`, and `nokv-python` -built from the same commit): the adapter verbs and the Section 10 checks -1 to 9 passed with two independent client handles. That run is recorded -here as evidence for the mapping only; it is not part of the merge gate, -and it does not qualify restart, recovery, HA, or performance. SDKs built -before NoKV 0.11.0 cannot decode a 0.11.0 control-plane routing record, so -the earlier `90883d13539e31185f0d78131989fb51912dbd7e` audit baseline is no -longer a usable pin. - -The live qualification is scripted and repeatable: `live_e2e.py` runs twelve -shared lifecycle scenarios (including renew, reclaim after grace, stale-fence -rejection, atomic completion/successor, competition, replay, lost response, -retention, and revision advancement) through the production -`CoordinationAuthorityExecutor` against the file-backed control provider and, -when `NOKV_COORDINATION_LIVE=1` and the stack variables are set, this NoKV -provider. One NoKV-only row performs a real commit/snapshot/restore and proves -the restored lineage fails closed as `store_lineage_mismatch`. Without a -reachable stack the NoKV rows report unverified and the script stays green, so -it is evidence tooling, not a merge gate. - -```bash -python3 examples/nokv-shadow-provider/live_e2e.py -``` - -Run the merge-relevant deterministic regression from the repository root with: - -```bash -python3 examples/nokv-shadow-provider/probes.py contract -``` - -It must prove all of the following: - -- only an explicitly bootstrapped, runnable todo can be claimed; -- A applies, B advances the head, and a reconstructed authority replays A; -- replay returns A's original authority receipt field for field; -- replay leaves the current revision and aggregate unchanged; -- the same operation identity with a different semantic request is rejected; -- transport-only retry metadata does not change operation identity; -- competing claims on the same todo have one winner; -- concurrent claims on independent todos both succeed after internal CAS - revalidation and rebase, within the reference's empty-write-scope boundary; -- stale target or named preconditions return a domain conflict; -- bounded unrelated contention fails without creating a receipt or pretending - that the target todo conflicted; -- pre/post-CAS faults and ambiguous results recover success only from a stored - receipt or a later successful CAS after target revalidation; same-generation - receipt absence fails unproved; -- the NoKV adapter maps every SDK outcome it can observe (missing head, - create-only collision, stale generation, pre-publish failure) onto the typed - provider verbs and never leaks an SDK exception class into the authority; -- a hand-evolved post-completion head read back through the provider byte-CAS - projects to the same typed continuation outcomes (`successor | no_followup | - active_goal`) as the LoopX durable-completion seam, failing closed on a - contradictory record (both `no_followup` and successors), on a dangling - declared successor, on an explicit `completion_continuation` that - contradicts the recorded fields, and on a done record that omits its - explicit continuation, with replay-stable projections. - -The durable-completion probes remain the offline read-side comparison. The -Stage 3 focused tests and live matrix qualify the matching atomic completion -write side at the reference boundary. - -The nine current result tags are -`contract.bootstrap_and_preconditions`, -`contract.a_success_b_advance_replay_a`, `contract.operation_identity`, -`contract.competing_claims`, `contract.crash_windows_and_ambiguity`, -`contract.version_domains_and_retain_all`, -`contract.nokv_adapter_exception_mapping`, -`contract.durable_completion_projection`, and -`contract.durable_completion_fail_closed`. - -`probes.py` deliberately remains offline; use `live_e2e.py` for the real stack. -The reference still does not establish multi-host wake delivery, automatic -provider promotion, HA/failover, receipt compaction or GC, production -performance, a dynamic eligibility-projection publisher, non-empty write-scope -overlap enforcement, or a full LoopX state migration. The NoKV storage-plane -issues linked from the RFC remain production-canary holds; a green ordered -single-node exercise does not erase them. diff --git a/examples/nokv-shadow-provider/live_e2e.py b/examples/nokv-shadow-provider/live_e2e.py deleted file mode 100644 index a12cbf6a01..0000000000 --- a/examples/nokv-shadow-provider/live_e2e.py +++ /dev/null @@ -1,471 +0,0 @@ -"""Repeatable live Stage-3 lifecycle over file and NoKV providers. - -This is the bounded live qualification the direction tracker asks for before -any provider promotion: the SAME invariant scenarios run against the -file-backed control provider and the live NoKV candidate, and the script -prints a compact public-safe pass/fail/unverified matrix. It is evidence -tooling, not a merge gate: without a reachable stack it reports every live -row as unverified and exits 0 only when nothing that DID run failed. - -Environment (all required for the NoKV rows): - NOKV_COORDINATION_LIVE=1 - NOKV_ETCD / NOKV_ETCD_PREFIX / NOKV_ROOT_ID - NOKV_BUCKET / NOKV_OBJECT_ENDPOINT / NOKV_OBJECT_ROOT - NOKV_OBJECT_KEY / NOKV_OBJECT_SECRET - -Run from the repository root: - python3 examples/nokv-shadow-provider/live_e2e.py -""" - -from __future__ import annotations - -import json -import os -import sys -import tempfile -import threading -import uuid -from pathlib import Path - -sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from loopx.control_plane.coordination.executor import ( # noqa: E402 - CoordinationAuthorityExecutor, - sample_claim_envelope, - sample_work_envelope, -) -from loopx.control_plane.coordination.file_provider import ( # noqa: E402 - FileCoordinationProvider, -) -from loopx.control_plane.coordination.head import bootstrap_head # noqa: E402 - -from provider import open_nokv_coordination_provider # noqa: E402 - - -def eligibility() -> dict: - return { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "allowed_agent_ids": ["agent-a", "agent-b"], - "dependencies_satisfied": True, - "dependency_revision": 12, - "gates_open": True, - "gate_revision": 5, - } - - -def todo() -> dict: - return { - "todo_revision": 7, - "status": "open", - "claimed_by": None, - "eligibility": eligibility(), - "repository": "git:example/repo", - "code_revision": "0123456789abcdef", - "last_lease_epoch": 6, - } - - -class MatrixClock: - """Deterministic, advanceable executor clock: expiry adjudication is the - authority's own decision, so the live matrix drives it explicitly while - the provider underneath stays real.""" - - def __init__(self, value: float = 1_800_000_000.0): - self.value = value - - def __call__(self) -> float: - return self.value - - -def work(executor, agent: str, operation_id: str, command: dict): - return executor.apply( - sample_work_envelope( - goal_id=executor.goal_id, - operation_id=operation_id, - agent_id=agent, - device_id=f"dev-{agent}", - command=command, - ) - ) - - -def claim( - executor, - agent: str, - todo_id: str, - operation_id: str, - ttl: int = 600, - revision: int = 7, -): - return executor.apply( - sample_claim_envelope( - goal_id=executor.goal_id, - operation_id=operation_id, - agent_id=agent, - device_id=f"dev-{agent}", - todo_id=todo_id, - expected_todo_revision=revision, - expected_preconditions={ - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "dependency_revision": 12, - "gate_revision": 5, - }, - lease_ttl_seconds=ttl, - ) - ) - - -def scenario_matrix(make_provider) -> dict: - """The shared invariant script, provider-agnostic. Returns row -> bool.""" - - rows: dict[str, bool] = {} - goal_id = "g" + uuid.uuid4().hex[:8] - provider_a = make_provider(goal_id) - provider_b = make_provider(goal_id) - head = bootstrap_head( - goal_id, - {"todo-1": todo(), "todo-2": todo()}, - store_binding=provider_a.store_identity(), - ) - assert provider_a.load() == (None, 0) - assert provider_a.compare_and_put(0, head)["result"] == "applied" - executor_a = CoordinationAuthorityExecutor( - provider_a, goal_id=goal_id, now=lambda: 1_800_000_000.0 - ) - executor_b = CoordinationAuthorityExecutor( - provider_b, goal_id=goal_id, now=lambda: 1_800_000_000.0 - ) - - # same-todo race with two independent handles and a real thread barrier - barrier = threading.Barrier(2) - outcomes: dict[str, dict] = {} - - def race(name, executor, agent): - barrier.wait() - outcomes[name] = claim(executor, agent, "todo-1", f"race-{agent}") - - threads = [ - threading.Thread(target=race, args=("a", executor_a, "agent-a")), - threading.Thread(target=race, args=("b", executor_b, "agent-b")), - ] - for thread in threads: - thread.start() - for thread in threads: - thread.join() - kinds = sorted(outcome["result"] for outcome in outcomes.values()) - rows["same_todo_one_winner"] = kinds == ["applied", "conflict"] - - winner = next(o for o in outcomes.values() if o["result"] == "applied") - winner_agent = winner["original_receipt"]["actor"]["agent_id"] - other_agent = "agent-b" if winner_agent == "agent-a" else "agent-a" - - # independent todo applies for the other endpoint after internal rebase - second = claim(executor_b, other_agent, "todo-2", "independent-1") - rows["independent_todo_applies"] = second["result"] == "applied" - - # exact replay across a reconstructed executor - replay_executor = CoordinationAuthorityExecutor( - make_provider(goal_id), goal_id=goal_id, now=lambda: 1_800_000_000.0 - ) - replayed = claim(replay_executor, winner_agent, "todo-1", f"race-{winner_agent}") - rows["replay_returns_original_receipt"] = ( - replayed["result"] == "already_applied" - and replayed["original_receipt"] == winner["original_receipt"] - ) - - # identity reuse with different semantics - mutated = claim( - replay_executor, winner_agent, "todo-1", f"race-{winner_agent}", ttl=601 - ) - rows["identity_mismatch_rejected"] = ( - mutated["result"] == "rejected" - and mutated["reason"] == "operation_identity_mismatch" - ) - - # stale caller revision conflicts without state change - head_now, generation_now = provider_a.load() - stale = claim(executor_a, winner_agent, "todo-2", "stale-1") - rows["stale_revision_conflicts"] = ( - stale["result"] == "conflict" - and provider_a.load() == (head_now, generation_now) - ) - - # lost response after commit recovers through the receipt index - class LossyOnce: - def __init__(self, inner): - self.inner = inner - self.armed = True - - def load(self): - return self.inner.load() - - def store_identity(self): - return self.inner.store_identity() - - def compare_and_put(self, expected, proposed): - outcome = self.inner.compare_and_put(expected, proposed) - if self.armed and outcome.get("result") == "applied": - self.armed = False - return {"result": "ambiguous"} - return outcome - - lossy_executor = CoordinationAuthorityExecutor( - LossyOnce(make_provider(goal_id)), - goal_id=goal_id, - now=lambda: 1_800_000_000.0, - ) - goal2 = "g" + uuid.uuid4().hex[:8] - provider2 = make_provider(goal2) - provider2.compare_and_put( - 0, - bootstrap_head( - goal2, {"todo-1": todo()}, store_binding=provider2.store_identity() - ), - ) - lossy2 = CoordinationAuthorityExecutor( - LossyOnce(provider2), goal_id=goal2, now=lambda: 1_800_000_000.0 - ) - lost = claim(lossy2, "agent-a", "todo-1", "lost-1") - rows["lost_response_recovers_receipt"] = lost["result"] == "already_applied" - del lossy_executor - - final_head, _ = provider_a.load() - rows["receipts_retained"] = len(final_head["receipt_index"]) == 2 - rows["authority_revision_advanced_twice"] = final_head["authority_revision"] == 2 - - # ---- Stage 3: recoverable execution ownership over the same seam ---- - goal3 = "g" + uuid.uuid4().hex[:8] - provider3 = make_provider(goal3) - clock = MatrixClock() - provider3.compare_and_put( - 0, - bootstrap_head( - goal3, - {"todo_parent01": todo(), "todo_other01": todo()}, - store_binding=provider3.store_identity(), - ), - ) - executor3 = CoordinationAuthorityExecutor( - provider3, goal_id=goal3, now=clock - ) - first = claim(executor3, "agent-a", "todo_parent01", "r3-claim") - fence = { - "lease_id": first["original_receipt"]["lease_id"], - "expected_lease_epoch": first["original_receipt"]["lease_epoch"], - } - renewed = work(executor3, "agent-a", "r3-renew", { - "type": "renew_work", "todo_id": "todo_parent01", - "expected_todo_revision": 8, **fence, "lease_ttl_seconds": 600, - }) - rows["renew_extends_the_active_lease"] = ( - renewed["result"] == "applied" - and renewed["original_receipt"]["lease_epoch"] - == first["original_receipt"]["lease_epoch"] - ) - - clock.value += 600 + 31 - reclaimed = work(executor3, "agent-b", "r3-reclaim", { - "type": "reclaim_work", "todo_id": "todo_parent01", - "expected_todo_revision": 9, - "expected_preconditions": { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "dependency_revision": 12, - "gate_revision": 5, - }, - "lease_ttl_seconds": 600, - }) - rows["expired_lease_reclaimed_with_new_epoch"] = ( - reclaimed["result"] == "applied" - and reclaimed["original_receipt"]["lease_epoch"] - == first["original_receipt"]["lease_epoch"] + 1 - and reclaimed["original_receipt"]["superseded_owner"] == "agent-a" - ) - - stale = work(executor3, "agent-a", "r3-stale-writeback", { - "type": "complete_work", "todo_id": "todo_parent01", - "expected_todo_revision": 10, **fence, - "no_followup": False, "successor_todo_ids": [], "evidence": None, - }) - rows["superseded_executor_cannot_write_back"] = ( - stale["result"] == "rejected" and stale["reason"] == "stale_lease_fence" - ) - - new_fence = { - "lease_id": reclaimed["original_receipt"]["lease_id"], - "expected_lease_epoch": reclaimed["original_receipt"]["lease_epoch"], - } - done = work(executor3, "agent-b", "r3-complete", { - "type": "complete_work", "todo_id": "todo_parent01", - "expected_todo_revision": 10, **new_fence, - "no_followup": False, "successor_todo_ids": ["todo_next01"], - "evidence": None, - }) - successor_claim = claim( - executor3, "agent-a", "todo_next01", "r3-successor", ttl=600, revision=0 - ) if done["result"] == "applied" else {"result": "skipped"} - head3, _ = provider3.load() - rows["complete_creates_claimable_successor_atomically"] = ( - done["result"] == "applied" - and done["original_receipt"]["completion_continuation"] == "successor" - and successor_claim["result"] == "applied" - and head3["coordination"]["todos"]["todo_parent01"]["status"] == "done" - and "todo_parent01" not in head3["coordination"]["leases"] - ) - return rows - - -def file_matrix(root: Path) -> dict: - return scenario_matrix( - lambda goal_id: FileCoordinationProvider(root / "coordination", goal_id) - ) - - -def nokv_matrix() -> tuple[dict | None, str | None]: - if os.environ.get("NOKV_COORDINATION_LIVE") != "1": - return None, "NOKV_COORDINATION_LIVE unset" - try: - import nokv - except ImportError: - return None, "nokv SDK not installed" - env = os.environ - routing = nokv.RoutingConfig.etcd([env["NOKV_ETCD"]], env["NOKV_ETCD_PREFIX"], 10) - - def make_client(): - objects = nokv.ObjectStoreConfig.s3( - env["NOKV_BUCKET"], - region="us-east-1", - root=env["NOKV_OBJECT_ROOT"], - endpoint=env["NOKV_OBJECT_ENDPOINT"], - access_key_id=env["NOKV_OBJECT_KEY"], - secret_access_key=env["NOKV_OBJECT_SECRET"], - ) - return nokv.Client( - env["NOKV_ROOT_ID"], - routing, - objects, - workbench_root="/agents/live-e2e/wb", - ) - - workbench = "wbstage2" + uuid.uuid4().hex[:10] - # This handle provisions the evidence workspace; it cannot authorize a - # coordination-head state transition or participate in provider fallback. - provisioning_client = make_client() - provisioning_client.create_workspace(workbench) - - def make_provider(goal_id): - return open_nokv_coordination_provider( - make_client, - workbench, - goal_id, - ) - - rows = scenario_matrix(make_provider) - rows["restored_lineage_fails_closed"] = _restored_lineage_fails_closed( - make_client - ) - return rows, None - - -def _restored_lineage_fails_closed(make_client) -> bool: - """The Stage 3 binding fence against a REAL NoKV restore: a head - bootstrapped in workbench A, committed, snapshotted, and restored into - workbench B must refuse every command on B with store_lineage_mismatch - - restored bytes never grant live authority.""" - - # Workspace lifecycle operations belong to the live-evidence provisioner; - # coordination reads and writes use a separately admitted provider handle. - provisioning_client = make_client() - source = "wbline" + uuid.uuid4().hex[:10] - provisioning_client.create_workspace(source) - goal_id = "g" + uuid.uuid4().hex[:8] - provider = open_nokv_coordination_provider(make_client, source, goal_id) - head = bootstrap_head( - goal_id, {"todo_parent01": todo()}, - store_binding=provider.store_identity(), - ) - if provider.compare_and_put(0, head)["result"] != "applied": - return False - executor = CoordinationAuthorityExecutor( - provider, goal_id=goal_id, now=MatrixClock() - ) - if claim(executor, "agent-a", "todo_parent01", "line-claim")["result"] != "applied": - return False - - stat = provisioning_client.stat(source, provider.head_path) - provisioning_client.commit( - source, - {"purpose": "stage3-lineage-fence"}, - stat["body_digest"], - ) - snapshot = provisioning_client.snapshot(source) - destination = "wbline" + uuid.uuid4().hex[:10] - provisioning_client.restore( - source, - destination, - at_snapshot=snapshot["snapshot_id"], - ) - - restored = open_nokv_coordination_provider( - make_client, - destination, - goal_id, - ) - restored_executor = CoordinationAuthorityExecutor( - restored, goal_id=goal_id, now=MatrixClock() - ) - fenced = claim( - restored_executor, "agent-b", "todo_parent01", "line-claim-2", revision=8 - ) - original_still_serves = claim( - executor, "agent-b", "todo_parent01", "line-claim-3", revision=8 - ) - return ( - fenced["result"] == "failed" - and fenced["reason"] == "store_lineage_mismatch" - and original_still_serves["result"] == "rejected" - and original_still_serves["reason"] == "todo_not_open" - ) - - -def main() -> int: - matrix: dict[str, dict] = {} - with tempfile.TemporaryDirectory() as root: - matrix["file_provider"] = file_matrix(Path(root)) - nokv_rows, skip_reason = nokv_matrix() - if nokv_rows is None: - matrix["nokv_provider"] = {"unverified": skip_reason} - else: - matrix["nokv_provider"] = nokv_rows - shared = { - row: value - for row, value in nokv_rows.items() - if row in matrix["file_provider"] - } - parity = { - row: matrix["file_provider"][row] == value - for row, value in shared.items() - } - matrix["file_nokv_parity"] = { - "identical_row_outcomes": all(parity.values()), - "rows": len(parity), - "provider_specific_rows": sorted(set(nokv_rows) - set(shared)), - } - print(json.dumps(matrix, indent=2, sort_keys=True)) - failed = [ - f"{provider}.{row}" - for provider, rows in matrix.items() - for row, value in rows.items() - if value is False - ] - if failed: - print("FAILED rows:", failed, file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/examples/nokv-shadow-provider/probes.py b/examples/nokv-shadow-provider/probes.py deleted file mode 100644 index f37ba5b2b4..0000000000 --- a/examples/nokv-shadow-provider/probes.py +++ /dev/null @@ -1,1196 +0,0 @@ -"""Deterministic probes for the coordination authority proof. - -Run ``python probes.py contract`` without NoKV or external services. Every -claim/CAS probe drives the production coordination modules -(``loopx.control_plane.coordination.head`` and ``.executor``) - there is no -second reference authority - and finishes by round-tripping its persisted -head through the production ``validated_head``, so a probe that passes is -evidence about the exact code the runtime ships. The claim/CAS probes do -not qualify NoKV restart, recovery, GC, HA, or a live deployment. The -durable-completion probes are the offline read-side comparison registered by -RFC shared-goal-authority-state-provider-v0. They prove the provider byte-CAS -can hold and read back a post-completion -head whose durable records project to the same typed continuation outcomes -(``successor | no_followup | active_goal``, fail-closed on -contradiction/dangling) as the LoopX projection seam. They deliberately -mutate provider bytes to construct negative read fixtures; Stage 3 focused -tests and ``live_e2e.py`` qualify the actual atomic completion write side. -""" - -from __future__ import annotations - -import copy -import json -import os -import sys -import threading -from concurrent.futures import ThreadPoolExecutor - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -sys.path.insert( - 0, - os.path.dirname( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - ), -) - -from loopx.control_plane.coordination.executor import ( # noqa: E402 - CoordinationAuthorityExecutor, - EnvelopeError, - sample_claim_envelope, -) -from loopx.control_plane.coordination.head import ( # noqa: E402 - bootstrap_head, - validated_head, -) -from loopx.control_plane.todos.completion_state import ( # noqa: E402 - completion_continuation_for_write, -) -from loopx.control_plane.todos.durable_completion import ( # noqa: E402 - project_durable_completion_outcome, -) - -from provider import ( # noqa: E402 - NoKVCoordinationProvider, - ProviderProtocolError, - ProviderUnavailableError, - open_nokv_coordination_provider, -) - - -def out(probe: str, **values) -> None: - print(json.dumps({"probe": probe, **values}, sort_keys=True), flush=True) - - -class SimulatedCrash(RuntimeError): - pass - - -class DeterministicProvider: - """Byte-level CAS whose generation deliberately cannot alias domain versions.""" - - def __init__(self, generation_step: int = 17): - self._aggregate = None - self._generation = 0 - self._generation_step = generation_step - self._lock = threading.Lock() - self._barrier = None - self._barrier_loads_left = 0 - self._fault = None - self._contention_advances = 0 - self.identity = "probe:store" - - def store_identity(self) -> str: - return self.identity - - def arm_load_barrier(self, parties: int) -> None: - self._barrier = threading.Barrier(parties) - self._barrier_loads_left = parties - - def arm_fault(self, fault: str) -> None: - if fault not in { - "failed_before", - "crash_before", - "crash_after", - "ambiguous_before", - "ambiguous_with_unrelated_advance", - "ambiguous_after", - "ambiguous_after_with_unrelated_advance", - }: - raise ValueError(f"unknown fault: {fault}") - self._fault = fault - - def arm_contention(self, advances: int) -> None: - self._contention_advances = advances - - def load(self): - barrier = None - with self._lock: - value = copy.deepcopy(self._aggregate) - generation = self._generation - if self._barrier_loads_left: - self._barrier_loads_left -= 1 - barrier = self._barrier - if barrier is not None: - barrier.wait(timeout=5) - return value, generation - - def compare_and_put(self, expected_generation: int, aggregate: dict): - with self._lock: - if self._fault == "failed_before": - self._fault = None - return {"result": "failed"} - if self._fault == "crash_before": - self._fault = None - raise SimulatedCrash("crash_before") - if expected_generation != self._generation: - return { - "result": "conflict", - "current_provider_generation": self._generation, - } - if self._fault == "ambiguous_before": - self._fault = None - return {"result": "ambiguous"} - if self._fault == "ambiguous_with_unrelated_advance": - self._fault = None - self._generation += self._generation_step - return {"result": "ambiguous"} - if self._contention_advances: - # Simulate an unrelated provider-level rewrite. The opaque - # generation advances while the target todo stays unchanged. - self._contention_advances -= 1 - self._generation += self._generation_step - return { - "result": "conflict", - "current_provider_generation": self._generation, - } - self._aggregate = copy.deepcopy(aggregate) - self._generation += self._generation_step - generation = self._generation - if self._fault == "crash_after": - self._fault = None - raise SimulatedCrash("crash_after") - if self._fault == "ambiguous_after": - self._fault = None - return {"result": "ambiguous"} - if self._fault == "ambiguous_after_with_unrelated_advance": - self._fault = None - self._generation += self._generation_step - return {"result": "ambiguous"} - return {"result": "applied", "provider_generation": generation} - - -def initial_todo( - allowed_agents=None, - dependencies_satisfied: bool = True, - gates_open: bool = True, -) -> dict: - return { - "todo_revision": 7, - "status": "open", - "claimed_by": None, - "last_lease_epoch": 0, - "eligibility": { - "allowed_agent_ids": allowed_agents or ["agent-a", "agent-0", "agent-1"], - "authorization_projection_digest": "sha256:bootstrap-auth", - "authorization_projection_revision": 3, - "dependencies_satisfied": dependencies_satisfied, - "dependency_revision": 12, - "gates_open": gates_open, - "gate_revision": 5, - }, - "repository": "git:example/repo", - "code_revision": "0123456789abcdef", - } - - -def bootstrap(provider, goal_id: str, todo_ids) -> int: - head = bootstrap_head( - goal_id, - {todo_id: initial_todo() for todo_id in todo_ids}, - store_binding=provider.store_identity(), - ) - result = provider.compare_and_put(0, head) - assert result["result"] == "applied", result - return result["provider_generation"] - - -def load_head(provider, goal_id: str): - del goal_id # provider handles are already bound to one goal key - return provider.load() - - -def authority(provider, goal_id: str, when: float) -> CoordinationAuthorityExecutor: - return CoordinationAuthorityExecutor(provider, goal_id=goal_id, now=lambda: when) - - -def assert_production_valid(provider, goal_id: str) -> None: - """The persisted probe artifact must pass the production validator.""" - - head, _generation = provider.load() - validated_head(head, goal_id=goal_id) - - -def assert_exact_replay(first: dict, replay: dict) -> None: - assert first["result"] == "applied", first - assert replay["result"] == "already_applied", replay - assert replay["original_receipt"] == first["original_receipt"], (first, replay) - - -def claim(operation_id: str, goal_id: str, todo_id: str, **values) -> dict: - expected_preconditions = values.pop("expected_preconditions", None) - if expected_preconditions is None: - expected_preconditions = { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "dependency_revision": 12, - "gate_revision": 5, - } - envelope = sample_claim_envelope( - goal_id=goal_id, - operation_id=operation_id, - agent_id=values.pop("agent_id", "agent-a"), - device_id=values.pop("device_id", "dev-laptop"), - todo_id=todo_id, - expected_todo_revision=values.pop("expected_todo_revision", 7), - expected_preconditions=expected_preconditions, - lease_ttl_seconds=values.pop("lease_ttl_seconds", 600), - transport=values.pop("transport", None), - ) - if values: - raise TypeError(f"unknown claim kwargs: {sorted(values)}") - return envelope - - -def assert_bootstrap_rejected(todo: dict, message: str | None = None) -> None: - try: - bootstrap_head("goal-private", {"todo-private": todo}, store_binding="probe:store") - except ValueError as exc: - if message is not None: - assert message in str(exc) - else: - raise AssertionError("unsafe todo entered the shared head") - - -def probe_bootstrap_and_preconditions() -> None: - provider = DeterministicProvider() - uninitialized = authority(provider, "goal-preconditions", 1000).apply( - claim("op-uninitialized", "goal-preconditions", "todo-known") - ) - assert uninitialized["result"] == "failed" - assert uninitialized["reason"] == "coordination_head_uninitialized" - - initial_head = bootstrap_head( - "goal-preconditions", - { - "todo-known": initial_todo(), - "todo-dependency-blocked": initial_todo(dependencies_satisfied=False), - "todo-gate-blocked": initial_todo(gates_open=False), - }, - store_binding=provider.store_identity(), - ) - bootstrap_result = provider.compare_and_put(0, initial_head) - assert bootstrap_result["result"] == "applied" - initial_generation = bootstrap_result["provider_generation"] - executor = authority(provider, "goal-preconditions", 1000) - missing = executor.apply(claim("op-missing", "goal-preconditions", "todo-missing")) - stale = executor.apply( - claim("op-stale", "goal-preconditions", "todo-known", expected_todo_revision=6) - ) - expected_preconditions = { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "dependency_revision": 12, - "gate_revision": 5, - } - stale_values = { - "authorization_projection_revision": 2, - "authorization_projection_digest": "sha256:stale-auth", - "dependency_revision": 11, - "gate_revision": 4, - } - changed_preconditions = [] - for field, stale_value in stale_values.items(): - preconditions = copy.deepcopy(expected_preconditions) - preconditions[field] = stale_value - changed_preconditions.append(executor.apply(claim( - f"op-stale-{field}", - "goal-preconditions", - "todo-known", - expected_preconditions=preconditions, - ))) - ineligible = executor.apply(claim( - "op-ineligible", - "goal-preconditions", - "todo-known", - agent_id="agent-not-allowed", - )) - dependency_blocked = executor.apply( - claim("op-dependency-blocked", "goal-preconditions", "todo-dependency-blocked") - ) - gate_blocked = executor.apply( - claim("op-gate-blocked", "goal-preconditions", "todo-gate-blocked") - ) - head, generation = load_head(provider, "goal-preconditions") - assert missing == { - "result": "rejected", - "reason": "todo_not_found", - "observed_authority_revision": 0, - "provider_generation": initial_generation, - } - assert stale["result"] == "conflict" and stale["reason"] == "todo_revision_mismatch" - assert all(result["result"] == "conflict" for result in changed_preconditions) - assert all( - result["reason"] == "precondition_snapshot_mismatch" - for result in changed_preconditions - ) - assert ineligible["result"] == "rejected" and ineligible["reason"] == "actor_ineligible" - assert dependency_blocked["result"] == "rejected" - assert dependency_blocked["reason"] == "dependencies_not_satisfied" - assert gate_blocked["result"] == "rejected" and gate_blocked["reason"] == "gate_closed" - assert head["authority_revision"] == 0 and head["receipt_index"] == {} - assert generation == initial_generation - assert_production_valid(provider, "goal-preconditions") - private_todo = initial_todo() - private_todo["raw_todo_body"] = "must not enter the shared head" - assert_bootstrap_rejected(private_todo, "fields do not match v0") - absolute_path_todo = initial_todo() - absolute_path_todo["repository"] = "/" + "synthetic/local/repository" - assert_bootstrap_rejected(absolute_path_todo, "repository is not portable") - credential_repository_todo = initial_todo() - credential_repository_todo["repository"] = "git:" + "user@host/repository" - invalid_revision_todo = initial_todo() - invalid_revision_todo["code_revision"] = "review-ready" - for unsafe_todo in (credential_repository_todo, invalid_revision_todo): - assert_bootstrap_rejected(unsafe_todo) - out( - "contract.bootstrap_and_preconditions", - ok=True, - uninitialized_failed=True, - no_implicit_todo_creation=True, - todo_revision_checked=True, - named_preconditions_checked=True, - eligibility_checked=True, - dependency_and_gate_checked=True, - bootstrap_privacy_allowlist_checked=True, - repository_metadata_checked=True, - production_validator_round_trip=True, - ) - - -def probe_a_b_replay_a() -> None: - provider = DeterministicProvider() - bootstrap(provider, "goal-review", ["todo-review", "todo-followup"]) - executor = authority(provider, "goal-review", 1100) - envelope_a = claim("op-review-a", "goal-review", "todo-review") - first_a = executor.apply(envelope_a) - first_b = executor.apply(claim("op-followup-b", "goal-review", "todo-followup")) - replay_a = authority(provider, "goal-review", 9000).apply(envelope_a) - head, generation = load_head(provider, "goal-review") - assert first_b["result"] == "applied" - assert_exact_replay(first_a, replay_a) - assert head["authority_revision"] == 2 and generation == 51 - assert head["receipt_index"]["op-review-a"]["original_receipt"] == first_a[ - "original_receipt" - ] - assert len(head["receipt_index"]) == 2 - assert_production_valid(provider, "goal-review") - out( - "contract.a_success_b_advance_replay_a", - ok=True, - authority_reconstructed=True, - original_receipt_equal=True, - authority_revision=2, - provider_generation=generation, - lease_epoch=first_a["original_receipt"]["lease_epoch"], - receipt_count=2, - ) - - -def probe_operation_identity() -> None: - provider = DeterministicProvider() - bootstrap(provider, "goal-digest", ["todo-original", "todo-mutated"]) - executor = authority(provider, "goal-digest", 1200) - original = claim( - "op-stable", - "goal-digest", - "todo-original", - transport={"attempt": 1, "trace_id": "trace-a"}, - ) - first = executor.apply(original) - transport_retry = copy.deepcopy(original) - transport_retry["transport"] = {"attempt": 2, "trace_id": "trace-b"} - assert_exact_replay(first, executor.apply(transport_retry)) - - changed = copy.deepcopy(original) - changed["command"]["todo_id"] = "todo-mutated" - mismatch = executor.apply(changed) - assert mismatch["result"] == "rejected" - assert mismatch["reason"] == "operation_identity_mismatch" - unknown = copy.deepcopy(original) - unknown["unexpected_semantic_field"] = True - try: - executor.apply(unknown) - except EnvelopeError as exc: - assert "unknown command envelope fields" in str(exc) - else: - raise AssertionError("unknown semantic field was ignored") - head, _ = load_head(provider, "goal-digest") - assert len(head["receipt_index"]) == 1 - assert_production_valid(provider, "goal-digest") - out( - "contract.operation_identity", - ok=True, - transport_metadata_excluded=True, - semantic_change_rejected=True, - unknown_fields_fail_closed=True, - ) - - -def probe_competing_claims() -> None: - def race(provider, goal_id, envelopes): - provider.arm_load_barrier(2) - - def run(index: int): - return authority(provider, goal_id, 1300 + index).apply(envelopes[index]) - - with ThreadPoolExecutor(max_workers=2) as pool: - return list(pool.map(run, range(2))) - - same_provider = DeterministicProvider(generation_step=23) - bootstrap(same_provider, "goal-race", ["todo-one-winner"]) - same_envelopes = [ - claim( - f"op-claim-{index}", - "goal-race", - "todo-one-winner", - agent_id=f"agent-{index}", - device_id=f"device-{index}", - ) - for index in range(2) - ] - same_results = race(same_provider, "goal-race", same_envelopes) - applied = [result for result in same_results if result["result"] == "applied"] - conflicts = [result for result in same_results if result["result"] == "conflict"] - head, generation = load_head(same_provider, "goal-race") - assert len(applied) == 1 and len(conflicts) == 1, same_results - winner = applied[0]["original_receipt"]["actor"]["agent_id"] - assert conflicts[0]["reason"] == "todo_revision_mismatch" - assert "original_receipt" not in conflicts[0] and "lease_id" not in conflicts[0] - assert head["authority_revision"] == 1 and len(head["receipt_index"]) == 1 - same_todo = head["coordination"]["todos"]["todo-one-winner"] - assert same_todo["todo_revision"] == 8 - assert same_todo["status"] == "open" - assert same_todo["claimed_by"] == winner - assert set(head["coordination"]["leases"]) == {"todo-one-winner"} - assert head["coordination"]["leases"]["todo-one-winner"]["owner"] == winner - assert generation == 46 - assert_production_valid(same_provider, "goal-race") - - independent_provider = DeterministicProvider(generation_step=29) - bootstrap(independent_provider, "goal-independent", ["todo-a", "todo-b"]) - independent_envelopes = [ - claim( - f"op-independent-{index}", - "goal-independent", - f"todo-{'ab'[index]}", - agent_id=f"agent-{index}", - device_id=f"device-{index}", - ) - for index in range(2) - ] - independent_results = race( - independent_provider, - "goal-independent", - independent_envelopes, - ) - independent_head, independent_generation = load_head( - independent_provider, - "goal-independent", - ) - assert [result["result"] for result in independent_results].count("applied") == 2, ( - independent_results - ) - assert independent_head["authority_revision"] == 2 - assert len(independent_head["receipt_index"]) == 2 - assert set(independent_head["coordination"]["leases"]) == {"todo-a", "todo-b"} - assert independent_head["coordination"]["todos"]["todo-a"]["todo_revision"] == 8 - assert independent_head["coordination"]["todos"]["todo-b"]["todo_revision"] == 8 - assert independent_head["coordination"]["todos"]["todo-a"]["status"] == "open" - assert independent_head["coordination"]["todos"]["todo-b"]["status"] == "open" - assert independent_head["coordination"]["todos"]["todo-a"]["claimed_by"] == "agent-0" - assert independent_head["coordination"]["todos"]["todo-b"]["claimed_by"] == "agent-1" - assert independent_head["coordination"]["leases"]["todo-a"]["owner"] == "agent-0" - assert independent_head["coordination"]["leases"]["todo-b"]["owner"] == "agent-1" - assert set(independent_head["receipt_index"]) == { - "op-independent-0", - "op-independent-1", - } - assert independent_head["receipt_index"]["op-independent-0"][ - "original_receipt" - ]["todo_id"] == "todo-a" - assert independent_head["receipt_index"]["op-independent-1"][ - "original_receipt" - ]["todo_id"] == "todo-b" - assert independent_generation == 87 - replayed = authority(independent_provider, "goal-independent", 9000).apply( - independent_envelopes[0] - ) - assert_exact_replay(independent_results[0], replayed) - assert load_head(independent_provider, "goal-independent") == ( - independent_head, - independent_generation, - ) - assert_production_valid(independent_provider, "goal-independent") - out( - "contract.competing_claims", - ok=True, - same_todo_applied=1, - same_todo_conflicts=1, - independent_todos_applied=2, - independent_authority_revision=2, - independent_replay_exact=True, - claim_preserves_open_status=True, - ) - - -def probe_crash_windows_and_ambiguity() -> None: - provider = DeterministicProvider() - bootstrap(provider, "goal-faults", ["todo-before", "todo-after", "todo-ambiguous"]) - before = claim("op-before", "goal-faults", "todo-before") - provider.arm_fault("failed_before") - failed = authority(provider, "goal-faults", 1400).apply(before) - assert failed["result"] == "failed" - assert failed["reason"] == "provider_failed_before_cas" - head, generation = load_head(provider, "goal-faults") - assert head["authority_revision"] == 0 and head["receipt_index"] == {} - assert generation == 17 - provider.arm_fault("crash_before") - try: - authority(provider, "goal-faults", 1400).apply(before) - except SimulatedCrash: - pass - else: - raise AssertionError("before-CAS crash was not injected") - head, generation = load_head(provider, "goal-faults") - assert head["authority_revision"] == 0 and head["receipt_index"] == {} - assert generation == 17 - - after = claim("op-after", "goal-faults", "todo-after") - provider.arm_fault("crash_after") - try: - authority(provider, "goal-faults", 1500).apply(after) - except SimulatedCrash: - pass - else: - raise AssertionError("after-CAS crash was not injected") - committed, committed_generation = load_head(provider, "goal-faults") - durable_receipt = committed["receipt_index"]["op-after"]["original_receipt"] - replay = authority(provider, "goal-faults", 9000).apply(after) - assert replay["result"] == "already_applied" - assert replay["original_receipt"] == durable_receipt - assert load_head(provider, "goal-faults") == (committed, committed_generation) - - ambiguous = claim("op-ambiguous", "goal-faults", "todo-ambiguous") - provider.arm_fault("ambiguous_after") - reconciled = authority(provider, "goal-faults", 1600).apply(ambiguous) - final_head, _ = load_head(provider, "goal-faults") - assert reconciled["result"] == "already_applied" - assert final_head["authority_revision"] == 2 and len(final_head["receipt_index"]) == 2 - assert_production_valid(provider, "goal-faults") - - ambiguous_before_provider = DeterministicProvider() - bootstrap(ambiguous_before_provider, "goal-ambiguous-before", ["todo-target"]) - before_head = load_head(ambiguous_before_provider, "goal-ambiguous-before") - ambiguous_before_provider.arm_fault("ambiguous_before") - unproved = authority(ambiguous_before_provider, "goal-ambiguous-before", 1650).apply( - claim("op-unproved", "goal-ambiguous-before", "todo-target") - ) - assert unproved["result"] == "failed" - assert unproved["reason"] == "provider_outcome_unproved" - assert load_head(ambiguous_before_provider, "goal-ambiguous-before") == before_head - assert_production_valid(ambiguous_before_provider, "goal-ambiguous-before") - - ambiguous_advance_provider = DeterministicProvider() - bootstrap(ambiguous_advance_provider, "goal-ambiguous-advance", ["todo-target"]) - ambiguous_advance_provider.arm_fault("ambiguous_with_unrelated_advance") - retried = authority( - ambiguous_advance_provider, "goal-ambiguous-advance", 1675 - ).apply(claim("op-retried", "goal-ambiguous-advance", "todo-target")) - retried_head, _ = load_head(ambiguous_advance_provider, "goal-ambiguous-advance") - assert retried["result"] == "applied" - assert retried_head["authority_revision"] == 1 - assert set(retried_head["receipt_index"]) == {"op-retried"} - assert_production_valid(ambiguous_advance_provider, "goal-ambiguous-advance") - - ambiguous_committed_provider = DeterministicProvider() - bootstrap(ambiguous_committed_provider, "goal-ambiguous-committed", ["todo-target"]) - ambiguous_committed_provider.arm_fault("ambiguous_after_with_unrelated_advance") - recovered = authority( - ambiguous_committed_provider, "goal-ambiguous-committed", 1685 - ).apply(claim("op-recovered", "goal-ambiguous-committed", "todo-target")) - recovered_head, _ = load_head( - ambiguous_committed_provider, - "goal-ambiguous-committed", - ) - assert recovered["result"] == "already_applied" - assert recovered_head["authority_revision"] == 1 - assert set(recovered_head["receipt_index"]) == {"op-recovered"} - assert_production_valid(ambiguous_committed_provider, "goal-ambiguous-committed") - - contention_provider = DeterministicProvider() - bootstrap(contention_provider, "goal-contention", ["todo-target"]) - contention_provider.arm_contention(8) - exhausted = authority(contention_provider, "goal-contention", 1700).apply( - claim("op-contention", "goal-contention", "todo-target") - ) - contention_head, _ = load_head(contention_provider, "goal-contention") - assert exhausted["result"] == "failed" - assert exhausted["reason"] == "provider_contention_exhausted" - assert "op-contention" not in contention_head["receipt_index"] - assert contention_head["coordination"]["todos"]["todo-target"]["status"] == "open" - assert_production_valid(contention_provider, "goal-contention") - out( - "contract.crash_windows_and_ambiguity", - ok=True, - before_cas_no_partial_state=True, - failed_before_cas_no_write=True, - after_cas_exact_receipt_recovered=True, - ambiguous_reconciled_from_receipt=True, - ambiguous_same_generation_failed_unproved=True, - ambiguous_after_unrelated_advance_retried=True, - ambiguous_committed_then_advanced_replayed=True, - bounded_contention_failed_without_receipt=True, - no_double_apply=True, - ) - - -def probe_version_domains_and_retain_all() -> None: - provider = DeterministicProvider(generation_step=101) - bootstrap(provider, "goal-versions", ["todo-a", "todo-b"]) - executor = authority(provider, "goal-versions", 1700) - first = executor.apply(claim("op-a", "goal-versions", "todo-a")) - second = executor.apply(claim("op-b", "goal-versions", "todo-b")) - head, generation = load_head(provider, "goal-versions") - assert generation == 303 and head["authority_revision"] == 2 - assert first["original_receipt"]["lease_epoch"] == 1 - assert second["original_receipt"]["lease_epoch"] == 1 - assert head["receipt_retention"] == {"mode": "retain_all_v0"} - assert len(head["receipt_index"]) == 2 - assert_production_valid(provider, "goal-versions") - out( - "contract.version_domains_and_retain_all", - ok=True, - provider_generation=generation, - authority_revision=2, - lease_epochs=[1, 1], - receipt_retention="retain_all_v0", - receipt_count=2, - ) - - -def _evolve_completion_head(provider, todo_mutations: dict) -> None: - """CAS-write an evolved post-completion head over a bootstrapped open one. - - Each mutated todo is committed in the shape the atomic ``complete_work`` - transition leaves behind (``status="done"``, - the declared continuation fields, and the explicit - ``completion_continuation`` the LoopX lifecycle records durably), so the - probes read the bytes back through the provider seam rather than through - an in-memory fixture. The continuation is derived with the same LoopX - helper the lifecycle write uses; a mutation may pin an explicit - ``completion_continuation`` to model a contradictory record. - """ - head, generation = load_head(provider, "goal-completion") - todos = head["coordination"]["todos"] - for todo_id, fields in todo_mutations.items(): - record = copy.deepcopy(todos[todo_id]) - record["status"] = "done" - record["todo_revision"] = record["todo_revision"] + 1 - for key, value in fields.items(): - record[key] = value - if "completion_continuation" not in record: - record["completion_continuation"] = completion_continuation_for_write( - no_followup=record.get("no_followup") is True, - has_successor=bool(record.get("successor_todo_ids")), - ) - todos[todo_id] = record - result = provider.compare_and_put(generation, head) - assert result["result"] == "applied", result - - -def _project_from_provider_head(provider) -> dict[str, dict]: - """Read the head back through the provider and project every done record. - - Returns ``{todo_id: typed outcome}`` for each durably-done record (the seam - is invoked after a completion write, so open records are not projected), - using the head's full Todo id universe as ``existing_todo_ids`` (the - read-side seam's provider-first shape: same projection, same id universe, - no host JSON). - """ - head, _generation = load_head(provider, "goal-completion") - todos = head["coordination"]["todos"] - existing = set(todos) - outcomes: dict[str, dict] = {} - for todo_id in sorted(todos): - record = {"todo_id": todo_id, **todos[todo_id]} - if record["status"] != "done": - continue - outcomes[todo_id] = project_durable_completion_outcome( - todo=record, - expected_todo_id=todo_id, - existing_todo_ids=existing, - ) - return outcomes - - -def probe_durable_completion_projection() -> None: - provider = DeterministicProvider() - bootstrap( - provider, - "goal-completion", - ["todo_done01", "todo_done02", "todo_done03", "todo_next01"], - ) - _evolve_completion_head( - provider, - { - "todo_done01": {"successor_todo_ids": ["todo_next01"]}, - "todo_done02": {"no_followup": True}, - "todo_done03": {}, - }, - ) - outcomes = _project_from_provider_head(provider) - assert outcomes["todo_done01"] == { - "todo_id": "todo_done01", - "continuation": "successor", - "successor_todo_ids": ["todo_next01"], - } - assert outcomes["todo_done02"] == { - "todo_id": "todo_done02", - "continuation": "no_followup", - } - assert outcomes["todo_done03"] == { - "todo_id": "todo_done03", - "continuation": "active_goal", - } - # Replay stability: re-reading the same provider bytes and projecting again - # yields the identical typed outcomes (no clock, no randomness). - replay = _project_from_provider_head(provider) - assert replay == outcomes - out( - "contract.durable_completion_projection", - ok=True, - continuations=[ - outcomes["todo_done01"]["continuation"], - outcomes["todo_done02"]["continuation"], - outcomes["todo_done03"]["continuation"], - ], - replay_stable=True, - ) - - -def probe_durable_completion_fail_closed() -> None: - provider = DeterministicProvider() - bootstrap( - provider, - "goal-completion", - ["todo_done04", "todo_done05", "todo_done06", "todo_next01"], - ) - _evolve_completion_head( - provider, - { - # Contradiction: both no_followup and a (existing) successor. - "todo_done04": { - "no_followup": True, - "successor_todo_ids": ["todo_next01"], - "completion_continuation": "no_followup", - }, - # Dangling: one declared successor exists, one does not. - "todo_done05": { - "successor_todo_ids": ["todo_next01", "todo_missing9"] - }, - # The explicit continuation contradicts the recorded fields. - "todo_done06": { - "successor_todo_ids": ["todo_next01"], - "completion_continuation": "active_goal", - }, - }, - ) - head, _generation = load_head(provider, "goal-completion") - todos = head["coordination"]["todos"] - existing = set(todos) - expected_failures = { - "todo_done04": "both no_followup and successor_todo_ids", - "todo_done05": "declares missing successor Todo ids: todo_missing9", - "todo_done06": "completion_continuation contradicts successor_todo_ids", - } - for todo_id, expected in expected_failures.items(): - try: - project_durable_completion_outcome( - todo={"todo_id": todo_id, **todos[todo_id]}, - expected_todo_id=todo_id, - existing_todo_ids=existing, - ) - except ValueError as exc: - assert expected in str(exc), (todo_id, str(exc)) - else: - raise AssertionError(f"{todo_id} did not fail closed") - # A durably done record without its explicit continuation is not - # projectable: the seam fails closed instead of guessing. - stripped = {"todo_id": "todo_done05", **todos["todo_done05"]} - del stripped["completion_continuation"] - stripped["successor_todo_ids"] = ["todo_next01"] - try: - project_durable_completion_outcome( - todo=stripped, expected_todo_id="todo_done05", existing_todo_ids=existing - ) - except ValueError as exc: - assert "missing completion_continuation" in str(exc) - else: - raise AssertionError("missing completion_continuation did not fail closed") - out( - "contract.durable_completion_fail_closed", - ok=True, - contradiction_rejected=True, - dangling_successor_rejected=True, - continuation_contradiction_rejected=True, - missing_continuation_rejected=True, - ) - - -class FakeNoKVClient: - """Minimal double for the NoKV Python SDK ``Client`` surface the adapter uses. - - It raises the exception classes the SDK raises since NoKV 0.11.0 - (``nokv-python`` maps ``NotFound`` to ``FileNotFoundError`` and - ``AlreadyExists`` to ``FileExistsError``; every other client failure stays - ``RuntimeError``). Generations restart at 1 per path lifetime and every - replacement advances by one, like the live workspace. - """ - - def __init__(self): - self.paths: dict[tuple[str, str], tuple[bytes, int]] = {} - self.stat_failure: Exception | None = None - self.read_failure: Exception | None = None - - def stat(self, workbench: str, path: str) -> dict: - if self.stat_failure is not None: - failure, self.stat_failure = self.stat_failure, None - raise failure - try: - _bytes, generation = self.paths[(workbench, path)] - except KeyError: - raise FileNotFoundError("workspace request failed: path does not exist") from None - return {"generation": generation} - - def read(self, workbench: str, path: str) -> dict: - if self.read_failure is not None: - failure, self.read_failure = self.read_failure, None - raise failure - try: - data, generation = self.paths[(workbench, path)] - except KeyError: - raise FileNotFoundError("workspace request failed: path does not exist") from None - return {"bytes": data, "metadata": {"generation": generation}} - - def publish_bytes(self, workbench: str, path: str, data: bytes, **options) -> dict: - expected = options.get("expected_generation") - current = self.paths.get((workbench, path)) - if expected is None: - if current is not None: - raise FileExistsError( - "artifact publication failed during complete: workspace request " - "failed: path already exists" - ) - generation = 1 - else: - if current is None or current[1] != expected: - raise RuntimeError( - "artifact publication failed during complete: workspace request " - f"failed: path generation mismatch: expected {expected}" - ) - generation = current[1] + 1 - self.paths[(workbench, path)] = (bytes(data), generation) - return {"generation": generation} - - -def probe_nokv_adapter_exception_mapping() -> None: - """The NoKV byte-CAS adapter classifies SDK failures by exception class only. - - ``load`` returns ``(None, 0)`` only for the SDK's typed missing signal - (``FileNotFoundError``); any other client failure raises the typed - ``ProviderUnavailableError`` instead of masquerading as an uninitialized - goal or escaping as a bare ``RuntimeError``. ``compare_and_put`` reports - typed ``applied | conflict | ambiguous | failed`` for every SDK outcome: - error prose is never a channel, because real non-missing failures carry - messages such as ``invalid root route: root placement does not exist``. - """ - - client = FakeNoKVClient() - goal_id = "adapter-mapping" - provider = NoKVCoordinationProvider(client, "wb-adapter", goal_id) - head = bootstrap_head(goal_id, {}, store_binding="probe:adapter") - - assert provider.load() == (None, 0) - created = provider.compare_and_put(0, head) - assert created == {"result": "applied", "provider_generation": 1}, created - loaded, generation = provider.load() - assert loaded == head and generation == 1 - - # bootstrap race: the loser observed generation 0 before the winner landed - lost_race = provider.compare_and_put(0, head) - assert lost_race["result"] == "conflict", lost_race - assert lost_race["current_provider_generation"] == 1 - provider._generation = lambda: 0 # the stat pre-check raced too - lost_race_at_publish = provider.compare_and_put(0, head) - assert lost_race_at_publish == {"result": "ambiguous"}, lost_race_at_publish - del provider._generation - - # replacement CAS: stale expected generation, both before and at publish - advanced = copy.deepcopy(head) - advanced["authority_revision"] = 1 - replaced = provider.compare_and_put(1, advanced) - assert replaced == {"result": "applied", "provider_generation": 2}, replaced - stale = provider.compare_and_put(1, advanced) - assert stale == {"result": "conflict", "current_provider_generation": 2}, stale - provider._generation = lambda: 1 - stale_at_publish = provider.compare_and_put(1, advanced) - assert stale_at_publish == {"result": "ambiguous"}, stale_at_publish - del provider._generation - - # a provider failure before any publish attempt proves that nothing was written - client.stat_failure = RuntimeError("RPC transport failed: connection refused") - failed = provider.compare_and_put(2, advanced) - assert failed["result"] == "failed", failed - assert provider.load() == (advanced, 2) - - # A routing outage whose message carries a not-found token must classify - # as unavailable, never as missing: (None, 0) would tell the authority - # the goal is uninitialized and authorize a bootstrap-create during an - # outage. This is the real string shape ClientError::InvalidRoute - # produces through the 0.11 SDK. - routing_outage = RuntimeError("invalid root route: root placement does not exist") - client.read_failure = routing_outage - try: - provider.load() - except ProviderUnavailableError as exc: - assert "root placement does not exist" in str(exc) - else: - raise AssertionError("routing outage was classified as missing") - - # The same routing outage during the CAS pre-check is a typed failed - # verdict, never the conflict-or-create path a misclassified generation 0 - # would take. - client.stat_failure = RuntimeError( - "logical shard LogicalShardId([34]) was not found" - ) - outage_verdict = provider.compare_and_put(2, advanced) - assert outage_verdict["result"] == "failed", outage_verdict - assert "was not found" in outage_verdict["error"] - - # A token-free outage is the same typed unavailable, not a bare - # RuntimeError leak. - client.read_failure = RuntimeError("connection refused by endpoint") - try: - provider.load() - except ProviderUnavailableError: - pass - else: - raise AssertionError("token-free outage did not raise typed unavailable") - - # Pre-0.11 SDKs signalled missing with RuntimeError prose. They cannot - # route the post-#465 control plane and are outside the pinned baseline, - # so that prose now classifies as unavailable rather than missing. - class LegacyClient(FakeNoKVClient): - def read(self, workbench: str, path: str) -> dict: - if (workbench, path) not in self.paths: - raise RuntimeError("workspace request failed: path not found") - return super().read(workbench, path) - - legacy = NoKVCoordinationProvider(LegacyClient(), "wb-legacy", goal_id) - try: - legacy.load() - except ProviderUnavailableError: - pass - else: - raise AssertionError("legacy prose was still classified as missing") - - # Corrupt persisted bytes and unserializable heads stay typed too, in - # parity with the file provider's seam contract. - corrupt_client = FakeNoKVClient() - corrupt_client.paths[("wb-corrupt", f"goals/{goal_id}/coordination-head.json")] = ( - b"{not json", - 3, - ) - corrupt = NoKVCoordinationProvider(corrupt_client, "wb-corrupt", goal_id) - try: - corrupt.load() - except ProviderProtocolError: - pass - else: - raise AssertionError("corrupt persisted bytes escaped untyped") - unserializable = provider.compare_and_put(2, {"x": float("nan")}) - assert unserializable["result"] == "failed", unserializable - assert "serializable" in unserializable["error"] - - # The authoritative CAS token is typed state at both ends of the seam. - # A bool or otherwise invalid caller expectation is a typed failed - # verdict before any client I/O, and a malformed SDK response carrying - # generation true / a negative / a string must never be repaired into a - # legitimate generation. - class ExplodingClient: - def __getattr__(self, name): - raise AssertionError("no client I/O may happen for invalid input") - - untouchable = NoKVCoordinationProvider(ExplodingClient(), "wb-typed", goal_id) - for invalid_expected in (True, False, -1, "1"): - verdict = untouchable.compare_and_put(invalid_expected, head) - assert verdict["result"] == "failed", (invalid_expected, verdict) - assert "non-negative integer" in verdict["error"] - - class MalformedClient(FakeNoKVClient): - def __init__(self, generation_value): - super().__init__() - self.generation_value = generation_value - - def stat(self, workbench, path): - return {"generation": self.generation_value} - - def read(self, workbench, path): - return { - "bytes": b"{}", - "metadata": {"generation": self.generation_value}, - } - - for bad_generation in (True, -3, "7", None): - malformed = NoKVCoordinationProvider( - MalformedClient(bad_generation), "wb-malformed", goal_id - ) - try: - malformed.load() - except ProviderProtocolError: - pass - else: - raise AssertionError(f"generation {bad_generation!r} was repaired") - try: - malformed._generation() - except ProviderProtocolError: - pass - else: - raise AssertionError(f"stat generation {bad_generation!r} was repaired") - - class ShapelessClient(FakeNoKVClient): - def read(self, workbench, path): - return {"metadata": {"generation": 1}} - - shapeless = NoKVCoordinationProvider(ShapelessClient(), "wb-shapeless", goal_id) - try: - shapeless.load() - except ProviderProtocolError as exc: - assert "bytes" in str(exc) - else: - raise AssertionError("missing read bytes escaped untyped") - - class BoolPublishClient(FakeNoKVClient): - def publish_bytes(self, workbench, path, data, **options): - return {"generation": True} - - bool_publish = NoKVCoordinationProvider(BoolPublishClient(), "wb-boolpub", goal_id) - try: - bool_publish.compare_and_put(0, head) - except ProviderProtocolError: - pass - else: - raise AssertionError("publish generation true was repaired to applied") - - out( - "contract.nokv_adapter_exception_mapping", - ok=True, - uninitialized_load_typed=True, - create_only_race_typed=True, - stale_generation_typed=True, - pre_publish_failure_typed=True, - routing_outage_not_missing=True, - outage_raises_typed_unavailable=True, - legacy_prose_unsupported=True, - corrupt_bytes_typed=True, - unserializable_head_typed_failed=True, - invalid_expected_generation_typed=True, - malformed_generation_never_repaired=True, - malformed_result_shape_typed=True, - ) - - -def probe_nokv_fresh_client_failure_is_typed() -> None: - """A fresh SDK admission failure belongs to the provider boundary too. - - Python evaluates constructor arguments before ``NoKVCoordinationProvider`` - can run, so ``NoKVCoordinationProvider(make_client(), ...)`` leaks the - SDK's bare ``RuntimeError`` when eager route admission fails. The - adapter-owned factory must classify that failure before any provider can - be returned or any coordination write can be attempted. An already - admitted client must keep the same typed behavior on later calls. - """ - - calls = {"factory": 0, "publish": 0} - - def unavailable_client_factory(): - calls["factory"] += 1 - raise RuntimeError("invalid root route: root placement does not exist") - - try: - open_nokv_coordination_provider( - unavailable_client_factory, - "wb-fresh-outage", - "fresh-outage", - ) - except ProviderUnavailableError as exc: - assert "root placement does not exist" in str(exc) - assert isinstance(exc.__cause__, RuntimeError) - else: - raise AssertionError("fresh client failure escaped the typed boundary") - - assert calls == {"factory": 1, "publish": 0} - - class AdmittedThenUnavailable(FakeNoKVClient): - def publish_bytes(self, workbench, path, data, **options): - calls["publish"] += 1 - return super().publish_bytes(workbench, path, data, **options) - - client = AdmittedThenUnavailable() - provider = open_nokv_coordination_provider( - lambda: client, - "wb-existing-outage", - "existing-outage", - ) - client.read_failure = RuntimeError("transport closed after admission") - try: - provider.load() - except ProviderUnavailableError as exc: - assert "transport closed after admission" in str(exc) - else: - raise AssertionError("established provider leaked a bare client failure") - - # Neither construction/read failure authorizes a create, local-file - # fallback, or other provider write. - assert calls == {"factory": 1, "publish": 0} - - out( - "contract.nokv_fresh_client_failure_is_typed", - ok=True, - construction_failure_typed=True, - established_failure_typed=True, - no_write_or_fallback=True, - ) - - -PROBES = ( - probe_bootstrap_and_preconditions, - probe_a_b_replay_a, - probe_operation_identity, - probe_competing_claims, - probe_crash_windows_and_ambiguity, - probe_version_domains_and_retain_all, - probe_nokv_adapter_exception_mapping, - probe_nokv_fresh_client_failure_is_typed, - probe_durable_completion_projection, - probe_durable_completion_fail_closed, -) - - -def run_contract() -> None: - for probe in PROBES: - probe() - out("contract.summary", ok=True, probes=len(PROBES)) - - -def main() -> int: - if len(sys.argv) != 2 or sys.argv[1] != "contract": - print("usage: python probes.py contract", file=sys.stderr) - return 2 - run_contract() - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/examples/nokv-shadow-provider/provider.py b/examples/nokv-shadow-provider/provider.py deleted file mode 100644 index 8e585ccb35..0000000000 --- a/examples/nokv-shadow-provider/provider.py +++ /dev/null @@ -1,243 +0,0 @@ -"""NoKV byte-CAS coordination provider for the shared-goal authority RFC. - -Storage adapter only. Domain semantics, receipts, and head validation belong -to the production authority modules under -``loopx.control_plane.coordination``; this module maps the RFC provider verbs -(``load`` / ``compare_and_put``) onto the NoKV Python SDK and nothing else. -The canonical byte encoding is imported from the production head codec so the -adapter cannot fork the digest/parity basis. - -Failure classification is by exception class ONLY. Since NoKV 0.11.0 (the -pinned RFC baseline) the SDK raises ``FileNotFoundError`` for a missing path -and ``FileExistsError`` for a create-only collision; every other RPC, -transport, routing, or publication failure is a ``RuntimeError``. Error prose -is never a channel: real non-missing failures carry messages such as -``invalid root route: root placement does not exist`` and ``logical shard ... -was not found``, so any message-token fallback misclassifies an outage as an -uninitialized goal - the one confusion RFC section 6.2 forbids (``missing`` -must never collapse into ``unavailable``). Pre-0.11 SDKs cannot route the -post-#465 control plane at all and are outside the supported baseline. -""" - -from __future__ import annotations - -import json -import uuid -from typing import Any, Callable - -from loopx.control_plane.coordination.head import ( - HeadValidationError, - canonical_head_bytes, -) - - -class ProviderProtocolError(RuntimeError): - """Persisted bytes or a provider result violated the reviewed contract.""" - - -class ProviderUnavailableError(RuntimeError): - """The storage plane could not serve the request; nothing is proven. - - Raised on read paths, where the RFC verbs offer no typed channel. It is - categorically different from ``(None, 0)``: missing is a proven absence - that authorizes bootstrap-create, while unavailable means the head's - existence is unknown and every decision must fail closed. - """ - - -def _decoded_generation(value, *, source: str) -> int: - """The one strict decoder for the authoritative CAS token. - - bool is an int subclass and ``int(True) == 1``: a malformed SDK response - carrying ``generation: true`` must never be repaired into generation 1, - exactly as the head codec refuses bool-disguised revisions and epochs. - Stored generations start at 1; the absent sentinel 0 is synthesized by - the adapter, never decoded. - """ - - if not isinstance(value, int) or isinstance(value, bool) or value < 1: - raise ProviderProtocolError( - f"{source} carried an invalid generation: {value!r}" - ) - return value - - -def _result_field(result, field: str, *, source: str): - if not isinstance(result, dict) or field not in result: - raise ProviderProtocolError(f"{source} omitted required field {field!r}") - return result[field] - - -class NoKVCoordinationProvider: - """Map one goal's canonical head bytes to a NoKV generation CAS. - - ``load`` returns ``(head | None, provider_generation)`` where ``None`` - is proven absence (``FileNotFoundError`` from the SDK) and any other - client failure raises the typed :class:`ProviderUnavailableError`. - ``compare_and_put`` reports the four RFC verbs: an outage before the - publish is typed ``failed`` (provably no write), and a publish that - raises is ``ambiguous`` - human error text is never parsed as commit - proof; the authority reloads and trusts only its atomically stored - receipt index. - """ - - _CLIENT_ERRORS = (RuntimeError, OSError) - - def __init__(self, client, workbench: str, goal_id: str): - self.client = client - self.workbench = workbench - self.goal_id = goal_id - self.head_path = f"goals/{goal_id}/coordination-head.json" - self._store_identity: str | None = None - - def store_identity(self) -> str: - """The stable identity of this store lineage (Stage 3 binding fence). - - NoKV assigns every workbench incarnation a never-reused 32-hex - ``workspace_incarnation_id`` that is permanently claimed server-side; - a restore lands in a NEW workbench with a NEW incarnation, so a head - bound to ``workbench:incarnation`` detects the restored lineage - before any write. The identity is immutable for the life of the - workbench, so one successful lookup is cached. - """ - - if self._store_identity is not None: - return self._store_identity - cursor = None - try: - while True: - page = self.client.find_workspaces(cursor=cursor, limit=100) - for entry in page.get("workspaces", []): - workspace = entry.get("workspace") or {} - if workspace.get("workbench") == self.workbench: - incarnation = workspace.get("workspace_incarnation_id") - if isinstance(incarnation, str) and incarnation: - self._store_identity = ( - f"nokv:{self.workbench}:{incarnation}" - ) - return self._store_identity - cursor = page.get("cursor") or page.get("next_cursor") - if not cursor: - break - except self._CLIENT_ERRORS as exc: - raise ProviderUnavailableError(str(exc)) from exc - raise ProviderProtocolError( - f"workbench {self.workbench!r} has no visible incarnation identity" - ) - - def load(self): - """Return ``(aggregate | None, provider_generation)``.""" - try: - result = self.client.read(self.workbench, self.head_path) - except FileNotFoundError: - return None, 0 - except self._CLIENT_ERRORS as exc: - raise ProviderUnavailableError(str(exc)) from exc - raw = _result_field(result, "bytes", source="read result") - try: - aggregate = json.loads(bytes(raw)) - except (TypeError, ValueError) as exc: - raise ProviderProtocolError( - f"coordination head is not valid JSON: {exc}" - ) from exc - if not isinstance(aggregate, dict): - raise ProviderProtocolError("coordination head must be an object") - metadata = _result_field(result, "metadata", source="read result") - return aggregate, _decoded_generation( - _result_field(metadata, "generation", source="read metadata"), - source="read metadata", - ) - - def _generation(self) -> int: - try: - metadata = self.client.stat(self.workbench, self.head_path) - except FileNotFoundError: - return 0 - except self._CLIENT_ERRORS as exc: - raise ProviderUnavailableError(str(exc)) from exc - return _decoded_generation( - _result_field(metadata, "generation", source="stat result"), - source="stat result", - ) - - def compare_and_put( - self, - expected_provider_generation: int, - aggregate: dict, - ) -> dict: - """Serialize and conditionally store an opaque aggregate.""" - if ( - not isinstance(expected_provider_generation, int) - or isinstance(expected_provider_generation, bool) - or expected_provider_generation < 0 - ): - # The caller's CAS token is typed state exactly like the stored - # one: a bool or negative expectation must not reach comparison - # or publish arithmetic. - return { - "result": "failed", - "error": "expected_provider_generation must be a non-negative integer", - } - try: - current = self._generation() - except ProviderUnavailableError as exc: - # No publish was attempted, so this failure proves no write. - return {"result": "failed", "error": str(exc)} - if current != expected_provider_generation: - return {"result": "conflict", "current_provider_generation": current} - try: - payload = canonical_head_bytes(aggregate) - except HeadValidationError as exc: - # Serialization runs before any publish, so this failure proves - # no write; it surfaces as the typed verb exactly like the file - # provider, never as an unclassified exception through the seam. - return {"result": "failed", "error": str(exc)} - try: - result = self.client.publish_bytes( - self.workbench, - self.head_path, - payload, - content_type="application/json", - expected_generation=( - None if expected_provider_generation == 0 - else expected_provider_generation - ), - operation_id=uuid.uuid4().hex, - artifact_revision_id=uuid.uuid4().hex, - ) - except self._CLIENT_ERRORS: - # Do not parse human error text as commit proof: a create-only - # collision (``FileExistsError``), a generation mismatch, or a lost - # response all reload; the authority trusts only its atomically - # stored receipt index. - return {"result": "ambiguous"} - return { - "result": "applied", - "provider_generation": _decoded_generation( - _result_field(result, "generation", source="publish result"), - source="publish result", - ), - } - - -def open_nokv_coordination_provider( - client_factory: Callable[[], Any], - workbench: str, - goal_id: str, -) -> NoKVCoordinationProvider: - """Admit and construct one fresh provider without a fallback. - - The NoKV SDK performs route admission eagerly in ``Client(...)``. A - direct ``NoKVCoordinationProvider(make_client(), ...)`` therefore evaluates - the fallible SDK constructor before the adapter exists and leaks a bare - client exception. Live composition roots enter here so construction-time - availability failures have the same fail-closed type as later reads. - """ - - try: - client = client_factory() - except ProviderUnavailableError: - raise - except NoKVCoordinationProvider._CLIENT_ERRORS as exc: - raise ProviderUnavailableError(str(exc)) from exc - return NoKVCoordinationProvider(client, workbench, goal_id) diff --git a/examples/shared-goal-authority-e2e/README.md b/examples/shared-goal-authority-e2e/README.md index a9ab0ea531..d7071c0b1f 100644 --- a/examples/shared-goal-authority-e2e/README.md +++ b/examples/shared-goal-authority-e2e/README.md @@ -32,8 +32,8 @@ and the transaction-bound outbox instead of requiring a second writable history. | Row | Stage | Path | Gate | Asserts | | --- | --- | --- | --- | --- | -| `s0.file_matrix_twelve_rows` | 0 | store_direct | deterministic | `examples/nokv-shadow-provider/live_e2e.py` reports exactly the twelve known file-provider scenario rows, all true | -| `s0.nokv_live_matrix` | 0 | store_direct | env:nokv_legacy | the same twelve rows plus `restored_lineage_fails_closed` are true on a live NoKV stack and file/NoKV outcomes are identical | +| `s0.native_file_conformance` | 0 | store_direct | deterministic | Runs the complete `authority_store.test.ts` suite against the current TS FileAuthorityStore; every selected test must pass, with no skip or missing trailer | +| `s0.native_sqlite_conformance` | 0 | store_direct | deterministic | Runs the complete `sqlite_authority_store.test.ts` suite against real SQLite files and the same shared conformance contract | | `s1.cli_document_decodes_through_ts_store` | 1 | real_cli | deterministic | Explicit bootstrap plus three CLI writes load at cursor `4`; paged `scanCommitted` returns four distinct transactions in source order and `readReceipt` finds the first source write | | `s2a.nokv_live_qualification` | 2a | store_direct | env:nokv_authority | runs the merged `examples/nokv-authority-store/live-qualification.ts --execute-live` against an existing workbench with a fresh tenant/goal pair; requires `ok=true`, the single-node store-conformance scope, every check `passed`, NoKV SDK `0.11.1` / API `1`, the two stale-incarnation fence checks (`stale_incarnation_fence_rejected`, `stale_incarnation_fence_left_generation_unchanged`), and no promotion or availability claim; evidence carries check ids, counts, and config and workbench digest prefixes, never a configuration value or the workbench name | | `s2b.postgresql_conformance_live` | 2b | store_direct | env:postgresql | `postgresql_authority_store.integration.test.ts` under node's TAP reporter: `# pass >= 9`, `# fail 0`, `# skipped 0` | @@ -73,13 +73,65 @@ byte; every assertion still goes through `status`, `drain`, `inspect`, `qualify`, `read-candidate`, `rollback`, `migrate-state` and the retained TypeScript store read. +## Native qualification and prototype retirement + +Stage 0 now exercises the providers used by current Goals. Run from a source +checkout after `npm ci` and `uv sync --extra test`: + +```bash +uv run --extra test python examples/shared-goal-authority-e2e/ladder.py --stage 0 +``` + +These are complete suites, so allow several minutes. The regular TypeScript test +job already runs them; the default pytest projection skips these two duplicate +runs. Set `LOOPX_LADDER_FULL=1` for pytest to execute them too. The standalone +ladder always executes selected rows. The test runner's successful +exit is necessary but insufficient: all tests must pass, the selection must be +nonempty, and failures, skips, cancellations or an incomplete TAP trailer fail +qualification. Missing Node or source suites is explicitly `unverified`. +Installing a wheel alone does not install the source qualification suite. + +The Python `CoordinationAuthorityExecutor`, head codec, `FileCoordinationProvider` +and bootstrap bridge had no production CLI callers. Their remaining callers were +the prototype examples, unit tests and the old ladder. They are removed together; +current TS executors, stores, logical archives and File v0-to-v1 migration remain. +`coordination-head-*.json` was the experimental head format, not a production +`authority-store-*.json` journal. This retirement neither rewrites those prototype +artifacts nor converts them into a full Goal. Historical probe records remain +historical; `s0.file_matrix_twelve_rows` and `s0.nokv_live_matrix` are no longer +selectable and are never aliases for current proof. + +Coverage follows the current semantic owner rather than reproducing prototype +schemas or its superseded rules: + +| Retired prototype obligation | Retained native qualification | +| --- | --- | +| Same-Todo CAS winner, independent writes, stale revision and operation identity | `authority_store_conformance.ts`: CAS, replay/fencing, atomic Todo claim and lease acquisition | +| Lost response, receipt recovery and bounded retries | `coordination_receipt_conformance.ts` and `claim_acquisition_proof_conformance.ts` | +| Renew, release, transfer, expiry and stale ownership | `lease_lifecycle_conformance.ts`, `lease_acquisition_conformance.ts`, `claim_transfer_conformance.ts` and claim acquisition proof | +| Completion plus successor, durable continuation and unchanged unrelated rows | Native terminal lifecycle and production-scale scenarios in `authority_store_conformance.ts` | +| Restore lineage, corrupt records, publication and reopen | File/SQLite provider suites, authority source and promotion recovery conformance | +| Large state and observation isolation | Existing production-scale coordination fixtures, complete snapshots and paginated authority scan conformance | + +Two intentional differences already belong to the current runtime: an expired +lease may still be released by its matching holder for cleanup, and an original +claim receipt does not authorize execution after the current lease has retired. +Retiring the prototype must not reinstate its expired-release rejection or treat +historical receipt replay as fresh execution authority. + +NoKV keeps its actual TS store and Stage 2A live qualification; removing the old +head adapter does not remove that provider. Open #4726 touches both implementations: +its native SDK routing/schema work remains relevant, while its prototype-only +changes must be dropped when rebasing across this retirement. Local conformance does not certify +SQLite D2 capacity/soak, production NoKV availability, PostgreSQL deployment or +default cutover. + ## Gates and environment variables | Gate | Requirement | Unverified reason when absent | | --- | --- | --- | | `deterministic` | none (needs `node` on `PATH` for the CLI's TypeScript runtime and the read-back probe) | `node_missing` when the probe cannot run | | `env:postgresql` | `LOOPX_TEST_POSTGRES_URL` plus `node_modules/pg` (`npm ci`) | `postgres_url_missing`, `pg_dependency_missing`, `node_missing` | -| `env:nokv_legacy` | `NOKV_COORDINATION_LIVE=1` and `NOKV_ETCD`, `NOKV_ETCD_PREFIX`, `NOKV_ROOT_ID`, `NOKV_BUCKET`, `NOKV_OBJECT_ENDPOINT`, `NOKV_OBJECT_ROOT`, `NOKV_OBJECT_KEY`, `NOKV_OBJECT_SECRET`; the `nokv` SDK importable | `nokv_live_env_missing`, `nokv_coordination_live_not_enabled`, `nokv_sdk_missing` | | `env:nokv_authority` | `LOOPX_NOKV_AUTHORITY_LIVE=1` (the probe writes durable test data), `LOOPX_NOKV_AUTHORITY_CONFIG_JSON` (absolute path to the ignored NoKV client configuration), `LOOPX_NOKV_AUTHORITY_PYTHON` (absolute path to the Python executable that resolves NoKV SDK 0.11.1), `LOOPX_NOKV_AUTHORITY_WORKBENCH` (an existing workbench); `node` on `PATH` | `nokv_authority_env_missing`, `loopx_nokv_authority_live_not_enabled`, `nokv_authority_config_missing`, `nokv_authority_python_missing`, `node_missing` | POSIX-only rows report `unverified/posix_only` on Windows. diff --git a/examples/visible-governance-slice-smoke.py b/examples/visible-governance-slice-smoke.py index 532e83927e..d37c32b13d 100644 --- a/examples/visible-governance-slice-smoke.py +++ b/examples/visible-governance-slice-smoke.py @@ -311,16 +311,16 @@ def main() -> None: f"checked={consistency['checked_agent_todo_count']} todos" ) - # authority boundary (proposal vs Stage-2 shipped truth) + # authority boundary (implementation availability, not per-Goal promotion) boundary = gov_slice["authority_boundary"] assert isinstance(boundary, list) assert len(boundary) >= 5, f"expected >= 5 RFC entries, got {len(boundary)}" expected_shipped = { - "3 -- State Classification": False, + "3 -- State Classification": True, "4 -- Coordination Ledger Shape": False, "5.1 -- claim_work command": False, "7 -- Receipt Retention": True, - "8 -- Local vs Shared Mode": False, + "8 -- Local vs Shared Mode": True, "Appendix B -- handoff_mode": True, "9 -- Offline/Local Boundaries": False, } @@ -333,26 +333,22 @@ def main() -> None: assert "gap" in entry assert entry["shipped"] is expect_shipped, ( f"{section}: shipped={entry['shipped']!r}, " - f"expected {expect_shipped} after the Stage-3 boundary refresh" + f"expected {expect_shipped} after native prototype retirement" ) claim_work = by_section["5.1 -- claim_work command"] claim_text = ( f"{claim_work['shipped_equivalent']} {claim_work['gap']}".lower() ) - assert "coverage-only" in claim_text, ( - "claim_work must stay explicit as a reference path, not production" - ) - assert "rather than write authority" in claim_work["gap"].lower() or ( - "not write authority" in claim_work["shipped_equivalent"].lower() - ), "claim_work gap must keep leases out of write authority" + assert "retired" in claim_text and "todo_claim.ts" in claim_text + assert "not current execution authority" in claim_text shipped_count = sum(1 for e in boundary if e["shipped"]) assert shipped_count == sum( 1 for value in expected_shipped.values() if value ), f"unexpected shipped count: {shipped_count}" print( f" [OK] Authority boundary: {len(boundary)} RFC sections, " - f"{shipped_count} retained/handoff shipped, " - f"{len(boundary) - shipped_count} still proposal-only" + f"{shipped_count} implemented, " + f"{len(boundary) - shipped_count} original proposals not shipped" ) # ── Negative: active lease is never write authority ── diff --git a/loopx/control_plane/coordination/executor.py b/loopx/control_plane/coordination/executor.py deleted file mode 100644 index 2cd604ad4a..0000000000 --- a/loopx/control_plane/coordination/executor.py +++ /dev/null @@ -1,1207 +0,0 @@ -"""The authority execution layer over a coordination provider (RFC section 5). - -The executor owns exactly what the Stage 1 core refuses: envelope -normalization and request digests, aggregate-level preconditions -(todo revisions, eligibility snapshots, dependency and gate booleans), -receipt construction and replay, wall-clock expiry minting, the three version -domains (``provider_generation`` / ``authority_revision`` / ``lease_epoch``), -and the bounded load -> decide -> compare_and_put -> reload loop. Every -domain decision about actors and leases is delegated to -``authority_core.decide``; no coordination rule is re-derived here. - -The only command in this slice is ``claim_work`` (RFC section 5.1): one -accepted transition records the claim, the lease with its next epoch, and the -original receipt in the same provider CAS. -""" - -from __future__ import annotations - -import copy -import hashlib -import json -import math -import re -from dataclasses import replace -from datetime import datetime, timezone -from typing import Any, Callable - -from .authority_core import ( - CoordinationSnapshot, - DecisionOutcome, - LeaseAcquireCommand, - LeaseReleaseCommand, - LeaseRenewCommand, - LeaseSnapshot, - LifecycleGrant, - TodoAction, - TodoMutationCommand, - decide, -) -from .head import ( - HeadMigrationRequired, - HeadValidationError, - canonical_head_bytes, - claim_snapshot_for_todo, - evidence_contract_violation, - validated_head, -) - -COMMAND_SCHEMA_VERSION = "loopx_command_v0" -RECEIPT_SCHEMA_VERSION = "loopx_authority_receipt_v0" - -_ENVELOPE_FIELDS = {"schema_version", "operation_id", "actor", "goal_id", "command", "transport"} -_ACTOR_FIELDS = {"agent_id", "device_id"} -# Per-verb closed command field sets (RFC section 5: unknown fields fail -# closed; the request digest covers every semantic field automatically). -_COMMAND_FIELD_SETS = { - "claim_work": { - "type", - "todo_id", - "expected_todo_revision", - "expected_preconditions", - "lease_ttl_seconds", - }, - "renew_work": { - "type", - "todo_id", - "expected_todo_revision", - "lease_id", - "expected_lease_epoch", - "lease_ttl_seconds", - }, - "release_work": { - "type", - "todo_id", - "expected_todo_revision", - "lease_id", - "expected_lease_epoch", - }, - "reclaim_work": { - "type", - "todo_id", - "expected_todo_revision", - "expected_preconditions", - "lease_ttl_seconds", - }, - "complete_work": { - "type", - "todo_id", - "expected_todo_revision", - "lease_id", - "expected_lease_epoch", - "no_followup", - "successor_todo_ids", - "evidence", - }, -} -_SUCCESSOR_ID_PATTERN = re.compile(r"^todo_[a-z0-9_-]{3,64}$") -# Reclaim only takes over once the authority's own clock has seen the lease -# expired for at least this grace window, bounding clock skew between the -# superseded holder and the adjudicating authority. Recorded per receipt. -DEFAULT_RECLAIM_GRACE_SECONDS = 30.0 -_PRECONDITION_FIELDS = { - "authorization_projection_revision", - "authorization_projection_digest", - "dependency_revision", - "gate_revision", -} -_MAX_CAS_ATTEMPTS = 8 -# The local task-lease authority's ceiling -# (work_items.task_lease.MAX_TASK_LEASE_TTL_SECONDS), mirrored here so this -# module's import closure stays inside the strict type gate; a pin test -# asserts the two never drift. -MAX_LEASE_TTL_SECONDS = 24 * 60 * 60 - -# Core code -> RFC reason vocabulary for the claim_work slice. Actor and -# owner ineligibility collapse onto the RFC's one actor reason; ownership -# codes that the executor's aggregate prechecks should have intercepted are -# aggregate-integrity failures, so they fail closed instead of masquerading -# as caller errors. -_CORE_TO_RFC_REASON = { - "actor_required": "actor_ineligible", - "actor_not_registered": "actor_ineligible", - "actor_excluded": "actor_ineligible", - "claim_actor_mismatch": "actor_ineligible", - "invalid_owner": "actor_ineligible", - "owner_not_registered": "actor_ineligible", - "owner_excluded_from_todo": "actor_ineligible", - "todo_not_open": "todo_not_open", - "todo_not_found": "todo_not_found", - # Stage 3: the core detects a fence the executor precheck let through - # only when ownership diverged between precheck and decision; both are - # the same caller-visible truth - the fence the caller holds is stale. - "lease_cas_mismatch": "stale_lease_fence", - "lease_not_active": "lease_not_active", - "handoff_mode_requires_lease": "lease_not_active", - "lease_fence_required": "stale_lease_fence", -} -_FAIL_CLOSED_CORE_CODES = { - "claim_owner_mismatch", - "owner_conflicts_with_claim", - "invalid_lease_snapshot", - # The executor synthesizes the reclaim grant itself; a delegation - # rejection means the executor and core disagree about that synthesis. - "delegation_action_not_granted", - "delegation_reason_required", - "handoff_mode_lease_claim_divergence", -} - - -def _classified(plan: Any) -> dict[str, Any]: - """Map one non-APPLY core TransitionPlan onto an RFC result class.""" - - if plan.code in _FAIL_CLOSED_CORE_CODES: - # The executor's revision and open/unclaimed prechecks make these - # unreachable on a well-formed head; reaching one means the aggregate - # and the core disagree, which is an integrity failure, not caller error. - return {"result": "failed", "reason": f"aggregate_integrity:{plan.code}"} - if plan.outcome is DecisionOutcome.CONFLICT: - return {"result": "conflict", "reason": plan.code} - if plan.outcome is DecisionOutcome.NO_CHANGE: - # A replay the receipt index does not know about cannot be proven to - # be this operation's own effect (acceptance check 6): fail closed. - return {"result": "failed", "reason": f"state_without_receipt:{plan.code}"} - return { - "result": "rejected", - "reason": _CORE_TO_RFC_REASON.get(plan.code, plan.code), - } - - -class EnvelopeError(ValueError): - """The command envelope violates the reviewed v0 contract.""" - - -def _digest(value: dict[str, Any]) -> str: - encoded = json.dumps( - value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, - allow_nan=False, - ).encode("utf-8") - return "sha256:" + hashlib.sha256(encoded).hexdigest() - - -def _continuation_for_write(*, no_followup: bool, has_successor: bool) -> str: - """The local completion_state rule, mirrored so the executor's import - closure stays inside the strict type gate; a pin test asserts equality - with the TS-backed facade on every input combination.""" - - if no_followup and has_successor: - raise EnvelopeError( - "todo completion cannot record both no_followup and a successor" - ) - if no_followup: - return "no_followup" - if has_successor: - return "successor" - return "active_goal" - - -def _lease_id(operation_id: str) -> str: - return "lease_" + hashlib.sha256(f"lease:{operation_id}".encode()).hexdigest()[:24] - - -def _format_time(timestamp: float) -> str: - return ( - datetime.fromtimestamp(timestamp, timezone.utc) - .isoformat(timespec="milliseconds") - .replace("+00:00", "Z") - ) - - -def _parse_time(value: str) -> float: - return datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() - - -def sample_claim_envelope( - *, - goal_id: str, - operation_id: str, - agent_id: str, - device_id: str, - todo_id: str, - expected_todo_revision: int, - expected_preconditions: dict[str, Any], - lease_ttl_seconds: int, - transport: dict[str, Any] | None = None, -) -> dict[str, Any]: - """Build one well-formed ``claim_work`` envelope (tests and adapters).""" - - envelope: dict[str, Any] = { - "schema_version": COMMAND_SCHEMA_VERSION, - "operation_id": operation_id, - "actor": {"agent_id": agent_id, "device_id": device_id}, - "goal_id": goal_id, - "command": { - "type": "claim_work", - "todo_id": todo_id, - "expected_todo_revision": expected_todo_revision, - "expected_preconditions": copy.deepcopy(expected_preconditions), - "lease_ttl_seconds": lease_ttl_seconds, - }, - } - if transport is not None: - envelope["transport"] = copy.deepcopy(transport) - return envelope - - -def sample_work_envelope( - *, - goal_id: str, - operation_id: str, - agent_id: str, - device_id: str, - command: dict[str, Any], - transport: dict[str, Any] | None = None, -) -> dict[str, Any]: - """Build one well-formed command envelope for any Stage 3 verb.""" - - envelope: dict[str, Any] = { - "schema_version": COMMAND_SCHEMA_VERSION, - "operation_id": operation_id, - "actor": {"agent_id": agent_id, "device_id": device_id}, - "goal_id": goal_id, - "command": copy.deepcopy(command), - } - if transport is not None: - envelope["transport"] = copy.deepcopy(transport) - return envelope - - -def _validated_reclaim_grace(value: Any) -> float: - """The grace window is a skew bound: it may only DELAY a takeover. - - A NaN grace makes ``expired_for < grace`` always false so every active - lease becomes reclaimable, a negative grace advances the takeover before - expiry, and bool is the usual coercion accident - so the configuration - boundary rejects everything but a finite non-negative number. - """ - - if isinstance(value, bool) or not isinstance(value, (int, float)): - raise ValueError( - "reclaim_grace_seconds must be a finite non-negative number" - ) - try: - grace = float(value) - except OverflowError as error: - raise ValueError( - "reclaim_grace_seconds must be a finite non-negative number" - ) from error - if not math.isfinite(grace) or grace < 0.0: - raise ValueError( - "reclaim_grace_seconds must be a finite non-negative number" - ) - return grace - - -class CoordinationAuthorityExecutor: - """Apply normalized coordination commands through one provider CAS. - - This executor is the RFC's reference implementation: LoopX's runtime - does not construct it yet (coverage-only per the visible governance - ledger), and wiring it to a product entry point is a later-stage, - owner-gated decision. - """ - - def __init__( - self, - provider: Any, - *, - goal_id: str, - now: Callable[[], float], - reclaim_grace_seconds: float = DEFAULT_RECLAIM_GRACE_SECONDS, - ): - self.provider = provider - self.goal_id = goal_id - self.now = now - self.reclaim_grace_seconds = _validated_reclaim_grace(reclaim_grace_seconds) - - # ---- envelope normalization (RFC section 5) ----------------------------- - - def _semantic_request(self, envelope: Any) -> dict[str, Any]: - if not isinstance(envelope, dict): - raise EnvelopeError("command envelope must be an object") - unknown = set(envelope) - _ENVELOPE_FIELDS - if unknown: - raise EnvelopeError(f"unknown command envelope fields: {sorted(unknown)}") - if envelope.get("schema_version") != COMMAND_SCHEMA_VERSION: - raise EnvelopeError("unsupported command envelope schema") - operation_id = envelope.get("operation_id") - if not isinstance(operation_id, str) or not operation_id: - raise EnvelopeError("operation_id must be a non-empty string") - if envelope.get("goal_id") != self.goal_id: - raise EnvelopeError("command goal_id does not match this authority") - if "transport" in envelope and not isinstance(envelope["transport"], dict): - raise EnvelopeError("transport metadata must be an object") - actor = envelope.get("actor") - if not isinstance(actor, dict) or set(actor) != _ACTOR_FIELDS: - raise EnvelopeError("actor must contain only agent_id and device_id") - if not all(isinstance(actor[key], str) and actor[key] for key in _ACTOR_FIELDS): - raise EnvelopeError("actor ids must be non-empty strings") - command = envelope.get("command") - if not isinstance(command, dict): - raise EnvelopeError("command must be an object") - command_type = command.get("type") - if command_type not in _COMMAND_FIELD_SETS: - raise EnvelopeError( - f"unsupported command type; this slice supports " - f"{sorted(_COMMAND_FIELD_SETS)}" - ) - expected_fields = _COMMAND_FIELD_SETS[command_type] - if set(command) != expected_fields: - raise EnvelopeError( - f"{command_type} fields do not match the v0 contract" - ) - if not isinstance(command["todo_id"], str) or not command["todo_id"]: - raise EnvelopeError("todo_id must be a non-empty string") - revision = command["expected_todo_revision"] - if not isinstance(revision, int) or isinstance(revision, bool): - raise EnvelopeError("expected_todo_revision must be an integer") - if "expected_preconditions" in expected_fields: - self._validated_preconditions(command["expected_preconditions"]) - if "lease_ttl_seconds" in expected_fields: - ttl = command["lease_ttl_seconds"] - # The bound is the local task-lease authority's own ceiling, so - # the shared envelope cannot mint a lease the local contract - # would refuse, and an unbounded caller value can never reach - # wall-clock arithmetic (an astronomical TTL overflows timestamp - # formatting). - if ( - not isinstance(ttl, int) - or isinstance(ttl, bool) - or ttl <= 0 - or ttl > MAX_LEASE_TTL_SECONDS - ): - raise EnvelopeError( - f"lease_ttl_seconds must be between 1 and {MAX_LEASE_TTL_SECONDS}" - ) - if "lease_id" in expected_fields: - lease_id = command["lease_id"] - epoch = command["expected_lease_epoch"] - if not isinstance(lease_id, str) or not lease_id: - raise EnvelopeError("lease_id must be a non-empty string") - if not isinstance(epoch, int) or isinstance(epoch, bool) or epoch < 1: - raise EnvelopeError( - "expected_lease_epoch must be a positive integer" - ) - if command_type == "complete_work": - self._validated_completion_command(command) - # Transport metadata is deliberately excluded from the semantic request. - return { - "schema_version": COMMAND_SCHEMA_VERSION, - "operation_id": operation_id, - "actor": {key: actor[key] for key in sorted(_ACTOR_FIELDS)}, - "goal_id": self.goal_id, - "command": { - key: copy.deepcopy(command[key]) for key in sorted(expected_fields) - }, - } - - @staticmethod - def _validated_preconditions(preconditions: Any) -> None: - if not isinstance(preconditions, dict) or set(preconditions) != _PRECONDITION_FIELDS: - raise EnvelopeError("expected_preconditions fields do not match v0") - for field in ( - "authorization_projection_revision", - "dependency_revision", - "gate_revision", - ): - value = preconditions[field] - if not isinstance(value, int) or isinstance(value, bool) or value < 0: - raise EnvelopeError("expected_preconditions revisions must be non-negative") - digest = preconditions["authorization_projection_digest"] - if not isinstance(digest, str) or not digest.startswith("sha256:"): - raise EnvelopeError("expected authorization projection digest is invalid") - - @staticmethod - def _validated_completion_command(command: dict[str, Any]) -> None: - no_followup = command["no_followup"] - successors = command["successor_todo_ids"] - evidence = command["evidence"] - if not isinstance(no_followup, bool): - raise EnvelopeError("no_followup must be a boolean") - if not isinstance(successors, list) or not all( - isinstance(item, str) and _SUCCESSOR_ID_PATTERN.fullmatch(item) - for item in successors - ): - raise EnvelopeError( - "successor_todo_ids must be public todo ids (todo_)" - ) - if len(set(successors)) != len(successors): - raise EnvelopeError("successor_todo_ids must be distinct") - if no_followup and successors: - # The same contradiction the local durable-completion write - # refuses: a completion cannot record both. - raise EnvelopeError( - "todo completion cannot record both no_followup and a successor" - ) - if evidence is not None: - # One oracle with head validation: what the boundary refuses, - # a stored head can never carry, and vice versa. - violation = evidence_contract_violation(evidence) - if violation is not None: - raise EnvelopeError(violation) - - # ---- replay ------------------------------------------------------------- - - def _replay( - self, - head: dict[str, Any], - provider_generation: int, - operation_id: str, - request_digest: str, - ) -> dict[str, Any] | None: - entry = head["receipt_index"].get(operation_id) - if entry is None: - return None - if entry.get("request_digest") != request_digest: - return { - "result": "rejected", - "reason": "operation_identity_mismatch", - "operation_id": operation_id, - "observed_authority_revision": head["authority_revision"], - "provider_generation": provider_generation, - } - receipt = entry.get("original_receipt") - if not isinstance(receipt, dict): - raise HeadValidationError("receipt entry lacks original_receipt") - return self._success("already_applied", receipt, head, provider_generation) - - def _success( - self, - result: str, - receipt: dict[str, Any], - head: dict[str, Any], - generation: int, - ) -> dict[str, Any]: - lease = head["coordination"]["leases"].get(receipt["todo_id"]) - if lease is None or ( - lease.get("lease_id") != receipt.get("lease_id") - or lease.get("lease_epoch") != receipt.get("lease_epoch") - ): - status = "superseded" - elif _parse_time(lease["expires_at"]) <= self.now(): - status = "expired" - else: - status = "active" - return { - "result": result, - "original_receipt": copy.deepcopy(receipt), - "observed_authority_revision": head["authority_revision"], - "authorization_status": status, - "provider_generation": generation, - } - - # ---- the one transition of this slice ----------------------------------- - - def _claim_transition( - self, - head: dict[str, Any], - request: dict[str, Any], - request_digest: str, - ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: - """Return either a typed non-apply dict, or (next_head, receipt).""" - - command = request["command"] - todo_id = command["todo_id"] - todo = head["coordination"]["todos"].get(todo_id) - if todo is None: - return {"result": "rejected", "reason": "todo_not_found"} - if todo["todo_revision"] != command["expected_todo_revision"]: - return { - "result": "conflict", - "reason": "todo_revision_mismatch", - "expected_todo_revision": command["expected_todo_revision"], - "observed_todo_revision": todo["todo_revision"], - } - eligibility = todo["eligibility"] - observed_preconditions = { - field: eligibility[field] for field in _PRECONDITION_FIELDS - } - if observed_preconditions != command["expected_preconditions"]: - return { - "result": "conflict", - "reason": "precondition_snapshot_mismatch", - "expected_preconditions": copy.deepcopy( - command["expected_preconditions"] - ), - "observed_preconditions": observed_preconditions, - } - if todo["status"] != "open" or todo["claimed_by"] is not None: - return {"result": "rejected", "reason": "todo_not_open"} - if eligibility["dependencies_satisfied"] is not True: - return {"result": "rejected", "reason": "dependencies_not_satisfied"} - if eligibility["gates_open"] is not True: - return {"result": "rejected", "reason": "gate_closed"} - - actor = request["actor"]["agent_id"] - snapshot = claim_snapshot_for_todo(head, todo_id) - - core_lease = self._acquire_and_claim( - snapshot, actor, request["operation_id"], command["lease_ttl_seconds"] - ) - if isinstance(core_lease, dict): - return core_lease - - now = float(self.now()) - expires_at = _format_time(now + command["lease_ttl_seconds"]) - next_head = copy.deepcopy(head) - next_head["authority_revision"] += 1 - next_todo = next_head["coordination"]["todos"][todo_id] - next_todo.update( - { - "todo_revision": todo["todo_revision"] + 1, - # LoopX keeps a claimed todo open; ``claimed_by`` plus the - # lease carry ownership without a new lifecycle status. - "status": "open", - "claimed_by": actor, - "last_lease_epoch": core_lease.lease_epoch, - } - ) - next_head["coordination"]["leases"][todo_id] = { - "lease_id": core_lease.idempotency_key, - "owner": core_lease.owner, - "lease_epoch": core_lease.lease_epoch, - "expires_at": expires_at, - "write_scopes": list(core_lease.write_scopes), - } - receipt = { - "schema_version": RECEIPT_SCHEMA_VERSION, - "operation_id": request["operation_id"], - "request_digest": request_digest, - "command": "claim_work", - "actor": copy.deepcopy(request["actor"]), - "todo_id": todo_id, - "accepted_authority_revision": next_head["authority_revision"], - "accepted_todo_revision": next_todo["todo_revision"], - "applied_at": _format_time(now), - "lease_id": core_lease.idempotency_key, - "lease_epoch": core_lease.lease_epoch, - "expires_at": expires_at, - } - next_head["receipt_index"][request["operation_id"]] = { - "request_digest": request_digest, - "original_receipt": copy.deepcopy(receipt), - } - return next_head, receipt - - # ---- Stage 3 shared helpers --------------------------------------------- - - def _store_binding_fence( - self, - head: dict[str, Any], - provider_generation: int, - ) -> dict[str, Any] | None: - """The lineage binding fence (RFC Stage 3 gate). - - The head is permanently bound at bootstrap to the provider-issued - store identity. A head observed through a provider whose identity - differs was restored or copied into a different store lineage; - every command fails closed there until an explicit, reviewed - re-bootstrap re-binds it - restored bytes never grant live - authority. - """ - - identity = self.provider.store_identity() - if head["store_binding"] == identity: - return None - return { - "result": "failed", - "reason": "store_lineage_mismatch", - "head_store_binding": head["store_binding"], - "provider_store_identity": identity, - "provider_generation": provider_generation, - } - - def _todo_prechecks( - self, - head: dict[str, Any], - command: dict[str, Any], - ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: - """Return (todo, None) or (None, typed non-apply dict).""" - - todo = head["coordination"]["todos"].get(command["todo_id"]) - if todo is None: - return None, {"result": "rejected", "reason": "todo_not_found"} - if todo["todo_revision"] != command["expected_todo_revision"]: - return None, { - "result": "conflict", - "reason": "todo_revision_mismatch", - "expected_todo_revision": command["expected_todo_revision"], - "observed_todo_revision": todo["todo_revision"], - } - if todo["status"] != "open": - return None, {"result": "rejected", "reason": "todo_not_open"} - return todo, None - - def _lease_fence( - self, - head: dict[str, Any], - command: dict[str, Any], - ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]: - """Validate the caller-held fence. Return (lease, None) or typed dict. - - This is the stale-fence rejection at the heart of recoverable - execution: after a reclaim mints a new lease generation, every write - the superseded executor sends still carries the old (lease_id, - lease_epoch) pair and lands here, terminally - a stale fence is never - rebased past. - """ - - lease = head["coordination"]["leases"].get(command["todo_id"]) - if lease is None: - return None, {"result": "rejected", "reason": "lease_missing"} - if ( - lease["lease_id"] != command["lease_id"] - or lease["lease_epoch"] != command["expected_lease_epoch"] - ): - return None, { - "result": "rejected", - "reason": "stale_lease_fence", - "observed_lease_epoch": lease["lease_epoch"], - "expected_lease_epoch": command["expected_lease_epoch"], - } - return lease, None - - @staticmethod - def _holder_gate( - lease: dict[str, Any], - actor: str, - ) -> dict[str, Any] | None: - """A correct fence in the wrong hands is still not authority: only - the recorded holder may renew, release, or complete. Keeping this - precheck here also keeps the core's owner_conflicts_with_claim code - unreachable, preserving its aggregate-integrity classification.""" - - if lease["owner"] != actor: - return { - "result": "rejected", - "reason": "not_lease_holder", - "lease_owner": lease["owner"], - } - return None - - def _lease_is_active(self, lease: dict[str, Any]) -> bool: - """Expiry adjudication happens here, against the authority's own - clock and the loaded head's expires_at - never against the caller's - opinion of time (RFC section 6.4).""" - - return float(self.now()) < _parse_time(lease["expires_at"]) - - def _held_lease_context( - self, - head: dict[str, Any], - command: dict[str, Any], - actor: str, - ) -> tuple[dict[str, Any], dict[str, Any], CoordinationSnapshot] | dict[str, Any]: - """Adjudicate the opening every holder verb shares. - - Renew, release, and complete all face the same sequence: todo - prechecks, the stale-lease fence, the live holder gate, then a - snapshot carrying the authority's own liveness verdict for the core - to adjudicate against. Returns (todo, lease, snapshot) or the typed - non-apply dict. - """ - - todo, rejection = self._todo_prechecks(head, command) - if rejection is not None: - return rejection - assert todo is not None - lease, rejection = self._lease_fence(head, command) - if rejection is not None: - return rejection - assert lease is not None - rejection = self._holder_gate(lease, actor) - if rejection is not None: - return rejection - snapshot = claim_snapshot_for_todo( - head, command["todo_id"], lease_active=self._lease_is_active(lease) - ) - return todo, lease, snapshot - - @staticmethod - def _acquire_and_claim( - snapshot: CoordinationSnapshot, - actor: str, - operation_id: str, - ttl_seconds: int, - ) -> LeaseSnapshot | dict[str, Any]: - """Mint a lease, then claim under it - the shared ownership tail. - - Composition order is fixed by the Stage 1 core: the lease is minted - first, then the claim passes the hard-lease holder gate against the - freshly minted lease. Claim-first would silently bypass the - Appendix B invariant that ownership changes require the holder. - Reclaim reuses this tail unchanged, so a reclaimed lease passes the - same true holder gate as any first claim. - """ - - acquire_plan = decide( - snapshot, - LeaseAcquireCommand( - owner=actor, - idempotency_key=_lease_id(operation_id), - ttl_seconds=ttl_seconds, - ), - ) - if acquire_plan.outcome is not DecisionOutcome.APPLY: - return _classified(acquire_plan) - assert acquire_plan.next_snapshot is not None - claim_plan = decide( - acquire_plan.next_snapshot, - TodoMutationCommand( - action=TodoAction.CLAIM, - actor_agent_id=actor, - requested_claimed_by=actor, - ownership_mutation=True, - ), - ) - if claim_plan.outcome is not DecisionOutcome.APPLY: - return _classified(claim_plan) - assert claim_plan.next_snapshot is not None - core_lease = claim_plan.next_snapshot.lease - assert core_lease is not None - return core_lease - - def _next_head_for( - self, - head: dict[str, Any], - request: dict[str, Any], - request_digest: str, - *, - todo_id: str, - command_name: str, - receipt_extra: dict[str, Any], - ) -> tuple[dict[str, Any], dict[str, Any]]: - """Clone the head, advance authority/todo revisions, mint the receipt.""" - - next_head = copy.deepcopy(head) - next_head["authority_revision"] += 1 - next_todo = next_head["coordination"]["todos"][todo_id] - next_todo["todo_revision"] += 1 - receipt = { - "schema_version": RECEIPT_SCHEMA_VERSION, - "operation_id": request["operation_id"], - "request_digest": request_digest, - "command": command_name, - "actor": copy.deepcopy(request["actor"]), - "todo_id": todo_id, - "accepted_authority_revision": next_head["authority_revision"], - "accepted_todo_revision": next_todo["todo_revision"], - "applied_at": _format_time(float(self.now())), - **receipt_extra, - } - next_head["receipt_index"][request["operation_id"]] = { - "request_digest": request_digest, - "original_receipt": copy.deepcopy(receipt), - } - return next_head, receipt - - # ---- Stage 3 transitions ------------------------------------------------ - - def _renew_transition( - self, - head: dict[str, Any], - request: dict[str, Any], - request_digest: str, - ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: - command = request["command"] - actor = request["actor"]["agent_id"] - context = self._held_lease_context(head, command, actor) - if isinstance(context, dict): - return context - _todo, lease, snapshot = context - plan = decide( - snapshot, - LeaseRenewCommand( - owner=actor, - idempotency_key=command["lease_id"], - ttl_seconds=command["lease_ttl_seconds"], - expected_version=command["expected_lease_epoch"], - ), - ) - if plan.outcome is not DecisionOutcome.APPLY: - return _classified(plan) - now = float(self.now()) - expires_at = _format_time(now + command["lease_ttl_seconds"]) - next_head, receipt = self._next_head_for( - head, - request, - request_digest, - todo_id=command["todo_id"], - command_name="renew_work", - receipt_extra={ - "lease_id": command["lease_id"], - "lease_epoch": lease["lease_epoch"], - "expires_at": expires_at, - }, - ) - next_head["coordination"]["leases"][command["todo_id"]]["expires_at"] = ( - expires_at - ) - return next_head, receipt - - def _release_transition( - self, - head: dict[str, Any], - request: dict[str, Any], - request_digest: str, - ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: - command = request["command"] - actor = request["actor"]["agent_id"] - context = self._held_lease_context(head, command, actor) - if isinstance(context, dict): - return context - _todo, lease, snapshot = context - # Release is the holder giving up early, so the claim is cleared - # first while the holder gate is still real; an expired lease is - # resolved by reclaim, not release (the core rejects the clear). - clear_plan = decide( - snapshot, - TodoMutationCommand( - action=TodoAction.UPDATE, - actor_agent_id=actor, - clear_claim=True, - ownership_mutation=True, - ), - ) - if clear_plan.outcome is not DecisionOutcome.APPLY: - return _classified(clear_plan) - assert clear_plan.next_snapshot is not None - release_plan = decide( - clear_plan.next_snapshot, - LeaseReleaseCommand( - owner=actor, - idempotency_key=command["lease_id"], - expected_version=command["expected_lease_epoch"], - ), - ) - if release_plan.outcome is not DecisionOutcome.APPLY: - return _classified(release_plan) - next_head, receipt = self._next_head_for( - head, - request, - request_digest, - todo_id=command["todo_id"], - command_name="release_work", - receipt_extra={ - "lease_id": command["lease_id"], - "lease_epoch": lease["lease_epoch"], - }, - ) - next_todo = next_head["coordination"]["todos"][command["todo_id"]] - next_todo["claimed_by"] = None - # last_lease_epoch stays: the watermark is the shared aggregate's - # no-ABA terminal record, so a re-claim mints strictly above it. - del next_head["coordination"]["leases"][command["todo_id"]] - return next_head, receipt - - def _reclaim_transition( - self, - head: dict[str, Any], - request: dict[str, Any], - request_digest: str, - ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: - command = request["command"] - todo, rejection = self._todo_prechecks(head, command) - if rejection is not None: - return rejection - assert todo is not None - eligibility = todo["eligibility"] - observed = {field: eligibility[field] for field in _PRECONDITION_FIELDS} - if observed != command["expected_preconditions"]: - return { - "result": "conflict", - "reason": "precondition_snapshot_mismatch", - "expected_preconditions": copy.deepcopy( - command["expected_preconditions"] - ), - "observed_preconditions": observed, - } - if todo["claimed_by"] is None: - return {"result": "rejected", "reason": "todo_not_claimed"} - lease = head["coordination"]["leases"].get(command["todo_id"]) - if lease is None: - # An open, claimed todo always carries its lease in a valid head. - return { - "result": "failed", - "reason": "aggregate_integrity:claim_without_lease", - } - now = float(self.now()) - expired_for = now - _parse_time(lease["expires_at"]) - if expired_for < self.reclaim_grace_seconds: - return { - "result": "rejected", - "reason": "lease_not_reclaimable", - "expires_at": lease["expires_at"], - "reclaim_grace_seconds": self.reclaim_grace_seconds, - } - actor = request["actor"]["agent_id"] - # Reclaim is a standing delegation to eligible agents, adjudicated by - # the authority clock plus grace above; the core enforces everything - # else (actor registration, eligibility, the fresh holder gate). - grant = LifecycleGrant( - agent_id=actor, actions=frozenset({"reclaim"}), requires_reason=False - ) - snapshot = claim_snapshot_for_todo( - head, command["todo_id"], lease_active=False, lifecycle_grants=(grant,) - ) - clear_plan = decide( - snapshot, - TodoMutationCommand( - action=TodoAction.UPDATE, - actor_agent_id=actor, - clear_claim=True, - ownership_mutation=True, - authority_action="reclaim", - ), - ) - if clear_plan.outcome is not DecisionOutcome.APPLY: - return _classified(clear_plan) - assert clear_plan.next_snapshot is not None - core_lease = self._acquire_and_claim( - # The clock-authorized delegation applies only to clearing the - # expired claim. Acquire/claim must use ordinary holder rules, - # without carrying that ephemeral authority into a later command. - replace(clear_plan.next_snapshot, lifecycle_grants=()), - actor, - request["operation_id"], - command["lease_ttl_seconds"], - ) - if isinstance(core_lease, dict): - return core_lease - expires_at = _format_time(now + command["lease_ttl_seconds"]) - next_head, receipt = self._next_head_for( - head, - request, - request_digest, - todo_id=command["todo_id"], - command_name="reclaim_work", - receipt_extra={ - "lease_id": core_lease.idempotency_key, - "lease_epoch": core_lease.lease_epoch, - "expires_at": expires_at, - "superseded_owner": lease["owner"], - "superseded_lease_epoch": lease["lease_epoch"], - }, - ) - next_todo = next_head["coordination"]["todos"][command["todo_id"]] - next_todo["claimed_by"] = actor - next_todo["last_lease_epoch"] = core_lease.lease_epoch - next_head["coordination"]["leases"][command["todo_id"]] = { - "lease_id": core_lease.idempotency_key, - "owner": core_lease.owner, - "lease_epoch": core_lease.lease_epoch, - "expires_at": expires_at, - "write_scopes": list(core_lease.write_scopes), - } - return next_head, receipt - - def _complete_transition( - self, - head: dict[str, Any], - request: dict[str, Any], - request_digest: str, - ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: - command = request["command"] - actor = request["actor"]["agent_id"] - context = self._held_lease_context(head, command, actor) - if isinstance(context, dict): - return context - todo, lease, snapshot = context - # Ownership is adjudicated before payload semantics: a non-holder - # learns nothing about successor-id availability. - for successor in command["successor_todo_ids"]: - if successor in head["coordination"]["todos"]: - return { - "result": "rejected", - "reason": "successor_todo_exists", - "successor_todo_id": successor, - } - plan = decide( - snapshot, - TodoMutationCommand( - action=TodoAction.COMPLETE, - actor_agent_id=actor, - lease_idempotency_key=command["lease_id"], - lease_expected_version=command["expected_lease_epoch"], - ), - ) - if plan.outcome is not DecisionOutcome.APPLY: - return _classified(plan) - continuation = _continuation_for_write( - no_followup=command["no_followup"], - has_successor=bool(command["successor_todo_ids"]), - ) - next_head, receipt = self._next_head_for( - head, - request, - request_digest, - todo_id=command["todo_id"], - command_name="complete_work", - receipt_extra={ - "lease_id": command["lease_id"], - "lease_epoch": lease["lease_epoch"], - "completion_continuation": continuation, - }, - ) - next_todo = next_head["coordination"]["todos"][command["todo_id"]] - next_todo["status"] = "done" - next_todo["completion_continuation"] = continuation - if command["no_followup"]: - next_todo["no_followup"] = True - if command["successor_todo_ids"]: - next_todo["successor_todo_ids"] = list(command["successor_todo_ids"]) - if command["evidence"] is not None: - next_todo["evidence"] = copy.deepcopy(command["evidence"]) - # Completion retires the lease in the same transition, exactly like - # the local write; the watermark keeps the epoch history. - del next_head["coordination"]["leases"][command["todo_id"]] - # Successors are born open, unclaimed, revision 0, inheriting the - # parent's execution context - atomically with the completion. - for successor in command["successor_todo_ids"]: - next_head["coordination"]["todos"][successor] = { - "todo_revision": 0, - "status": "open", - "claimed_by": None, - "eligibility": copy.deepcopy(todo["eligibility"]), - "repository": todo["repository"], - "code_revision": todo["code_revision"], - "last_lease_epoch": 0, - } - return next_head, receipt - - # ---- RFC section 5 steps 1-10 ------------------------------------------- - - def apply(self, envelope: dict[str, Any]) -> dict[str, Any]: - request = self._semantic_request(envelope) - operation_id = request["operation_id"] - request_digest = _digest(request) - - head, provider_generation = self.provider.load() - if head is None: - return { - "result": "failed", - "reason": "coordination_head_uninitialized", - "provider_generation": provider_generation, - } - try: - head = validated_head(head, goal_id=self.goal_id) - except HeadMigrationRequired: - # A Stage 2 head is a classification, not a crash: nothing - # applies until the explicit migrate_head_v0_to_v1 has run. - return { - "result": "failed", - "reason": "head_schema_migration_required", - "provider_generation": provider_generation, - } - fence = self._store_binding_fence(head, provider_generation) - if fence is not None: - return fence - - transitions = { - "claim_work": self._claim_transition, - "renew_work": self._renew_transition, - "release_work": self._release_transition, - "reclaim_work": self._reclaim_transition, - "complete_work": self._complete_transition, - } - transition_for = transitions[request["command"]["type"]] - - for _attempt in range(_MAX_CAS_ATTEMPTS): - replay = self._replay(head, provider_generation, operation_id, request_digest) - if replay is not None: - return replay - transition = transition_for(head, request, request_digest) - if isinstance(transition, dict): - transition.update( - { - "observed_authority_revision": head["authority_revision"], - "provider_generation": provider_generation, - } - ) - return transition - proposed, original_receipt = transition - provider_result = self.provider.compare_and_put( - provider_generation, proposed - ) - result_kind = provider_result.get("result") - if result_kind == "applied": - return self._success( - "applied", - original_receipt, - proposed, - provider_result["provider_generation"], - ) - if result_kind not in {"conflict", "ambiguous", "failed"}: - raise HeadValidationError( - f"unknown provider result: {provider_result!r}" - ) - if result_kind == "failed": - # The verb claims the write provably never happened. That - # claim is verified, not trusted: one reload against the - # receipt index catches a provider that misreported a landed - # write as failed, at the cost of a single load. - try: - check, check_generation = self.provider.load() - if check is not None: - check = validated_head(check, goal_id=self.goal_id) - replay = self._replay( - check, check_generation, operation_id, request_digest - ) - if replay is not None: - return replay - except Exception: # noqa: BLE001 - verification is best-effort - pass - return { - "result": "failed", - "reason": "provider_failed_before_cas", - "observed_authority_revision": head["authority_revision"], - "provider_generation": provider_generation, - } - - latest, latest_generation = self.provider.load() - if latest is None: - return { - "result": "failed", - "reason": "coordination_head_missing_after_cas", - "provider_generation": latest_generation, - } - try: - latest = validated_head(latest, goal_id=self.goal_id) - except HeadMigrationRequired: - return { - "result": "failed", - "reason": "head_schema_migration_required", - "provider_generation": latest_generation, - } - fence = self._store_binding_fence(latest, latest_generation) - if fence is not None: - return fence - replay = self._replay( - latest, latest_generation, operation_id, request_digest - ) - if replay is not None: - return replay - if latest_generation == provider_generation: - # Same generation and no receipt: the ambiguous attempt - # provably did not land. Receipt absence never proves success, - # so an eventual applied requires a new successful CAS. - return { - "result": "failed", - "reason": "provider_outcome_unproved", - "observed_authority_revision": latest["authority_revision"], - "provider_generation": latest_generation, - } - head, provider_generation = latest, latest_generation - - return { - "result": "failed", - "reason": "provider_contention_exhausted", - "observed_authority_revision": head["authority_revision"], - "provider_generation": provider_generation, - } - - -def deterministic_head_bytes(head: dict[str, Any]) -> bytes: - """Canonical bytes for providers that store raw bytes (NoKV adapter).""" - - return canonical_head_bytes(head) diff --git a/loopx/control_plane/coordination/file_provider.py b/loopx/control_plane/coordination/file_provider.py deleted file mode 100644 index 29c91f0e95..0000000000 --- a/loopx/control_plane/coordination/file_provider.py +++ /dev/null @@ -1,309 +0,0 @@ -"""File-backed coordination provider: one atomic CAS document per goal. - -Storage plane only (RFC section 6.2): it serializes the opaque head it is -given, replaces the document atomically, and reports typed outcomes. It never -parses commands, mints clocks or leases, or interprets the head. Concurrency -is resolved by generation compare while holding the repository's one -cross-platform lock owner (``loopx.file_lock``) with its bounded deadline; a -lock that cannot be acquired in time is a typed ``failed`` because no write -was attempted. - -Durability is a fixed commit sequence: write the complete canonical bytes to -an exclusive temporary file (short writes are continued, never ignored), -fsync the file, atomically rename it over the document, then fsync the parent -directory so the rename itself is durable. ``applied`` is returned only after -the whole sequence converges; any storage fault inside the sequence surfaces -as ``ambiguous`` so the authority reloads and trusts only its atomically -stored receipt index. On Windows there is no directory-handle fsync; the -rename's durability there follows platform semantics and the directory step -is a no-op. -""" - -from __future__ import annotations - -import hashlib -import json -import os -import re -import uuid -from pathlib import Path -from typing import Any - -from ...file_lock import LockAcquireTimeoutError, exclusive_file_lock - - -class ProviderProtocolError(RuntimeError): - """Persisted bytes or a provider outcome violated the storage contract.""" - - -_STORE_IDENTITY_PATTERN = re.compile(r"file:[0-9a-f]{32}\Z") - - -def _canonical(envelope: dict[str, Any]) -> bytes: - # allow_nan=False: non-finite floats would produce bytes a strict JSON - # reader rejects, so they must fail before ever reaching the document. - return json.dumps( - envelope, - sort_keys=True, - separators=(",", ":"), - ensure_ascii=False, - allow_nan=False, - ).encode("utf-8") - - -def _write_all(descriptor: int, payload: bytes) -> None: - # os.write may write fewer bytes than asked; a partial write that is not - # continued would let a truncated document masquerade as applied. - view = memoryview(payload) - while view: - written = os.write(descriptor, view) - if written <= 0: - raise OSError("write made no progress") - view = view[written:] - - -# The commit-sequence steps below are module seams on purpose: fault -# injection in tests targets the provider's own document commit without -# touching the global os attributes that loopx.file_lock's holder -# bookkeeping also uses (its Windows sidecar path calls os.fsync and -# os.replace of its own). - - -def _fsync_file(descriptor: int) -> None: - os.fsync(descriptor) - - -def _replace_document(source: Path, target: Path) -> None: - os.replace(source, target) - - -def _fsync_directory(directory: Path) -> None: - # The rename only becomes durable once the parent directory entry is - # flushed. Windows exposes no directory-handle fsync; there the rename's - # durability follows platform semantics. - if os.name != "posix": # pragma: no cover - exercised on Windows hosts - return - descriptor = os.open(directory, os.O_RDONLY) - try: - os.fsync(descriptor) - finally: - os.close(descriptor) - - -def _durably_replace_bytes(target: Path, payload: bytes, temp_path: Path) -> None: - """Publish complete bytes atomically and make the directory entry durable.""" - - try: - descriptor = os.open( - temp_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644 - ) - try: - _write_all(descriptor, payload) - _fsync_file(descriptor) - finally: - os.close(descriptor) - _replace_document(temp_path, target) - _fsync_directory(target.parent) - except OSError: - try: - temp_path.unlink(missing_ok=True) - except OSError: - pass - raise - - -class FileCoordinationProvider: - """Map one goal's head document onto an atomically replaced JSON file.""" - - def __init__( - self, - directory: Path | str, - goal_id: str, - *, - lock_timeout_seconds: float | None = None, - ): - if not goal_id: - raise ProviderProtocolError("provider goal_id must be non-empty") - self.directory = Path(directory) - self.goal_id = goal_id - self._lock_timeout_seconds = lock_timeout_seconds - digest = hashlib.sha256(goal_id.encode("utf-8")).hexdigest()[:16] - self._document = self.directory / f"coordination-head-{digest}.json" - self._identity_path = self.directory / "store-identity" - self._store_identity: str | None = None - - def store_identity(self) -> str: - """The stable identity of this store lineage (Stage 3 binding fence). - - Minted once per directory under the provider's cross-process lock and - published only after a complete temp-file write, file fsync, atomic - rename, and parent-directory fsync. Every competing creator therefore - observes the same complete value. A copy of the documents into a fresh - directory yields a NEW identity, so a head bound to the original - lineage is detectable there. - """ - - if self._store_identity is not None: - return self._store_identity - try: - self.directory.mkdir(parents=True, exist_ok=True) - with exclusive_file_lock( - self._identity_path, - timeout_seconds=self._lock_timeout_seconds, - operation="coordination_store_identity", - ): - try: - raw_identity = self._identity_path.read_bytes() - except FileNotFoundError: - identity = f"file:{uuid.uuid4().hex}" - temp_path = self._identity_path.with_name( - f"{self._identity_path.name}.tmp-{os.getpid()}-{uuid.uuid4().hex}" - ) - _durably_replace_bytes( - self._identity_path, - identity.encode("ascii"), - temp_path, - ) - self._store_identity = identity - return identity - try: - identity = raw_identity.decode("ascii") - except UnicodeDecodeError as exc: - raise ProviderProtocolError( - "store identity does not match file:<32 lowercase hex>" - ) from exc - if _STORE_IDENTITY_PATTERN.fullmatch(identity) is None: - raise ProviderProtocolError( - "store identity does not match file:<32 lowercase hex>" - ) - # A previous creator may have renamed successfully but failed - # while fsyncing the directory. Retrying the read completes - # that durability sequence before the identity is trusted. - _fsync_directory(self.directory) - self._store_identity = identity - return identity - except LockAcquireTimeoutError as exc: - raise ProviderProtocolError( - f"store identity lock is unavailable: {exc}" - ) from exc - except ProviderProtocolError: - raise - except OSError as exc: - raise ProviderProtocolError( - f"store identity is unavailable: {exc}" - ) from exc - - def _read_envelope(self) -> tuple[dict[str, Any] | None, int]: - try: - raw = self._document.read_bytes() - except FileNotFoundError: - return None, 0 - try: - envelope = json.loads(raw) - except json.JSONDecodeError as exc: - raise ProviderProtocolError( - f"coordination document is not valid JSON: {exc}" - ) from exc - generation = envelope.get("provider_generation") if isinstance(envelope, dict) else None - if ( - not isinstance(envelope, dict) - or set(envelope) != {"provider_generation", "head"} - # bool is an int subclass; JSON true must not load as generation 1 - # and silently repair a corrupt envelope into a valid lineage. - or not isinstance(generation, int) - or isinstance(generation, bool) - or generation < 1 - or not isinstance(envelope["head"], dict) - ): - raise ProviderProtocolError( - "coordination document envelope does not match the v0 contract" - ) - return envelope["head"], envelope["provider_generation"] - - def load(self) -> tuple[dict[str, Any] | None, int]: - """Return ``(head | None, provider_generation)``.""" - - return self._read_envelope() - - def compare_and_put( - self, - expected_provider_generation: int, - head: dict[str, Any], - ) -> dict[str, Any]: - """Serialize and conditionally replace the opaque head document.""" - - if ( - not isinstance(expected_provider_generation, int) - or isinstance(expected_provider_generation, bool) - or expected_provider_generation < 0 - ): - return { - "result": "failed", - "error": "expected_provider_generation must be a non-negative integer", - } - try: - self.directory.mkdir(parents=True, exist_ok=True) - except OSError as exc: - # No replace was attempted, so this failure proves no write. - return {"result": "failed", "error": str(exc)} - outcome: dict[str, Any] | None = None - try: - with exclusive_file_lock( - self._document, - timeout_seconds=self._lock_timeout_seconds, - operation="coordination_compare_and_put", - ): - outcome = self._replace_under_lock( - expected_provider_generation, head - ) - except LockAcquireTimeoutError as exc: - # Bounded wait expired before any write was attempted. - return {"result": "failed", "error": str(exc)} - except ProviderProtocolError: - raise - except OSError as exc: - if outcome is not None: - # The verdict was already computed; a release-bookkeeping - # failure afterwards must not misreport a durable write. - return outcome - return {"result": "failed", "error": str(exc)} - return outcome - - def _replace_under_lock( - self, - expected_provider_generation: int, - head: dict[str, Any], - ) -> dict[str, Any]: - current_head, current_generation = self._read_envelope() - del current_head - if current_generation != expected_provider_generation: - return { - "result": "conflict", - "current_provider_generation": current_generation, - } - next_generation = expected_provider_generation + 1 - try: - envelope = _canonical( - {"provider_generation": next_generation, "head": head} - ) - except (TypeError, ValueError) as exc: - # Serialization runs before any write, so this failure proves - # no write; it must surface as the typed verb, never as an - # unclassified exception through the seam. - return { - "result": "failed", - "error": f"head is not canonically serializable: {exc}", - } - temp_path = self._document.with_suffix( - f".tmp-{os.getpid()}-{next_generation}-{uuid.uuid4().hex}" - ) - try: - _durably_replace_bytes(self._document, envelope, temp_path) - except OSError: - # The commit sequence did not provably converge: the temp write - # may or may not have been renamed, and a rename may or may not - # be durable yet. Never parse error text as commit proof — - # report ambiguous and let the authority reload its atomically - # stored receipt index. - return {"result": "ambiguous"} - return {"result": "applied", "provider_generation": next_generation} diff --git a/loopx/control_plane/coordination/goal_state_shadow.py b/loopx/control_plane/coordination/goal_state_shadow.py deleted file mode 100644 index f4f2a4572c..0000000000 --- a/loopx/control_plane/coordination/goal_state_shadow.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Bridge the legacy Markdown goal state into a coordination bootstrap head. - -This is the read side of the RFC section 11 shadow: it projects the current -``ACTIVE_GOAL_STATE.md`` text through loopx's own todo projection and admits -exactly the open, unclaimed agent todos into an explicit ``bootstrap_head``. -Nothing is written back. - -It lives apart from ``head`` deliberately: the head module is the pure v0 -document codec with a stdlib-plus-core import closure and sits inside the -repository's strict type gate, while this bridge is the one place the -coordination contract reaches into the untyped legacy projection world -(`todos.contract`, ``todos.handoff_mode``, the active-state parser). When -that world joins the typed gate, this module follows. -""" - -from __future__ import annotations - -import hashlib -import json -from typing import Any - -from ..todos.contract import normalize_todo_claimed_by, normalize_todo_id -from ..todos.handoff_mode import goal_handoff_mode -from .authority_core import HandoffMode -from .head import HeadValidationError, bootstrap_head - - -def _static_projection_digest(allowed_agent_ids: list[str]) -> str: - encoded = json.dumps(sorted(allowed_agent_ids), separators=(",", ":")) - return "sha256:" + hashlib.sha256(encoded.encode("utf-8")).hexdigest() - - -def bootstrap_head_from_goal_state( - state_text: str, - *, - goal_id: str, - repository: str, - code_revision: str, - allowed_agent_ids: list[str], - store_binding: str, - with_report: bool = False, -) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: - """Shadow the current Markdown active state into a bootstrap head. - - Claimed and done todos are reported, never silently dropped; a - ``soft_claim`` goal fails closed instead of having its declared no-lease - semantics silently inverted by shared ``claim_work``. - """ - - from ..todos.active_state_todo_parser import parse_active_state_todos - - source_mode = goal_handoff_mode(state_text) - if source_mode == HandoffMode.SOFT_CLAIM.value: - raise HeadValidationError( - "a soft_claim goal cannot bootstrap into shared authority: its" - " declared mode rejects lease minting, which shared claim_work" - " performs on every accepted claim" - ) - projected = parse_active_state_todos(state_text) - skipped: dict[str, str] = {} - todos: dict[str, Any] = {} - for item in projected["agent_todos"]["items"]: - todo_id = normalize_todo_id(item.get("todo_id")) - if not todo_id: - continue - if item.get("done"): - skipped[todo_id] = "done" - continue - if normalize_todo_claimed_by(item.get("claimed_by")): - skipped[todo_id] = "claimed" - continue - todos[todo_id] = { - "todo_revision": 0, - "status": "open", - "claimed_by": None, - "eligibility": { - "authorization_projection_revision": 0, - "authorization_projection_digest": _static_projection_digest( - allowed_agent_ids - ), - "allowed_agent_ids": list(allowed_agent_ids), - "dependencies_satisfied": True, - "dependency_revision": 0, - "gates_open": True, - "gate_revision": 0, - }, - "repository": repository, - "code_revision": code_revision, - "last_lease_epoch": 0, - } - head = bootstrap_head(goal_id, todos, store_binding=store_binding) - if not with_report: - return head - report = { - "skipped": skipped, - "source_handoff_mode": source_mode, - "admitted": sorted(todos), - } - return head, report diff --git a/loopx/control_plane/coordination/head.py b/loopx/control_plane/coordination/head.py deleted file mode 100644 index 48a684f30e..0000000000 --- a/loopx/control_plane/coordination/head.py +++ /dev/null @@ -1,831 +0,0 @@ -"""The ``loopx_coordination_head_v1`` aggregate for the shared-goal RFC. - -One goal's coordination facts, its replayable receipt index, and the -``retain_all_v0`` retention declaration form one document; a coordination -provider stores it behind an opaque generation CAS and never interprets it. -This module owns the document's shape: fail-closed validation, deterministic -canonical bytes, the adapters that project aggregate facts into the Stage 1 -core's snapshot types, and the explicit bootstrap constructors (RFC section 8). - -It performs no I/O and makes no domain decisions: transitions belong to -``authority_core.decide`` and the execution layer in ``executor``. -""" - -from __future__ import annotations - -import copy -import hashlib -import json -import re -from datetime import datetime, timedelta -from pathlib import PurePosixPath, PureWindowsPath -from typing import Any, cast - -from .authority_core import ( - CoordinationSnapshot, - HandoffMode, - LeaseSnapshot, - TodoSnapshot, -) - -HEAD_SCHEMA_VERSION = "loopx_coordination_head_v1" -# v0 predates the store-lineage binding: it is recognized, never read as -# current, and upgraded only through migrate_head_v0_to_v1. -LEGACY_HEAD_SCHEMA_V0 = "loopx_coordination_head_v0" -RECEIPT_SCHEMA_VERSION = "loopx_authority_receipt_v0" -RETAIN_ALL = {"mode": "retain_all_v0"} - -_HEAD_FIELDS = { - "schema_version", - "goal_id", - "handoff_mode", - "authority_revision", - "store_binding", - "coordination", - "receipt_index", - "receipt_retention", -} -_LEGACY_HEAD_V0_FIELDS = _HEAD_FIELDS - {"store_binding"} -_TODO_FIELDS = { - "todo_revision", - "status", - "claimed_by", - "eligibility", - "repository", - "code_revision", - "last_lease_epoch", -} -_TODO_STATUS_VALUES = {"open", "done"} -_COMPLETION_FIELDS = { - "completion_continuation", - "no_followup", - "successor_todo_ids", - "evidence", -} -_CONTINUATION_VALUES = {"active_goal", "successor", "no_followup"} -_EVIDENCE_FIELDS = {"pointer", "digest", "privacy_class"} -_EVIDENCE_PRIVACY_CLASSES = {"public", "private"} -# One provider-neutral URI shape binds privacy into the pointer itself. A -# sibling privacy_class can therefore be checked rather than merely trusted, -# while the opaque id remains portable across artifact providers. -_EVIDENCE_POINTER_PATTERN = re.compile( - r"artifact://(?Ppublic|private)/" - r"(?P[A-Za-z0-9][A-Za-z0-9._~/-]{0,511})" -) -_ELIGIBILITY_FIELDS = { - "authorization_projection_revision", - "authorization_projection_digest", - "allowed_agent_ids", - "dependencies_satisfied", - "dependency_revision", - "gates_open", - "gate_revision", -} -_ELIGIBILITY_REVISION_FIELDS = ( - "authorization_projection_revision", - "dependency_revision", - "gate_revision", -) -_LEASE_FIELDS = {"lease_id", "owner", "lease_epoch", "expires_at", "write_scopes"} -_RECEIPT_ENTRY_FIELDS = {"request_digest", "original_receipt"} -_RECEIPT_BASE_FIELDS = { - "schema_version", - "operation_id", - "request_digest", - "command", - "actor", - "todo_id", - "accepted_authority_revision", - "accepted_todo_revision", - "applied_at", -} -# Each verb persists exactly the authority proof it minted: lease verbs carry -# the fence they issued, reclaim additionally records whom it superseded, and -# completion records the continuation it accepted. -_RECEIPT_COMMAND_FIELDS = { - "claim_work": _RECEIPT_BASE_FIELDS | {"lease_id", "lease_epoch", "expires_at"}, - "renew_work": _RECEIPT_BASE_FIELDS | {"lease_id", "lease_epoch", "expires_at"}, - "release_work": _RECEIPT_BASE_FIELDS | {"lease_id", "lease_epoch"}, - "reclaim_work": _RECEIPT_BASE_FIELDS - | { - "lease_id", - "lease_epoch", - "expires_at", - "superseded_owner", - "superseded_lease_epoch", - }, - "complete_work": _RECEIPT_BASE_FIELDS - | {"lease_id", "lease_epoch", "completion_continuation"}, -} -_RECEIPT_ACTOR_FIELDS = {"agent_id", "device_id"} -_REPOSITORY_PATTERN = re.compile(r"git:[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)+") -_CODE_REVISION_PATTERN = re.compile(r"[0-9a-fA-F]{7,64}") -_REQUEST_DIGEST_PATTERN = re.compile(r"sha256:[0-9a-f]{64}") - - -class HeadValidationError(ValueError): - """The aggregate document violates the reviewed contract.""" - - -class HeadMigrationRequired(HeadValidationError): - """A legacy v0 head was read: it lacks the store-lineage binding. - - This is a classification, not corruption: the head is a valid Stage 2 - document that must pass through the explicit, operator-reviewed - ``migrate_head_v0_to_v1`` before any command applies. The binding is - never inferred automatically - a restored copy of the store would then - authorize itself, which is exactly what the binding fence exists to - prevent (RFC section 6.4). - """ - - -def canonical_head_bytes(head: dict[str, Any]) -> bytes: - """Deterministic canonical serialization of one head document. - - This is the one canonical encoding (sorted keys, minimal separators, - UTF-8 without ASCII escapes) that digests and byte-level provider parity - are defined against. Values with no faithful strict-JSON form fail closed - here: non-finite floats would serialize into bytes a strict RFC 8259 - reader rejects, so they must never become "canonical" bytes. - """ - - try: - return json.dumps( - head, - sort_keys=True, - separators=(",", ":"), - ensure_ascii=False, - allow_nan=False, - ).encode("utf-8") - except (TypeError, ValueError) as error: - raise HeadValidationError( - f"head is not canonically serializable: {error}" - ) from None - - -def head_digest(head: dict[str, Any]) -> str: - return "sha256:" + hashlib.sha256(canonical_head_bytes(head)).hexdigest() - - -def _require(condition: bool, message: str) -> None: - if not condition: - raise HeadValidationError(message) - - -def evidence_contract_violation(evidence: Any) -> str | None: - """Why ``evidence`` violates the portable contract, or ``None``. - - The pointer must use the provider-neutral - ``artifact:///`` shape: never a host filesystem - location, provider URL, query, or credential-bearing URI. Its privacy - namespace must match the sibling closed enum. One validator serves both - the command boundary and head validation so the two can never drift. - """ - - if not isinstance(evidence, dict) or set(evidence) != _EVIDENCE_FIELDS: - return "evidence fields do not match the portable contract" - pointer = evidence["pointer"] - pointer_match = ( - _EVIDENCE_POINTER_PATTERN.fullmatch(pointer) - if isinstance(pointer, str) - else None - ) - if pointer_match is None: - return ( - "evidence pointer must use " - "artifact:///" - ) - artifact_id = pointer_match.group("artifact_id") - if any(part in {"", ".", ".."} for part in artifact_id.split("/")): - return "evidence artifact id must not contain empty or traversal segments" - if not isinstance(evidence["digest"], str) or not _is_request_digest( - evidence["digest"] - ): - return "evidence digest must be a sha256 content digest" - privacy_class = evidence["privacy_class"] - if ( - not isinstance(privacy_class, str) - or privacy_class not in _EVIDENCE_PRIVACY_CLASSES - ): - return "evidence privacy_class must be one of " + "|".join( - sorted(_EVIDENCE_PRIVACY_CLASSES) - ) - if pointer_match.group("privacy_class") != privacy_class: - return "evidence pointer privacy namespace does not match privacy_class" - return None - - -def _validated_legacy_head_v0(head: dict[str, Any], *, goal_id: str) -> None: - """Prove a document is valid Stage 2 v0 before classifying migration. - - A schema token alone is not migration evidence. Project the exact v0 - field set into a validation-only v1 copy and reuse the complete validator - for every shared field. No binding is inferred or returned. - """ - - _require( - set(head) == _LEGACY_HEAD_V0_FIELDS, - "legacy coordination head fields do not match v0", - ) - _require( - head.get("schema_version") == LEGACY_HEAD_SCHEMA_V0 - and head.get("goal_id") == goal_id, - "legacy coordination head identity mismatch", - ) - coordination = head.get("coordination") - if not isinstance(coordination, dict): - raise HeadValidationError("legacy coordination head todos are invalid") - todos = coordination.get("todos") - if not isinstance(todos, dict): - raise HeadValidationError("legacy coordination head todos are invalid") - for todo_id, todo in todos.items(): - _require( - isinstance(todo, dict) and set(todo) == _TODO_FIELDS, - f"legacy head todo {todo_id!r} fields do not match v0", - ) - # Stage 2 bootstrap and its only command, claim_work, emitted open - # records. The old validator forgot to close the status vocabulary; - # migration must not grant v1 authority to an untyped legacy value. - _require( - todo.get("status") == "open", - f"legacy head todo {todo_id!r} status is not safely migratable", - ) - receipt_index = head.get("receipt_index") - if not isinstance(receipt_index, dict): - raise HeadValidationError( - "legacy coordination head receipt_index is invalid" - ) - for operation_id, entry in receipt_index.items(): - receipt = entry.get("original_receipt") if isinstance(entry, dict) else None - _require( - isinstance(receipt, dict) - and receipt.get("command") == "claim_work" - and set(receipt) == _RECEIPT_COMMAND_FIELDS["claim_work"], - f"legacy receipt {operation_id!r} is outside the Stage 2 slice", - ) - validation_copy = copy.deepcopy(head) - validation_copy["schema_version"] = HEAD_SCHEMA_VERSION - validation_copy["store_binding"] = "validation-only:legacy-v0" - validated_head(validation_copy, goal_id=goal_id) - _validated_legacy_claim_history(validation_copy) - - -def _is_count(value: Any, *, minimum: int = 0) -> bool: - # bool is an int subclass; a True epoch would silently mint True+1. - return isinstance(value, int) and not isinstance(value, bool) and value >= minimum - - -def _is_timestamp(value: Any) -> bool: - # The executor parses these fields unconditionally (expiry decisions, - # authorization status); an unparseable persisted timestamp must fail - # closed here instead of escaping as a bare ValueError later. The value - # must also be timezone-aware UTC: a naive timestamp is interpreted in - # the executing host's local timezone, so the same persisted bytes would - # read as active on one endpoint and expired on another, and v0 mints - # UTC only, so UTC is also what it accepts. - if not isinstance(value, str) or not value: - return False - try: - parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) - except ValueError: - return False - return parsed.tzinfo is not None and parsed.utcoffset() == timedelta(0) - - -def _is_request_digest(value: Any) -> bool: - return isinstance(value, str) and _REQUEST_DIGEST_PATTERN.fullmatch(value) is not None - - -def _validated_todo(todo_id: str, source: Any) -> dict[str, Any]: - _require( - isinstance(todo_id, str) and bool(todo_id), - "head todo ids must be non-empty strings", - ) - _require(isinstance(source, dict), f"head todo {todo_id!r} must be an object") - status = source.get("status") - _require( - status in _TODO_STATUS_VALUES, - f"head todo {todo_id!r} status must be one of {sorted(_TODO_STATUS_VALUES)}", - ) - if status == "done": - _require( - _TODO_FIELDS <= set(source) - and set(source) <= (_TODO_FIELDS | _COMPLETION_FIELDS) - and "completion_continuation" in source, - f"head todo {todo_id!r} fields do not match v0", - ) - else: - _require( - set(source) == _TODO_FIELDS, - f"head todo {todo_id!r} fields do not match v0", - ) - _require( - _is_count(source["todo_revision"]), - f"head todo {todo_id!r} todo_revision must be a non-negative integer", - ) - _require( - _is_count(source["last_lease_epoch"]), - f"head todo {todo_id!r} last_lease_epoch must be a non-negative integer", - ) - repository = source["repository"] - _require( - isinstance(repository, str) - and bool(repository) - and not repository.startswith("file://") - and not PurePosixPath(repository).is_absolute() - and not PureWindowsPath(repository).is_absolute() - and _REPOSITORY_PATTERN.fullmatch(repository) is not None, - f"head todo {todo_id!r} repository is not portable", - ) - code_revision = source["code_revision"] - _require( - isinstance(code_revision, str) - and _CODE_REVISION_PATTERN.fullmatch(code_revision) is not None, - f"head todo {todo_id!r} code_revision is invalid", - ) - eligibility = source["eligibility"] - _require( - isinstance(eligibility, dict) and set(eligibility) == _ELIGIBILITY_FIELDS, - f"head todo {todo_id!r} eligibility fields do not match v0", - ) - for field in _ELIGIBILITY_REVISION_FIELDS: - _require( - _is_count(eligibility[field]), - f"head todo {todo_id!r} eligibility revisions are invalid", - ) - digest = eligibility["authorization_projection_digest"] - _require( - isinstance(digest, str) and digest.startswith("sha256:"), - f"head todo {todo_id!r} authorization digest is invalid", - ) - allowed = eligibility["allowed_agent_ids"] - _require( - isinstance(allowed, list) - and all(isinstance(agent, str) and agent for agent in allowed), - f"head todo {todo_id!r} allowed_agent_ids are invalid", - ) - _require( - isinstance(eligibility["dependencies_satisfied"], bool) - and isinstance(eligibility["gates_open"], bool), - f"head todo {todo_id!r} eligibility decisions are invalid", - ) - if status == "done": - _validated_completion(todo_id, source) - return cast(dict[str, Any], source) - - -def _validated_completion(todo_id: str, source: dict[str, Any]) -> None: - """Durably-done records must satisfy the same fail-closed continuation - rules as the local durable-completion projection seam: an explicit - continuation always, never both no_followup and successors, and the - continuation must match the recorded fields.""" - - continuation = source["completion_continuation"] - _require( - continuation in _CONTINUATION_VALUES, - f"head todo {todo_id!r} completion_continuation is invalid", - ) - no_followup = source.get("no_followup") - successors = source.get("successor_todo_ids") - _require( - no_followup is None or no_followup is True, - f"head todo {todo_id!r} no_followup must be true when present", - ) - _require( - not (no_followup and successors), - f"head todo {todo_id!r} records both no_followup and successor_todo_ids", - ) - if successors is not None: - _require( - isinstance(successors, list) - and bool(successors) - and len(set(successors)) == len(successors) - and all(isinstance(item, str) and item for item in successors), - f"head todo {todo_id!r} successor_todo_ids are invalid", - ) - expected = ( - "no_followup" - if no_followup - else ("successor" if successors else "active_goal") - ) - _require( - continuation == expected, - f"head todo {todo_id!r} completion_continuation contradicts its fields", - ) - evidence = source.get("evidence") - if evidence is not None: - violation = evidence_contract_violation(evidence) - _require(violation is None, f"head todo {todo_id!r}: {violation}") - - -def _validated_receipt_entry( - operation_id: Any, - entry: Any, - todos: dict[str, Any], -) -> None: - """Fail closed unless one receipt-index entry is a complete v0 record. - - The executor dereferences these fields unconditionally on the replay and - success paths, so this is part of the trust boundary persisted state must - cross before any of it is consumed. - """ - - _require( - isinstance(operation_id, str) and bool(operation_id), - "receipt index keys must be non-empty operation ids", - ) - _require( - isinstance(entry, dict) and set(entry) == _RECEIPT_ENTRY_FIELDS, - f"receipt entry {operation_id!r} fields do not match v0", - ) - _require( - _is_request_digest(entry["request_digest"]), - f"receipt entry {operation_id!r} request_digest is invalid", - ) - receipt = entry["original_receipt"] - _require(isinstance(receipt, dict), f"receipt {operation_id!r} must be an object") - command = receipt.get("command") - _require( - command in _RECEIPT_COMMAND_FIELDS, - f"receipt {operation_id!r} command is outside the v0 slice", - ) - _require( - set(receipt) == _RECEIPT_COMMAND_FIELDS[command], - f"receipt {operation_id!r} fields do not match v0", - ) - _require( - receipt["schema_version"] == RECEIPT_SCHEMA_VERSION, - f"receipt {operation_id!r} schema_version is invalid", - ) - _require( - receipt["operation_id"] == operation_id, - f"receipt {operation_id!r} does not record its own operation id", - ) - _require( - receipt["request_digest"] == entry["request_digest"], - f"receipt {operation_id!r} digest disagrees with its index entry", - ) - - actor = receipt["actor"] - _require( - isinstance(actor, dict) - and set(actor) == _RECEIPT_ACTOR_FIELDS - and all(isinstance(actor[key], str) and actor[key] for key in _RECEIPT_ACTOR_FIELDS), - f"receipt {operation_id!r} actor identity is invalid", - ) - _require( - isinstance(receipt["todo_id"], str) and receipt["todo_id"] in todos, - f"receipt {operation_id!r} names a todo the head does not carry", - ) - _require( - _is_count(receipt["accepted_authority_revision"], minimum=1) - and _is_count(receipt["accepted_todo_revision"], minimum=1), - f"receipt {operation_id!r} accepted revisions are invalid", - ) - _require( - isinstance(receipt["lease_id"], str) and bool(receipt["lease_id"]), - f"receipt {operation_id!r} lease_id is invalid", - ) - _require( - _is_count(receipt["lease_epoch"], minimum=1), - f"receipt {operation_id!r} lease_epoch must be a positive integer", - ) - _require( - _is_timestamp(receipt["applied_at"]), - f"receipt {operation_id!r} timestamps are invalid", - ) - if "expires_at" in receipt: - _require( - _is_timestamp(receipt["expires_at"]), - f"receipt {operation_id!r} timestamps are invalid", - ) - if "superseded_owner" in receipt: - _require( - isinstance(receipt["superseded_owner"], str) - and bool(receipt["superseded_owner"]) - and _is_count(receipt["superseded_lease_epoch"], minimum=1), - f"receipt {operation_id!r} superseded lease facts are invalid", - ) - if "completion_continuation" in receipt: - _require( - receipt["completion_continuation"] in _CONTINUATION_VALUES, - f"receipt {operation_id!r} completion_continuation is invalid", - ) - - -def _validated_claim_lease_relationships( - todos: dict[str, Any], - leases: dict[str, Any], -) -> None: - """Bind every open claim to the one live lease that authorizes it.""" - - for todo_id, todo in todos.items(): - if todo["status"] != "open": - # Completion deliberately retains claimed_by as attribution while - # retiring the lease. The done/lease exclusion is checked above. - continue - lease = leases.get(todo_id) - claimed_by = todo["claimed_by"] - if claimed_by is None: - _require( - lease is None, - f"unclaimed todo {todo_id!r} cannot carry an active lease", - ) - continue - if not isinstance(lease, dict): - raise HeadValidationError( - f"claimed todo {todo_id!r} must carry an active lease" - ) - _require( - lease["owner"] == claimed_by, - f"lease {todo_id!r} owner does not match claimed_by", - ) - _require( - lease["lease_epoch"] == todo["last_lease_epoch"], - f"lease {todo_id!r} lease_epoch does not match the todo watermark", - ) - - -def _validated_legacy_claim_history(head: dict[str, Any]) -> None: - """Prove that a v0 live claim is exactly the Stage 2 writer's output. - - Stage 2 could only bootstrap and apply ``claim_work``. With retain-all - receipts, a claimed todo therefore has exactly one live claim receipt and - no authority revision can be missing. This check prevents migration from - granting v1 authority to a partially edited or provider-corrupted v0 head. - """ - - coordination = cast(dict[str, Any], head["coordination"]) - todos = cast(dict[str, Any], coordination["todos"]) - leases = cast(dict[str, Any], coordination["leases"]) - receipt_index = cast(dict[str, Any], head["receipt_index"]) - receipts = [ - cast(dict[str, Any], entry["original_receipt"]) - for entry in receipt_index.values() - ] - accepted_revisions = sorted( - receipt["accepted_authority_revision"] for receipt in receipts - ) - _require( - accepted_revisions == list(range(1, head["authority_revision"] + 1)), - "legacy authority receipt sequence does not reconstruct the head", - ) - - claimed_todo_ids = { - todo_id for todo_id, todo in todos.items() if todo["claimed_by"] is not None - } - receipt_todo_ids = [receipt["todo_id"] for receipt in receipts] - _require( - len(receipt_todo_ids) == len(set(receipt_todo_ids)) - and set(receipt_todo_ids) == claimed_todo_ids, - "legacy claim receipts do not match the live claimed todos", - ) - for receipt in receipts: - todo_id = receipt["todo_id"] - todo = cast(dict[str, Any], todos[todo_id]) - lease = cast(dict[str, Any], leases[todo_id]) - _require( - receipt["actor"]["agent_id"] - == todo["claimed_by"] - == lease["owner"] - and receipt["accepted_todo_revision"] == todo["todo_revision"] - and receipt["lease_id"] == lease["lease_id"] - and receipt["lease_epoch"] - == todo["last_lease_epoch"] - == lease["lease_epoch"] - and receipt["expires_at"] == lease["expires_at"], - f"legacy claim receipt for {todo_id!r} does not prove the live claim", - ) - - -def validated_head(head: Any, *, goal_id: str) -> dict[str, Any]: - """Fail closed unless ``head`` is a complete v1 aggregate for ``goal_id``.""" - - _require(isinstance(head, dict), "coordination head must be an object") - if head.get("schema_version") == LEGACY_HEAD_SCHEMA_V0: - # Only a complete Stage 2 document receives the migration class; - # malformed input remains an ordinary validation failure. - _validated_legacy_head_v0(head, goal_id=goal_id) - raise HeadMigrationRequired( - "coordination head is a legacy loopx_coordination_head_v0 " - "document: an explicit store-binding migration is required" - ) - _require(set(head) == _HEAD_FIELDS, "coordination head fields do not match v1") - _require( - head["schema_version"] == HEAD_SCHEMA_VERSION and head["goal_id"] == goal_id, - "coordination head identity mismatch", - ) - _require( - head["handoff_mode"] == HandoffMode.HARD_LEASE.value, - "v0 shared authority is defined only for the hard_lease handoff mode", - ) - _require( - _is_count(head["authority_revision"]), - "authority_revision must be a non-negative integer", - ) - _require( - isinstance(head["store_binding"], str) and bool(head["store_binding"]), - "store_binding must be the provider-issued store identity", - ) - coordination = head["coordination"] - _require( - isinstance(coordination, dict) and set(coordination) == {"todos", "leases"}, - "coordination must contain todos and leases", - ) - _require( - isinstance(coordination["todos"], dict) - and isinstance(coordination["leases"], dict), - "coordination must contain todos and leases objects", - ) - for todo_id, todo in coordination["todos"].items(): - _validated_todo(todo_id, todo) - for todo_id, todo in coordination["todos"].items(): - for successor in todo.get("successor_todo_ids") or (): - _require( - successor in coordination["todos"], - f"head todo {todo_id!r} declares missing successor {successor!r}", - ) - for todo_id, lease in coordination["leases"].items(): - _require( - todo_id in coordination["todos"], - f"lease {todo_id!r} has no todo in the head", - ) - _require( - coordination["todos"][todo_id]["status"] == "open", - f"lease {todo_id!r} attached to a durably done todo", - ) - _require( - isinstance(lease, dict) and set(lease) == _LEASE_FIELDS, - f"lease {todo_id!r} fields do not match v0", - ) - _require( - isinstance(lease["lease_id"], str) - and bool(lease["lease_id"]) - and isinstance(lease["owner"], str) - and bool(lease["owner"]), - f"lease {todo_id!r} identity fields must be non-empty strings", - ) - _require( - _is_count(lease["lease_epoch"], minimum=1), - f"lease {todo_id!r} lease_epoch must be a positive integer", - ) - _require( - _is_timestamp(lease["expires_at"]), - f"lease {todo_id!r} expires_at must be a parseable timestamp", - ) - _require( - lease["write_scopes"] == [], - f"lease {todo_id!r} write_scopes must be empty in v0", - ) - _validated_claim_lease_relationships( - coordination["todos"], - coordination["leases"], - ) - _require(isinstance(head["receipt_index"], dict), "receipt_index must be an object") - for operation_id, entry in head["receipt_index"].items(): - _validated_receipt_entry(operation_id, entry, coordination["todos"]) - _require(head["receipt_retention"] == RETAIN_ALL, "v0 requires retain_all_v0") - return cast(dict[str, Any], head) - - -def bootstrap_head( - goal_id: str, - todos: dict[str, Any], - *, - store_binding: str, -) -> dict[str, Any]: - """Build the explicit migration head from already-existing open todos. - - ``store_binding`` is the provider-issued store identity - (``provider.store_identity()``): the head is permanently bound to the - store lineage it was bootstrapped into, so a restore into a different - lineage is detectable before any write (the Stage 3 binding fence). - """ - - _require( - isinstance(goal_id, str) and bool(goal_id), - "bootstrap goal_id must be a non-empty string", - ) - _require( - isinstance(store_binding, str) and bool(store_binding), - "bootstrap store_binding must be the provider-issued store identity", - ) - _require(isinstance(todos, dict), "bootstrap todos must be an object") - normalized: dict[str, Any] = {} - for todo_id, source in todos.items(): - todo = _validated_todo(todo_id, source) - _require( - todo["status"] == "open" and todo["claimed_by"] is None, - f"bootstrap todo {todo_id!r} must be open and unclaimed", - ) - normalized[todo_id] = { - "todo_revision": todo["todo_revision"], - "status": "open", - "claimed_by": None, - "eligibility": { - field: copy.deepcopy(todo["eligibility"][field]) - for field in sorted(_ELIGIBILITY_FIELDS) - }, - "repository": todo["repository"], - "code_revision": todo["code_revision"], - "last_lease_epoch": todo["last_lease_epoch"], - } - return { - "schema_version": HEAD_SCHEMA_VERSION, - "goal_id": goal_id, - # The shared head owns the mode once a goal migrates (Appendix B); - # v0 defines shared coordination only for hard_lease, and recording - # it in the CAS'd document keeps every acceptance auditable against - # the mode that authorized it. - "handoff_mode": HandoffMode.HARD_LEASE.value, - "authority_revision": 0, - "store_binding": store_binding, - "coordination": {"todos": normalized, "leases": {}}, - "receipt_index": {}, - "receipt_retention": copy.deepcopy(RETAIN_ALL), - } - - -def migrate_head_v0_to_v1( - head: Any, - *, - goal_id: str, - store_binding: str, -) -> dict[str, Any]: - """Explicitly upgrade a legacy v0 head by attesting its store lineage. - - ``store_binding`` is an operator attestation that the store this head - lives in IS its authoritative lineage - typically the operator calls - ``provider.store_identity()`` on the store they have reviewed and passes - the result here, then writes the migrated head back through the same - provider CAS. The binding is deliberately a required argument and never - read from the loading provider: an automatic binding would let any - restored copy of a v0 store authorize itself as the live lineage, which - is the exact capture the binding fence exists to prevent. - """ - - _require(isinstance(head, dict), "coordination head must be an object") - _validated_legacy_head_v0(head, goal_id=goal_id) - migrated = copy.deepcopy(head) - migrated["schema_version"] = HEAD_SCHEMA_VERSION - migrated["store_binding"] = store_binding - # Full v1 validation guards the rest of the document (including the - # binding shape); migration adds authority to nothing else. - return validated_head(migrated, goal_id=goal_id) - - -def claim_snapshot_for_todo( - head: dict[str, Any], - todo_id: str, - *, - lease_active: bool = False, - lifecycle_grants: tuple[Any, ...] = (), -) -> CoordinationSnapshot: - """Project one todo's aggregate facts into the Stage 1 core snapshot. - - The aggregate's ``allowed_agent_ids`` play the registered-agent role for - the core's actor and owner checks; dependency/gate booleans and every - revision stay execution-layer preconditions because the core owns neither - (Stage 1 boundary). ``handoff_mode`` comes from the head itself, where it - is revision-covered by the aggregate CAS; v0 validation pins it to - hard_lease, under which a claim and its lease travel together and the - core's holder gate is a real invariant, not a vacuous branch. When the - todo carries no lease the snapshot holds a non-present tombstone at the - todo's ``last_lease_epoch`` so the core mints the next epoch monotonically - (no-ABA); ``lease_active`` is the executor's clock decision, never - computed here. - """ - - todo = head["coordination"]["todos"].get(todo_id) - _require(todo is not None, f"head has no todo {todo_id!r}") - lease = head["coordination"]["leases"].get(todo_id) - if lease is not None: - lease_snapshot = LeaseSnapshot( - present=True, - active=lease_active, - status="active" if lease_active else "expired", - owner=lease["owner"], - idempotency_key=lease["lease_id"], - version=lease["lease_epoch"], - lease_epoch=lease["lease_epoch"], - write_scopes=tuple(lease["write_scopes"]), - ) - else: - lease_snapshot = LeaseSnapshot( - present=False, - active=False, - version=todo["last_lease_epoch"], - lease_epoch=todo["last_lease_epoch"], - ) - return CoordinationSnapshot( - handoff_mode=HandoffMode(head["handoff_mode"]), - registered_agents=tuple(todo["eligibility"]["allowed_agent_ids"]), - todo=TodoSnapshot( - todo_id=todo_id, - status=todo["status"], - role="agent", - claimed_by=todo["claimed_by"], - ), - lease=lease_snapshot, - lifecycle_grants=lifecycle_grants, - ) diff --git a/loopx/control_plane/testing/authority_e2e_ladder.py b/loopx/control_plane/testing/authority_e2e_ladder.py index a2416b3d26..6be52bd15a 100644 --- a/loopx/control_plane/testing/authority_e2e_ladder.py +++ b/loopx/control_plane/testing/authority_e2e_ladder.py @@ -76,7 +76,6 @@ "deterministic", "env:postgresql", "env:nokv_authority", - "env:nokv_legacy", ) ROW_STATUSES: tuple[str, ...] = ("pass", "fail", "unverified") EXIT_POLICY_RULE = ( @@ -85,18 +84,6 @@ ) POSTGRES_URL_VARIABLE = "LOOPX_TEST_POSTGRES_URL" -NOKV_LIVE_FLAG = "NOKV_COORDINATION_LIVE" -NOKV_STACK_VARIABLES: tuple[str, ...] = ( - "NOKV_ETCD", - "NOKV_ETCD_PREFIX", - "NOKV_ROOT_ID", - "NOKV_BUCKET", - "NOKV_OBJECT_ENDPOINT", - "NOKV_OBJECT_ROOT", - "NOKV_OBJECT_KEY", - "NOKV_OBJECT_SECRET", -) -NOKV_SECRET_VARIABLES: tuple[str, ...] = ("NOKV_OBJECT_KEY", "NOKV_OBJECT_SECRET") # Stage 2A qualification inputs: the probe writes durable test data into an # existing workbench, so it needs an explicit opt-in flag plus the ignored # client configuration file, the Python executable that resolves the qualified @@ -110,20 +97,21 @@ NOKV_AUTHORITY_PYTHON_VARIABLE, NOKV_AUTHORITY_WORKBENCH_VARIABLE, ) -LIVE_OPT_IN_FLAGS: tuple[str, ...] = (NOKV_LIVE_FLAG, NOKV_AUTHORITY_LIVE_FLAG) +LIVE_OPT_IN_FLAGS: tuple[str, ...] = (NOKV_AUTHORITY_LIVE_FLAG,) GATE_REQUIREMENTS: dict[str, tuple[str, ...]] = { "deterministic": (), "env:postgresql": (POSTGRES_URL_VARIABLE,), "env:nokv_authority": (NOKV_AUTHORITY_LIVE_FLAG, *NOKV_AUTHORITY_VARIABLES), - "env:nokv_legacy": (NOKV_LIVE_FLAG, *NOKV_STACK_VARIABLES), } GATE_UNVERIFIED_REASON: dict[str, str] = { "env:postgresql": "postgres_url_missing", "env:nokv_authority": "nokv_authority_env_missing", - "env:nokv_legacy": "nokv_live_env_missing", } -LIVE_E2E_SCRIPT = Path("examples") / "nokv-shadow-provider" / "live_e2e.py" +LOCAL_CONFORMANCE_TESTS = { + "file": Path("tests/control_plane_ts/authority_store.test.ts"), + "sqlite": Path("tests/control_plane_ts/sqlite_authority_store.test.ts"), +} PG_INTEGRATION_TEST = ( Path("tests") / "control_plane_ts" / "postgresql_authority_store.integration.test.ts" ) @@ -140,7 +128,8 @@ "stale_incarnation_fence_left_generation_unchanged", ) PROBE_SOURCES: tuple[Path, ...] = ( - LIVE_E2E_SCRIPT, + *LOCAL_CONFORMANCE_TESTS.values(), + Path("tests/control_plane_ts/authority_store_conformance.ts"), TS_READBACK_PROBE, PG_INTEGRATION_TEST, NOKV_QUALIFICATION_SCRIPT, @@ -151,21 +140,6 @@ Path("loopx") / "control_plane" / "testing" / "authority_e2e_rows_stage2c.py", Path("loopx") / "control_plane" / "testing" / "authority_e2e_rows_stage2c2.py", ) -FILE_MATRIX_ROWS: tuple[str, ...] = ( - "same_todo_one_winner", - "independent_todo_applies", - "replay_returns_original_receipt", - "identity_mismatch_rejected", - "stale_revision_conflicts", - "lost_response_recovers_receipt", - "receipts_retained", - "authority_revision_advanced_twice", - "renew_extends_the_active_lease", - "expired_lease_reclaimed_with_new_epoch", - "superseded_executor_cannot_write_back", - "complete_creates_claimable_successor_atomically", -) -NOKV_ONLY_MATRIX_ROW = "restored_lineage_fails_closed" MINIMUM_POSTGRES_TAP_PASSES = 9 @dataclass(frozen=True) class LadderRow: @@ -233,73 +207,38 @@ def as_dict(self) -> JsonObject: } -def _run_live_matrix_script(environ: Mapping[str, str], *, live: bool) -> JsonObject: - env = dict(environ) - env["PYTHONPATH"] = str(REPO_ROOT) - if not live: - env.pop(NOKV_LIVE_FLAG, None) - completed = subprocess.run( - [sys.executable, str(REPO_ROOT / LIVE_E2E_SCRIPT)], - cwd=REPO_ROOT, - env=env, - capture_output=True, - text=True, encoding="utf-8", errors="replace", - timeout=900, - check=False, - ) - matrix = parse_json_object(completed.stdout) - matrix["_exit_code"] = completed.returncode - return matrix - - -def _matrix_rows(matrix: Mapping[str, object], key: str) -> dict[str, object]: - rows = matrix.get(key) - expect(isinstance(rows, dict), f"live matrix must report {key}") - assert isinstance(rows, dict) - return {str(name): value for name, value in rows.items()} - - -def _false_rows(rows: Mapping[str, object]) -> list[str]: - return sorted(name for name, value in rows.items() if value is not True) - - # --------------------------------------------------------------------------- -# Stage 0: recoverable reference foundation (store_direct) +# Stage 0: current native local providers, not the retired Python head prototype # --------------------------------------------------------------------------- -def _row_file_matrix_twelve_rows(context: RowContext) -> RowOutcome: - matrix = _run_live_matrix_script(context.environ, live=False) - file_rows = _matrix_rows(matrix, "file_provider") - expect( - set(file_rows) == set(FILE_MATRIX_ROWS), - "file provider matrix must contain exactly the twelve known rows", - ) - expect(not _false_rows(file_rows), "every file provider matrix row must be true") - expect(matrix["_exit_code"] == 0, "live matrix script must exit 0 without a stack") - return passed(matrix_rows=len(file_rows), script_exit_code=matrix["_exit_code"]) - - -def _row_nokv_live_matrix(context: RowContext) -> RowOutcome: - matrix = _run_live_matrix_script(context.environ, live=True) - nokv_rows = _matrix_rows(matrix, "nokv_provider") - if "unverified" in nokv_rows: - reason = str(nokv_rows["unverified"]) - code = "nokv_sdk_missing" if "SDK" in reason else "nokv_matrix_unverified" - return unverified(code) - expected = {*FILE_MATRIX_ROWS, NOKV_ONLY_MATRIX_ROW} - expect(set(nokv_rows) == expected, "NoKV matrix must contain the shared rows plus the lineage row") - expect(not _false_rows(nokv_rows), "every NoKV matrix row must be true") - parity = _matrix_rows(matrix, "file_nokv_parity") - expect(parity.get("identical_row_outcomes") is True, "file and NoKV rows must be identical") - expect(parity.get("rows") == len(FILE_MATRIX_ROWS), "parity must cover the twelve shared rows") - expect(matrix["_exit_code"] == 0, "live matrix script must exit 0") - return passed( - nokv_rows=len(nokv_rows), - parity_rows=parity.get("rows"), - restored_lineage_fails_closed=True, - script_exit_code=matrix["_exit_code"], +def _row_local_conformance(context: RowContext, provider: str) -> RowOutcome: + node = node_executable() + if node is None: + return unverified("node_missing") + suite = LOCAL_CONFORMANCE_TESTS[provider] + if not (REPO_ROOT / suite).is_file(): + return unverified("local_conformance_suite_missing") + summary = tap_summary( + [node, "--no-warnings", "--experimental-sqlite", "--experimental-strip-types", + "--test", "--test-reporter=tap", str(suite)], + env=context.environ, + timeout=900, ) + # A successful process alone is insufficient: a missing/truncated trailer, + # empty selection, cancellation, TODO or skipped test is not qualification. + expect(summary.returncode == 0, + f"native {provider} conformance exited {summary.returncode}; " + f"TAP tests={summary.tests}, pass={summary.passed}, " + f"fail={summary.failed}, skipped={summary.skipped}") + expect(summary.failed == 0, "native local conformance must report zero failures") + expect(summary.skipped == 0, "native local conformance must not skip tests") + expect(summary.tests is not None and summary.tests > 0 + and summary.passed == summary.tests, + "native local conformance must execute and pass every selected test") + return passed(provider=provider, suite=suite.as_posix(), + tap_tests=summary.tests, tap_pass=summary.passed, + tap_fail=summary.failed, tap_skipped=summary.skipped) # --------------------------------------------------------------------------- @@ -541,22 +480,22 @@ def _row_postgresql_conformance_live(context: RowContext) -> RowOutcome: LADDER_ROWS: tuple[LadderRow, ...] = ( LadderRow( - id="s0.file_matrix_twelve_rows", + id="s0.native_file_conformance", stage="0", - title="Twelve shared lifecycle scenarios pass on the file coordination provider", + title="Current TS FileAuthorityStore passes its complete native conformance suite", product_path="store_direct", gate="deterministic", posix_only=False, - run=_row_file_matrix_twelve_rows, + run=lambda context: _row_local_conformance(context, "file"), ), LadderRow( - id="s0.nokv_live_matrix", + id="s0.native_sqlite_conformance", stage="0", - title="The same matrix passes on a live NoKV stack with identical outcomes", + title="Current TS SqliteAuthorityStore passes its complete native conformance suite", product_path="store_direct", - gate="env:nokv_legacy", + gate="deterministic", posix_only=False, - run=_row_nokv_live_matrix, + run=lambda context: _row_local_conformance(context, "sqlite"), ), LadderRow( id="s1.cli_document_decodes_through_ts_store", @@ -741,7 +680,7 @@ def default_forbidden_tokens(roots: Iterable[Path], environ: Mapping[str, str]) tokens.update({str(temp_root), str(temp_root.resolve())}) tokens.add(environ.get("HOME") or str(Path.home())) tokens.update({str(REPO_ROOT), str(REPO_ROOT.resolve())}) - for name in (POSTGRES_URL_VARIABLE, *NOKV_STACK_VARIABLES, *NOKV_AUTHORITY_VARIABLES): + for name in (POSTGRES_URL_VARIABLE, *NOKV_AUTHORITY_VARIABLES): value = environ.get(name) if value: tokens.add(value) @@ -876,14 +815,7 @@ def _nokv_client_config_digest(environ: Mapping[str, str]) -> str | None: return _nokv_authority_config_sha256(config_path) except (OSError, ValueError): return None - public = { - name: environ[name] - for name in NOKV_STACK_VARIABLES - if name not in NOKV_SECRET_VARIABLES and environ.get(name) - } - if len(public) != len(NOKV_STACK_VARIABLES) - len(NOKV_SECRET_VARIABLES): - return None - return sha256_hex(json.dumps(public, sort_keys=True, separators=(",", ":"))) + return None def _nokv_sdk_version() -> str | None: @@ -1144,7 +1076,6 @@ def main(argv: Sequence[str] | None = None) -> int: __all__ = [ "EXIT_POLICY_RULE", - "FILE_MATRIX_ROWS", "GATES", "GATE_REQUIREMENTS", "LADDER_ROWS", @@ -1154,8 +1085,6 @@ def main(argv: Sequence[str] | None = None) -> int: "NOKV_AUTHORITY_PYTHON_VARIABLE", "NOKV_AUTHORITY_VARIABLES", "NOKV_AUTHORITY_WORKBENCH_VARIABLE", - "NOKV_LIVE_FLAG", - "NOKV_STACK_VARIABLES", "PENDING_ROWS", "POSTGRES_URL_VARIABLE", "PRODUCT_PATHS", diff --git a/loopx/visible_governance.py b/loopx/visible_governance.py index afcc9de3d7..ca89e6c912 100644 --- a/loopx/visible_governance.py +++ b/loopx/visible_governance.py @@ -28,147 +28,74 @@ # ── authority-boundary static table ───────────────────────────────────────── def _build_authority_boundary_table() -> list[dict[str, Any]]: - """Static map of each RFC proposal to its shipped-truth status. + """Implementation availability, not a claim that this Goal was promoted. - Sensitive tokens are split with ``+`` concatenation so the public- - boundary scanner never self-detects (same pattern as ``contract.py``). + Keep the original RFC section identifiers for report consumers. The original + prototype commands/formats are distinct from their shipped native successors. + Per-Goal authority must still be read from its actual binding and writer fence. """ return [ { "rfc_section": "3 -- State Classification", - "proposal": ( - "Shared canonical: todo lifecycle, soft claims, dependency, " - "gate fields, hard task leases, operation receipts" - ), - "shipped": False, + "proposal": "Shared canonical Todo lifecycle, claims, leases and operation receipts", + "shipped": True, "shipped_equivalent": ( - "Default runtime: task_lease.py file-backed leases + " - "ACTIVE_GOAL_STATE.md soft claims (two stores). Stage-2 " - "coordination head unifies them only on the additive path." - ), - "gap": ( - "Default path still has separate lease/claim stores with no " - "shared revision counter; legacy handoff_mode keeps soft " - "claim overriding hard lease. Canonical shared state waits " - "on Stage 3 promotion." + "Promoted Goals use native TS commands and AuthorityStore transactions. " + "Unpromoted Goals retain the Markdown/lease adapters." ), + "gap": "Implementation availability does not select the default provider or migrate this Goal.", }, { "rfc_section": "4 -- Coordination Ledger Shape", - "proposal": ( - "loopx_coordination_head_v1: unified aggregate with " - "authority_revision, todo_revision, lease_epoch, " - "receipt_index, eligibility projections" - ), + "proposal": "Original coordination head aggregate with an embedded receipt index", "shipped": False, "shipped_equivalent": ( - "Native task-lease acquire is owned by the TypeScript default " - "path. The coordination head codec + recoverable execution " - "reference executor + file/NoKV candidates exercise v1 behind " - "one CAS seam as coverage-only modules, not the runtime " - "source of truth. PostgreSQL remains an RFC workstream." - ), - "gap": ( - "Runtime still writes Markdown/lease files; no production " - "provider-neutral transaction boundary or canonical " - "coordination head exists. Stage 3 contract proof is complete " - "at the reference boundary; canonical promotion still needs " - "migration, writer fencing, authorization, provider " - "qualification, rollback, projection, and retention decisions" + "The Python prototype is retired. Native File/SQLite journals retain " + "head, committed history and receipts through one AuthorityStore contract. " + "PostgreSQL and NoKV have separate candidate qualification." ), + "gap": "The retired prototype head is not a supported Goal journal or migration source.", }, { "rfc_section": "5.1 -- claim_work command", - "proposal": ( - "CAS claim+lease+receipt in one atomic transition against " - "the coordination head" - ), + "proposal": "Atomic claim, lease and receipt publication", "shipped": False, "shipped_equivalent": ( - "The default path has task_lease_acquire.ts native hard-fence " - "acquire plus an independently versioned active-state soft " - "claim. The coverage-only coordination executor proves atomic " - "claim+lease+receipt behind the reference CAS seam." + "The experimental claim_work executor is retired. Native todo_claim.ts " + "owns atomic claim/lease acquisition on canonical providers." ), "gap": ( - "claim_work is not a production write path. Default leases " - "remain an optional runtime fence rather than write authority, " - "and the soft claim still wins under legacy handoff_mode." + "A historical receipt is not current execution authority. Lease/effect " + "checks and the actual Goal binding remain required; claim_work is not a public command." ), }, { "rfc_section": "7 -- Receipt Retention", - "proposal": ( - "retain_all_v0: no GC; missing receipt -> fail closed" - ), + "proposal": "Retain original receipts and fail closed on unproved outcomes", "shipped": True, - "shipped_equivalent": ( - "Stage-2 coordination head receipt_index under " - "retain_all_v0 (fail closed on missing receipt for that " - "path). Default runtime still uses turn-scoped quota " - "settlement receipts." - ), - "gap": ( - "Default path has no goal-wide receipt_index. Stage-2 " - "retain_all_v0 is load-bearing for canary sizing; Section 12 " - "retention policy is still an open Stage-3 decision." - ), + "shipped_equivalent": "Native AuthorityStore readReceipt and committed history preserve replay evidence.", + "gap": "Receipt correctness does not qualify unbounded capacity or the SQLite D2 profile.", }, { "rfc_section": "8 -- Local vs Shared Mode", - "proposal": ( - "Explicit per-goal opt-in with provider binding, local " - "writer fencing during migration, parity validation" - ), - "shipped": False, - "shipped_equivalent": ( - "Default local mode only -- project registry, Markdown " - "active state, run history, task leases, status, quota, " - "host behavior remain unchanged. Stage-2 file provider is " - "local shadow, not shared-authority mode." - ), - "gap": ( - "No shared-authority mode is implemented; installing a " - "provider does not enable shared authority or replace " - "local writers" - ), + "proposal": "Explicit provider binding, writer fencing and migration parity", + "shipped": True, + "shipped_equivalent": "Reviewed per-Goal promotion and fenced rollback are implemented.", + "gap": "Whole-Goal cohort qualification and default onboarding remain separate from per-Goal opt-in.", }, { "rfc_section": "Appendix B -- handoff_mode", - "proposal": ( - "handoff_mode field on goal state front matter: " - "legacy | soft_claim | hard_lease" - ), + "proposal": "Explicit legacy, soft_claim or hard_lease coordination mode", "shipped": True, - "shipped_equivalent": ( - "handoff_mode front-matter + loopx handoff-mode show|set " - "with quiescence gate; hard_lease gates claim/lease and " - "auto-acquire completion keys. Stage-2 head pins " - "hard_lease on bootstrap." - ), - "gap": ( - "legacy remains the default and keeps the soft-claim / " - "hard-lease divergence hole by design" - ), + "shipped_equivalent": "The native handoff-mode command owns transition admission and quiescence checks.", + "gap": "Provider selection does not silently replace the Goal's chosen handoff mode.", }, { "rfc_section": "9 -- Offline/Local Boundaries", - "proposal": ( - "When shared-mode authority is unavailable: cached " - "projections may be read (stale), no new controlled writes " - "accepted, already-authorized local computation may " - "continue under existing lease/effect boundaries, NO " - "automatic local-file write fallback" - ), + "proposal": "Unavailable shared authority must not silently fall back to local writers", "shipped": False, - "shipped_equivalent": ( - "No shared-mode path exists; Stage-2 file provider is still " - "local. Default writes go to the local file system." - ), - "gap": ( - "Shared-mode offline boundary is aspirational -- no online " - "authority provider is the runtime source of truth" - ), + "shipped_equivalent": "Promoted local providers fail closed when canonical authority cannot be read.", + "gap": "Deployed cross-host service availability and execution-interval fencing need separate qualification.", }, ] diff --git a/pyproject.toml b/pyproject.toml index 01b1958d84..ca68c13916 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -151,9 +151,6 @@ strict = true files = [ "loopx/control_plane/__init__.py", "loopx/control_plane/coordination/canonical_snapshot.py", - "loopx/control_plane/coordination/executor.py", - "loopx/control_plane/coordination/file_provider.py", - "loopx/control_plane/coordination/head.py", "loopx/control_plane/coordination/local_authority_shadow_outbox.py", "loopx/control_plane/coordination/local_authority_shadow_projection.py", "loopx/control_plane/quota/effect_program.py", diff --git a/tests/control_plane/test_coordination_executor.py b/tests/control_plane/test_coordination_executor.py deleted file mode 100644 index 325eb9a87d..0000000000 --- a/tests/control_plane/test_coordination_executor.py +++ /dev/null @@ -1,541 +0,0 @@ -"""The authority execution layer over a coordination provider (RFC section 5). - -Covers the machine-verifiable acceptance checks of RFC section 10 that apply -to the claim_work slice (1-5, 7-11), plus the Stage-2 harmony requirement: -domain decisions are delegated to the Stage 1 core, never re-derived here. -""" - -from __future__ import annotations - -import copy -import threading - -import pytest - -from loopx.control_plane.coordination import authority_core -from loopx.control_plane.coordination.executor import ( - CoordinationAuthorityExecutor, - EnvelopeError, - sample_claim_envelope, -) -from loopx.control_plane.coordination.executor import ( - MAX_LEASE_TTL_SECONDS, -) -from loopx.control_plane.coordination.head import HeadValidationError, bootstrap_head - - -def eligibility(allowed=("agent-a", "agent-b")) -> dict: - return { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "allowed_agent_ids": list(allowed), - "dependencies_satisfied": True, - "dependency_revision": 12, - "gates_open": True, - "gate_revision": 5, - } - - -def todo(**overrides) -> dict: - base = { - "todo_revision": 7, - "status": "open", - "claimed_by": None, - "eligibility": eligibility(), - "repository": "git:example/repo", - "code_revision": "0123456789abcdef", - "last_lease_epoch": 6, - } - base.update(overrides) - return base - - -class FakeProvider: - """Deterministic in-memory provider with fault hooks for checks 7 and 9.""" - - def __init__(self, generation_step: int = 17): - self._head = None - self._generation = 0 - self._step = generation_step - self._lock = threading.Lock() - self.fault = None - self.unrelated_advances = 0 - self.identity = "test:store" - - def store_identity(self) -> str: - return self.identity - - def load(self): - with self._lock: - return copy.deepcopy(self._head), self._generation - - def compare_and_put(self, expected_generation, head): - with self._lock: - if self.fault == "failed_before": - self.fault = None - return {"result": "failed", "error": "provider unavailable"} - if expected_generation != self._generation: - return { - "result": "conflict", - "current_provider_generation": self._generation, - } - if self.fault == "ambiguous_before": - self.fault = None - return {"result": "ambiguous"} - if self.unrelated_advances: - self.unrelated_advances -= 1 - self._generation += self._step - return { - "result": "conflict", - "current_provider_generation": self._generation, - } - self._head = copy.deepcopy(head) - self._generation += self._step - if self.fault == "ambiguous_after": - self.fault = None - return {"result": "ambiguous"} - return {"result": "applied", "provider_generation": self._generation} - - -def bootstrap(provider, todos=("todo-1", "todo-2")) -> dict: - head = bootstrap_head( - "goal-a", - {todo_id: todo() for todo_id in todos}, - store_binding=provider.store_identity(), - ) - assert provider.compare_and_put(0, head)["result"] == "applied" - return head - - -def executor_for(provider) -> CoordinationAuthorityExecutor: - return CoordinationAuthorityExecutor( - provider, goal_id="goal-a", now=lambda: 1_800_000_000.0 - ) - - -def envelope(agent="agent-a", todo_id="todo-1", operation_id=None, **overrides): - return sample_claim_envelope( - goal_id="goal-a", - operation_id=operation_id or f"op-{agent}-{todo_id}", - agent_id=agent, - device_id=f"dev-{agent}", - todo_id=todo_id, - expected_todo_revision=7, - expected_preconditions={ - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "dependency_revision": 12, - "gate_revision": 5, - }, - lease_ttl_seconds=600, - **overrides, - ) - - -# ---- check 1: same-todo competition has exactly one winner ------------------ - - -def test_same_todo_two_actors_one_winner() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - - first = executor.apply(envelope("agent-a")) - second = executor.apply(envelope("agent-b")) - - assert first["result"] == "applied" - receipt = first["original_receipt"] - assert receipt["schema_version"] == "loopx_authority_receipt_v0" - assert receipt["command"] == "claim_work" - assert receipt["accepted_authority_revision"] == 1 - assert receipt["accepted_todo_revision"] == 8 - assert receipt["lease_epoch"] == 7 - assert second["result"] == "conflict" - assert second["reason"] == "todo_revision_mismatch" - - head, _ = provider.load() - claimed = head["coordination"]["todos"]["todo-1"] - assert claimed["status"] == "open" - assert claimed["claimed_by"] == "agent-a" - assert claimed["todo_revision"] == 8 - assert claimed["last_lease_epoch"] == 7 - lease = head["coordination"]["leases"]["todo-1"] - assert lease["owner"] == "agent-a" and lease["lease_epoch"] == 7 - assert len(head["receipt_index"]) == 1 - - -# ---- check 2: independent todos rebase internally and both apply ------------ - - -def test_independent_todos_both_apply_after_internal_rebase() -> None: - provider = FakeProvider() - bootstrap(provider) - executor_a = executor_for(provider) - executor_b = executor_for(provider) - - provider.unrelated_advances = 0 - first = executor_a.apply(envelope("agent-a", "todo-1")) - second = executor_b.apply(envelope("agent-b", "todo-2")) - - assert first["result"] == "applied" and second["result"] == "applied" - head, _ = provider.load() - assert head["authority_revision"] == 2 - assert head["coordination"]["todos"]["todo-1"]["todo_revision"] == 8 - assert head["coordination"]["todos"]["todo-2"]["todo_revision"] == 8 - accepted = sorted( - entry["original_receipt"]["accepted_authority_revision"] - for entry in head["receipt_index"].values() - ) - assert accepted == [1, 2] - - -# ---- checks 3 + 4: replay returns the original receipt ---------------------- - - -def test_replay_and_aba_recover_the_original_receipt() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - - request = envelope("agent-a", "todo-1", operation_id="op-A") - first = executor.apply(request) - assert first["result"] == "applied" - - immediate = executor.apply(copy.deepcopy(request)) - assert immediate["result"] == "already_applied" - assert immediate["original_receipt"] == first["original_receipt"] - - advanced = executor.apply(envelope("agent-b", "todo-2")) - assert advanced["result"] == "applied" - - reconstructed = executor_for(provider) - replay = reconstructed.apply(copy.deepcopy(request)) - assert replay["result"] == "already_applied" - assert replay["original_receipt"] == first["original_receipt"] - assert replay["observed_authority_revision"] == 2 - assert replay["authorization_status"] == "active" - - transported = copy.deepcopy(request) - transported["transport"] = {"attempt": 4} - assert reconstructed.apply(transported)["result"] == "already_applied" - - -# ---- check 5: identity reuse with different semantics is rejected ----------- - - -def test_operation_identity_mismatch_changes_nothing() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - request = envelope("agent-a", "todo-1", operation_id="op-A") - assert executor.apply(request)["result"] == "applied" - before = provider.load() - - mutated = copy.deepcopy(request) - mutated["command"]["lease_ttl_seconds"] = 601 - outcome = executor.apply(mutated) - assert outcome["result"] == "rejected" - assert outcome["reason"] == "operation_identity_mismatch" - assert provider.load() == before - - -# ---- check 7: crash windows and ambiguity ---------------------------------- - - -def test_ambiguous_with_commit_recovers_from_receipt() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - provider.fault = "ambiguous_after" - outcome = executor.apply(envelope("agent-a", "todo-1", operation_id="op-lost")) - assert outcome["result"] == "already_applied" - head, _ = provider.load() - assert head["authority_revision"] == 1 - assert "op-lost" in head["receipt_index"] - - -def test_ambiguous_without_commit_fails_unproved() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - provider.fault = "ambiguous_before" - outcome = executor.apply(envelope("agent-a", "todo-1")) - assert outcome["result"] == "failed" - assert outcome["reason"] == "provider_outcome_unproved" - head, _ = provider.load() - assert head["authority_revision"] == 0 and head["receipt_index"] == {} - - -def test_provider_failed_before_cas_is_typed() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - provider.fault = "failed_before" - outcome = executor.apply(envelope("agent-a", "todo-1")) - assert outcome["result"] == "failed" - assert outcome["reason"] == "provider_failed_before_cas" - - -# ---- check 8: rejections create no state ------------------------------------ - - -def test_rejections_and_stale_preconditions_change_nothing() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - before = provider.load() - - unknown = executor.apply(envelope("agent-a", "todo-9")) - assert unknown["result"] == "rejected" and unknown["reason"] == "todo_not_found" - - ineligible = executor.apply(envelope("agent-z", "todo-1")) - assert ineligible["result"] == "rejected" - assert ineligible["reason"] == "actor_ineligible" - - stale_revision = envelope("agent-a", "todo-1") - stale_revision["command"]["expected_todo_revision"] = 6 - stale = executor.apply(stale_revision) - assert stale["result"] == "conflict" - assert stale["reason"] == "todo_revision_mismatch" - - stale_preconditions = envelope("agent-a", "todo-1") - stale_preconditions["command"]["expected_preconditions"][ - "dependency_revision" - ] = 11 - mismatch = executor.apply(stale_preconditions) - assert mismatch["result"] == "conflict" - assert mismatch["reason"] == "precondition_snapshot_mismatch" - - blocked = bootstrap_head( - "goal-a", - {"todo-3": todo(eligibility=eligibility() | {"gates_open": False})}, - store_binding="test:store", - ) - gated_provider = FakeProvider() - assert gated_provider.compare_and_put(0, blocked)["result"] == "applied" - gated = executor_for(gated_provider).apply(envelope("agent-a", "todo-3")) - assert gated["result"] == "rejected" and gated["reason"] == "gate_closed" - - assert provider.load() == before - - -# ---- check 9: sustained unrelated contention fails typed -------------------- - - -def test_sustained_unrelated_contention_returns_typed_failed() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - provider.unrelated_advances = 99 - outcome = executor.apply(envelope("agent-a", "todo-1", operation_id="op-c")) - assert outcome["result"] == "failed" - assert outcome["reason"] == "provider_contention_exhausted" - head, _ = provider.load() - assert head["authority_revision"] == 0 - assert "op-c" not in head["receipt_index"] - - -# ---- checks 10 + 11: retention and version domains -------------------------- - - -def test_receipts_are_retained_and_version_domains_are_distinct() -> None: - provider = FakeProvider(generation_step=17) - bootstrap(provider) - executor = executor_for(provider) - assert executor.apply(envelope("agent-a", "todo-1"))["result"] == "applied" - assert executor.apply(envelope("agent-b", "todo-2"))["result"] == "applied" - head, generation = provider.load() - assert generation == 3 * 17 - assert head["authority_revision"] == 2 - assert head["coordination"]["leases"]["todo-1"]["lease_epoch"] == 7 - assert len(head["receipt_index"]) == 2 - assert head["receipt_retention"] == {"mode": "retain_all_v0"} - - -# ---- harmony: domain decisions come from the Stage 1 core ------------------- - - -def test_lease_acquire_rule_is_owned_by_the_native_decision(monkeypatch) -> None: - """The NoKV executor and local file transaction share one acquire rule.""" - - calls = [] - - def native_decision(method, payload): - calls.append((method, payload)) - return { - "outcome": "rejected", - "code": "native_rule_probe", - "idempotent": False, - "next_lease": None, - "conflict_indexes": [], - } - - monkeypatch.setattr(authority_core, "effect_runtime_result", native_decision) - snapshot = authority_core.CoordinationSnapshot( - handoff_mode=authority_core.HandoffMode.HARD_LEASE, - registered_agents=("agent-a",), - todo=authority_core.TodoSnapshot( - todo_id="todo-1", - status="open", - role="agent", - ), - ) - plan = authority_core.decide( - snapshot, - authority_core.LeaseAcquireCommand( - owner="agent-a", - idempotency_key="lease-a", - ttl_seconds=600, - ), - ) - - assert plan.code == "native_rule_probe" - assert calls[0][0] == "task_lease.acquire.decide" - - -def test_domain_rules_are_delegated_to_the_stage1_core(monkeypatch) -> None: - """Flipping one core rule must flip the executor: no duplicated rules.""" - - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - - real_decide = authority_core.decide - - def approving_decide(snapshot, command): - plan = real_decide(snapshot, command) - if plan.code in {"actor_not_registered", "owner_not_registered"}: - actor = getattr(command, "actor_agent_id", None) or getattr( - command, "owner", None - ) - patched = real_decide( - authority_core.CoordinationSnapshot( - handoff_mode=snapshot.handoff_mode, - registered_agents=(*snapshot.registered_agents, actor), - todo=snapshot.todo, - lease=snapshot.lease, - ), - command, - ) - return patched - return plan - - import loopx.control_plane.coordination.executor as executor_module - - monkeypatch.setattr(executor_module, "decide", approving_decide) - outcome = executor.apply(envelope("agent-z", "todo-1")) - assert outcome["result"] == "applied", ( - "the executor must consult authority_core.decide for actor eligibility; " - "a re-derived local rule would still reject agent-z" - ) - - -def test_uninitialized_head_fails_closed() -> None: - provider = FakeProvider() - executor = executor_for(provider) - outcome = executor.apply(envelope("agent-a", "todo-1")) - assert outcome["result"] == "failed" - assert outcome["reason"] == "coordination_head_uninitialized" - -# ---- audit hardening: provider verdicts and envelope corruption ------------- - - -class MisreportingProvider: - """Lands the write, then reports the CAS as failed (a lying provider).""" - - def __init__(self): - self.inner = FakeProvider() - - def store_identity(self): - return self.inner.store_identity() - - def load(self): - return self.inner.load() - - def compare_and_put(self, expected_generation, head): - outcome = self.inner.compare_and_put(expected_generation, head) - if outcome["result"] == "applied": - return {"result": "failed", "error": "spurious timeout"} - return outcome - - -def test_provider_failed_verdict_is_verified_not_trusted() -> None: - """``failed`` claims the write provably never happened. The executor - verifies that claim against the receipt index instead of trusting it, so a - provider that misreports a landed write cannot manufacture a zombie claim - whose caller was told it failed.""" - - provider = MisreportingProvider() - bootstrap(provider.inner) - executor = CoordinationAuthorityExecutor( - provider, goal_id="goal-a", now=lambda: 1_800_000_000.0 - ) - outcome = executor.apply(envelope("agent-a", "todo-1")) - assert outcome["result"] == "already_applied" - stored_head, _ = provider.load() - stored = stored_head["receipt_index"]["op-agent-a-todo-1"]["original_receipt"] - assert outcome["original_receipt"] == stored - - -def test_envelope_rejects_bool_disguised_integers() -> None: - """bool is an int subclass; a True revision or TTL must not pass the typed - integer gates and mint real state from a malformed envelope.""" - - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - for mutate in ( - lambda command: command.update(expected_todo_revision=True), - lambda command: command.update(lease_ttl_seconds=True), - lambda command: command["expected_preconditions"].update( - dependency_revision=True - ), - ): - request = envelope("agent-a", "todo-1") - mutate(request["command"]) - with pytest.raises(EnvelopeError): - executor.apply(request) - -def test_corrupt_stored_receipt_fails_typed_before_replay() -> None: - """A digest-matching receipt entry whose ``original_receipt`` was gutted - must surface as the typed head-validation failure at load, never as a - KeyError from inside the replay or success paths.""" - - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - assert executor.apply(envelope("agent-a", "todo-1"))["result"] == "applied" - stored, generation = provider.load() - stored["receipt_index"]["op-agent-a-todo-1"]["original_receipt"] = {} - assert provider.compare_and_put(generation, stored)["result"] == "applied" - with pytest.raises(HeadValidationError, match="receipt"): - executor.apply(envelope("agent-a", "todo-1")) - - -def test_lease_ttl_is_bounded_by_the_task_lease_ceiling() -> None: - """The shared envelope reuses the local task-lease TTL ceiling, so an - astronomical caller value is a typed rejection at the envelope boundary - and can never reach wall-clock arithmetic as an OverflowError.""" - - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider) - for ttl in (10**100, MAX_LEASE_TTL_SECONDS + 1): - request = envelope("agent-a", "todo-1") - request["command"]["lease_ttl_seconds"] = ttl - with pytest.raises(EnvelopeError, match="between 1 and"): - executor.apply(request) - at_ceiling = envelope("agent-a", "todo-1") - at_ceiling["command"]["lease_ttl_seconds"] = MAX_LEASE_TTL_SECONDS - assert executor.apply(at_ceiling)["result"] == "applied" - - -def test_lease_ttl_ceiling_matches_the_local_task_lease_authority() -> None: - """The executor mirrors the constant instead of importing the (untyped) - task-lease module; this pin keeps the two ceilings from drifting.""" - - from loopx.control_plane.work_items.task_lease import ( - MAX_TASK_LEASE_TTL_SECONDS, - ) - - assert MAX_LEASE_TTL_SECONDS == MAX_TASK_LEASE_TTL_SECONDS diff --git a/tests/control_plane/test_coordination_file_provider.py b/tests/control_plane/test_coordination_file_provider.py deleted file mode 100644 index 184c0c692a..0000000000 --- a/tests/control_plane/test_coordination_file_provider.py +++ /dev/null @@ -1,491 +0,0 @@ -"""The file-backed coordination provider: storage-only CAS with typed outcomes. - -RFC section 6.2: ``load() -> (head | None, provider_generation)`` and -``compare_and_put(expected_provider_generation, head) -> applied | conflict | -ambiguous | failed``. The provider never interprets the head; crash windows -must map onto ``ambiguous`` (never a silent success or a lost document). -""" - -from __future__ import annotations - -import json -import threading -from pathlib import Path - -import pytest - -from loopx.control_plane.coordination.file_provider import ( - FileCoordinationProvider, - ProviderProtocolError, -) - - -def head(revision: int = 0) -> dict: - return { - "schema_version": "loopx_coordination_head_v1", - "goal_id": "goal-a", - "handoff_mode": "hard_lease", - "store_binding": "test:store", - "authority_revision": revision, - "coordination": {"todos": {}, "leases": {}}, - "receipt_index": {}, - "receipt_retention": {"mode": "retain_all_v0"}, - } - - -@pytest.fixture -def provider(tmp_path) -> FileCoordinationProvider: - return FileCoordinationProvider(tmp_path / "coordination", "goal-a") - - -def test_load_uninitialized_returns_none_zero(provider) -> None: - assert provider.load() == (None, 0) - - -def test_store_identity_is_stable_and_strictly_formatted(provider) -> None: - identity = provider.store_identity() - assert identity.startswith("file:") - assert len(identity) == len("file:") + 32 - assert set(identity.removeprefix("file:")) <= set("0123456789abcdef") - assert provider.store_identity() == identity - - -@pytest.mark.parametrize( - "invalid_identity", - [ - b"", - b"f", - b"file:0123456789abcdef0123456789abcde", - b"file:0123456789abcdef0123456789abcdef\n", - b"file:0123456789ABCDEF0123456789ABCDEF", - b"nokv:0123456789abcdef0123456789abcdef", - b"file:\xff", - ], -) -def test_store_identity_rejects_invalid_persisted_bytes( - tmp_path, invalid_identity -) -> None: - directory = tmp_path / "coordination" - directory.mkdir() - (directory / "store-identity").write_bytes(invalid_identity) - provider = FileCoordinationProvider(directory, "goal-a") - with pytest.raises(ProviderProtocolError, match="32 lowercase hex"): - provider.store_identity() - - -def test_concurrent_store_identity_creation_publishes_one_complete_value( - tmp_path, monkeypatch -) -> None: - import loopx.control_plane.coordination.file_provider as module - - directory = tmp_path / "coordination" - providers = [ - FileCoordinationProvider(directory, "goal-a"), - FileCoordinationProvider(directory, "goal-b"), - ] - entered_replace = threading.Event() - allow_replace = threading.Event() - real_replace = module._replace_document - - def blocked_identity_replace(source: Path, target: Path) -> None: - if target.name == "store-identity": - entered_replace.set() - assert allow_replace.wait(timeout=5) - real_replace(source, target) - - monkeypatch.setattr(module, "_replace_document", blocked_identity_replace) - results: list[str] = [] - failures: list[BaseException] = [] - - def create_identity(provider: FileCoordinationProvider) -> None: - try: - results.append(provider.store_identity()) - except BaseException as exc: # pragma: no cover - asserted below - failures.append(exc) - - first = threading.Thread(target=create_identity, args=(providers[0],)) - second = threading.Thread(target=create_identity, args=(providers[1],)) - first.start() - assert entered_replace.wait(timeout=5) - second.start() - assert second.is_alive() - allow_replace.set() - first.join(timeout=5) - second.join(timeout=5) - monkeypatch.undo() - - assert not failures - assert len(results) == 2 - assert results[0] == results[1] - assert len(results[0]) == len("file:") + 32 - - -def test_store_identity_short_writes_are_continued(tmp_path, monkeypatch) -> None: - import loopx.control_plane.coordination.file_provider as module - - provider = FileCoordinationProvider(tmp_path / "coordination", "goal-a") - real_write = module.os.write - chunks: list[int] = [] - - def short_write(descriptor, view): - chunk = bytes(view)[:3] - chunks.append(len(chunk)) - return real_write(descriptor, chunk) - - monkeypatch.setattr(module.os, "write", short_write) - identity = provider.store_identity() - monkeypatch.undo() - assert len(chunks) > 1 - assert provider.store_identity() == identity - assert (tmp_path / "coordination" / "store-identity").read_text() == identity - - -def test_store_identity_crash_before_rename_retries_cleanly( - tmp_path, monkeypatch -) -> None: - import loopx.control_plane.coordination.file_provider as module - - directory = tmp_path / "coordination" - provider = FileCoordinationProvider(directory, "goal-a") - - def crash_before_identity_rename(_source: Path, target: Path) -> None: - assert target.name == "store-identity" - raise OSError("simulated crash before identity rename") - - monkeypatch.setattr(module, "_replace_document", crash_before_identity_rename) - with pytest.raises(ProviderProtocolError, match="store identity is unavailable"): - provider.store_identity() - assert not (directory / "store-identity").exists() - assert not list(directory.glob("store-identity.tmp-*")) - - monkeypatch.undo() - identity = provider.store_identity() - assert provider.store_identity() == identity - - -def test_store_identity_directory_fsync_failure_converges_on_retry( - tmp_path, monkeypatch -) -> None: - import loopx.control_plane.coordination.file_provider as module - - provider = FileCoordinationProvider(tmp_path / "coordination", "goal-a") - real_fsync_directory = module._fsync_directory - calls = 0 - - def fail_first_directory_fsync(directory: Path) -> None: - nonlocal calls - calls += 1 - if calls == 1: - raise OSError("simulated identity directory fsync failure") - real_fsync_directory(directory) - - monkeypatch.setattr(module, "_fsync_directory", fail_first_directory_fsync) - with pytest.raises(ProviderProtocolError, match="store identity is unavailable"): - provider.store_identity() - identity = provider.store_identity() - monkeypatch.undo() - assert calls == 2 - assert provider.store_identity() == identity - - -def test_create_replace_conflict_cycle(provider) -> None: - created = provider.compare_and_put(0, head()) - assert created == {"result": "applied", "provider_generation": 1} - loaded, generation = provider.load() - assert loaded == head() and generation == 1 - - replaced = provider.compare_and_put(1, head(1)) - assert replaced == {"result": "applied", "provider_generation": 2} - - stale = provider.compare_and_put(1, head(9)) - assert stale == {"result": "conflict", "current_provider_generation": 2} - lost_create = provider.compare_and_put(0, head()) - assert lost_create == {"result": "conflict", "current_provider_generation": 2} - assert provider.load() == (head(1), 2) - - -def test_two_handles_share_one_document(tmp_path) -> None: - a = FileCoordinationProvider(tmp_path / "c", "goal-a") - b = FileCoordinationProvider(tmp_path / "c", "goal-a") - assert a.compare_and_put(0, head())["result"] == "applied" - assert b.load() == (head(), 1) - assert b.compare_and_put(1, head(1))["result"] == "applied" - assert a.compare_and_put(1, head(2))["result"] == "conflict" - - -def test_goals_are_isolated_documents(tmp_path) -> None: - a = FileCoordinationProvider(tmp_path / "c", "goal-a") - b = FileCoordinationProvider(tmp_path / "c", "goal-b") - assert a.compare_and_put(0, head())["result"] == "applied" - assert b.load() == (None, 0) - - -def test_concurrent_cas_from_same_generation_has_one_winner(provider) -> None: - assert provider.compare_and_put(0, head())["result"] == "applied" - barrier = threading.Barrier(2) - results = {} - - def racer(name: str, revision: int) -> None: - barrier.wait() - results[name] = provider.compare_and_put(1, head(revision)) - - threads = [ - threading.Thread(target=racer, args=("a", 1)), - threading.Thread(target=racer, args=("b", 2)), - ] - for thread in threads: - thread.start() - for thread in threads: - thread.join() - outcomes = sorted(result["result"] for result in results.values()) - assert outcomes in (["applied", "conflict"], ["ambiguous", "applied"]), results - loaded, generation = provider.load() - assert generation == 2 - assert loaded in (head(1), head(2)) - - -def test_bool_disguised_generations_fail_closed(provider, tmp_path) -> None: - """The provider generation is typed state exactly like revisions and - epochs: JSON ``true`` must not load as generation 1, and a bool expected - generation must not hit the CAS and silently repair a corrupt envelope - into a valid lineage.""" - - for expected in (True, False): - outcome = provider.compare_and_put(expected, head()) - assert outcome["result"] == "failed" - assert "non-negative integer" in outcome["error"] - assert provider.compare_and_put(0, head())["result"] == "applied" - document = next((tmp_path / "coordination").glob("*.json")) - body = document.read_text(encoding="utf-8") - document.write_text( - body.replace('"provider_generation":1', '"provider_generation":true'), - encoding="utf-8", - ) - with pytest.raises(ProviderProtocolError, match="v0 contract"): - provider.load() - with pytest.raises(ProviderProtocolError, match="v0 contract"): - provider.compare_and_put(1, head(1)) - - -def test_corrupt_document_fails_closed(provider, tmp_path) -> None: - assert provider.compare_and_put(0, head())["result"] == "applied" - document = next((tmp_path / "coordination").glob("*.json")) - document.write_text("{not json", encoding="utf-8") - with pytest.raises(ProviderProtocolError): - provider.load() - with pytest.raises(ProviderProtocolError): - provider.compare_and_put(1, head(1)) - - -def test_crash_after_temp_write_before_rename_changes_nothing( - provider, monkeypatch -) -> None: - # Faults are injected through the provider's own commit seam, not the - # global os attributes: loopx.file_lock's Windows holder sidecar also - # calls os.replace, and a global patch would crash lock bookkeeping - # instead of the document commit under test. - assert provider.compare_and_put(0, head())["result"] == "applied" - import loopx.control_plane.coordination.file_provider as module - - def crash(_source, _target): - raise OSError("simulated crash before rename") - - monkeypatch.setattr(module, "_replace_document", crash) - outcome = provider.compare_and_put(1, head(1)) - assert outcome["result"] == "ambiguous" - monkeypatch.undo() - assert provider.load() == (head(), 1) - retry = provider.compare_and_put(1, head(1)) - assert retry == {"result": "applied", "provider_generation": 2} - - -def test_lost_response_after_rename_is_recoverable_by_reload( - provider, monkeypatch -) -> None: - assert provider.compare_and_put(0, head())["result"] == "applied" - import loopx.control_plane.coordination.file_provider as module - - real_replace = module._replace_document - - def replace_then_crash(source, target): - real_replace(source, target) - raise OSError("simulated lost response after rename") - - monkeypatch.setattr(module, "_replace_document", replace_then_crash) - outcome = provider.compare_and_put(1, head(1)) - assert outcome["result"] == "ambiguous" - monkeypatch.undo() - assert provider.load() == (head(1), 2) - - -def test_document_bytes_are_canonical_json(provider, tmp_path) -> None: - provider.compare_and_put(0, head()) - document = next((tmp_path / "coordination").glob("*.json")) - envelope = json.loads(document.read_text(encoding="utf-8")) - assert set(envelope) == {"provider_generation", "head"} - assert envelope["provider_generation"] == 1 - assert envelope["head"] == head() - - -def test_short_writes_are_continued_until_complete( - provider, tmp_path, monkeypatch -) -> None: - """os.write may write fewer bytes than asked; the commit sequence must - continue until every byte landed, or a truncated document could be - reported as applied and fail to parse on the next load.""" - - import loopx.control_plane.coordination.file_provider as module - - real_write = module.os.write - chunks = [] - - def short_write(descriptor, view): - chunk = bytes(view)[:7] - chunks.append(len(chunk)) - return real_write(descriptor, chunk) - - monkeypatch.setattr(module.os, "write", short_write) - outcome = provider.compare_and_put(0, head()) - monkeypatch.undo() - assert outcome == {"result": "applied", "provider_generation": 1} - assert len(chunks) > 1 - assert provider.load() == (head(), 1) - - -def test_write_fault_after_short_write_is_ambiguous_and_document_intact( - provider, tmp_path, monkeypatch -) -> None: - """A storage fault midway through the write must never surface as - applied: the document keeps its previous readable state and the verdict - is ambiguous, which the authority resolves by reload.""" - - import loopx.control_plane.coordination.file_provider as module - - assert provider.compare_and_put(0, head())["result"] == "applied" - real_write = module.os.write - state = {"calls": 0} - - def failing_write(descriptor, view): - state["calls"] += 1 - if state["calls"] == 1: - return real_write(descriptor, bytes(view)[: max(1, len(view) // 2)]) - raise OSError("simulated storage fault after a short write") - - monkeypatch.setattr(module.os, "write", failing_write) - outcome = provider.compare_and_put(1, head(1)) - monkeypatch.undo() - assert outcome == {"result": "ambiguous"} - assert provider.load() == (head(), 1) - assert not list((tmp_path / "coordination").glob("*.tmp-*")) - - -def test_commit_sequence_fsyncs_file_before_rename_and_directory_after( - provider, monkeypatch -) -> None: - import loopx.control_plane.coordination.file_provider as module - - events = [] - real_file_fsync = module._fsync_file - real_replace = module._replace_document - real_directory_fsync = module._fsync_directory - - def recording_file_fsync(descriptor): - events.append("fsync") - return real_file_fsync(descriptor) - - def recording_replace(source, target): - events.append("replace") - return real_replace(source, target) - - def recording_directory_fsync(directory): - events.append("dir_fsync") - return real_directory_fsync(directory) - - monkeypatch.setattr(module, "_fsync_file", recording_file_fsync) - monkeypatch.setattr(module, "_replace_document", recording_replace) - monkeypatch.setattr(module, "_fsync_directory", recording_directory_fsync) - assert provider.compare_and_put(0, head())["result"] == "applied" - monkeypatch.undo() - assert events == ["fsync", "replace", "dir_fsync"] - - -def test_directory_fsync_fault_is_ambiguous_not_applied( - provider, monkeypatch -) -> None: - """Until the parent directory entry is flushed the rename is not provably - durable, so a fault there must stay ambiguous instead of applied.""" - - import loopx.control_plane.coordination.file_provider as module - - def failing_directory_fsync(directory): - raise OSError("simulated directory fsync fault") - - monkeypatch.setattr(module, "_fsync_directory", failing_directory_fsync) - outcome = provider.compare_and_put(0, head()) - monkeypatch.undo() - assert outcome == {"result": "ambiguous"} - # The rename itself landed; reload recovers the write, which is exactly - # the ambiguous contract. - assert provider.load() == (head(), 1) - - -def test_lock_timeout_is_typed_failed_without_write(tmp_path) -> None: - from loopx.file_lock import exclusive_file_lock - - directory = tmp_path / "coordination" - provider = FileCoordinationProvider( - directory, "goal-a", lock_timeout_seconds=0.05 - ) - directory.mkdir(parents=True, exist_ok=True) - with exclusive_file_lock(directory / provider_document_name()): - outcome = provider.compare_and_put(0, head()) - assert outcome["result"] == "failed" - assert provider.load() == (None, 0) - assert provider.compare_and_put(0, head())["result"] == "applied" - - -def provider_document_name() -> str: - import hashlib - - digest = hashlib.sha256(b"goal-a").hexdigest()[:16] - return f"coordination-head-{digest}.json" - - -def test_module_imports_without_fcntl() -> None: - """The provider must stay importable on interpreters without ``fcntl`` - (Windows); platform locking belongs to ``loopx.file_lock``, the one lock - owner with both backends.""" - - import importlib - import sys - - import loopx.control_plane.coordination.file_provider as module - - sentinel = object() - saved = sys.modules.pop("fcntl", sentinel) - sys.modules["fcntl"] = None # type: ignore[assignment] - try: - reloaded = importlib.reload(module) - assert hasattr(reloaded, "FileCoordinationProvider") - finally: - if saved is sentinel: - sys.modules.pop("fcntl", None) - else: - sys.modules["fcntl"] = saved - importlib.reload(module) - - -def test_unserializable_head_fails_typed_and_writes_nothing( - provider, tmp_path -) -> None: - """A head with no faithful strict-JSON form must surface as the typed - ``failed`` verb (serialization runs before any write), never leak an - exception through the seam or leave partial bytes behind.""" - - for poison in ({"x": float("nan")}, {"x": object()}): - outcome = provider.compare_and_put(0, poison) - assert outcome["result"] == "failed" - assert "serializable" in outcome["error"] - assert provider.load() == (None, 0) - assert not list((tmp_path / "coordination").glob("*.tmp-*")) - assert provider.compare_and_put(0, head())["result"] == "applied" diff --git a/tests/control_plane/test_coordination_head.py b/tests/control_plane/test_coordination_head.py deleted file mode 100644 index 7a1b56d1f3..0000000000 --- a/tests/control_plane/test_coordination_head.py +++ /dev/null @@ -1,517 +0,0 @@ -"""The RFC ``loopx_coordination_head_v1`` aggregate: schema, canonical bytes, adapters. - -Stage 2 slice: the head is the one CAS document a coordination provider stores. -This file pins the aggregate contract before any executor logic exists. -""" - -from __future__ import annotations - -import copy -import json - -import pytest - -from loopx.control_plane.coordination.authority_core import ( - HandoffMode, - TodoSnapshot, -) -from loopx.control_plane.coordination.goal_state_shadow import ( - bootstrap_head_from_goal_state, -) -from loopx.control_plane.coordination.head import ( - HEAD_SCHEMA_VERSION, - HeadValidationError, - bootstrap_head, - canonical_head_bytes, - claim_snapshot_for_todo, - head_digest, - validated_head, -) - - -def eligibility() -> dict: - return { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "allowed_agent_ids": ["agent-a", "agent-b"], - "dependencies_satisfied": True, - "dependency_revision": 12, - "gates_open": True, - "gate_revision": 5, - } - - -def todo(**overrides) -> dict: - base = { - "todo_revision": 7, - "status": "open", - "claimed_by": None, - "eligibility": eligibility(), - "repository": "git:example/repo", - "code_revision": "0123456789abcdef", - "last_lease_epoch": 6, - } - base.update(overrides) - return base - - -def head() -> dict: - return bootstrap_head( - "goal-a", {"todo-1": todo(), "todo-2": todo()}, store_binding="test:store" - ) - - -# ---- bootstrap + validation ------------------------------------------------- - - -def test_bootstrap_head_matches_rfc_shape() -> None: - built = head() - assert built["schema_version"] == HEAD_SCHEMA_VERSION == "loopx_coordination_head_v1" - assert built["goal_id"] == "goal-a" - assert built["handoff_mode"] == "hard_lease" - assert built["store_binding"] == "test:store" - assert built["authority_revision"] == 0 - assert set(built["coordination"]) == {"todos", "leases"} - assert built["coordination"]["leases"] == {} - assert built["receipt_index"] == {} - assert built["receipt_retention"] == {"mode": "retain_all_v0"} - assert validated_head(built, goal_id="goal-a") is built - - -@pytest.mark.parametrize( - ("mutate", "match"), - [ - (lambda h: h.pop("receipt_retention"), "fields"), - (lambda h: h.update(receipt_retention={"mode": "bounded"}), "retain_all_v0"), - (lambda h: h.update(schema_version="v1"), "identity"), - (lambda h: h.update(goal_id="other"), "identity"), - (lambda h: h.update(authority_revision="9"), "authority_revision"), - (lambda h: h.update(authority_revision=True), "authority_revision"), - (lambda h: h.update(handoff_mode="soft_claim"), "hard_lease"), - (lambda h: h.update(handoff_mode="legacy"), "hard_lease"), - (lambda h: h["coordination"].pop("leases"), "todos and leases"), - (lambda h: h.update(extra=True), "fields"), - ], -) -def test_validated_head_fails_closed(mutate, match) -> None: - broken = head() - mutate(broken) - with pytest.raises(HeadValidationError, match=match): - validated_head(broken, goal_id="goal-a") - - -def test_bootstrap_rejects_non_portable_todo() -> None: - with pytest.raises(HeadValidationError, match="repository"): - bootstrap_head("goal-a", {"todo-1": todo(repository="/abs/path")}, store_binding="test:store") - with pytest.raises(HeadValidationError, match="open and unclaimed"): - bootstrap_head("goal-a", {"todo-1": todo(claimed_by="agent-a")}, store_binding="test:store") - with pytest.raises(HeadValidationError, match="fields"): - bad = todo() - bad.pop("last_lease_epoch") - bootstrap_head("goal-a", {"todo-1": bad}, store_binding="test:store") - - -@pytest.mark.parametrize( - "overrides", - [ - {"todo_revision": True}, - {"todo_revision": -1}, - {"last_lease_epoch": True}, - {"last_lease_epoch": -7}, - ], -) -def test_bootstrap_rejects_bool_and_negative_counts(overrides) -> None: - """bool is an int subclass; a ``True`` epoch would mint ``True + 1`` and a - negative one would mint decreasing epochs, so both fail closed exactly like - the local core's corrupt_lease.""" - - with pytest.raises(HeadValidationError): - bootstrap_head("goal-a", {"todo-1": todo(**overrides)}, store_binding="test:store") - - -def test_bootstrap_rejects_bool_eligibility_revision() -> None: - poisoned = eligibility() - poisoned["gate_revision"] = True - with pytest.raises(HeadValidationError, match="eligibility revisions"): - bootstrap_head("goal-a", {"todo-1": todo(eligibility=poisoned)}, store_binding="test:store") - - -def leased_head() -> dict: - built = head() - built["coordination"]["todos"]["todo-1"].update( - claimed_by="agent-a", - last_lease_epoch=7, - ) - built["coordination"]["leases"]["todo-1"] = { - "lease_id": "lease_abc", - "owner": "agent-a", - "lease_epoch": 7, - "expires_at": "2027-01-01T00:00:00.000Z", - "write_scopes": [], - } - return built - - -@pytest.mark.parametrize( - ("mutate", "match"), - [ - (lambda lease: lease.update(lease_epoch=0), "positive"), - (lambda lease: lease.update(lease_epoch=True), "positive"), - (lambda lease: lease.update(owner=""), "identity"), - (lambda lease: lease.update(expires_at=123), "timestamp"), - (lambda lease: lease.update(expires_at="not-a-time"), "timestamp"), - # Naive timestamps read differently under each host's local timezone, - # so the same persisted bytes would be active on one endpoint and - # expired on another; v0 mints and accepts aware UTC only. - (lambda lease: lease.update(expires_at="2030-01-01T00:00:00"), "timestamp"), - ( - lambda lease: lease.update(expires_at="2030-01-01T00:00:00+08:00"), - "timestamp", - ), - (lambda lease: lease.update(write_scopes=["repo"]), "write_scopes"), - ], -) -def test_validated_head_rejects_corrupt_lease_records(mutate, match) -> None: - assert validated_head(leased_head(), goal_id="goal-a") - broken = leased_head() - mutate(broken["coordination"]["leases"]["todo-1"]) - with pytest.raises(HeadValidationError, match=match): - validated_head(broken, goal_id="goal-a") - - -@pytest.mark.parametrize( - ("corruption", "match"), - [ - ("missing_lease", "claimed todo.*active lease"), - ("unclaimed_with_lease", "unclaimed todo.*active lease"), - ("owner_mismatch", "owner.*claimed_by"), - ("epoch_mismatch", "lease_epoch.*watermark"), - ], -) -def test_validated_head_binds_claim_to_its_live_lease( - corruption: str, - match: str, -) -> None: - broken = leased_head() - todo_record = broken["coordination"]["todos"]["todo-1"] - lease_record = broken["coordination"]["leases"]["todo-1"] - if corruption == "missing_lease": - del broken["coordination"]["leases"]["todo-1"] - elif corruption == "unclaimed_with_lease": - todo_record["claimed_by"] = None - elif corruption == "owner_mismatch": - lease_record["owner"] = "agent-b" - else: - todo_record["last_lease_epoch"] = 6 - - with pytest.raises(HeadValidationError, match=match): - validated_head(broken, goal_id="goal-a") - - -_RECEIPT_DIGEST = "sha256:" + "0" * 64 - - -def receipted_head() -> dict: - built = leased_head() - built["receipt_index"]["op-1"] = { - "request_digest": _RECEIPT_DIGEST, - "original_receipt": { - "schema_version": "loopx_authority_receipt_v0", - "operation_id": "op-1", - "request_digest": _RECEIPT_DIGEST, - "command": "claim_work", - "actor": {"agent_id": "agent-a", "device_id": "dev-a"}, - "todo_id": "todo-1", - "accepted_authority_revision": 1, - "accepted_todo_revision": 8, - "applied_at": "2027-01-01T00:00:00.000Z", - "lease_id": "lease_abc", - "lease_epoch": 7, - "expires_at": "2027-01-01T00:10:00.000Z", - }, - } - return built - - -@pytest.mark.parametrize( - ("mutate", "match"), - [ - (lambda e: e.update(original_receipt={}), "outside the v0 slice"), - (lambda e: e.update(extra=True), "fields do not match"), - (lambda e: e.update(request_digest="sha256:bootstrap"), "request_digest"), - ( - lambda e: e["original_receipt"].update(operation_id="op-2"), - "its own operation id", - ), - ( - lambda e: e["original_receipt"].update( - request_digest="sha256:" + "1" * 64 - ), - "disagrees with its index entry", - ), - ( - lambda e: e["original_receipt"].update(todo_id="todo-9"), - "todo the head does not carry", - ), - ( - lambda e: e["original_receipt"].update(accepted_todo_revision=0), - "accepted revisions", - ), - (lambda e: e["original_receipt"].update(lease_epoch=True), "lease_epoch"), - ( - lambda e: e["original_receipt"].update(applied_at="not-a-time"), - "timestamps", - ), - ( - lambda e: e["original_receipt"].update(applied_at="2030-01-01T00:00:00"), - "timestamps", - ), - ( - lambda e: e["original_receipt"].update( - expires_at="2030-01-01T00:00:00+08:00" - ), - "timestamps", - ), - ( - lambda e: e["original_receipt"].update(actor={"agent_id": ""}), - "actor identity", - ), - ( - lambda e: e["original_receipt"].update(command="transfer_work"), - "outside the v0 slice", - ), - ( - lambda e: e["original_receipt"].update(command="release_work"), - "fields do not match", - ), - ], -) -def test_validated_head_rejects_corrupt_receipt_entries(mutate, match) -> None: - """Persisted receipts cross the same trust boundary as todos and leases: - every field the executor later dereferences unconditionally is validated - here, so a digest-matching entry with a gutted ``original_receipt`` fails - closed instead of surfacing as a KeyError inside replay.""" - - assert validated_head(receipted_head(), goal_id="goal-a") - broken = receipted_head() - mutate(broken["receipt_index"]["op-1"]) - with pytest.raises(HeadValidationError, match=match): - validated_head(broken, goal_id="goal-a") - - -# ---- canonical bytes + digest ---------------------------------------------- - - -def test_canonical_bytes_are_key_order_independent() -> None: - a = head() - b = json.loads(json.dumps(a)) - b["coordination"] = dict(reversed(list(b["coordination"].items()))) - assert canonical_head_bytes(a) == canonical_head_bytes(b) - assert head_digest(a) == head_digest(b) - assert head_digest(a).startswith("sha256:") - - -def test_canonical_bytes_change_with_content() -> None: - a = head() - b = copy.deepcopy(a) - b["authority_revision"] = 1 - assert canonical_head_bytes(a) != canonical_head_bytes(b) - - -def test_canonical_bytes_fail_closed_on_unrepresentable_values() -> None: - """Non-finite floats would serialize into bytes a strict RFC 8259 reader - rejects, and non-JSON objects have no canonical form at all; neither may - ever become "canonical" bytes or an unclassified exception.""" - - poisoned = head() - poisoned["coordination"]["todos"]["todo-1"]["eligibility"][ - "dependency_revision" - ] = float("nan") - with pytest.raises(HeadValidationError, match="serializable"): - canonical_head_bytes(poisoned) - with pytest.raises(HeadValidationError, match="serializable"): - canonical_head_bytes({"x": object()}) - - -# ---- snapshot adapter ------------------------------------------------------- - - -def test_claim_snapshot_maps_aggregate_facts_into_core_types() -> None: - built = head() - snapshot = claim_snapshot_for_todo(built, "todo-1") - # The shared head carries claim and lease together, so the core's - # hard-lease holder gate is a live invariant for it. - assert snapshot.handoff_mode is HandoffMode.HARD_LEASE - assert snapshot.registered_agents == ("agent-a", "agent-b") - assert isinstance(snapshot.todo, TodoSnapshot) - assert snapshot.todo.todo_id == "todo-1" - assert snapshot.todo.status == "open" - assert snapshot.todo.claimed_by is None - # No lease in the head projects the no-ABA tombstone: the epoch watermark - # from the todo, not an absent lease, so the core mints last+1. - assert snapshot.lease is not None - assert snapshot.lease.present is False and snapshot.lease.active is False - assert snapshot.lease.lease_epoch == 6 and snapshot.lease.version == 6 - with pytest.raises(HeadValidationError, match="todo"): - claim_snapshot_for_todo(built, "todo-9") - - -def test_snapshot_mode_is_read_from_the_head() -> None: - """The mode is the head's revision-covered fact, not an adapter constant: - v0 validation pins it to hard_lease, and the projection follows whatever - the (validated) document says rather than re-deciding it.""" - - built = head() - assert claim_snapshot_for_todo(built, "todo-1").handoff_mode is HandoffMode.HARD_LEASE - relaxed = {**built, "handoff_mode": "legacy"} - assert claim_snapshot_for_todo(relaxed, "todo-1").handoff_mode is HandoffMode.LEGACY - - -# ---- shadow bootstrap from the local goal state ----------------------------- -# -# Fixtures are written by loopx's own writers so the parity is against the real -# on-disk format, not a hand-approximated markdown. - - -def _shadow_workspace(tmp_path): - import json as _json - - repo = tmp_path / "repo" - repo.mkdir(exist_ok=True) - state = repo / "ACTIVE_GOAL_STATE.md" - state.write_text( - "---\ngoal_id: shadow-goal\nupdated_at: 2026-08-01T00:00:00+00:00\n" - "handoff_mode: hard_lease\n---\n\n## Agent Todo\n\n", - encoding="utf-8", - ) - registry = tmp_path / "registry.global.json" - registry.write_text( - _json.dumps( - { - "common_runtime_root": str(tmp_path / "runtime"), - "goals": [ - { - "id": "shadow-goal", - "domain": "harness_self_improvement", - "status": "active", - "repo": str(repo), - "state_file": state.name, - "adapter": {"kind": "harness_self_improvement"}, - "coordination": { - "agent_model": "peer_v1", - "registered_agents": ["agent-a", "agent-b"], - }, - } - ], - } - ), - encoding="utf-8", - ) - return registry, state - - -def _seed_todos(registry): - from loopx.control_plane.work_items.task_lease import acquire_task_lease - from loopx.todos import add_goal_todo, complete_goal_todo - - claimed = add_goal_todo( - registry_path=registry, goal_id="shadow-goal", role="agent", - text="Ship the provider slice.", task_class="advancement_task", - claimed_by="agent-a", - ) - open_todo = add_goal_todo( - registry_path=registry, goal_id="shadow-goal", role="agent", - text="Review the provider slice.", task_class="advancement_task", - ) - done = add_goal_todo( - registry_path=registry, goal_id="shadow-goal", role="agent", - text="Land the prerequisite.", task_class="advancement_task", - claimed_by="agent-b", - ) - acquire_task_lease( - registry_path=registry, - runtime_root=registry.parent / "runtime", - goal_id="shadow-goal", - todo_id=done["todo_id"], - owner="agent-b", - idempotency_key="turn-lease-1", - ttl_seconds=600, - ) - complete_goal_todo( - registry_path=registry, goal_id="shadow-goal", todo_id=done["todo_id"], - agent_id="agent-b", evidence="landed", no_followup=True, - task_lease_idempotency_key="turn-lease-1", - task_lease_expected_version=1, - ) - return claimed["todo_id"], open_todo["todo_id"], done["todo_id"] - - -def test_bootstrap_from_goal_state_shadows_open_unclaimed_todos(tmp_path) -> None: - registry, state = _shadow_workspace(tmp_path) - claimed_id, open_id, done_id = _seed_todos(registry) - - built, report = bootstrap_head_from_goal_state( - state.read_text(encoding="utf-8"), - goal_id="shadow-goal", - store_binding="test:store", - repository="git:example/repo", - code_revision="0123456789abcdef", - allowed_agent_ids=["agent-a", "agent-b"], - with_report=True, - ) - - todos = built["coordination"]["todos"] - assert set(todos) == {open_id} - entry = todos[open_id] - assert entry["status"] == "open" and entry["claimed_by"] is None - assert entry["todo_revision"] == 0 and entry["last_lease_epoch"] == 0 - assert entry["eligibility"]["allowed_agent_ids"] == ["agent-a", "agent-b"] - assert validated_head(built, goal_id="shadow-goal") is built - assert report["skipped"] == {claimed_id: "claimed", done_id: "done"} - assert report["source_handoff_mode"] == "hard_lease" - - -def test_bootstrap_from_goal_state_read_parity_with_the_projection(tmp_path) -> None: - """Every open unclaimed todo in the real projection lands in the head, and - nothing else does — the RFC section 11 shadow read-parity for this slice.""" - - from loopx.status import parse_active_state_todos - - registry, state = _shadow_workspace(tmp_path) - _seed_todos(registry) - text = state.read_text(encoding="utf-8") - built = bootstrap_head_from_goal_state( - text, - goal_id="shadow-goal", - store_binding="test:store", - repository="git:example/repo", - code_revision="0123456789abcdef", - allowed_agent_ids=["agent-a"], - ) - projected = parse_active_state_todos(text)["agent_todos"]["items"] - expected = { - item["todo_id"] - for item in projected - if not item.get("done") and not item.get("claimed_by") - } - assert set(built["coordination"]["todos"]) == expected - - -def test_bootstrap_from_soft_claim_goal_fails_closed() -> None: - """A soft_claim goal's declared mode rejects lease minting locally; shared - claim_work mints a lease on every accepted claim, so migrating such a goal - would silently invert its semantics. Bootstrap refuses instead.""" - - state_text = ( - "---\ngoal_id: shadow-goal\nupdated_at: 2026-08-01T00:00:00+00:00\n" - "handoff_mode: soft_claim\n---\n\n## Agent Todo\n\n" - ) - with pytest.raises(HeadValidationError, match="soft_claim"): - bootstrap_head_from_goal_state( - state_text, - goal_id="shadow-goal", - store_binding="test:store", - repository="git:example/repo", - code_revision="0123456789abcdef", - allowed_agent_ids=["agent-a"], - ) diff --git a/tests/control_plane/test_coordination_provider_parity.py b/tests/control_plane/test_coordination_provider_parity.py deleted file mode 100644 index 64dcf52b1d..0000000000 --- a/tests/control_plane/test_coordination_provider_parity.py +++ /dev/null @@ -1,361 +0,0 @@ -"""Characterize the shipped file-backed ``claim_work`` path against the contract. - -``tests/control_plane/test_coordination_executor.py`` proves the authority -semantics with an in-memory provider, and -``test_coordination_file_provider.py`` proves the storage verbs. This fixture -closes the gap between them: one scenario matrix drives the real -``CoordinationAuthorityExecutor`` through the shipped -``FileCoordinationProvider``. - -The fixture is provider-neutral by construction. Every scenario speaks only the -storage protocol (``store_identity`` / ``load`` / ``compare_and_put``) and -receives providers from a handle factory, so registering another provider later -requires no change to the matrix or to the expectations. - -Expected outcomes are declared from the coordination domain contract (RFC -section 10, checks 1-5) before anything runs. They are never read back from -provider output, and no authority rule is re-derived here: the executor is the -only decision maker under test. - -In scope: characterization only. No production code changes, no new provider, -no default-provider or public-behavior change, and no live NoKV, credential, -service-startup, or provider-promotion surface. -""" - -from __future__ import annotations - -import copy -from dataclasses import dataclass, field -from typing import Any, Callable - -import pytest - -from loopx.control_plane.coordination.executor import ( - CoordinationAuthorityExecutor, - sample_claim_envelope, -) -from loopx.control_plane.coordination.file_provider import FileCoordinationProvider -from loopx.control_plane.coordination.head import bootstrap_head - - -GOAL_ID = "goal-a" -# Fixed wall clock: every lease expiry in this matrix is minted from it. -NOW = 1_800_000_000.0 -ELIGIBLE_AGENTS = ("agent-a", "agent-b") - - -# ---- synthetic, public-safe head fixtures ----------------------------------- - - -def eligibility(allowed: tuple[str, ...] = ELIGIBLE_AGENTS) -> dict[str, Any]: - return { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "allowed_agent_ids": list(allowed), - "dependencies_satisfied": True, - "dependency_revision": 12, - "gates_open": True, - "gate_revision": 5, - } - - -def todo(**overrides: Any) -> dict[str, Any]: - base: dict[str, Any] = { - "todo_revision": 7, - "status": "open", - "claimed_by": None, - "eligibility": eligibility(), - "repository": "git:example/repo", - "code_revision": "0123456789abcdef", - "last_lease_epoch": 6, - } - base.update(overrides) - return base - - -def claim( - agent: str = "agent-a", - todo_id: str = "todo-1", - operation_id: str | None = None, - **overrides: Any, -) -> dict[str, Any]: - return sample_claim_envelope( - goal_id=GOAL_ID, - operation_id=operation_id or f"op-{agent}-{todo_id}", - agent_id=agent, - device_id=f"dev-{agent}", - todo_id=todo_id, - expected_todo_revision=7, - expected_preconditions={ - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "dependency_revision": 12, - "gate_revision": 5, - }, - lease_ttl_seconds=600, - **overrides, - ) - - -def bootstrap(provider: Any, todo_ids: tuple[str, ...] = ("todo-1", "todo-2")) -> None: - head = bootstrap_head( - GOAL_ID, - {todo_id: todo() for todo_id in todo_ids}, - store_binding=provider.store_identity(), - ) - assert provider.compare_and_put(0, head)["result"] == "applied" - - -def executor_for(provider: Any) -> CoordinationAuthorityExecutor: - return CoordinationAuthorityExecutor(provider, goal_id=GOAL_ID, now=lambda: NOW) - - -class RecordingProvider: - """Storage-only recorder: delegates every verb and records CAS expectations. - - It holds no authority semantics; it exists so a scenario can prove that a - stale generation reached ``compare_and_put`` without duplicating a write. - """ - - def __init__(self, inner: Any) -> None: - self._inner = inner - self.cas_expectations: list[int] = [] - - def store_identity(self) -> str: - return self._inner.store_identity() - - def load(self) -> tuple[dict[str, Any] | None, int]: - return self._inner.load() - - def compare_and_put( - self, expected_provider_generation: int, head: dict[str, Any] - ) -> dict[str, Any]: - self.cas_expectations.append(expected_provider_generation) - return self._inner.compare_and_put(expected_provider_generation, head) - - -# ---- observation and expectation -------------------------------------------- - - -@dataclass -class Observation: - """What one scenario actually produced, in contract terms only.""" - - outcomes: list[dict[str, Any]] = field(default_factory=list) - head: dict[str, Any] = field(default_factory=dict) - flags: dict[str, bool] = field(default_factory=dict) - - -@dataclass(frozen=True) -class Expectation: - """The contract-derived verdict one scenario must produce.""" - - results: tuple[str, ...] - reasons: tuple[str | None, ...] - authority_revision: int - receipts: tuple[str, ...] - claimed_by: tuple[tuple[str, str | None], ...] - todo_revisions: tuple[tuple[str, int], ...] - flags: tuple[tuple[str, bool], ...] = () - - -# ---- scenarios -------------------------------------------------------------- - - -def same_target_competition(handles: Callable[[], Any]) -> Observation: - """Check 1: two actors on one target leave exactly one winner.""" - - provider = handles() - bootstrap(provider) - executor = executor_for(provider) - observation = Observation() - observation.outcomes.append(executor.apply(claim("agent-a", "todo-1"))) - observation.outcomes.append(executor.apply(claim("agent-b", "todo-1"))) - observation.head, _ = provider.load() - return observation - - -def independent_targets_rebase(handles: Callable[[], Any]) -> Observation: - """Check 2: independent targets both apply through internal rebase.""" - - provider = handles() - bootstrap(provider) - observation = Observation() - observation.outcomes.append( - executor_for(provider).apply(claim("agent-a", "todo-1")) - ) - observation.outcomes.append( - executor_for(provider).apply(claim("agent-b", "todo-2")) - ) - observation.head, _ = provider.load() - return observation - - -def replay_after_interleaved_write(handles: Callable[[], Any]) -> Observation: - """Check 3: `A -> B -> replay A` returns the exact original receipt. - - The replay runs through a freshly constructed executor and a fresh provider - handle, so nothing is served from in-process state. - """ - - provider = handles() - bootstrap(provider) - executor = executor_for(provider) - observation = Observation() - request_a = claim("agent-a", "todo-1", operation_id="op-A") - first = executor.apply(request_a) - observation.outcomes.append(first) - observation.outcomes.append( - executor.apply(claim("agent-b", "todo-2", operation_id="op-B")) - ) - reconstructed = executor_for(handles()) - replay = reconstructed.apply(copy.deepcopy(request_a)) - observation.outcomes.append(replay) - observation.flags["replay_returns_original_receipt"] = ( - replay.get("original_receipt") == first.get("original_receipt") - and replay.get("original_receipt") is not None - ) - observation.head, _ = provider.load() - return observation - - -def operation_identity_reuse(handles: Callable[[], Any]) -> Observation: - """Check 5: one operation id with different semantics changes nothing.""" - - provider = handles() - bootstrap(provider) - executor = executor_for(provider) - observation = Observation() - request = claim("agent-a", "todo-1", operation_id="op-A") - observation.outcomes.append(executor.apply(request)) - before, _ = provider.load() - mutated = copy.deepcopy(request) - mutated["command"]["lease_ttl_seconds"] = 601 - observation.outcomes.append(executor.apply(mutated)) - observation.head, _ = provider.load() - observation.flags["state_unchanged_after_rejection"] = observation.head == before - return observation - - -def stale_generation_does_not_duplicate(handles: Callable[[], Any]) -> Observation: - """Stale provider generation is refused rather than replayed. - - A handle observes a generation, the document advances behind it, and the - stale expectation is then offered for a raw CAS. The refused attempt must - not write, and the executor's bounded reload must land exactly one further - authority transition with no duplicate receipt. - """ - - provider = handles() - bootstrap(provider) - stale = RecordingProvider(handles()) - stale_generation = stale.load()[1] - - observation = Observation() - observation.outcomes.append( - executor_for(provider).apply(claim("agent-a", "todo-1")) - ) - current_head, current_generation = provider.load() - stale_attempt = stale.compare_and_put(stale_generation, current_head) - observation.flags["stale_cas_refused"] = stale_attempt["result"] == "conflict" - observation.flags["stale_cas_wrote_nothing"] = provider.load()[1] == current_generation - observation.flags["stale_expectation_offered"] = bool( - stale.cas_expectations and stale.cas_expectations[0] == stale_generation - ) - - observation.outcomes.append( - executor_for(stale).apply(claim("agent-b", "todo-2")) - ) - observation.head, _ = provider.load() - return observation - - -SCENARIOS: dict[str, Callable[[Callable[[], Any]], Observation]] = { - "same_target_competition": same_target_competition, - "independent_targets_rebase": independent_targets_rebase, - "replay_after_interleaved_write": replay_after_interleaved_write, - "operation_identity_reuse": operation_identity_reuse, - "stale_generation_does_not_duplicate": stale_generation_does_not_duplicate, -} - -EXPECTED: dict[str, Expectation] = { - "same_target_competition": Expectation( - results=("applied", "conflict"), - reasons=(None, "todo_revision_mismatch"), - authority_revision=1, - receipts=("op-agent-a-todo-1",), - claimed_by=(("todo-1", "agent-a"), ("todo-2", None)), - todo_revisions=(("todo-1", 8), ("todo-2", 7)), - ), - "independent_targets_rebase": Expectation( - results=("applied", "applied"), - reasons=(None, None), - authority_revision=2, - receipts=("op-agent-a-todo-1", "op-agent-b-todo-2"), - claimed_by=(("todo-1", "agent-a"), ("todo-2", "agent-b")), - todo_revisions=(("todo-1", 8), ("todo-2", 8)), - ), - "replay_after_interleaved_write": Expectation( - results=("applied", "applied", "already_applied"), - reasons=(None, None, None), - authority_revision=2, - receipts=("op-A", "op-B"), - claimed_by=(("todo-1", "agent-a"), ("todo-2", "agent-b")), - todo_revisions=(("todo-1", 8), ("todo-2", 8)), - # A replayed operation is a read, not a second transition. - flags=(("replay_returns_original_receipt", True),), - ), - "operation_identity_reuse": Expectation( - results=("applied", "rejected"), - reasons=(None, "operation_identity_mismatch"), - authority_revision=1, - receipts=("op-A",), - claimed_by=(("todo-1", "agent-a"), ("todo-2", None)), - todo_revisions=(("todo-1", 8), ("todo-2", 7)), - flags=(("state_unchanged_after_rejection", True),), - ), - "stale_generation_does_not_duplicate": Expectation( - results=("applied", "applied"), - reasons=(None, None), - authority_revision=2, - receipts=("op-agent-a-todo-1", "op-agent-b-todo-2"), - claimed_by=(("todo-1", "agent-a"), ("todo-2", "agent-b")), - todo_revisions=(("todo-1", 8), ("todo-2", 8)), - flags=( - ("stale_expectation_offered", True), - ("stale_cas_refused", True), - ("stale_cas_wrote_nothing", True), - ), - ), -} - - -@pytest.fixture -def handles(tmp_path) -> Callable[[], Any]: - """Return a factory for fresh provider handles onto one shared store.""" - - directory = tmp_path / "coordination" - return lambda: FileCoordinationProvider(directory, GOAL_ID) - - -@pytest.mark.parametrize("name", sorted(SCENARIOS)) -def test_claim_work_contract_holds_on_the_shipped_provider(name, handles): - expected = EXPECTED[name] - observation = SCENARIOS[name](handles) - - assert tuple(item["result"] for item in observation.outcomes) == expected.results - assert tuple(item.get("reason") for item in observation.outcomes) == expected.reasons - assert observation.head["authority_revision"] == expected.authority_revision - assert tuple(sorted(observation.head["receipt_index"])) == expected.receipts - - todos = observation.head["coordination"]["todos"] - assert tuple( - (todo_id, todos[todo_id]["claimed_by"]) for todo_id, _ in expected.claimed_by - ) == expected.claimed_by - assert tuple( - (todo_id, todos[todo_id]["todo_revision"]) - for todo_id, _ in expected.todo_revisions - ) == expected.todo_revisions - - for flag, value in expected.flags: - assert observation.flags.get(flag) is value, flag diff --git a/tests/control_plane/test_coordination_recoverable_execution.py b/tests/control_plane/test_coordination_recoverable_execution.py deleted file mode 100644 index d61fe821df..0000000000 --- a/tests/control_plane/test_coordination_recoverable_execution.py +++ /dev/null @@ -1,1046 +0,0 @@ -"""Stage 3 recoverable execution ownership over the shared coordination head. - -The horizon contract (RFC section 1.2): renew, release, expired-lease -reclaim, stale-fence rejection, and atomic completion with an accepted -continuation/evidence pointer - proven by crash and clock-boundary tests -showing that a superseded executor cannot write back. Every domain decision -is delegated to the Stage 1 core; the executor owns expiry adjudication -(its clock, the loaded head's expires_at, plus the reclaim grace window), -the store-lineage binding fence, and the aggregate writeback. -""" - -from __future__ import annotations - -import copy -import json -import threading -from pathlib import Path - -import pytest - -from loopx.control_plane.coordination.executor import ( - CoordinationAuthorityExecutor, - EnvelopeError, - sample_claim_envelope, - sample_work_envelope, -) -from loopx.control_plane.coordination.head import ( - HeadMigrationRequired, - HeadValidationError, - bootstrap_head, - head_digest, - migrate_head_v0_to_v1, - validated_head, -) -from loopx.control_plane.todos.durable_completion import ( - project_durable_completion_outcome, -) - - -def eligibility(allowed=("agent-a", "agent-b")) -> dict: - return { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "allowed_agent_ids": list(allowed), - "dependencies_satisfied": True, - "dependency_revision": 12, - "gates_open": True, - "gate_revision": 5, - } - - -def todo(**overrides) -> dict: - base = { - "todo_revision": 7, - "status": "open", - "claimed_by": None, - "eligibility": eligibility(), - "repository": "git:example/repo", - "code_revision": "0123456789abcdef", - "last_lease_epoch": 6, - } - base.update(overrides) - return base - - -class FakeProvider: - def __init__(self): - self._head = None - self._generation = 0 - self._lock = threading.Lock() - self.identity = "test:store" - - def store_identity(self) -> str: - return self.identity - - def load(self): - with self._lock: - return copy.deepcopy(self._head), self._generation - - def compare_and_put(self, expected_generation, head): - with self._lock: - if expected_generation != self._generation: - return { - "result": "conflict", - "current_provider_generation": self._generation, - } - self._head = copy.deepcopy(head) - self._generation += 1 - return {"result": "applied", "provider_generation": self._generation} - - -class Clock: - def __init__(self, value: float = 1_800_000_000.0): - self.value = value - - def __call__(self) -> float: - return self.value - - -PRECONDITIONS = { - "authorization_projection_revision": 3, - "authorization_projection_digest": "sha256:bootstrap-auth", - "dependency_revision": 12, - "gate_revision": 5, -} - - -def bootstrap(provider, todos=("todo-1", "todo-2")) -> dict: - head = bootstrap_head( - "goal-a", - {todo_id: todo() for todo_id in todos}, - store_binding=provider.store_identity(), - ) - assert provider.compare_and_put(0, head)["result"] == "applied" - return head - - -def executor_for(provider, clock, **kwargs) -> CoordinationAuthorityExecutor: - return CoordinationAuthorityExecutor( - provider, goal_id="goal-a", now=clock, **kwargs - ) - - -def claim(executor, agent, todo_id, operation_id, *, revision=7, ttl=600): - return executor.apply(sample_claim_envelope( - goal_id="goal-a", operation_id=operation_id, agent_id=agent, - device_id=f"dev-{agent}", todo_id=todo_id, - expected_todo_revision=revision, - expected_preconditions=copy.deepcopy(PRECONDITIONS), - lease_ttl_seconds=ttl, - )) - - -def verb(executor, agent, operation_id, command): - return executor.apply(sample_work_envelope( - goal_id="goal-a", operation_id=operation_id, agent_id=agent, - device_id=f"dev-{agent}", command=command, - )) - - -def claimed_fixture(ttl=600): - """provider, clock, executor, and the fence from a fresh claim.""" - - provider = FakeProvider() - bootstrap(provider) - clock = Clock() - executor = executor_for(provider, clock) - first = claim(executor, "agent-a", "todo-1", "op-claim", ttl=ttl) - assert first["result"] == "applied", first - receipt = first["original_receipt"] - fence = {"lease_id": receipt["lease_id"], "lease_epoch": receipt["lease_epoch"]} - return provider, clock, executor, fence - - -def head_of(provider): - head, _generation = provider.load() - return head - - -# ---- renew ------------------------------------------------------------------ - - -def test_renew_extends_expiry_without_minting_a_new_epoch() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - before = head_of(provider) - clock.value += 500 - renewed = verb(executor, "agent-a", "op-renew", { - "type": "renew_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "lease_ttl_seconds": 600, - }) - assert renewed["result"] == "applied", renewed - head = head_of(provider) - lease = head["coordination"]["leases"]["todo-1"] - assert lease["lease_epoch"] == fence["lease_epoch"] - assert lease["lease_id"] == fence["lease_id"] - assert lease["expires_at"] > before["coordination"]["leases"]["todo-1"]["expires_at"] - # Renewal advances todo_revision: the validity interval is a revision- - # covered fact, so a reclaim carrying pre-renew observations conflicts - # instead of surviving the internal rebase (RFC section 6.4). - assert head["coordination"]["todos"]["todo-1"]["todo_revision"] == 9 - assert renewed["original_receipt"]["command"] == "renew_work" - validated_head(head, goal_id="goal-a") - - replay = verb(executor, "agent-a", "op-renew", { - "type": "renew_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "lease_ttl_seconds": 600, - }) - assert replay["result"] == "already_applied" - assert replay["original_receipt"] == renewed["original_receipt"] - - -def test_renew_of_an_expired_lease_is_rejected() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - clock.value += 601 - rejected = verb(executor, "agent-a", "op-late-renew", { - "type": "renew_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "lease_ttl_seconds": 600, - }) - assert rejected["result"] == "rejected" - assert rejected["reason"] == "lease_not_active" - - -def test_renew_by_a_non_holder_is_rejected() -> None: - provider, clock, executor, fence = claimed_fixture() - rejected = verb(executor, "agent-b", "op-steal-renew", { - "type": "renew_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "lease_ttl_seconds": 600, - }) - assert rejected["result"] == "rejected" - assert rejected["reason"] == "not_lease_holder" - - -def test_renew_with_a_wrong_fence_is_a_stale_fence() -> None: - provider, clock, executor, fence = claimed_fixture() - rejected = verb(executor, "agent-a", "op-bad-fence", { - "type": "renew_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": "lease_forged", - "expected_lease_epoch": fence["lease_epoch"], - "lease_ttl_seconds": 600, - }) - assert rejected["result"] == "rejected" - assert rejected["reason"] == "stale_lease_fence" - - -# ---- release ---------------------------------------------------------------- - - -def test_release_clears_claim_and_keeps_the_epoch_watermark() -> None: - provider, clock, executor, fence = claimed_fixture() - released = verb(executor, "agent-a", "op-release", { - "type": "release_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - }) - assert released["result"] == "applied", released - head = head_of(provider) - record = head["coordination"]["todos"]["todo-1"] - assert record["claimed_by"] is None and record["status"] == "open" - assert record["last_lease_epoch"] == fence["lease_epoch"] - assert "todo-1" not in head["coordination"]["leases"] - validated_head(head, goal_id="goal-a") - - # No-ABA across release: the next claim mints strictly above the - # watermark even though the lease record is gone. - reclaimed = claim(executor, "agent-b", "todo-1", "op-reclaim-after-release", - revision=9) - assert reclaimed["result"] == "applied" - assert ( - reclaimed["original_receipt"]["lease_epoch"] == fence["lease_epoch"] + 1 - ) - - -def test_release_of_an_expired_lease_is_rejected() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - clock.value += 601 - rejected = verb(executor, "agent-a", "op-late-release", { - "type": "release_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - }) - assert rejected["result"] == "rejected" - assert rejected["reason"] == "lease_not_active" - - -# ---- reclaim and the clock boundary ----------------------------------------- - - -def reclaim_command(revision=9): - return { - "type": "reclaim_work", "todo_id": "todo-1", - "expected_todo_revision": revision, - "expected_preconditions": copy.deepcopy(PRECONDITIONS), - "lease_ttl_seconds": 600, - } - - -def test_reclaim_honors_the_grace_window_clock_boundaries() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - base = clock.value - - # Active lease: not reclaimable. - clock.value = base + 300 - active = verb(executor, "agent-b", "op-r1", reclaim_command(revision=8)) - assert active["result"] == "rejected" - assert active["reason"] == "lease_not_reclaimable" - - # Exactly at expiry: expired, but inside the grace window. - clock.value = base + 600 - at_expiry = verb(executor, "agent-b", "op-r2", reclaim_command(revision=8)) - assert at_expiry["result"] == "rejected" - assert at_expiry["reason"] == "lease_not_reclaimable" - - # One tick before the grace elapses: still not reclaimable. - clock.value = base + 600 + 29.999 - inside_grace = verb(executor, "agent-b", "op-r3", reclaim_command(revision=8)) - assert inside_grace["result"] == "rejected" - assert inside_grace["reason"] == "lease_not_reclaimable" - - # At expiry plus the full grace: reclaimable. - clock.value = base + 600 + 30.0 - reclaimed = verb(executor, "agent-b", "op-r4", reclaim_command(revision=8)) - assert reclaimed["result"] == "applied", reclaimed - receipt = reclaimed["original_receipt"] - assert receipt["command"] == "reclaim_work" - assert receipt["lease_epoch"] == fence["lease_epoch"] + 1 - assert receipt["superseded_owner"] == "agent-a" - assert receipt["superseded_lease_epoch"] == fence["lease_epoch"] - head = head_of(provider) - record = head["coordination"]["todos"]["todo-1"] - assert record["claimed_by"] == "agent-b" - assert record["last_lease_epoch"] == fence["lease_epoch"] + 1 - assert head["coordination"]["leases"]["todo-1"]["owner"] == "agent-b" - validated_head(head, goal_id="goal-a") - - -def test_reclaim_grace_is_configurable_and_recorded() -> None: - provider = FakeProvider() - bootstrap(provider) - clock = Clock() - executor = executor_for(provider, clock, reclaim_grace_seconds=5.0) - first = claim(executor, "agent-a", "todo-1", "op-claim", ttl=100) - assert first["result"] == "applied" - clock.value += 104.9 - early = verb(executor, "agent-b", "op-early", reclaim_command(revision=8)) - assert early["reason"] == "lease_not_reclaimable" - assert early["reclaim_grace_seconds"] == 5.0 - clock.value += 0.1 - assert verb(executor, "agent-b", "op-take", reclaim_command(revision=8))[ - "result" - ] == "applied" - - -def test_reclaim_of_an_unclaimed_todo_points_to_claim_work() -> None: - provider = FakeProvider() - bootstrap(provider) - executor = executor_for(provider, Clock()) - rejected = verb(executor, "agent-b", "op-noclaim", reclaim_command(revision=7)) - assert rejected["result"] == "rejected" - assert rejected["reason"] == "todo_not_claimed" - - -def test_reclaim_by_an_ineligible_actor_is_rejected() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - clock.value += 700 - rejected = verb(executor, "agent-z", "op-outsider", reclaim_command(revision=8)) - assert rejected["result"] == "rejected" - assert rejected["reason"] == "actor_ineligible" - - -def test_reclaim_with_stale_preconditions_conflicts() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - clock.value += 700 - command = reclaim_command(revision=8) - command["expected_preconditions"]["gate_revision"] = 4 - stale = verb(executor, "agent-b", "op-stale-pre", command) - assert stale["result"] == "conflict" - assert stale["reason"] == "precondition_snapshot_mismatch" - - -# ---- the superseded executor cannot write back ------------------------------ - - -def test_superseded_executor_writes_are_fenced_terminally() -> None: - """The Stage 3 horizon proof: after a reclaim, every write the old - holder sends with its old fence is a typed stale_lease_fence rejection - that never rebases past, and the head is untouched by any of them.""" - - provider, clock, executor, fence = claimed_fixture(ttl=600) - clock.value += 700 - reclaimed = verb(executor, "agent-b", "op-take", reclaim_command(revision=8)) - assert reclaimed["result"] == "applied" - settled = provider.load() - - stale_fence = { - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - } - stale_writes = [ - ("op-a-renew", {"type": "renew_work", "todo_id": "todo-1", - "expected_todo_revision": 9, **stale_fence, - "lease_ttl_seconds": 600}), - ("op-a-release", {"type": "release_work", "todo_id": "todo-1", - "expected_todo_revision": 9, **stale_fence}), - ("op-a-complete", {"type": "complete_work", "todo_id": "todo-1", - "expected_todo_revision": 9, **stale_fence, - "no_followup": False, "successor_todo_ids": [], - "evidence": None}), - ] - for operation_id, command in stale_writes: - outcome = verb(executor, "agent-a", operation_id, command) - assert outcome["result"] == "rejected", (operation_id, outcome) - assert outcome["reason"] == "stale_lease_fence", (operation_id, outcome) - assert provider.load() == settled - - # The old holder cannot even re-claim: the todo is claimed by agent-b. - retry = claim(executor, "agent-a", "todo-1", "op-a-retry", revision=9) - assert retry["result"] == "rejected" - assert retry["reason"] == "todo_not_open" - - -# ---- completion ------------------------------------------------------------- - - -def complete_command(*, revision, fence, no_followup=False, successors=(), - evidence=None): - return { - "type": "complete_work", "todo_id": "todo-1", - "expected_todo_revision": revision, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "no_followup": no_followup, - "successor_todo_ids": list(successors), - "evidence": evidence, - } - - -def test_complete_with_successor_is_one_atomic_transition() -> None: - provider, clock, executor, fence = claimed_fixture() - evidence = { - "pointer": "artifact://public/runs/run-1/report", - "digest": "sha256:" + "a" * 64, - "privacy_class": "public", - } - done = verb(executor, "agent-a", "op-done", complete_command( - revision=8, fence=fence, successors=("todo_next01",), evidence=evidence, - )) - assert done["result"] == "applied", done - assert done["original_receipt"]["completion_continuation"] == "successor" - head = head_of(provider) - record = head["coordination"]["todos"]["todo-1"] - assert record["status"] == "done" - assert record["claimed_by"] == "agent-a" - assert record["completion_continuation"] == "successor" - assert record["successor_todo_ids"] == ["todo_next01"] - assert record["evidence"] == evidence - assert "todo-1" not in head["coordination"]["leases"] - successor = head["coordination"]["todos"]["todo_next01"] - assert successor["status"] == "open" and successor["claimed_by"] is None - assert successor["todo_revision"] == 0 and successor["last_lease_epoch"] == 0 - assert successor["eligibility"] == record["eligibility"] - validated_head(head, goal_id="goal-a") - - # The successor is immediately claimable in the same shared head. - follow = claim(executor, "agent-b", "todo_next01", "op-follow", revision=0) - assert follow["result"] == "applied" - - -def test_completed_record_projects_through_the_production_seam() -> None: - """The shared head's done records satisfy the local durable-completion - projection exactly - the read-side seam both worlds share. The seam - accepts public todo ids only, so this chain uses them throughout (as - bootstrap_head_from_goal_state does for every real migration).""" - - provider = FakeProvider() - head = bootstrap_head( - "goal-a", {"todo_parent01": todo()}, - store_binding=provider.store_identity(), - ) - assert provider.compare_and_put(0, head)["result"] == "applied" - clock = Clock() - executor = executor_for(provider, clock) - first = claim(executor, "agent-a", "todo_parent01", "op-claim") - assert first["result"] == "applied" - receipt = first["original_receipt"] - done = verb(executor, "agent-a", "op-done", { - "type": "complete_work", "todo_id": "todo_parent01", - "expected_todo_revision": 8, - "lease_id": receipt["lease_id"], - "expected_lease_epoch": receipt["lease_epoch"], - "no_followup": False, "successor_todo_ids": ["todo_next01"], - "evidence": None, - }) - assert done["result"] == "applied", done - todos = head_of(provider)["coordination"]["todos"] - outcome = project_durable_completion_outcome( - todo={"todo_id": "todo_parent01", **todos["todo_parent01"]}, - expected_todo_id="todo_parent01", - existing_todo_ids=set(todos), - ) - assert outcome == { - "todo_id": "todo_parent01", - "continuation": "successor", - "successor_todo_ids": ["todo_next01"], - } - - -def test_complete_no_followup_and_active_goal_paths() -> None: - provider, clock, executor, fence = claimed_fixture() - done = verb(executor, "agent-a", "op-done", complete_command( - revision=8, fence=fence, no_followup=True, - )) - assert done["result"] == "applied" - record = head_of(provider)["coordination"]["todos"]["todo-1"] - assert record["completion_continuation"] == "no_followup" - assert record["no_followup"] is True - - second = claim(executor, "agent-b", "todo-2", "op-c2") - fence2 = { - "lease_id": second["original_receipt"]["lease_id"], - "lease_epoch": second["original_receipt"]["lease_epoch"], - } - active_goal = verb(executor, "agent-b", "op-done-2", { - "type": "complete_work", "todo_id": "todo-2", - "expected_todo_revision": 8, - "lease_id": fence2["lease_id"], - "expected_lease_epoch": fence2["lease_epoch"], - "no_followup": False, "successor_todo_ids": [], "evidence": None, - }) - assert active_goal["result"] == "applied" - record = head_of(provider)["coordination"]["todos"]["todo-2"] - assert record["completion_continuation"] == "active_goal" - assert "no_followup" not in record and "successor_todo_ids" not in record - validated_head(head_of(provider), goal_id="goal-a") - - -def test_complete_contradiction_and_bad_successors_fail_closed() -> None: - provider, clock, executor, fence = claimed_fixture() - with pytest.raises(EnvelopeError, match="cannot record both"): - verb(executor, "agent-a", "op-both", complete_command( - revision=8, fence=fence, no_followup=True, successors=("todo_x01",), - )) - with pytest.raises(EnvelopeError, match="public todo ids"): - verb(executor, "agent-a", "op-badid", complete_command( - revision=8, fence=fence, successors=("Not A Todo",), - )) - with pytest.raises(EnvelopeError, match="evidence"): - verb(executor, "agent-a", "op-badev", complete_command( - revision=8, fence=fence, - evidence={"pointer": "x", "digest": "nope", "privacy_class": "p"}, - )) - # A colliding-but-valid id reaches the semantic rejection (an invalid - # id like "todo-2" would already fail the envelope pattern above). - provider2, clock2, executor2, fence2 = claimed_fixture() - head = head_of(provider2) - head["coordination"]["todos"]["todo_next01"] = todo(todo_revision=0, - last_lease_epoch=0) - assert provider2.compare_and_put(2, head)["result"] == "applied" - clash = verb(executor2, "agent-a", "op-clash2", complete_command( - revision=8, fence=fence2, successors=("todo_next01",), - )) - assert clash["result"] == "rejected" - assert clash["reason"] == "successor_todo_exists" - - -def test_complete_with_an_expired_lease_is_rejected() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - clock.value += 601 - late = verb(executor, "agent-a", "op-late-done", complete_command( - revision=8, fence=fence, - )) - assert late["result"] == "rejected" - assert late["reason"] == "lease_not_active" - - -def test_continuation_rule_matches_the_local_facade() -> None: - """The executor mirrors the TS-owned continuation rule; this pin keeps - the two from drifting on any input combination.""" - - from loopx.control_plane.coordination.executor import _continuation_for_write - - try: - from loopx.control_plane.todos.completion_state import ( - completion_continuation_for_write, - ) - - facade = { - (False, False): completion_continuation_for_write( - no_followup=False, has_successor=False), - (False, True): completion_continuation_for_write( - no_followup=False, has_successor=True), - (True, False): completion_continuation_for_write( - no_followup=True, has_successor=False), - } - except Exception: # noqa: BLE001 - the TS effect runtime is optional here - pytest.skip("TypeScript effect runtime unavailable") - assert facade == { - (False, False): "active_goal", - (False, True): "successor", - (True, False): "no_followup", - } - assert _continuation_for_write(no_followup=False, has_successor=False) == "active_goal" - assert _continuation_for_write(no_followup=False, has_successor=True) == "successor" - assert _continuation_for_write(no_followup=True, has_successor=False) == "no_followup" - with pytest.raises(EnvelopeError): - _continuation_for_write(no_followup=True, has_successor=True) - - -# ---- the store-lineage binding fence ---------------------------------------- - - -def test_restored_lineage_fails_closed_on_every_verb() -> None: - provider, clock, executor, fence = claimed_fixture() - provider.identity = "test:restored-copy" - for operation_id, envelope_command in [ - ("op-f1", None), # claim_work via helper below - ("op-f2", {"type": "renew_work", "todo_id": "todo-1", - "expected_todo_revision": 8, **{ - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"]}, - "lease_ttl_seconds": 600}), - ("op-f3", {"type": "complete_work", "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "no_followup": True, "successor_todo_ids": [], - "evidence": None}), - ]: - if envelope_command is None: - outcome = claim(executor, "agent-b", "todo-2", operation_id) - else: - outcome = verb(executor, "agent-a", operation_id, envelope_command) - assert outcome["result"] == "failed", (operation_id, outcome) - assert outcome["reason"] == "store_lineage_mismatch" - assert outcome["head_store_binding"] == "test:store" - assert outcome["provider_store_identity"] == "test:restored-copy" - - -def test_bootstrap_binds_the_provider_identity() -> None: - provider = FakeProvider() - provider.identity = "test:lineage-42" - head = bootstrap_head( - "goal-a", {"todo-1": todo()}, store_binding=provider.store_identity() - ) - assert head["store_binding"] == "test:lineage-42" - - -# ---- the full recoverable lifecycle ----------------------------------------- - - -def test_full_lifecycle_chain_with_exact_receipt_replay() -> None: - """claim -> renew -> reclaim (after expiry) -> complete by the new owner, - with every receipt replayable field-for-field afterwards.""" - - provider, clock, executor, fence = claimed_fixture(ttl=600) - - renewed = verb(executor, "agent-a", "op-renew", { - "type": "renew_work", "todo_id": "todo-1", "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "lease_ttl_seconds": 600, - }) - assert renewed["result"] == "applied" - - clock.value += 600 + 31 - reclaimed = verb(executor, "agent-b", "op-take", reclaim_command(revision=9)) - assert reclaimed["result"] == "applied" - new_fence = { - "lease_id": reclaimed["original_receipt"]["lease_id"], - "lease_epoch": reclaimed["original_receipt"]["lease_epoch"], - } - - done = verb(executor, "agent-b", "op-finish", { - "type": "complete_work", "todo_id": "todo-1", - "expected_todo_revision": 10, - "lease_id": new_fence["lease_id"], - "expected_lease_epoch": new_fence["lease_epoch"], - "no_followup": False, "successor_todo_ids": ["todo_next01"], - "evidence": None, - }) - assert done["result"] == "applied", done - - head = head_of(provider) - assert head["authority_revision"] == 4 - assert len(head["receipt_index"]) == 4 - validated_head(head, goal_id="goal-a") - - replays = { - "op-renew": renewed, "op-take": reclaimed, "op-finish": done, - } - fresh = executor_for(provider, clock) - replayed_renew = verb(fresh, "agent-a", "op-renew", { - "type": "renew_work", "todo_id": "todo-1", "expected_todo_revision": 8, - "lease_id": fence["lease_id"], - "expected_lease_epoch": fence["lease_epoch"], - "lease_ttl_seconds": 600, - }) - assert replayed_renew["result"] == "already_applied" - assert replayed_renew["original_receipt"] == renewed["original_receipt"] - for operation_id, original in replays.items(): - entry = head["receipt_index"][operation_id]["original_receipt"] - assert entry == original["original_receipt"], operation_id - - -# ---- reclaim grace configuration boundary ----------------------------------- - - -def test_illegal_reclaim_grace_is_rejected_at_construction() -> None: - # NaN makes `expired_for < grace` always false (every active lease - # becomes reclaimable); negative grace advances the takeover before - # expiry; bool is the classic coercion accident. All fail closed. - provider = FakeProvider() - for bad in ( - float("nan"), float("inf"), float("-inf"), -1, -0.001, True, False, - "30", None, 10**400, - ): - with pytest.raises(ValueError): - executor_for(provider, Clock(), reclaim_grace_seconds=bad) - - -def test_zero_grace_is_legal_but_never_reclaims_an_active_lease() -> None: - provider, clock, executor, fence = claimed_fixture(ttl=600) - zero = executor_for(provider, clock, reclaim_grace_seconds=0) - # The clock has not advanced: the 600s lease is fully active, and the - # smallest accepted grace still refuses the takeover. - grab = verb(zero, "agent-b", "op-grab-active", reclaim_command(revision=8)) - assert grab["result"] == "rejected" - assert grab["reason"] == "lease_not_reclaimable" - assert head_of(provider)["coordination"]["leases"]["todo-1"]["owner"] == "agent-a" - - -# ---- evidence portability boundary ------------------------------------------ - - -GOOD_DIGEST = "sha256:" + "a" * 64 - - -def test_evidence_rejects_host_paths_and_unknown_privacy_classes() -> None: - provider, clock, executor, fence = claimed_fixture() - rejected = [ - # The reviewer's exact reproduction: an absolute local path with a - # typo'd privacy class must never enter the shared head. - {"pointer": "/private/example/secret.log", "digest": GOOD_DIGEST, - "privacy_class": "publci"}, - {"pointer": "/private/example/secret.log", "digest": GOOD_DIGEST, - "privacy_class": "public"}, - {"pointer": "file:///private/example/secret.log", - "digest": GOOD_DIGEST, "privacy_class": "private"}, - {"pointer": "FILE:///etc/passwd", "digest": GOOD_DIGEST, - "privacy_class": "private"}, - {"pointer": "c:\\runs\\report.log", "digest": GOOD_DIGEST, - "privacy_class": "public"}, - {"pointer": "runs/report.log", "digest": GOOD_DIGEST, - "privacy_class": "public"}, - {"pointer": "~/report.log", "digest": GOOD_DIGEST, - "privacy_class": "public"}, - {"pointer": "artifact://public/runs/run-1/report", "digest": GOOD_DIGEST, - "privacy_class": "PUBLIC"}, - {"pointer": "artifact://public/runs/run-1/report", "digest": GOOD_DIGEST, - "privacy_class": "internal"}, - {"pointer": "artifact://public/runs/run-1/report", "digest": GOOD_DIGEST, - "privacy_class": ""}, - ] - for evidence in rejected: - with pytest.raises(EnvelopeError): - verb(executor, "agent-a", "op-bad-evidence", complete_command( - revision=8, fence=fence, evidence=evidence, - )) - # Nothing above may have landed: the todo is still open and claimed. - head = head_of(provider) - assert head["coordination"]["todos"]["todo-1"]["status"] == "open" - - accepted = verb(executor, "agent-a", "op-good-evidence", complete_command( - revision=8, fence=fence, no_followup=True, evidence={ - "pointer": "artifact://private/nokv/wb-goals/goal-a/report", - "digest": GOOD_DIGEST, "privacy_class": "private", - }, - )) - assert accepted["result"] == "applied", accepted - - -def test_head_with_a_host_path_evidence_pointer_fails_closed() -> None: - provider, clock, executor, fence = claimed_fixture() - done = verb(executor, "agent-a", "op-done-evidence", complete_command( - revision=8, fence=fence, no_followup=True, evidence={ - "pointer": "artifact://public/runs/run-1/report", - "digest": GOOD_DIGEST, "privacy_class": "public", - }, - )) - assert done["result"] == "applied", done - head = head_of(provider) - validated_head(head, goal_id="goal-a") - # The boundary and head validation are one oracle: a pointer the - # envelope refuses can never validate out of a stored head either. - corrupted = copy.deepcopy(head) - corrupted["coordination"]["todos"]["todo-1"]["evidence"]["pointer"] = ( - "/private/example/secret.log" - ) - with pytest.raises(HeadValidationError): - validated_head(corrupted, goal_id="goal-a") - - -def test_evidence_pointer_binds_its_declared_privacy_class() -> None: - provider, clock, executor, fence = claimed_fixture() - rejected = [ - # An arbitrary URI scheme is not a reviewed artifact contract. - {"pointer": "https://localhost/private/report", "privacy_class": "public"}, - {"pointer": "nokv://private-workbench/secret", "privacy_class": "public"}, - {"pointer": "artifact:/etc/passwd", "privacy_class": "private"}, - # The URI's typed privacy namespace and the sibling enum must agree. - {"pointer": "artifact://private/runs/secret", "privacy_class": "public"}, - {"pointer": "artifact://public/runs/report", "privacy_class": "private"}, - # Opaque ids stay bounded and cannot smuggle traversal or URI metadata. - {"pointer": "artifact://public/../secret", "privacy_class": "public"}, - {"pointer": "artifact://public/report?format=json", "privacy_class": "public"}, - ] - for index, item in enumerate(rejected): - with pytest.raises(EnvelopeError): - verb( - executor, - "agent-a", - f"op-evidence-privacy-{index}", - complete_command( - revision=8, - fence=fence, - evidence={"digest": GOOD_DIGEST, **item}, - ), - ) - - for index, evidence in enumerate( - ( - { - "pointer": "artifact://public/runs/run-1/report", - "digest": GOOD_DIGEST, - "privacy_class": "public", - }, - { - "pointer": "artifact://private/nokv/wb-goals/goal-a/report", - "digest": GOOD_DIGEST, - "privacy_class": "private", - }, - ) - ): - isolated_provider, isolated_clock, isolated_executor, isolated_fence = ( - claimed_fixture() - ) - accepted = verb( - isolated_executor, - "agent-a", - f"op-evidence-valid-{index}", - complete_command( - revision=8, - fence=isolated_fence, - no_followup=True, - evidence=evidence, - ), - ) - assert accepted["result"] == "applied", accepted - - -# ---- legacy v0 heads and the explicit store-binding migration --------------- - - -def legacy_v0_head() -> dict: - """The exact Stage 2 shape: today's head minus the store binding.""" - - head = bootstrap_head( - "goal-a", {"todo-1": todo()}, store_binding="test:store", - ) - del head["store_binding"] - head["schema_version"] = "loopx_coordination_head_v0" - return head - - -def frozen_stage2_claimed_head() -> dict: - fixture_path = ( - Path(__file__).parents[1] - / "fixtures" - / "coordination_head_stage2_v0_claimed.json" - ) - return json.loads(fixture_path.read_text(encoding="utf-8")) - - -def test_a_legacy_v0_head_is_classified_not_crashed() -> None: - provider = FakeProvider() - assert provider.compare_and_put(0, legacy_v0_head())["result"] == "applied" - executor = executor_for(provider, Clock()) - outcome = claim(executor, "agent-a", "todo-1", "op-on-legacy") - assert outcome == { - "result": "failed", - "reason": "head_schema_migration_required", - "provider_generation": 1, - } - with pytest.raises(HeadMigrationRequired): - validated_head(legacy_v0_head(), goal_id="goal-a") - - -def test_only_a_valid_stage2_v0_head_is_classified_as_migratable() -> None: - valid = legacy_v0_head() - with pytest.raises(HeadMigrationRequired): - validated_head(valid, goal_id="goal-a") - - corruptions = [] - wrong_goal = copy.deepcopy(valid) - wrong_goal["goal_id"] = "goal-other" - corruptions.append(wrong_goal) - missing_field = copy.deepcopy(valid) - del missing_field["receipt_index"] - corruptions.append(missing_field) - extra_field = copy.deepcopy(valid) - extra_field["unreviewed"] = True - corruptions.append(extra_field) - smuggled_binding = copy.deepcopy(valid) - smuggled_binding["store_binding"] = "smuggled:binding" - corruptions.append(smuggled_binding) - - for corrupted in corruptions: - with pytest.raises(HeadValidationError) as exc_info: - validated_head(corrupted, goal_id="goal-a") - assert not isinstance(exc_info.value, HeadMigrationRequired) - - -def test_stage3_fields_cannot_be_smuggled_under_the_legacy_v0_token() -> None: - provider, _clock, executor, fence = claimed_fixture() - completed = verb( - executor, - "agent-a", - "op-stage3-done", - complete_command(revision=8, fence=fence, no_followup=True), - ) - assert completed["result"] == "applied" - stage3_shaped_v0 = head_of(provider) - stage3_shaped_v0["schema_version"] = "loopx_coordination_head_v0" - del stage3_shaped_v0["store_binding"] - with pytest.raises(HeadValidationError) as exc_info: - validated_head(stage3_shaped_v0, goal_id="goal-a") - assert not isinstance(exc_info.value, HeadMigrationRequired) - - provider2, _clock2, executor2, _fence2 = claimed_fixture() - non_claim_receipt_v0 = head_of(provider2) - non_claim_receipt_v0["schema_version"] = "loopx_coordination_head_v0" - del non_claim_receipt_v0["store_binding"] - receipt = non_claim_receipt_v0["receipt_index"]["op-claim"][ - "original_receipt" - ] - receipt["command"] = "renew_work" - with pytest.raises(HeadValidationError) as exc_info: - validated_head(non_claim_receipt_v0, goal_id="goal-a") - assert not isinstance(exc_info.value, HeadMigrationRequired) - - -def test_explicit_migration_upgrades_a_v0_head_end_to_end() -> None: - provider = FakeProvider() - assert provider.compare_and_put(0, legacy_v0_head())["result"] == "applied" - # The operator path: load, attest the reviewed store's own identity, - # migrate, and write back through the same CAS. - stale, generation = provider.load() - migrated = migrate_head_v0_to_v1( - stale, goal_id="goal-a", store_binding=provider.store_identity(), - ) - assert migrated["schema_version"] == "loopx_coordination_head_v1" - assert stale.get("store_binding") is None # input is never mutated - assert provider.compare_and_put(generation, migrated)["result"] == "applied" - executor = executor_for(provider, Clock()) - first = claim(executor, "agent-a", "todo-1", "op-post-migration") - assert first["result"] == "applied", first - - -def test_frozen_stage2_claimed_head_migrates_without_rewriting_history() -> None: - legacy = frozen_stage2_claimed_head() - assert head_digest(legacy) == ( - "sha256:a10866d23d0d61b8352163ef64c93b05656c6bc8717b2a944ba987dd5444aee6" - ) - with pytest.raises(HeadMigrationRequired): - validated_head(legacy, goal_id="goal-a") - - provider = FakeProvider() - migrated = migrate_head_v0_to_v1( - legacy, - goal_id="goal-a", - store_binding=provider.store_identity(), - ) - assert migrated["receipt_index"] == legacy["receipt_index"] - assert migrated["coordination"] == legacy["coordination"] - assert provider.compare_and_put(0, migrated)["result"] == "applied" - - renewed = verb( - executor_for(provider, Clock()), - "agent-a", - "op-after-stage2-migration", - { - "type": "renew_work", - "todo_id": "todo-1", - "expected_todo_revision": 8, - "lease_id": "lease_8c5b438a43110ce57000c32a", - "expected_lease_epoch": 7, - "lease_ttl_seconds": 600, - }, - ) - assert renewed["result"] == "applied", renewed - - -@pytest.mark.parametrize( - "corruption", - [ - "lease_watermark", - "unproved_owner", - "unproved_expiry", - "authority_revision", - "missing_lease", - "unclaimed_with_lease", - ], -) -def test_legacy_migration_requires_a_receipt_proved_live_claim( - corruption: str, -) -> None: - legacy = frozen_stage2_claimed_head() - todo_record = legacy["coordination"]["todos"]["todo-1"] - lease_record = legacy["coordination"]["leases"]["todo-1"] - if corruption == "lease_watermark": - todo_record["last_lease_epoch"] = 6 - elif corruption == "unproved_owner": - todo_record["claimed_by"] = "agent-b" - lease_record["owner"] = "agent-b" - elif corruption == "unproved_expiry": - lease_record["expires_at"] = "2027-01-15T09:10:00.000Z" - elif corruption == "authority_revision": - legacy["authority_revision"] = 0 - elif corruption == "missing_lease": - del legacy["coordination"]["leases"]["todo-1"] - else: - todo_record["claimed_by"] = None - - with pytest.raises(HeadValidationError) as exc_info: - validated_head(legacy, goal_id="goal-a") - assert not isinstance(exc_info.value, HeadMigrationRequired) - - -def test_migration_refuses_anything_but_a_clean_v0_document() -> None: - v1 = bootstrap_head("goal-a", {"todo-1": todo()}, store_binding="test:store") - with pytest.raises(HeadValidationError): - migrate_head_v0_to_v1(v1, goal_id="goal-a", store_binding="test:store") - already_bound = legacy_v0_head() - already_bound["store_binding"] = "smuggled:binding" - with pytest.raises(HeadValidationError): - migrate_head_v0_to_v1( - already_bound, goal_id="goal-a", store_binding="test:store", - ) - with pytest.raises(HeadValidationError): - migrate_head_v0_to_v1( - legacy_v0_head(), goal_id="goal-a", store_binding="", - ) diff --git a/tests/control_plane/test_local_coordination_authority.py b/tests/control_plane/test_local_coordination_authority.py index d3b531c0fe..91a4f00f8b 100644 --- a/tests/control_plane/test_local_coordination_authority.py +++ b/tests/control_plane/test_local_coordination_authority.py @@ -199,7 +199,8 @@ def test_promoted_claim_adapter_invokes_typescript_without_markdown_fallback( _engage_fence(tmp_path) calls: list[tuple[str, dict[str, object]]] = [] - def _claim(method: str, params: dict[str, object]) -> dict[str, object]: + def _claim(method: str, params: dict[str, object], *, timeout: float) -> dict[str, object]: + assert timeout > 0 calls.append((method, params)) return { "status": "applied", @@ -266,7 +267,8 @@ def test_promoted_add_invokes_native_create_without_markdown_state( lambda **_kwargs: {"todos": []}, ) - def _create(method: str, params: dict[str, object]) -> dict[str, object]: + def _create(method: str, params: dict[str, object], *, timeout: float) -> dict[str, object]: + assert timeout > 0 calls.append((method, params)) todo = params["todo"] assert isinstance(todo, dict) @@ -332,7 +334,8 @@ def test_promoted_add_delegates_semantic_duplicate_to_typescript( ) calls: list[tuple[str, dict[str, object]]] = [] - def _create(method: str, params: dict[str, object]) -> dict[str, object]: + def _create(method: str, params: dict[str, object], *, timeout: float) -> dict[str, object]: + assert timeout > 0 calls.append((method, params)) return { "status": "no_change", @@ -997,7 +1000,7 @@ def test_promoted_claim_protocol_failure_stays_infrastructure_outage( _engage_fence(tmp_path) monkeypatch.setattr( "loopx.control_plane.coordination.local_authority.effect_runtime_result", - lambda method, params: { + lambda method, params, **_kwargs: { "status": "failed", "reason_code": "invalid_local_coordination_todo_claim_request", "reason": "registered_agents must be a JSON array", diff --git a/tests/control_plane/test_shared_goal_authority_e2e.py b/tests/control_plane/test_shared_goal_authority_e2e.py index f5a2d7e2b3..9be2ebbe50 100644 --- a/tests/control_plane/test_shared_goal_authority_e2e.py +++ b/tests/control_plane/test_shared_goal_authority_e2e.py @@ -12,6 +12,7 @@ import json import os from collections.abc import Iterator +from dataclasses import replace from pathlib import Path import pytest @@ -21,19 +22,16 @@ LIVE_ENVIRONMENT_VARIABLES = ( ladder.POSTGRES_URL_VARIABLE, - ladder.NOKV_LIVE_FLAG, - *ladder.NOKV_STACK_VARIABLES, ladder.NOKV_AUTHORITY_LIVE_FLAG, *ladder.NOKV_AUTHORITY_VARIABLES, ) GATED_ROW_IDS = ( - "s0.nokv_live_matrix", "s2a.nokv_live_qualification", "s2b.postgresql_conformance_live", ) PENDING_ONLY_ROW_ID = "s2c2.sustained_parity_soak" PENDING_ROW_IDS = (PENDING_ONLY_ROW_ID,) -CHEAP_DETERMINISTIC_ROW_ID = "s0.file_matrix_twelve_rows" +LOCAL_FILE_ROW_ID = "s0.native_file_conformance" FULL_LADDER_VARIABLE = "LOOPX_LADDER_FULL" # Rows whose assertions the in-repo CLI E2E suite already pins through the same # product path. The pytest job runs close to its time budget, so the default CI @@ -85,6 +83,11 @@ def _row_parameters() -> Iterator[object]: ) if row.id in CLI_E2E_COVERED_ROW_IDS and not full_ladder: marks.append(pytest.mark.skip(reason=CLI_E2E_COVERAGE_REASON)) + if row.stage == "0" and not full_ladder: + marks.append(pytest.mark.skip(reason=( + "native provider suites run in npm run test:control-plane; " + "run the standalone ladder or set LOOPX_LADDER_FULL=1 for integrated evidence" + ))) if row.stage == "2c2": marks.append(pytest.mark.stage2c_e2e) yield pytest.param(row, id=row.id, marks=marks) @@ -158,14 +161,13 @@ def test_main_never_reports_green_while_unverified( assert report["summary"] == { "pass": 0, "fail": 0, - "unverified": 3, + "unverified": 2, "pending": 0, - "executed": 3, + "executed": 2, "privacy_violations": 0, } assert {row["status"] for row in report["rows"]} == {"unverified"} assert {row["reason_code"] for row in report["rows"]} == { - "nokv_live_env_missing", "nokv_authority_env_missing", "postgres_url_missing", } @@ -183,7 +185,7 @@ def test_main_never_reports_green_while_unverified( assert ladder.main([*argv, "--allow-unverified"]) == 0 relaxed = json.loads(report_path.read_text(encoding="utf-8")) - assert relaxed["summary"]["unverified"] == 3 + assert relaxed["summary"]["unverified"] == 2 assert relaxed["exit_policy"]["allow_unverified"] is True assert relaxed["exit_policy"]["exit_code"] == 0 capsys.readouterr() @@ -304,7 +306,11 @@ def test_pending_rows_never_exit_green_without_allow_pending( capsys.readouterr() # Mixed selection: one executable pass does not excuse a pending obligation. - mixed = ["--row", CHEAP_DETERMINISTIC_ROW_ID, "--row", PENDING_ONLY_ROW_ID, "--report-json", str(report_path)] + monkeypatch.setattr(ladder, "LADDER_ROWS", tuple( + replace(row, run=lambda _context: ladder.passed()) if row.id == LOCAL_FILE_ROW_ID else row + for row in ladder.LADDER_ROWS + )) + mixed = ["--row", LOCAL_FILE_ROW_ID, "--row", PENDING_ONLY_ROW_ID, "--report-json", str(report_path)] assert ladder.main(mixed) == 1 report = json.loads(report_path.read_text(encoding="utf-8")) assert report["summary"] == {"pass": 1, "fail": 0, "unverified": 0, "pending": 1, "executed": 1, "privacy_violations": 0} @@ -322,7 +328,7 @@ def test_pending_rows_never_exit_green_without_allow_pending( def test_privacy_scan_turns_leaks_into_failures(tmp_path: Path) -> None: - row = ladder.row_by_id("s0.file_matrix_twelve_rows") + row = ladder.row_by_id(LOCAL_FILE_ROW_ID) leaking = ladder.RowResult( row=row, status="pass", @@ -419,3 +425,67 @@ def test_list_prints_rows_and_pending_declarations( assert [row["id"] for row in stage_listing["rows"]] == list(STAGE_2C2_ROW_IDS) assert [row["id"] for row in stage_listing["pending"]] == list(PENDING_ROW_IDS) assert {row["stage"] for row in stage_listing["pending"]} == {"2c2"} + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +@pytest.mark.parametrize("output,returncode,expected", [ + ("# tests 3\n# pass 3\n# fail 0\n# skipped 0", 0, "pass"), + ("# tests 0\n# pass 0\n# fail 0\n# skipped 0", 0, "fail"), + ("# tests 3\n# pass 2\n# fail 0\n# skipped 1", 0, "fail"), + ("# tests 3\n# pass 2\n# fail 1\n# skipped 0", 0, "fail"), + ("# tests 3\n# pass 2\n# fail 0\n# skipped 0", 0, "fail"), + ("# tests 3\n# pass 3\n# fail 0\n# skipped 0", 1, "fail"), + ("TAP version 13\nok 1 - interrupted before the trailer", 0, "fail"), +]) +def test_native_local_qualification_rejects_partial_evidence( + provider: str, output: str, returncode: int, expected: str, + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + from loopx.control_plane.testing.authority_e2e_fixtures import parse_tap_summary + + calls = [] + monkeypatch.setattr(ladder, "node_executable", lambda: "node") + + def run(argv, **kwargs): + calls.append(argv) + return parse_tap_summary(output, returncode=returncode) + + monkeypatch.setattr(ladder, "tap_summary", run) + row = ladder.row_by_id(f"s0.native_{provider}_conformance") + result = ladder.run_row(row, root=tmp_path, environ={}) + assert result.status == expected + assert calls[0][-1] == ladder.LOCAL_CONFORMANCE_TESTS[provider].as_posix() + assert "--test-name-pattern" not in calls[0] + if expected == "pass": + assert result.evidence["provider"] == provider + assert result.evidence["tap_pass"] == 3 + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_native_local_qualification_missing_runtime_or_suite_is_unverified( + provider: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + row = ladder.row_by_id(f"s0.native_{provider}_conformance") + monkeypatch.setattr(ladder, "node_executable", lambda: None) + missing_node = ladder.run_row(row, root=tmp_path, environ={}) + assert (missing_node.status, missing_node.reason_code) == ("unverified", "node_missing") + monkeypatch.setattr(ladder, "node_executable", lambda: "node") + monkeypatch.setattr(ladder, "REPO_ROOT", tmp_path) + missing_suite = ladder.run_row(row, root=tmp_path, environ={}) + assert (missing_suite.status, missing_suite.reason_code) == ("unverified", "local_conformance_suite_missing") + + +def test_retired_prototype_rows_are_not_relabelled_as_native_evidence(capsys) -> None: + for row_id in ("s0.file_matrix_twelve_rows", "s0.nokv_live_matrix"): + assert ladder.main(["--row", row_id]) == 2 + capsys.readouterr() + + +def test_native_ladder_suites_remain_in_the_regular_typescript_test_job() -> None: + package = json.loads((ladder.REPO_ROOT / "package.json").read_text()) + assert "tests/control_plane_ts/*.test.ts" in package["scripts"]["test:control-plane"] + regular_suites = set(ladder.REPO_ROOT.glob("tests/control_plane_ts/*.test.ts")) + assert {ladder.REPO_ROOT / path for path in ladder.LOCAL_CONFORMANCE_TESTS.values()} <= regular_suites + assert {row.id for row in ladder.LADDER_ROWS if row.stage == "0"} == { + "s0.native_file_conformance", "s0.native_sqlite_conformance", + } diff --git a/tests/fixtures/coordination_head_stage2_v0_claimed.json b/tests/fixtures/coordination_head_stage2_v0_claimed.json deleted file mode 100644 index f7dc6bcfeb..0000000000 --- a/tests/fixtures/coordination_head_stage2_v0_claimed.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "authority_revision": 1, - "coordination": { - "leases": { - "todo-1": { - "expires_at": "2027-01-15T08:10:00.000Z", - "lease_epoch": 7, - "lease_id": "lease_8c5b438a43110ce57000c32a", - "owner": "agent-a", - "write_scopes": [] - } - }, - "todos": { - "todo-1": { - "claimed_by": "agent-a", - "code_revision": "0123456789abcdef", - "eligibility": { - "allowed_agent_ids": [ - "agent-a", - "agent-b" - ], - "authorization_projection_digest": "sha256:bootstrap-auth", - "authorization_projection_revision": 3, - "dependencies_satisfied": true, - "dependency_revision": 12, - "gate_revision": 5, - "gates_open": true - }, - "last_lease_epoch": 7, - "repository": "git:example/repo", - "status": "open", - "todo_revision": 8 - } - } - }, - "goal_id": "goal-a", - "handoff_mode": "hard_lease", - "receipt_index": { - "op-stage2-claim": { - "original_receipt": { - "accepted_authority_revision": 1, - "accepted_todo_revision": 8, - "actor": { - "agent_id": "agent-a", - "device_id": "dev-a" - }, - "applied_at": "2027-01-15T08:00:00.000Z", - "command": "claim_work", - "expires_at": "2027-01-15T08:10:00.000Z", - "lease_epoch": 7, - "lease_id": "lease_8c5b438a43110ce57000c32a", - "operation_id": "op-stage2-claim", - "request_digest": "sha256:f77b509b8e5a7b65d8cdfe44c890ec246e9af400098d29a500b375e35dd668c6", - "schema_version": "loopx_authority_receipt_v0", - "todo_id": "todo-1" - }, - "request_digest": "sha256:f77b509b8e5a7b65d8cdfe44c890ec246e9af400098d29a500b375e35dd668c6" - } - }, - "receipt_retention": { - "mode": "retain_all_v0" - }, - "schema_version": "loopx_coordination_head_v0" -} diff --git a/tests/test_nokv_shadow_provider_probes.py b/tests/test_nokv_shadow_provider_probes.py deleted file mode 100644 index 781e383398..0000000000 --- a/tests/test_nokv_shadow_provider_probes.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Keep the RFC shared-goal provider evidence command green. - -``examples/nokv-shadow-provider/README.md`` names -``python3 examples/nokv-shadow-provider/probes.py contract`` as the merge -evidence for the coordination contract. The probes import the LoopX -durable-completion seam, so a lifecycle contract change can silently turn that -evidence red; this test runs the command exactly as documented. -""" - -from __future__ import annotations - -import ast -import json -import subprocess -import sys -from pathlib import Path - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -PROBES = REPOSITORY_ROOT / "examples" / "nokv-shadow-provider" / "probes.py" -LIVE_E2E = REPOSITORY_ROOT / "examples" / "nokv-shadow-provider" / "live_e2e.py" - -EXPECTED_TAGS = ( - "contract.bootstrap_and_preconditions", - "contract.a_success_b_advance_replay_a", - "contract.operation_identity", - "contract.competing_claims", - "contract.crash_windows_and_ambiguity", - "contract.version_domains_and_retain_all", - "contract.nokv_adapter_exception_mapping", - "contract.nokv_fresh_client_failure_is_typed", - "contract.durable_completion_projection", - "contract.durable_completion_fail_closed", -) - - -def test_contract_probes_pass_as_documented() -> None: - completed = subprocess.run( - [sys.executable, str(PROBES), "contract"], - cwd=REPOSITORY_ROOT, - capture_output=True, - text=True, - timeout=120, - check=False, - ) - assert completed.returncode == 0, completed.stderr[-2000:] - rows = [json.loads(line) for line in completed.stdout.splitlines() if line.strip()] - tags = [row["probe"] for row in rows] - assert tags == [*EXPECTED_TAGS, "contract.summary"], tags - assert all(row["ok"] is True for row in rows) - assert rows[-1]["probes"] == len(EXPECTED_TAGS) - - -def test_live_nokv_provider_handles_use_typed_composition_factory() -> None: - """Keep fallible SDK construction inside the adapter-owned boundary.""" - - tree = ast.parse(LIVE_E2E.read_text(encoding="utf-8"), filename=str(LIVE_E2E)) - direct_import_lines: list[int] = [] - direct_names: set[str] = set() - factory_names: set[str] = set() - provider_modules: set[str] = set() - - for node in ast.walk(tree): - if isinstance(node, ast.ImportFrom) and node.module == "provider": - for alias in node.names: - binding = alias.asname or alias.name - if alias.name == "NoKVCoordinationProvider": - direct_import_lines.append(node.lineno) - direct_names.add(binding) - elif alias.name == "open_nokv_coordination_provider": - factory_names.add(binding) - elif isinstance(node, ast.Import): - for alias in node.names: - if alias.name == "provider": - provider_modules.add(alias.asname or alias.name) - - direct_call_lines: list[int] = [] - factory_call_lines: list[int] = [] - eager_factory_argument_lines: list[int] = [] - for node in ast.walk(tree): - if not isinstance(node, ast.Call): - continue - is_factory_call = False - if isinstance(node.func, ast.Name): - if node.func.id in direct_names: - direct_call_lines.append(node.lineno) - if node.func.id in factory_names: - factory_call_lines.append(node.lineno) - is_factory_call = True - elif isinstance(node.func, ast.Attribute) and isinstance( - node.func.value, ast.Name - ): - if ( - node.func.value.id in provider_modules - and node.func.attr == "NoKVCoordinationProvider" - ): - direct_call_lines.append(node.lineno) - if ( - node.func.value.id in provider_modules - and node.func.attr == "open_nokv_coordination_provider" - ): - factory_call_lines.append(node.lineno) - is_factory_call = True - - if is_factory_call: - client_factory = node.args[0] if node.args else None - if client_factory is None: - client_factory = next( - ( - keyword.value - for keyword in node.keywords - if keyword.arg == "client_factory" - ), - None, - ) - if client_factory is None or isinstance(client_factory, ast.Call): - eager_factory_argument_lines.append(node.lineno) - - assert not direct_import_lines, direct_import_lines - assert not direct_call_lines, direct_call_lines - assert factory_call_lines, "live NoKV composition must use the typed factory" - assert not eager_factory_argument_lines, eager_factory_argument_lines