diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 34f9004..19ac08c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: abruption/session-peer - ref: 47c23713d0a2a3c11ebde6186afd8c43489b8b65 + ref: 8ca77cf31254c8287a2593fe484bb30a7dba2727 path: .reference-python - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1d53485..a054f95 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -62,7 +62,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: abruption/session-peer - ref: 47c23713d0a2a3c11ebde6186afd8c43489b8b65 + ref: 8ca77cf31254c8287a2593fe484bb30a7dba2727 path: .reference-python persist-credentials: false - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dc7ce35..163be52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ This repository follows the PR-based workflow of npm ci --ignore-scripts npm run build node scripts/check-repository.mjs -SESSION_PEER_PYTHON_ROOT=/path/to/python-v1.0.2 npm test +SESSION_PEER_PYTHON_ROOT=/path/to/python-v1.1.0 npm test npm run test:package npm audit ``` @@ -44,9 +44,11 @@ separate TypeScript consumer. The package root exposes only pure protocol helper The npm TypeScript indicator requires publishing these declarations in a new version; it does not retroactively alter `0.1.0-preview.0`. -Python reference commit: `47c23713d0a2a3c11ebde6186afd8c43489b8b65`. -CI checks it out separately. Python is a development oracle, never a runtime -fallback. Match Node 22/24, macOS/Linux and the native dependency requirements. +Python reference: 1.1.0 commit `8ca77cf31254c8287a2593fe484bb30a7dba2727`. +CI checks it out separately; `test/fixtures/python-reference.pin.json` verifies +selected source hashes before the ordinary list/discovery comparison tests run. +This limited comparison does not establish full send/handoff interoperability. +Python is a development oracle, never a runtime fallback. Match Node 22/24, macOS/Linux and the native dependency requirements. Windows CI separately runs `npm run test:windows` and `npm run test:package` on x64 Node 22/24. The Windows fixture compiles with the runner's .NET Framework C# compiler and uses real locks/processes plus an isolated fake SSH endpoint diff --git a/PARITY.md b/PARITY.md index c9a1162..2b8e2be 100644 --- a/PARITY.md +++ b/PARITY.md @@ -1,5 +1,51 @@ # Python compatibility and npm migration +## Current development comparison — Python 1.1.0 / 0.4.0 candidate + +Updated 2026-10-11 KST. The unpublished 0.4.0 source candidate uses published +Python **1.1.0**, commit +[`8ca77cf`](https://github.com/abruption/session-peer/tree/8ca77cf31254c8287a2593fe484bb30a7dba2727), +as the ordinary list/discovery comparison baseline. Candidate JSON reports +`referenceVersion: "1.1.0"`; this identifies a comparison source, not complete +runtime parity. The rebased candidate keeps published `VERSION=0.3.6`; its handoff features +remain unpublished until a separately reviewed release. It does not change +immutable public npm artifacts or their historical comparison markers. + +- The exact-release `compatibility-v1.json` and compatibility document are + byte-identical to the historical Python 1.0.2 versions. The existing + `conformance.test.ts` and `discovery.test.ts` selection passed **21 tests** on + macOS arm64 / Node 24.16.0 / Python 3.15.0 against an isolated export of the + exact 1.1.0 source. This is ordinary list/discovery and synthetic refusal + evidence, not full send/handoff/SSH interoperability or live ACK evidence. +- [`python-reference.pin.json`](test/fixtures/python-reference.pin.json) records + the exact ordinary source, comparison scope and selected source-file hashes; + the tests check those hashes before using the oracle. CI and the publishing + workflow use the same immutable source. This test-only manifest is not an npm + runtime dependency or an installed-package authenticity check. +- Python 1.1.0 source adds bounded Claude registry reads and fail-closed Codex + SQLite-storage checks, along with revised SSH evidence handling and opt-in + target-generation/handoff behavior. TS retains its own error codes, stricter + selected-storage parser and distinct native result shapes; it does not copy + Python output mechanically or weaken its existing guards. These differences + were inspected statically; the 21 tests are not coverage of every new path. +- Python 1.1.0 has an experimental local Claude receipt-only ACK path. This + does not establish automated ACK, SSH handoff or Windows handoff support in + the TS 0.4.0 drafts. Receipt bootstrap, original generation, body-free native + observation, cleanup and platform qualification remain separate gates. + Neither a native queue result nor turn completion establishes ACK or safe + retry; no end-to-end consumption/exactly-once guarantee is introduced. + +The frozen handoff design and its historical Python 1.0.3 design-review baseline +remain separate from this ordinary runtime comparison. Exact frozen bytes and +hashes are preserved. See [the published design checkpoint](#published-python-handoff-checkpoint--2026-10-11) +and [VALIDATION.md](VALIDATION.md#python-110-ordinary-comparison--2026-10-11-kst). + +## Historical 0.3.x comparison context + +The retained tables and dated migration sections below describe their original +Python 1.0.2/1.0.3 comparisons and TS 0.3.x source snapshots. Their historical +pins and evidence are not rewritten as new-release conformance claims. + Updated 2026-10-14 KST. Python reference: **1.0.2**, [`47c2371`](https://github.com/abruption/session-peer/tree/47c23713d0a2a3c11ebde6186afd8c43489b8b65/session_peer_core). This matrix describes the **TS 0.3.6 source/package contract**: the 0.2.0 unified listing, safe home selection, @@ -74,7 +120,7 @@ not a missing implementation. Issue links describe work, not available features. | Relay server / hosted control service | Separate infrastructure and deployment | Not part of the TS CLI deliverable | Deliberately different scope; #29 must define client boundaries before implementation | | Wait / general ACK / exactly-once consumption | No general CLI wait/ACK or exactly-once consumption guarantee | No general CLI wait/ACK or exactly-once consumption guarantee | Shared limit, not a TS parity gap; paired-device deduplication is not end-to-end consumption | -Historical 0.2.0 source anchors and the unchanged Python oracle: +Historical 0.2.0 source anchors and the then-pinned Python oracle: [TS CLI](https://github.com/abruption/session-peer-ts/blob/0edc4f8ae05256698f591b7402e89cd3d5858eef/src/cli.ts), [writer](https://github.com/abruption/session-peer-ts/blob/0edc4f8ae05256698f591b7402e89cd3d5858eef/src/writer.ts), [send](https://github.com/abruption/session-peer-ts/blob/0edc4f8ae05256698f591b7402e89cd3d5858eef/src/send.ts), @@ -196,9 +242,10 @@ Output sources: TS CLI/send above; Python ## Ordinary CLI migration notes — #117 -This comparison excludes Relay/MCP and paired-device transports. The existing -Python 1.0.2 oracle and `referenceVersion` stay unchanged. A separate fixed-source -review on 2026-10-09 uses published Python 1.0.3 +This dated 0.3.x comparison excludes Relay/MCP and paired-device transports. +It retained the Python 1.0.2 oracle and `referenceVersion` at that checkpoint; +the current 0.4.0 candidate baseline is described above. A fixed-source +review on 2026-10-09 used published Python 1.0.3 [`0d252550`](https://github.com/abruption/session-peer/tree/0d252550ab40c26d1ac4a19193df6ca2f84d830b) and TS 0.3.2 [`f335f073`](https://github.com/abruption/session-peer-ts/tree/f335f07352c842f2f6ceb12bd2ca6274b52f69f7) for migration guidance, not byte/shape parity or a runtime-reference repin. @@ -307,6 +354,111 @@ Windows support, WSL/POSIX SSH and TS native Windows SSH are separate paths. external Relay infrastructure or a remote host automatically. To return to Python, select its recorded path or uninstall only the npm-owned installation. +## Handoff v1 design candidate — #69 + +**Status: jointly approved, frozen DESIGN v0.2; runtime unimplemented and unshipped.** All four issues +[#69](https://github.com/abruption/session-peer-ts/issues/69), +[#70](https://github.com/abruption/session-peer-ts/issues/70), +[#71](https://github.com/abruption/session-peer-ts/issues/71) and +[#72](https://github.com/abruption/session-peer-ts/issues/72) remain in the +single 0.4.0 milestone. Milestone assignment is not delivery or publication evidence. + +The upstream candidate is [Python Draft PR #261](https://github.com/abruption/session-peer/pull/261), +commit `99d8ec3855d57da34a6fa789f3a375820a8fbe85`: +[document](https://github.com/abruption/session-peer/blob/99d8ec3855d57da34a6fa789f3a375820a8fbe85/docs/contracts/handoff-v1.md) +and [synthetic fixture](https://github.com/abruption/session-peer/blob/99d8ec3855d57da34a6fa789f3a375820a8fbe85/tests/fixtures/handoff-v1.json). +Exact copies are retained in `test/fixtures/handoff-v1.md` and +`test/fixtures/handoff-v1.json`; `handoff-v1.pin.json` records their SHA-256 hashes, +review status and separate runtime/design references. These test-only files are +excluded from the npm package; the existing 51-file package allowlist is unchanged. + +The design review uses published Python 1.0.3 at +`0d252550ab40c26d1ac4a19193df6ca2f84d830b` as its compatibility baseline. +The original review retained the then-current Python 1.0.2 ordinary comparison +pin. The current candidate separately checks ordinary list/discovery against +published Python 1.1.0; that does not change these frozen design bytes or claim +identical Python/TS legacy outputs or implemented cross-runtime handoff parity. +An approved design commit pins the shared contract fixture independently of +runtime publication and the bounded ordinary comparison. + +### Proposed separation and TS implementation gates + +| Axis | Candidate meaning / TS boundary | +| --- | --- | +| Submission | Preserve native status, target, queueId, submitted and optional-field absence; an observer failure must not pass through a catch that erases known submission evidence. | +| Observation | Opt-in, body-free metadata for an exact native message ID/home/original generation. Queue deletion or turn completion is not receipt ACK. | +| Receipt ACK | Receipt-only capability or explicitly recorded operator attestation; never generic reply parsing, a new model prompt, wake or permission to act. | +| Wait | Separate immutable wait operation/results; a late receipt can add current evidence without changing the original timeout result. | +| Retry | Native local/SSH replay remains forbidden. Sender fences do not implement receiver dedup or a universal exactly-once guarantee. | +| SSH | A fully validated, requested explicit-wait result may preserve queued/true with ok:false/exit1. Unrequested handoff data cannot enable this exception; target validation is still required. | + +The candidate adds a nested `handoff` object only in opted-in mode. Its proposed +init/prepare/status/wait/confirm/ack commands, epoch IDs, private collector, +30-second per-destination budget and 5-second cleanup reserve are **not current +CLI capabilities**. Budgets 1–5 seconds are syntactically valid in the proposal +but refuse before effect. Source-streamed SSH without a compatible receipt handler +remains ACK-unsupported under this candidate. No transcript/body-returning observer +or fallback after a possible submission is authorized. + +### Amended candidate review and remaining gates + +The amended exact candidate supersedes the first `605061` review snapshot. Its +normative document and synthetic vectors now address the seven prior findings: + +1. Duplicate receipt queries require original capability-hash authentication, + including the same-committed-receipt expiry exception; wrong tokens are denied. +2. Receipt classification and wait-order facts commit atomically. Proven committed + ACKs survive clock restart; unprovable manual confirmation refuses without rewriting history. +3. Receipt evidence binds the original generation. Null generation cannot mint + authority; copied original capability data is explicitly only token-possession assurance. +4. Sender/collector outputs and own persistence exclude secrets, while intended + recipient native storage may retain effect-input authority outside CLI control. +5. Missing/corrupt ledger or unknown ID returns a closed `handoffQuery` error, + without a fabricated epoch or native result. Known tombstones retain original context. +6. Raw private input, compact public subtree and outer/fanout limits are defined + separately; lexical constraints stay inside the new schema and native facts are preserved. +7. Explicit-wait wire exceptions require the original request and validated native + target/IDs/goal; returned metadata cannot opt a legacy request in or promote forged ACK. + +The final candidate now aligns the three remaining fixture/profile rules: + +- Four original-request profiles preserve Python Claude's absent native fields, + TS Claude's nullable name/optional agent and Codex's queued/true/false result, + optional queue ID, native UUID normalization and canonical home/context. +- A post-submission unsupported channel is a request-gated explicit-wait failure + at exit 1, preserving known submission and any injection evidence. +- Interrupting a pending explicit wait records stopped/stopped_by_operator and + returns complete structured output with ok:false/exit130. A successful status + query of that record returns exit 0; terminal waits remain immutable. + +The shared candidate has 39 synthetic request-aware wire vectors for positive, +forged, wrong-target/home/agent/profile, unsupported and interruption cases. +The exact candidate has TS design-only approval after independent review; no +additional normative change is requested for these reviewed findings. +**Joint design freeze is recorded for the exact source/hash tuple above.** +[Python acceptance record](https://github.com/abruption/session-peer/pull/261#issuecomment-6016310362) +confirms both sides' design-only approval; TS acceptance was recorded at +`63473fb80936b0085e08039997ac39f7158a58dd`. `handoff-v1.pin.json` now has +`jointFreezeApproved:true`, `runtimeImplemented:false` and no open design-review +gates. The byte-identical source document and synthetic fixture retain their +historical proposal/draft labels; the external approval freezes DESIGN, not a +shipped or implemented runtime protocol. No regeneration or runtime/reference +repin occurred. Normative/schema/vector changes require a new exact joint review. +Design-only approval does not establish receipt/bootstrap/runtime acceptance or +authorize merge, implementation, live effects, operational changes or publication. + +Python's exact-candidate documentation tests and CodeQL passed; its optional +Control dependency audit and required release gate failed. That CI status is +separate from this static design review. No bypass, dependency repair or claim of +a fully passing upstream PR is made here. Runtime producer/collector/bootstrap, +platform cleanup, metadata-only observation and live acceptance remain future gates. + +`handoff-contract.test.ts` checks exact pins, synthetic status and selected golden +safety invariants. It does not implement or test a collector, native receipt, +crash recovery, monotonic clock continuity, app-server or live SSH. Upstream's +separate test-only validator likewise supplies design/schema checks, not runtime +acceptance evidence. No issue is closed and no implementation is enabled here. + ## Evidence and ongoing maintenance | Evidence class | What it establishes | What it does not establish | @@ -862,3 +1014,28 @@ Version 0.3.3 uses compiled `VERSION=0.3.3` on the production doctor inspection See the [bounded grammar, matrix and reader evidence](docs/design/typescript-skill-compatibility.md). Companion [Draft #20 design acceptance at `699a325`](https://github.com/abruption/session-peer-skill/blob/699a3251830c30c62d0f812363faf4d0eb0c8e0c/docs/typescript-compatibility.md) records the 2026-10-09 static design review of exact #126 `ba18bb806a6ba503381259b4255211af3adc0336` and release Draft #129 `bfb3d6f1256ab076a76660e08a7bf1ad071a6c6c`; their reviewed design SHA-256 values are recorded in the linked guard design. No tests were rerun in that review. At this dated checkpoint, runtime and successor skill were unpublished. The earlier queued coordination request remains historical, without independently established consumption or approval. Design/fixture acceptance does not authorize runtime merge/release, final skill metadata/publication/installation/pin changes or claim public compatibility; the immutable npm 0.3.2 checker stays legacy-only. The guard runtime must actually publish before separate successor skill finalization/tag/publication/pin changes. At that checkpoint the install pin remained `081cc3c1d16a394bd92824333f4bc61c36951799`; managers own upgrades, and no postinstall is introduced. At the 2026-10-09 KST preparation checkpoint, [#128](https://github.com/abruption/session-peer-ts/issues/128) integrated open PRs #125/#127 and Draft #126 for review; that preparation did not bypass their human merge gate or approve staging/publication. 0.3.2 and 0.3.3 refuse each other's SSH preflight. Windows jump-host #20, provisioning #23, the Python 1.0.2 oracle, and unshipped 0.4/native ACK boundaries remain unchanged. + + +### Published Python handoff checkpoint — 2026-10-11 + +Python [1.1.0](https://github.com/abruption/session-peer/releases/tag/v1.1.0) is now public at +`8ca77cf31254c8287a2593fe484bb30a7dba2727`. Its +[handoff contract](https://github.com/abruption/session-peer/blob/8ca77cf31254c8287a2593fe484bb30a7dba2727/docs/contracts/handoff-v1.md) +SHA-256 is `46a953010c49214bd8f969412f26713384fbe09a0ba78ebf0fef75da40d0d889`, +byte-identical to the jointly reviewed design. The published fixture SHA-256 is +`963b94e78f510c2535e405813558275d512a5bfe6bbd99ffdf0994b4d2e6e441`, +also byte-identical to the frozen shared fixture. Both identities were rechecked +against the exact release source through local Git without replace objects and +the GitHub contents API. +The exact reviewed TS mirror remains unchanged; publication is not permission +to regenerate shared vectors or declare runtime conformance. + +The handoff pin manifest records this source-only comparison separately from the +historical Python 1.0.3 design baseline and the historical Python 1.0.2 ordinary +oracle. The current candidate's ordinary source pin is recorded separately in +`python-reference.pin.json`. +The Python release's experimental local Claude receipt path does not establish +automated ACK support in the TS implementation. Cross-runtime execution, +receipt bootstrap, original generation and platform acceptance remain #69–72 +implementation gates. This design-identity record alone does not repin a runtime +reference or establish runtime conformance. diff --git a/README.ja.md b/README.ja.md index d8ff55a..e2a7c0c 100644 --- a/README.ja.md +++ b/README.ja.md @@ -13,7 +13,7 @@ [English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [简体中文](README.zh-CN.md) - + **文書・ソースのバージョン: 0.3.6。** インストール前に、選択したリリースとレジストリのメタデータを確認してください。日付付きの準備・検証記録は [VALIDATION.md](VALIDATION.md)、リリース手順は [RELEASING.md](RELEASING.md) にあります。SSH の両端には同じ TypeScript クライアントのバージョンが必要で、0.3.5 と 0.3.6 は互いの SSH 事前確認を拒否します。 @@ -112,6 +112,8 @@ session-peer --version - [開発・貢献](CONTRIBUTING.md). - [リリース手順](RELEASING.md). +- [0.4.0 handoff 設計(共同合意・未実装)](PARITY.md#handoff-v1-design-candidate--69). + ## License [MIT ライセンス](LICENSE)で公開しています。 diff --git a/README.ko.md b/README.ko.md index 0071713..6e8ad66 100644 --- a/README.ko.md +++ b/README.ko.md @@ -18,7 +18,7 @@ Python을 설치하지 않아도 Node.js에서 실행되는 TypeScript 클라이 - + **문서·소스 기준 버전: 0.3.6.** 설치 전에 선택한 릴리스와 레지스트리 메타데이터를 확인하세요. 날짜별 준비·검증 기록은 [VALIDATION.md](VALIDATION.md), 릴리스 절차는 [RELEASING.md](RELEASING.md)에 있습니다. SSH 양쪽에는 같은 TypeScript 클라이언트 버전이 필요하며, 0.3.5와 0.3.6는 서로의 SSH 사전 확인을 거부합니다. @@ -126,6 +126,8 @@ session-peer --version - [개발·기여 안내](CONTRIBUTING.md). - [릴리스 절차](RELEASING.md). +- [0.4.0 handoff 설계(공동 확정·미구현)](PARITY.md#handoff-v1-design-candidate--69). + ## 라이선스 diff --git a/README.md b/README.md index 21eca50..f57d25a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [简体中文](README.zh-CN.md) - + **Documentation and source version: 0.3.6.** Review the selected release and registry metadata before installation. Dated preparation and validation records are in [VALIDATION.md](VALIDATION.md); release procedures are in [RELEASING.md](RELEASING.md). SSH endpoints require the same TypeScript client version; 0.3.5 and 0.3.6 refuse each other’s SSH preflight. @@ -112,6 +112,8 @@ does not install a skill. Review its pinned-source instructions in the guide. - [Development and contributions](CONTRIBUTING.md). - [Release process](RELEASING.md). +- [0.4.0 handoff design (jointly agreed, unimplemented)](PARITY.md#handoff-v1-design-candidate--69). + ## License Licensed under the [MIT License](LICENSE). diff --git a/README.zh-CN.md b/README.zh-CN.md index e1de074..d289cb4 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -13,7 +13,7 @@ [English](README.md) | [한국어](README.ko.md) | [日本語](README.ja.md) | [简体中文](README.zh-CN.md) - + **文档和源码版本:0.3.6。** 安装前,请审核所选发布版本和注册表元数据。按日期记录的准备与验证结果见 [VALIDATION.md](VALIDATION.md),发布流程见 [RELEASING.md](RELEASING.md)。SSH 两端必须使用相同的 TypeScript 客户端版本;0.3.5 与 0.3.6 会拒绝彼此的 SSH 预检。 @@ -108,6 +108,8 @@ session-peer --version - [开发与贡献](CONTRIBUTING.md). - [发布流程](RELEASING.md). +- [0.4.0 handoff 设计(联合确认、未实现)](PARITY.md#handoff-v1-design-candidate--69). + ## License 采用 [MIT 许可证](LICENSE)。 diff --git a/VALIDATION.md b/VALIDATION.md index f4084b9..e30a08f 100644 --- a/VALIDATION.md +++ b/VALIDATION.md @@ -142,6 +142,48 @@ request-count assertions are preserved. This was test isolation, not a runtime failure or a CI rerun treated as an unchanged-head success. Final PR-head CI results are recorded in the PR after the correction. +## Python 1.1.0 ordinary comparison — 2026-10-11 KST + +Unpublished 0.4.0 development checkpoint: an isolated `git archive` export of +published Python 1.1.0 commit `8ca77cf31254c8287a2593fe484bb30a7dba2727` +was compared with the #69 worktree at `d62e813c8146e9832659654a850734fc986fd3d7`. +Only the subsequent published-design identity correction was pending; it did +not change the selected list/discovery source. No user's Python checkout, +agent home or saved thread was used. Archive SHA-256: +`7243c124398ec3b4cf4541881788dac955247d50949339e5f9f659bbb0d15ff9`. + +macOS arm64 / Node 24.16.0 / Python 3.15.0 command: + +```sh +SESSION_PEER_PYTHON_ROOT= TASK_TEMP= PYTHONDONTWRITEBYTECODE=1 node --import ./test/fixtures/isolate-env.mjs --experimental-strip-types --test test/conformance.test.ts test/discovery.test.ts +``` + +Result: **21 tests, 21 passed, zero failed/cancelled/skipped**, exit 0 and empty +stderr. The private fixture directory was empty after test cleanup. These +existing tests compare ordinary listing/discovery projections and exercise +synthetic refusal paths; they do not establish full send/handoff/SSH +interoperability, native model delivery or ACK. No real native queue, message, +receipt, installation, policy or operation change occurred. + +The subsequent candidate ordinary reference pin uses this exact release; +`test/fixtures/python-reference.pin.json` records its scope and selected source +hashes. `compatibility-v1.json` SHA-256 remains +`afd5a0b37562bb6a81afa9c20628bfae0cfe4d1caf9e88db402005fc42aabcd7`, +byte-identical to the historical 1.0.2 fixture. Frozen handoff design bytes and +the historical design baseline are unchanged. The earlier 0.3.x validation +records below retain their actual 1.0.2 oracle; the public npm 0.3.3 artifact +is not rewritten. Current integrated CI on the new pin is a separate gate. + +After applying the candidate reference-pin changes on the #69 worktree based +on `f99417b`, `npm run build` and `npm run test:types` passed. The same isolated +reference export was used for the conformance/discovery/updates/handoff-contract +selection: **44 tests passed, zero failed/cancelled/skipped**, exit 0. The new +source-hash pin checks executed. Update tests use fixture registries and private +caches, not the public registry or an actual installation update. The repository +metadata/link/anchor checker and `git diff --check` passed. These are candidate +checks before root integration/current-head CI, not a public release or broader +native/ACK validation. + ## 0.3.3 candidate preparation — 2026-10-09 KST Preparation [#128](https://github.com/abruption/session-peer-ts/issues/128) diff --git a/docs/guide.ja.md b/docs/guide.ja.md index 9bac729..1471f07 100644 --- a/docs/guide.ja.md +++ b/docs/guide.ja.md @@ -22,9 +22,11 @@ - npm、Python、リモートホスト、スキルは別管理です。TS は自己更新やリモート更新を提供しません。通知は既定で無効で、無効化指定が優先します。明示的な `update --check` はレジストリを照会します。公開 TS 0.3.5 は公開スキル `0.2.1` と旧 `0.1.0`・`0.2.0` の厳密なプロファイルを受け入れます。公開 TS 0.3.2・0.3.3・0.3.4 は `0.2.1` を拒否します。導入の節には独立して公開されたスキルのソースを固定しています。 - Windows の検証は公開 0.3.1、Windows 10 x64、管理者アカウント、PowerShell 5.1、Node 24.16.0 で行いました。オフライン導入、ラッパー・ネイティブロック、運用者の doctor・存在しない宛先の dry-run、ローカル fixture 31 件を検証しました。Node 22 はバージョン表示とネイティブロックの確認のみです。非管理者、他のアーキテクチャ、F6 のランタイム検査、以前の Linux 実験の最新規則は未検証です。0.3.2 の実機 ACK やデプロイ補助ツールの検証ではありません。 -[移行の詳細](../PARITY.md#ordinary-cli-migration-notes--117)と [Windows の検証記録](../VALIDATION.md#windows-ssh-destination-f5--2026-10-07-kst)を参照してください。Python 1.0.2 のテスト基準は維持します。比較対象の公開 CLI はどちらも一般のネイティブ ACK/wait、エンドツーエンドの重複防止、Side Session を提供しません。Relay/MCP はこの比較の対象外です。 +[移行の詳細](../PARITY.md#ordinary-cli-migration-notes--117)と [Windows の検証記録](../VALIDATION.md#windows-ssh-destination-f5--2026-10-07-kst)を参照してください。上の日付付き移行比較は当時の公開版を対象にしています。未公開の 0.4.0 ソース候補は開発時の比較基準を Python 1.1.0 に更新しますが、公開 npm 0.3.3 の従来の 1.0.2 表示は変わりません。Python 1.1.0 には実験的なローカル Claude の受領確認専用 ACK 経路があります。これは TS 候補の自動 ACK、SSH handoff、Windows handoff 対応を意味しません。どちらの基準もエンドツーエンドの消費を保証せず、Side Session は実験段階です。Relay/MCP は比較対象外です。 +[0.4.0 handoff 設計(共同合意・未実装)](../PARITY.md#handoff-v1-design-candidate--69) + ## 目次 - [機能と範囲](#機能と範囲) @@ -202,7 +204,7 @@ npm run test:package npm audit ``` -Python は開発時の互換検証基準のみです(v1.0.2、`47c23713d0a2a3c11ebde6186afd8c43489b8b65`)。実行時依存ではありません。POSIX 契約テストには C コンパイラーと lsof も必要です。CI は三つの OS で Node 22/24 を使います。macOS/Linux は固定した基準に対する全契約・パッケージ検査、Windows x64 はビルド・型検査、ネイティブ Claude inbox、保持/未保持ロック、writer 所有権と提出直前の競合、CLI queue 結果、隔離した模擬 SSH 宛先での実 PowerShell/.cmd フレーミング、パッケージのインストール・ネイティブ読込・削除と audit を実行します。これらは fixture 検査で、Windows では POSIX/Python 参照スイートを実行しません。Windows x64/Node 24 の実機 ACK は別の一回限りの証拠です。SQLite・Unix inbox・実 lock の fixture と、専用実 TUI の証拠 [VALIDATION.md](../VALIDATION.md) は別です。fixture 成功は ACK ではありません。パッケージ内容、反復 pack ハッシュ、新規インストール、アンインストールも検証します。ネイティブ依存の通常の install script は実行せず、検証した prebuilt 経路は `--ignore-scripts` を使います。SQLite 読み取り専用接続も WAL 共有メモリー管理に関与し得るため、スナップショットではありません。 +Python は開発時の比較基準です(v1.1.0、`8ca77cf31254c8287a2593fe484bb30a7dba2727`)。実行時依存ではありません。検証範囲は通常の一覧・探索結果であり、送信や handoff 全体の相互運用性を証明するものではありません。POSIX 契約テストには C コンパイラーと lsof も必要です。CI は三つの OS で Node 22/24 を使います。macOS/Linux は固定した基準に対する全契約・パッケージ検査、Windows x64 はビルド・型検査、ネイティブ Claude inbox、保持/未保持ロック、writer 所有権と提出直前の競合、CLI queue 結果、隔離した模擬 SSH 宛先での実 PowerShell/.cmd フレーミング、パッケージのインストール・ネイティブ読込・削除と audit を実行します。これらは fixture 検査で、Windows では POSIX/Python 参照スイートを実行しません。Windows x64/Node 24 の実機 ACK は別の一回限りの証拠です。SQLite・Unix inbox・実 lock の fixture と、専用実 TUI の証拠 [VALIDATION.md](../VALIDATION.md) は別です。fixture 成功は ACK ではありません。パッケージ内容、反復 pack ハッシュ、新規インストール、アンインストールも検証します。ネイティブ依存の通常の install script は実行せず、検証した prebuilt 経路は `--ignore-scripts` を使います。SQLite 読み取り専用接続も WAL 共有メモリー管理に関与し得るため、スナップショットではありません。 [CONTRIBUTING.md](../CONTRIBUTING.md)、[RELEASING.md](../RELEASING.md)、[SECURITY.md](../SECURITY.md) を参照してください。今後のリリースの公開には別途承認が必要で、自動 npm 公開はありません。[MIT](../LICENSE) ライセンスです。 diff --git a/docs/guide.ko.md b/docs/guide.ko.md index ab46653..e9faeea 100644 --- a/docs/guide.ko.md +++ b/docs/guide.ko.md @@ -22,9 +22,11 @@ - npm, Python, 원격 호스트, 스킬은 각각 별도로 관리합니다. TS는 자체·원격 업데이트를 제공하지 않습니다. 알림은 기본 꺼짐이며 비활성화 설정이 우선합니다. 명시적 `update --check`는 레지스트리를 조회합니다. 공개 TS 0.3.5는 공개 스킬 `0.2.1`과 기존 `0.1.0`·`0.2.0`의 정확한 프로필을 허용하며, 공개 TS 0.3.2·0.3.3·0.3.4는 `0.2.1`을 거부합니다. 설치 절에는 별도로 공개된 스킬 소스를 고정했습니다. - Windows 검증은 공개 0.3.1, Windows 10 x64, 관리자 계정, PowerShell 5.1, Node 24.16.0에서 수행했습니다. 오프라인 설치, 래퍼·네이티브 잠금, 운영자의 doctor·없는 대상 dry-run과 로컬 fixture 31개가 검증됐습니다. Node 22는 버전 표시·네이티브 잠금 확인만 수행했습니다. 비관리자, 다른 아키텍처, F6 런타임 검사, 이전 Linux 실험의 최신 규칙 검증은 남아 있습니다. 이는 0.3.2 실기 ACK나 배포 도우미 검증이 아닙니다. -[마이그레이션 상세](../PARITY.md#ordinary-cli-migration-notes--117)와 [Windows 검증 기록](../VALIDATION.md#windows-ssh-destination-f5--2026-10-07-kst)을 참고하세요. Python 1.0.2 테스트 기준은 유지합니다. 비교 대상 공개 CLI 모두 일반 네이티브 ACK/wait, 종단 간 중복 방지, Side Session을 제공하지 않습니다. Relay/MCP는 이 비교 범위에서 제외합니다. +[마이그레이션 상세](../PARITY.md#ordinary-cli-migration-notes--117)와 [Windows 검증 기록](../VALIDATION.md#windows-ssh-destination-f5--2026-10-07-kst)을 참고하세요. 위 날짜별 마이그레이션 비교는 당시 공개 버전을 기준으로 합니다. 미발행 0.4.0 소스 후보는 개발 비교 기준을 Python 1.1.0으로 갱신하며, 공개 npm 0.3.3의 기존 1.0.2 표시는 바뀌지 않습니다. Python 1.1.0에는 실험적인 로컬 Claude 수신 확인 전용 ACK 경로가 있습니다. 이것이 TS 후보의 자동 ACK·SSH handoff·Windows handoff 지원을 뜻하지는 않습니다. 어느 기준도 종단 간 소비를 보장하지 않으며 Side Session은 실험 범위입니다. Relay/MCP는 이 비교에서 제외합니다. +[0.4.0 handoff 설계(공동 확정·미구현)](../PARITY.md#handoff-v1-design-candidate--69) + ## 목차 - [주요 기능과 범위](#주요-기능과-범위) @@ -419,8 +421,9 @@ npm run test:package npm audit ``` -Python은 개발 시 TypeScript 구현과 동작이 일치하는지 대조하는 기준 구현(conformance oracle)입니다(v1.0.2 커밋 -`47c23713d0a2a3c11ebde6186afd8c43489b8b65`)일 뿐 런타임 의존성이 아닙니다. POSIX 계약 테스트에는 +Python은 개발 시 동작을 대조하는 기준 구현입니다(v1.1.0 커밋 +`8ca77cf31254c8287a2593fe484bb30a7dba2727`). 런타임 의존성이 아닙니다. +검증 범위는 일반 목록·탐색 결과이며, 전체 전송·handoff 상호운용성을 입증하지 않습니다. POSIX 계약 테스트에는 C 컴파일러와 lsof도 필요합니다. CI(지속적 통합)는 세 OS에서 Node 22/24를 사용합니다. macOS/Linux는 고정된 참조 기준으로 전체 계약·패키지 검사를 실행하고, Windows는 x64에서 빌드·타입 검사, 네이티브 Claude 받은 메시지함·잠금 점유/해제, writer 소유권·재검증 diff --git a/docs/guide.md b/docs/guide.md index 3086398..44c4eef 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -50,11 +50,17 @@ not a commit hash; operators must align reviewed source commits separately. See [migration details](../PARITY.md#ordinary-cli-migration-notes--117) and [Windows qualification](../VALIDATION.md#windows-ssh-destination-f5--2026-10-07-kst). -The Python 1.0.2 test reference stays pinned; neither compared public CLI -provides general native ACK/wait, end-to-end deduplication or Side Session. -Relay/MCP are outside this migration comparison. +The dated migration comparison above uses the historical public versions. +The unpublished 0.4.0 source candidate now uses Python 1.1.0 as its development +comparison baseline; public npm 0.3.3 retains its historical 1.0.2 marker. +Python 1.1.0 includes an experimental local Claude receipt-only ACK path. +That does not provide automated ACK, SSH handoff or Windows handoff in this TS +candidate. Neither baseline provides an end-to-end consumption guarantee; +Side Session remains experimental. Relay/MCP are outside this comparison. +[0.4.0 handoff design (jointly agreed, unimplemented)](../PARITY.md#handoff-v1-design-candidate--69) + ## Contents - [Quick start](#quick-start) @@ -441,8 +447,10 @@ npm run test:package npm audit ``` -Python is only the development conformance oracle (v1.0.2 commit -`47c23713d0a2a3c11ebde6186afd8c43489b8b65`), never a runtime dependency. Tests also +Python is only the development comparison oracle (v1.1.0 commit +`8ca77cf31254c8287a2593fe484bb30a7dba2727`), never a runtime dependency. +The tested projection covers ordinary list/discovery; it does not establish full +send/handoff interoperability. Tests also need a C compiler and lsof for the POSIX contract suite. CI uses Node 22/24 on macOS, Linux and Windows. macOS/Linux run the full contract suite and package checks against the pinned reference. Windows x64 runs build/type checks, native diff --git a/docs/guide.zh-CN.md b/docs/guide.zh-CN.md index 1a13b10..47ade0f 100644 --- a/docs/guide.zh-CN.md +++ b/docs/guide.zh-CN.md @@ -22,9 +22,11 @@ - npm、Python、远程主机、技能分别管理。TS 不提供自身更新或远程更新。通知默认关闭,禁用设置优先。显式 `update --check` 仍会查询注册表。公开 TS 0.3.5 接受公开技能 `0.2.1` 及旧 `0.1.0`、`0.2.0` 的精确配置;公开 TS 0.3.2、0.3.3 和 0.3.4 拒绝 `0.2.1`。安装部分固定了独立发布的技能源码。 - Windows 验证使用公开 0.3.1、Windows 10 x64、管理员账号、PowerShell 5.1 和 Node 24.16.0。验证范围包括离线安装、包装器与原生锁、操作端 doctor 和不存在目标的 dry-run,以及 31 项本地 fixture。Node 22 仅确认版本标识和原生锁。非管理员、其他架构、F6 运行时校验、此前 Linux 实验的最新规则仍未验证。这不是 0.3.2 实机 ACK 或部署辅助工具的验证。 -请参阅[迁移详情](../PARITY.md#ordinary-cli-migration-notes--117)和 [Windows 验证记录](../VALIDATION.md#windows-ssh-destination-f5--2026-10-07-kst)。Python 1.0.2 测试基准保持不变。对比的两个公开 CLI 都没有通用原生 ACK/wait、端到端去重或 Side Session 支持。Relay/MCP 不在此次迁移对比范围内。 +请参阅[迁移详情](../PARITY.md#ordinary-cli-migration-notes--117)和 [Windows 验证记录](../VALIDATION.md#windows-ssh-destination-f5--2026-10-07-kst)。上述带日期的迁移比较针对当时的公开版本。未发布的 0.4.0 源码候选将开发比较基准更新为 Python 1.1.0;公开 npm 0.3.3 原有的 1.0.2 标识保持不变。Python 1.1.0 包含实验性的本地 Claude 收件确认专用 ACK 路径。这不表示 TS 候选支持自动 ACK、SSH handoff 或 Windows handoff。两种基准均不保证端到端消费,Side Session 仍属实验功能。Relay/MCP 不在比较范围内。 +[0.4.0 handoff 设计(联合确认、未实现)](../PARITY.md#handoff-v1-design-candidate--69) + ## 目录 - [功能与边界](#功能与边界) @@ -201,7 +203,7 @@ npm run test:package npm audit ``` -Python 只用作开发时的兼容性基准(v1.0.2,`47c23713d0a2a3c11ebde6186afd8c43489b8b65`),不是运行时依赖。POSIX 契约测试还需要 C 编译器和 lsof。CI 在三种 OS 上使用 Node 22/24。macOS/Linux 对固定基准执行完整契约与包检查;Windows x64 执行构建、类型检查、原生 Claude inbox、持有/空闲锁、writer 归属与提交前竞争、CLI 队列结果、隔离模拟 SSH 目标的实际 PowerShell/.cmd 帧处理、包安装/原生加载/卸载及 audit。这些属于 fixture 检查;Windows 不运行 POSIX/Python 参考测试套件。Windows x64/Node 24 实机 ACK 是单独的一次性证据。临时 SQLite、Unix inbox、真实锁 fixture 与 [VALIDATION.md](../VALIDATION.md) 中专用真实 TUI 的证据分开记录;fixture 通过不是 ACK。包测试检查文件清单、重复打包哈希、全新安装与卸载。原生依赖通常有安装脚本,已验证的预编译路径使用 `--ignore-scripts`。SQLite 只读连接仍可能参与 WAL 共享内存管理,不能视为快照。 +Python 只用作开发时的比较基准(v1.1.0,`8ca77cf31254c8287a2593fe484bb30a7dba2727`),不是运行时依赖。验证范围为普通列表和发现结果,不代表发送或 handoff 的完整互操作性。POSIX 契约测试还需要 C 编译器和 lsof。CI 在三种 OS 上使用 Node 22/24。macOS/Linux 对固定基准执行完整契约与包检查;Windows x64 执行构建、类型检查、原生 Claude inbox、持有/空闲锁、writer 归属与提交前竞争、CLI 队列结果、隔离模拟 SSH 目标的实际 PowerShell/.cmd 帧处理、包安装/原生加载/卸载及 audit。这些属于 fixture 检查;Windows 不运行 POSIX/Python 参考测试套件。Windows x64/Node 24 实机 ACK 是单独的一次性证据。临时 SQLite、Unix inbox、真实锁 fixture 与 [VALIDATION.md](../VALIDATION.md) 中专用真实 TUI 的证据分开记录;fixture 通过不是 ACK。包测试检查文件清单、重复打包哈希、全新安装与卸载。原生依赖通常有安装脚本,已验证的预编译路径使用 `--ignore-scripts`。SQLite 只读连接仍可能参与 WAL 共享内存管理,不能视为快照。 参见 [CONTRIBUTING.md](../CONTRIBUTING.md)、[RELEASING.md](../RELEASING.md) 和 [SECURITY.md](../SECURITY.md)。后续版本的发布需单独批准,未启用自动 npm 发布。采用 [MIT](../LICENSE) 许可证。 diff --git a/scripts/check-repository.mjs b/scripts/check-repository.mjs index 217ba75..d832dc3 100644 --- a/scripts/check-repository.mjs +++ b/scripts/check-repository.mjs @@ -9,6 +9,15 @@ process.chdir(fileURLToPath(root)); const read = path => readFileSync(path, 'utf8'); const pkg = JSON.parse(read('package.json')); const lock = JSON.parse(read('package-lock.json')); +const pythonReference = JSON.parse(read('test/fixtures/python-reference.pin.json')); +assert.equal(pythonReference.repository, 'abruption/session-peer'); +assert.equal(pythonReference.version, '1.1.0'); +assert.equal(pythonReference.commit, '8ca77cf31254c8287a2593fe484bb30a7dba2727'); +assert.equal(pythonReference.scope, 'ordinary-list-discovery-projection'); +for (const workflow of ['.github/workflows/ci.yml', '.github/workflows/publish.yml']) { + assert.ok(read(workflow).includes(`ref: ${pythonReference.commit}`), `${workflow}: Python reference pin`); +} +assert.ok(read('src/cli.ts').includes(`referenceVersion: '${pythonReference.version}'`), 'CLI Python reference marker'); assert.equal(pkg.name, 'session-peer'); assert.equal(pkg.private, false); assert.deepEqual(pkg.publishConfig, { registry: 'https://registry.npmjs.org/', access: 'public', tag: 'latest' }); @@ -20,7 +29,7 @@ assert.equal(read('src/protocol.ts').match(/export const VERSION = '([^']+)'/)[1 const locales = ['README.md', 'README.ko.md', 'README.ja.md', 'README.zh-CN.md']; const guides = locales.map(file => file.replace('README', 'docs/guide')); -const marker = ''; +const marker = ''; for (const file of locales) { const content = read(file); for (const token of [marker, diff --git a/src/cli.ts b/src/cli.ts index 1c0cac9..ad8b272 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -353,7 +353,7 @@ try { result.requestedCodexHome = options.values.get('--codex-home'); // Without a return host there is nothing to probe from the destination. if (command === 'doctor' && options.flags.has('--check-return-route') && !returnTo) results = results?.map(item => item.ok === false ? item : ({ ...item, returnRoute: { status: 'failed', transport: 'ssh', host: null, reason: 'return_host_unavailable' } })); - const shape = (item: Record) => ({ schemaVersion: 1, host: hostname(), command, ok: item.ok !== false, version: VERSION, referenceVersion: '1.0.2', ...item }); + const shape = (item: Record) => ({ schemaVersion: 1, host: hostname(), command, ok: item.ok !== false, version: VERSION, referenceVersion: '1.1.0', ...item }); // Cached advisory notice, computed once per invocation by this client and never // in wire mode. JSON: additive `clientUpdate` on a flat (single-result) object // only; a repeated --host array keeps its exact element shape. Text: one stderr line. diff --git a/test/conformance.test.ts b/test/conformance.test.ts index 3538682..d860fea 100644 --- a/test/conformance.test.ts +++ b/test/conformance.test.ts @@ -8,10 +8,16 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { createServer } from 'node:net'; +import { createHash } from 'node:crypto'; const root = process.env.SESSION_PEER_PYTHON_ROOT ?? fileURLToPath(new URL('../../../', import.meta.url)); const cli = fileURLToPath(new URL('../dist/cli.js', import.meta.url)); const fixture = JSON.parse(readFileSync(new URL('./fixtures/compatibility-v1.json', import.meta.url), 'utf8')); +const pin = JSON.parse(readFileSync(new URL('./fixtures/python-reference.pin.json', import.meta.url), 'utf8')); +for (const [path, digest] of Object.entries(pin.files)) { + assert.equal(createHash('sha256').update(readFileSync(join(root, path))).digest('hex'), digest, + `Python ${pin.version} reference must match ${pin.commit}: ${path}`); +} assert.deepEqual(fixture, JSON.parse(readFileSync(join(root, 'tests/fixtures/compatibility-v1.json'), 'utf8')), 'vendored contract must match the pinned Python reference'); const python = process.env.PYTHON ?? 'python3'; diff --git a/test/discovery.test.ts b/test/discovery.test.ts index 847bd84..3235577 100644 --- a/test/discovery.test.ts +++ b/test/discovery.test.ts @@ -1,4 +1,4 @@ -// Port of Python 1.0.2 unified-list/multi-home-list contracts. No live user homes. +// Ordinary list/discovery projection checked against Python 1.1.0. No live user homes. import test, { type TestContext } from 'node:test'; import assert from 'node:assert/strict'; import fs, { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; diff --git a/test/fixtures/handoff-v1.json b/test/fixtures/handoff-v1.json new file mode 100644 index 0000000..ad72f5d --- /dev/null +++ b/test/fixtures/handoff-v1.json @@ -0,0 +1,1882 @@ +{ + "schemaVersion": 1, + "proposalVersion": "0.2", + "status": "draft_unimplemented", + "provenance": { + "pythonRuntimeVersion": "1.0.3", + "pythonRuntimeCommit": "0d252550ab40c26d1ac4a19193df6ca2f84d830b", + "designBaseCommit": "817d92ab780f657ec754d40fcf86087b5b01a883", + "issue": 181, + "correlation": "STS-040-COMMON-CONTRACT-20261006-A", + "exampleKind": "synthetic_normalized_not_live", + "legacySources": [ + "session_peer_core/adapters.py", + "session_peer_core/codex.py", + "session_peer_core/antigravity.py", + "session_peer_core/ssh.py", + "session_peer_core/output.py" + ], + "legacyExampleScope": "normalized projections; not exhaustive responses or live captures", + "typescriptNativeProfileCommit": "6b48092a0e42992d2d970dd2ff5cf457f62d3ff2", + "typescriptNativeProfileSources": ["src/send.ts:send", "src/cli.ts:remote target validation", "src/writer.ts:uuid"], + "typescriptSourceScope": "published v0.3.1 positive profile evidence only; no TS parity/reference marker repin" + }, + "bounds": { + "safeIntegerMax": 9007199254740991, + "nativeIdUtf8Bytes": 128, + "generationUtf8Bytes": 256, + "receiptFrameBytes": 4096, + "handoffFrameBytes": 8192, + "timeoutMinS": 1, + "timeoutMaxS": 60, + "defaultBudgetS": 30, + "cleanupReserveS": 5, + "ledgerIntentQuota": 10000, + "ledgerByteQuota": 33554432, + "detailRetentionDays": 30, + "capabilityTtlHours": 24, + "maxWaitsPerIntent": 64, + "capabilityEntropyBytes": 32, + "optInOuterFrameBytes": 1048576, + "optInFanoutDestinations": 32, + "optInFanoutStdoutBytes": 34603008 + }, + "enums": { + "state": [ + "validated", + "refused", + "submitted", + "delivered", + "acknowledged", + "unknown", + "timed_out_unknown" + ], + "submission": [ + "not_attempted", + "submitted", + "refused", + "unknown" + ], + "observation": [ + "not_requested", + "pending", + "observed", + "unsupported", + "failed" + ], + "ack": [ + "not_requested", + "pending", + "acknowledged", + "unsupported" + ], + "assurance": [ + "token_possession", + "operator_confirmed" + ], + "waitFor": [ + "none", + "delivered", + "acknowledged" + ], + "waitStatus": [ + "not_requested", + "pending", + "satisfied", + "timed_out_unknown", + "stopped", + "unsupported", + "failed" + ], + "turn": [ + "running", + "completed", + "failed", + "interrupted", + "unknown" + ], + "waitReason": [ + "insufficient_budget", + "deadline_before_effect", + "evidence_unsupported", + "evidence_failed", + "history_unavailable", + "stopped_by_operator", + "invalid_handoff" + ], + "nextActions": [ + "keep_waiting", + "reconcile", + "stop_waiting" + ] + }, + "legacySnapshots": [ + { + "name": "python_claude_success", + "exit": 0, + "source": "session_peer_core/adapters.py:ClaudeAdapter.submit", + "result": { + "schemaVersion": 1, + "ok": true, + "host": "local", + "command": "send", + "target": { + "pid": 1, + "name": "fixture" + }, + "chars": 7, + "dryRun": false + }, + "mustRemainAbsent": [ + "status", + "submitted", + "consumptionConfirmed", + "handoff" + ] + }, + { + "name": "python_codex_queued", + "exit": 0, + "source": "session_peer_core/codex.py:queue_codex", + "result": { + "schemaVersion": 1, + "ok": true, + "host": "local", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + }, + "mustRemainAbsent": [ + "handoff" + ] + }, + { + "name": "python_codex_validated", + "exit": 0, + "source": "session_peer_core/codex.py:queue_codex", + "result": { + "schemaVersion": 1, + "ok": true, + "host": "local", + "command": "send", + "status": "validated", + "submitted": false, + "consumptionConfirmed": false, + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "codexHome": "/fixture/codex-home", + "dryRun": true + }, + "mustRemainAbsent": [ + "handoff" + ] + }, + { + "name": "legacy_unknown_false_not_no_effect", + "exit": 1, + "source": "session_peer_core/antigravity.py", + "result": { + "schemaVersion": 1, + "ok": false, + "host": "local", + "command": "send", + "status": "unknown", + "submitted": false, + "consumptionConfirmed": false, + "agent": "antigravity", + "retryAllowed": false, + "requestId": "22222222-2222-4222-8222-222222222222", + "generation": "fixture-generation" + }, + "mustRemainAbsent": [ + "handoff" + ], + "noEffectProof": false + } + ], + "accepted": [ + { + "name": "queued_best_effort_observer_failed", + "mode": "opt_in", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "submitted", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "failed", + "injectionObserved": false + }, + "ack": { + "status": "not_requested" + }, + "wait": { + "for": "none", + "status": "not_requested" + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "queued_explicit_ack_timeout", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "timed_out_unknown", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "pending" + }, + "wait": { + "for": "acknowledged", + "status": "timed_out_unknown", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "keep_waiting", + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "delivered_ack_timeout", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "timed_out_unknown", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "observed", + "injectionObserved": true, + "clientUserMessageId": "message-fixture", + "turn": { + "id": "turn-fixture", + "status": "completed" + } + }, + "ack": { + "status": "pending" + }, + "wait": { + "for": "acknowledged", + "status": "timed_out_unknown", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "delivered_wait_satisfied", + "mode": "opt_in", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "delivered", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "observed", + "injectionObserved": true, + "clientUserMessageId": "message-fixture" + }, + "ack": { + "status": "not_requested" + }, + "wait": { + "for": "delivered", + "status": "satisfied", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "receipt_satisfies_wait_without_injection_claim", + "mode": "opt_in", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "acknowledged", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "acknowledged", + "assurance": "token_possession", + "receivedAtUtcMs": 999, + "late": false + }, + "wait": { + "for": "acknowledged", + "status": "satisfied", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "late_receipt_preserves_timeout_history", + "mode": "status", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "handoff", + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "acknowledged", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "acknowledged", + "assurance": "token_possession", + "receivedAtUtcMs": 1001, + "late": true + }, + "wait": { + "for": "acknowledged", + "status": "timed_out_unknown", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + } + } + }, + { + "name": "manual_operator_receipt", + "mode": "status", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "handoff", + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "acknowledged", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "acknowledged", + "assurance": "operator_confirmed", + "receivedAtUtcMs": 1001, + "late": false + }, + "wait": { + "for": "none", + "status": "not_requested" + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + } + } + }, + { + "name": "status_query_of_timeout_is_not_ack", + "mode": "status", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "handoff", + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "timed_out_unknown", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "pending" + }, + "wait": { + "for": "acknowledged", + "status": "timed_out_unknown", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + } + } + }, + { + "name": "required_ack_channel_unsupported", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "refused", + "submission": { + "status": "refused" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "unsupported" + }, + "wait": { + "for": "acknowledged", + "status": "unsupported", + "reason": "evidence_unsupported", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [] + } + } + }, + { + "name": "deadline_before_effect", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "refused", + "submission": { + "status": "refused" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "not_requested" + }, + "wait": { + "for": "delivered", + "status": "failed", + "reason": "deadline_before_effect", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [] + } + } + }, + { + "name": "dry_run_preserves_codex_false", + "mode": "opt_in", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "send", + "status": "validated", + "submitted": false, + "consumptionConfirmed": false, + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "validated", + "submission": { + "status": "not_attempted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "not_requested" + }, + "wait": { + "for": "none", + "status": "not_requested" + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [] + }, + "codexHome": "/fixture/codex-home", + "dryRun": true + } + }, + { + "name": "unknown_preserves_legacy_unknown_false", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "status": "unknown", + "submitted": false, + "consumptionConfirmed": false, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "unknown", + "submission": { + "status": "unknown" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "not_requested" + }, + "wait": { + "for": "none", + "status": "not_requested" + }, + "targetGeneration": null, + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + } + } + }, + { + "name": "claude_success_preserves_optional_absence", + "mode": "opt_in", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "send", + "target": { + "pid": 1, + "name": "fixture" + }, + "chars": 7, + "dryRun": false, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "submitted", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "not_requested" + }, + "wait": { + "for": "none", + "status": "not_requested" + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + } + } + }, + { + "name": "new_wait_insufficient_budget_preserves_submission", + "mode": "wait", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "handoff", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "submitted", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "failed", + "injectionObserved": false + }, + "ack": { + "status": "not_requested" + }, + "wait": { + "for": "acknowledged", + "status": "failed", + "reason": "insufficient_budget", + "operationId": "44444444-4444-4444-8444-444444444444", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "python_claude_explicit_ack_timeout", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "target": { + "pid": 1, + "name": "fixture" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "timed_out_unknown", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "pending" + }, + "wait": { + "for": "acknowledged", + "status": "timed_out_unknown", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "keep_waiting", + "reconcile" + ] + }, + "dryRun": false, + "chars": 7 + } + }, + { + "name": "typescript_claude_explicit_ack_timeout", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "status": "posted", + "submitted": true, + "consumptionConfirmed": false, + "target": { + "agent": "claude", + "pid": 1, + "name": "fixture" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "timed_out_unknown", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "pending" + }, + "wait": { + "for": "acknowledged", + "status": "timed_out_unknown", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000 + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "keep_waiting", + "reconcile" + ] + }, + "dryRun": false, + "chars": 7 + } + }, + { + "name": "queued_post_submission_unsupported", + "mode": "opt_in", + "valid": true, + "exit": 1, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "submitted", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "unsupported" + }, + "wait": { + "for": "acknowledged", + "status": "unsupported", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000, + "reason": "evidence_unsupported" + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "queued_interrupted_pending_wait", + "mode": "opt_in", + "valid": true, + "exit": 130, + "result": { + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "send", + "status": "queued", + "submitted": true, + "consumptionConfirmed": false, + "queueId": "queue-fixture", + "target": { + "agent": "codex", + "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + }, + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "submitted", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "pending" + }, + "wait": { + "for": "acknowledged", + "status": "stopped", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000, + "reason": "stopped_by_operator" + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "keep_waiting", + "reconcile" + ] + }, + "codexHome": "/fixture/codex-home", + "dryRun": false + } + }, + { + "name": "stopped_status_query", + "mode": "status", + "valid": true, + "exit": 0, + "result": { + "schemaVersion": 1, + "ok": true, + "host": "fixture", + "command": "handoff", + "handoff": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "state": "submitted", + "submission": { + "status": "submitted" + }, + "observation": { + "status": "not_requested", + "injectionObserved": false + }, + "ack": { + "status": "pending" + }, + "wait": { + "for": "acknowledged", + "status": "stopped", + "operationId": "33333333-3333-4333-8333-333333333333", + "deadlineAtUtcMs": 1000, + "reason": "stopped_by_operator" + }, + "targetGeneration": "fixture-generation", + "decisionOwner": "sender_operator", + "retry": { + "allowed": false, + "reason": "receiver_dedup_unavailable" + }, + "nextActions": [ + "keep_waiting", + "reconcile" + ] + } + } + } + ], + "knownTombstone": { + "name": "known_tombstone_retains_original_epoch", "exit": 0, + "result": {"schemaVersion": 1, "ok": true, "host": "fixture", "command": "handoff", "handoff": {"schemaVersion": 1, "correlationId": "22222222-2222-4222-8222-222222222222", "ledgerEpoch": "11111111-1111-4111-8111-111111111111", "state": "unknown", "submission": {"status": "unknown"}, "observation": {"status": "not_requested", "injectionObserved": false}, "ack": {"status": "not_requested"}, "wait": {"for": "none", "status": "not_requested"}, "targetGeneration": "fixture-generation", "decisionOwner": "sender_operator", "retry": {"allowed": false, "reason": "receiver_dedup_unavailable"}, "nextActions": ["reconcile"]}} + }, + "rejected": [ + { + "name": "null_original_generation_pending_ack", + "base": "queued_explicit_ack_timeout", + "set": {"targetGeneration": null}, + "valid": false + }, + { + "name": "boolean_schema_version", + "base": "queued_best_effort_observer_failed", + "set": { + "schemaVersion": true + }, + "valid": false + }, + { + "name": "fraction_schema_version", + "base": "queued_best_effort_observer_failed", + "set": { + "schemaVersion": 1.5 + }, + "valid": false + }, + { + "name": "wrong_schema_version", + "base": "queued_best_effort_observer_failed", + "set": { + "schemaVersion": 2 + }, + "valid": false + }, + { + "name": "noncanonical_uuid", + "base": "queued_best_effort_observer_failed", + "set": { + "correlationId": "22222222-2222-4222-8222-22222222222A" + }, + "valid": false + }, + { + "name": "invalid_epoch", + "base": "queued_best_effort_observer_failed", + "set": { + "ledgerEpoch": "not-an-epoch" + }, + "valid": false + }, + { + "name": "unknown_state", + "base": "queued_best_effort_observer_failed", + "set": { + "state": "completed" + }, + "valid": false + }, + { + "name": "resend_action", + "base": "queued_best_effort_observer_failed", + "set": { + "nextActions": [ + "resend_same_id" + ] + }, + "valid": false + }, + { + "name": "duplicate_action", + "base": "queued_best_effort_observer_failed", + "set": { + "nextActions": [ + "reconcile", + "reconcile" + ] + }, + "valid": false + }, + { + "name": "native_retry_allowed", + "base": "queued_best_effort_observer_failed", + "set": { + "retry.allowed": true + }, + "valid": false + }, + { + "name": "future_dedup_claim", + "base": "queued_best_effort_observer_failed", + "set": { + "retry.reason": "verified_receiver_dedup" + }, + "valid": false + }, + { + "name": "boolean_injection", + "base": "queued_best_effort_observer_failed", + "set": { + "observation.injectionObserved": 1 + }, + "valid": false + }, + { + "name": "injection_without_id", + "base": "queued_best_effort_observer_failed", + "set": { + "observation.injectionObserved": true + }, + "valid": false + }, + { + "name": "native_id_control", + "base": "queued_best_effort_observer_failed", + "set": { + "observation.clientUserMessageId": "id\u001b[31m" + }, + "valid": false + }, + { + "name": "native_id_too_long", + "base": "queued_best_effort_observer_failed", + "set": { + "observation.clientUserMessageId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + "valid": false + }, + { + "name": "generation_too_long", + "base": "queued_best_effort_observer_failed", + "set": { + "targetGeneration": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + "valid": false + }, + { + "name": "generation_control", + "base": "queued_best_effort_observer_failed", + "set": { + "targetGeneration": "x…" + }, + "valid": false + }, + { + "name": "unpaired_surrogate", + "base": "queued_best_effort_observer_failed", + "set": { + "targetGeneration": "\ud800" + }, + "valid": false + }, + { + "name": "ack_without_receipt", + "base": "queued_best_effort_observer_failed", + "set": { + "state": "acknowledged" + }, + "valid": false + }, + { + "name": "secret_extra_field", + "base": "queued_best_effort_observer_failed", + "set": { + "capability": "placeholder" + }, + "valid": false + }, + { + "name": "unknown_nested_field", + "base": "queued_best_effort_observer_failed", + "set": { + "submission.extra": true + }, + "valid": false + }, + { + "name": "integer_boolean_utc", + "base": "queued_best_effort_observer_failed", + "set": { + "wait.deadlineAtUtcMs": true + }, + "valid": false + }, + { + "name": "negative_utc", + "base": "queued_best_effort_observer_failed", + "set": { + "wait.deadlineAtUtcMs": -1 + }, + "valid": false + }, + { + "name": "unsafe_utc", + "base": "queued_best_effort_observer_failed", + "set": { + "wait.deadlineAtUtcMs": 9007199254740992 + }, + "valid": false + }, + { + "name": "turn_without_observation", + "base": "queued_best_effort_observer_failed", + "set": { + "observation.turn": { + "id": "turn", + "status": "completed" + } + }, + "valid": false + }, + { + "name": "completed_as_ack", + "base": "queued_best_effort_observer_failed", + "set": { + "ack.status": "completed" + }, + "valid": false + }, + { + "name": "refused_with_submitted", + "base": "queued_best_effort_observer_failed", + "set": { + "state": "refused" + }, + "valid": false + }, + { + "name": "accepted_receipt_wrong_current_state", + "base": "queued_best_effort_observer_failed", + "set": { + "ack": { + "status": "acknowledged", + "assurance": "token_possession", + "receivedAtUtcMs": 999, + "late": false + } + }, + "valid": false + }, + { + "name": "observed_injection_with_unknown_submission", + "base": "queued_best_effort_observer_failed", + "set": { + "state": "unknown", + "submission.status": "unknown", + "observation": { + "status": "observed", + "injectionObserved": true, + "clientUserMessageId": "message-fixture" + } + }, + "valid": false + } + ], + "wireRejected": [ + { + "name": "duplicate_key", + "raw": "{\"schemaVersion\":1,\"schemaVersion\":1}", + "reason": "duplicate_key" + }, + { + "name": "fraction_integer_token", + "raw": "{\"schemaVersion\":1.0}", + "reason": "integer_token" + }, + { + "name": "exponent_integer_token", + "raw": "{\"schemaVersion\":1e0}", + "reason": "integer_token" + }, + { + "name": "nonfinite_number", + "raw": "{\"schemaVersion\":NaN}", + "reason": "nonfinite" + }, + { + "name": "trailing_data", + "raw": "{} {}", + "reason": "trailing_data" + }, + { + "name": "invalid_utf8", + "bytesHex": "ff", + "reason": "invalid_utf8" + } + ], + "timeoutLexemes": { + "accepted": [ + "1", + "6", + "30", + "60" + ], + "rejected": [ + "0", + "61", + "01", + " 1", + "1 ", + "+1", + "-1", + "1.0", + "1e0", + "true", + "1" + ] + }, + "budgets": [ + { + "name": "minimum_budget_valid_but_no_effect", + "totalS": 1, + "elapsedS": 0, + "effectAllowed": false, + "submission": "refused", + "reason": "insufficient_budget", + "exit": 1 + }, + { + "name": "cleanup_only_budget_no_effect", + "totalS": 5, + "elapsedS": 0, + "effectAllowed": false, + "submission": "refused", + "reason": "insufficient_budget", + "exit": 1 + }, + { + "name": "strictly_before_cutoff", + "totalS": 30, + "elapsedS": 24.999, + "effectAllowed": true + }, + { + "name": "at_cutoff", + "totalS": 30, + "elapsedS": 25, + "effectAllowed": false, + "submission": "refused", + "reason": "deadline_before_effect", + "exit": 1 + }, + { + "name": "remote_remaining_not_reset", + "totalS": 30, + "elapsedS": 26, + "effectAllowed": false, + "remainingS": 4, + "submission": "refused", + "reason": "deadline_before_effect", + "exit": 1 + } + ], + "behaviorCases": [ + { + "name": "unknown_custom_uuid", + "given": "supplied ID absent from intact ledger", + "expected": { + "nativeSubmissions": 0, + "submission": "refused", + "freshIdAllocated": false + } + }, + { + "name": "intent_crash_before_effect", + "given": "effect intent synced, no native outcome recorded", + "expected": { + "nativeSubmissionsOnRecovery": 0, + "submission": "unknown", + "retryAllowed": false + } + }, + { + "name": "duplicate_valid_receipt", + "given": "same committed receipt ID, original epoch/correlation/generation, constant-time proof of original capability hash, retained receipt detail, no revocation/quarantine", + "expected": { + "receiptCommitCount": 1, + "state": "acknowledged" + } + }, + { + "name": "wrong_receipt_id_or_binding", + "given": "invalid epoch/correlation/generation/capability", + "expected": { + "stateAdvances": false, + "nativeSubmissions": 0 + } + }, + { + "name": "target_restart", + "given": "original generation changes", + "expected": { + "newGenerationBound": false, + "resubmit": false + } + }, + { + "name": "ledger_missing", + "given": "previous ledger unavailable", + "expected": { + "state": "unknown", + "resubmit": false, + "autoInit": false + } + }, + { + "name": "ledger_restored", + "given": "operator marks known restore", + "expected": { + "quarantined": true, + "resubmit": false, + "receiptAccept": false + } + }, + { + "name": "detail_expired", + "given": "30 day detail pruning; fence retained", + "expected": { + "state": "unknown", + "resubmit": false, + "fenceRetained": true + } + }, + { + "name": "ledger_capacity_full", + "given": "intent or byte quota exhausted", + "expected": { + "newEffect": false, + "evictFence": false + } + }, + { + "name": "wait_quota_full", + "given": "64 committed wait records", + "expected": { + "newWait": false, + "submissionFactsPreserved": true + } + }, + { + "name": "late_receipt_new_wait", + "given": "originating wait timed out; receipt late; second wait starts", + "expected": { + "originalWait": "timed_out_unknown", + "newWait": "satisfied", + "ackLate": true, + "nativeSubmissions": 0 + } + }, + { + "name": "unsupported_no_keep_waiting", + "given": "permanently unsupported ACK producer", + "expected": { + "keepWaiting": false, + "mintCapability": false + } + }, + { + "name": "utc_clock_jump", + "given": "UTC diagnostic clock moves", + "expected": { + "monotonicBudgetUnchanged": true + } + }, + { + "name": "collector_restart_without_raw_capability", + "given": "only credential hash and original receipt binding retained", + "expected": { + "capabilityRegenerated": false, + "nativeSubmissions": 0 + } + }, + { + "name": "fanout_reconcile", + "given": "original per-destination intents stored", + "expected": { + "newIdsAllocated": false, + "newSubmissions": 0 + } + }, + { + "name": "malformed_flags_without_id", + "given": "invalid CLI before canonical intent", + "expected": { + "exit": 2, + "handoffPresent": false, + "synthesizeSubmitted": false + } + }, + { + "name": "opt_out_shape", + "given": "no opt-in flags", + "expected": { + "handoffPresent": false, + "legacyExitUnchanged": true + } + }, + { + "name": "duplicate_wrong_token", + "given": "committed receipt with correct IDs but failed constant-time capability hash proof", + "expected": {"storedReceiptReturned": false, "stateAdvances": false, "receiptCommitCount": 1} + }, + { + "name": "duplicate_expired_capability", + "given": "original capability expired or spent by first receipt; valid original hash proof and retained committed receipt detail; not revoked/quarantined", + "expected": {"storedReceiptReturned": true, "stateAdvances": false, "receiptCommitCount": 1} + }, + { + "name": "duplicate_expired_receipt_detail", + "given": "original binding and token proof valid but receipt detail retention expired", + "expected": {"storedReceiptReturned": false, "stateAdvances": false, "fenceRetained": true} + }, + { + "name": "duplicate_revoked_or_quarantined", + "given": "correct original binding and token proof but authority revoked or epoch quarantined", + "expected": {"storedReceiptReturned": false, "stateAdvances": false} + }, + { + "name": "committed_ack_restart", + "given": "atomic durable receipt includes proven assurance/late and originating wait ordering; collector monotonic clock restarts", + "expected": {"state": "acknowledged", "classificationPreserved": true, "originalWaitRewritten": false} + }, + { + "name": "receipt_precommit_crash", + "given": "collector crashes before classification and atomic durable receipt commit", + "expected": {"ackPromoted": false, "inventLate": false, "originalWaitRewritten": false} + }, + { + "name": "receipt_unknown_ordering", + "given": "receipt proof is valid but original wait monotonic ordering cannot be proven and no committed classification exists", + "expected": {"ackPromoted": false, "inventLate": false, "originalWaitRewritten": false} + }, + { + "name": "manual_confirmation_unproven_order", + "given": "operator now confirms a prior receipt, but ordering against the originating wait cannot be proven", + "expected": {"exit": 1, "reason": "receipt_order_unprovable", "ackPromoted": false, "originalWaitRewritten": false} + }, + { + "name": "original_generation_late_receipt", + "given": "valid hash proof plus immutable original epoch/correlation/generation; original receipt delayed until current owner has restarted; ordered late classification proven", + "expected": {"state": "acknowledged", "ackLate": true, "generationRebound": false, "modelIdentityProven": false, "copiedTokenExcluded": false} + }, + { + "name": "successor_generation_receipt", + "given": "receipt explicitly claims current successor generation instead of immutable original generation", + "expected": {"ackPromoted": false, "generationRebound": false} + }, + { + "name": "missing_generation_required_wait", + "given": "original generation is null and required delivered or acknowledged wait requested", + "expected": {"submission": "refused", "wait": "unsupported", "mintCapability": false, "exit": 1} + }, + { + "name": "recipient_native_storage_retention", + "given": "authorized capability is delegated through native effect input that the recipient may store in its own history", + "expected": {"recipientStorageMayRetainCapability": true, "sameUserReadersMayAcquireAuthority": true, "observerReadsTranscript": false, "independentAgentAuthentication": false} + }, + { + "name": "secret_not_reflected_in_errors_or_ack", + "given": "private capability submitted with malformed frame or valid ACK; sender/collector ordinary outputs and own raw journal inspected", + "expected": {"secretReflected": false, "rawCapabilityPersisted": false, "authority": "receipt_only"} + }, + { + "name": "first_receipt_restriction", + "given": "already committed first receipt; later receipt uses a different receiptId even with valid token", + "expected": {"newReceiptCommitted": false, "stateAdvances": false} + }, + { + "name": "copied_original_authority_indistinguishable", + "given": "successor or same-user reader possesses the complete immutable original tuple AND copied original capability", + "expected": {"distinguishableFromOriginalProducer": false, "assurance": "token_possession", "modelIdentityProven": false} + } + ], + "sshCases": [ + { + "name": "complete_queued_wait_timeout", + "sample": "queued_explicit_ack_timeout", + "transport": "nonzero_or_timeout", + "expected": { + "preserveNativeSnapshot": true, + "explicitWaitExit": 1, + "additionalSubmission": false + } + }, + { + "name": "claude_reconstruction", + "sample": "claude_success_preserves_optional_absence", + "expected": { + "preserveHandoff": true, + "preserveLegacyAbsence": true + } + }, + { + "name": "malformed_handoff_valid_native", + "given": "valid native queued receipt plus invalid new object", + "expected": { + "nativeEvidencePreserved": true, + "ackPromoted": false, + "explicitWaitExit": 1, + "additionalSubmission": false + } + }, + { + "name": "incomplete_stdout", + "given": "truncated single JSON frame", + "expected": { + "submission": "unknown", + "additionalSubmission": false + } + }, + { + "name": "stderr_non_utf8", + "given": "valid stdout frame and ff diagnostic stderr", + "expected": { + "stdoutStrict": true, + "stderrReplacement": true, + "preserveNativeSnapshot": true + } + } + ], + "queryErrors": [ + { + "name": "missing_ledger_no_invented_epoch", "exit": 1, + "result": {"schemaVersion": 1, "ok": false, "host": "fixture", "command": "handoff", "reason": "handoff_history_unavailable", "handoffQuery": {"schemaVersion": 1, "correlationId": "22222222-2222-4222-8222-222222222222", "status": "unknown", "context": "ledger_missing", "retry": {"allowed": false, "reason": "history_unavailable"}}} + }, + { + "name": "corrupt_ledger_no_invented_epoch", "exit": 1, + "result": {"schemaVersion": 1, "ok": false, "host": "fixture", "command": "handoff", "reason": "handoff_history_unavailable", "handoffQuery": {"schemaVersion": 1, "correlationId": "22222222-2222-4222-8222-222222222222", "status": "unknown", "context": "ledger_corrupt", "retry": {"allowed": false, "reason": "history_unavailable"}}} + }, + { + "name": "unknown_id_no_invented_epoch", "exit": 1, + "result": {"schemaVersion": 1, "ok": false, "host": "fixture", "command": "handoff", "reason": "handoff_history_unavailable", "handoffQuery": {"schemaVersion": 1, "correlationId": "22222222-2222-4222-8222-222222222222", "status": "unknown", "context": "id_unknown", "retry": {"allowed": false, "reason": "history_unavailable"}}} + } + ], + "requestAwareWireCases": [ + {"name":"explicit_wait_matching_target","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"request_opt_out_forged_handoff","sample":"queued_explicit_ack_timeout","optIn":false,"explicitWait":false,"waitFor":"none","exit":1,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"best_effort_cannot_enable_wait_exception","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":false,"waitFor":"none","exit":1,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"wrong_native_target","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"target.id":"wrong-target"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"malformed_handoff_preserves_native","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"handoff.ack.status":"acknowledged"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"wrong_wait_goal","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"delivered","exit":1,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"wrong_correlation","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"handoff.correlationId":"33333333-3333-4333-8333-333333333333"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"posted_explicit_wait_timeout","sample":"typescript_claude_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{},"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false},"request":{"nativeProfile":"typescript_claude","target":{"agent":"claude","pid":1,"name":"fixture"}}}, + {"name":"posted_opt_out_forged_handoff","sample":"typescript_claude_explicit_ack_timeout","optIn":false,"explicitWait":false,"waitFor":"none","exit":1,"set":{},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false},"request":{"nativeProfile":"typescript_claude","target":{"agent":"claude","pid":1,"name":"fixture"}}}, + {"name":"posted_wrong_native_target","sample":"typescript_claude_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"target.pid":2},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false},"request":{"nativeProfile":"typescript_claude","target":{"agent":"claude","pid":1,"name":"fixture"}}}, + {"name":"malformed_handoff_ok_true_cannot_satisfy_wait","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":0,"set":{"ok":true,"handoff.ack.status":"acknowledged"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"ack_wrong_wait_goal","sample":"receipt_satisfies_wait_without_injection_claim","optIn":true,"explicitWait":true,"waitFor":"delivered","exit":0,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"ack_forged_opt_out","sample":"receipt_satisfies_wait_without_injection_claim","optIn":false,"explicitWait":false,"waitFor":"none","exit":0,"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"ack_wrong_native_target","sample":"receipt_satisfies_wait_without_injection_claim","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":0,"set":{"target.id":"wrong-target"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"codex_posted_is_invalid","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"status":"posted"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"codex_consumption_overwrite","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"consumptionConfirmed":true},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"wrong_codex_home","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"codexHome":"/fixture/other-home"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"wrong_native_host","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"host":"other-fixture"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"python_claude_absent_fields_timeout","sample":"python_claude_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"request":{"nativeProfile":"python_claude","target":{"pid":1,"name":"fixture"}},"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"python_claude_invented_submitted","sample":"python_claude_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"submitted":true},"request":{"nativeProfile":"python_claude","target":{"pid":1,"name":"fixture"}},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"typescript_claude_omitted_agent_null_name","sample":"typescript_claude_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"target.name":null},"request":{"nativeProfile":"typescript_claude","target":{"agent":"claude","pid":1,"name":"fixture"}},"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false},"remove":["target.agent"]}, + {"name":"typescript_codex_case_insensitive_agent_absent","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"target.id":"AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA"},"request":{"nativeProfile":"typescript_codex"},"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false},"remove":["target.agent"]}, + {"name":"typescript_codex_native_non_v4_uuid","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"target.id":"AAAAAAAA-AAAA-7AAA-8AAA-AAAAAAAAAAAA"},"request":{"nativeProfile":"typescript_codex","target":{"agent":"codex","id":"aaaaaaaa-aaaa-7aaa-8aaa-aaaaaaaaaaaa"}},"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"optional_codex_queue_id","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false},"remove":["queueId"]}, + {"name":"post_submission_channel_unsupported","sample":"queued_post_submission_unsupported","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"post_submission_unsupported_opt_out","sample":"queued_post_submission_unsupported","optIn":false,"explicitWait":false,"waitFor":"none","exit":1,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"post_submission_unsupported_wrong_binding","sample":"queued_post_submission_unsupported","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"handoff.targetGeneration":"other-generation"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_queued_success_snapshot","sample":"queued_interrupted_pending_wait","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":130,"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_opt_out_forgery","sample":"queued_interrupted_pending_wait","optIn":false,"explicitWait":false,"waitFor":"none","exit":130,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_not_explicitly_requested","sample":"queued_interrupted_pending_wait","optIn":true,"explicitWait":false,"waitFor":"none","exit":130,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_wrong_exit_one","sample":"queued_interrupted_pending_wait","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_wrong_exit_zero","sample":"queued_interrupted_pending_wait","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":0,"set":{"ok":true},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_wrong_native_target","sample":"queued_interrupted_pending_wait","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":130,"set":{"target.id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_wrong_wait_reason","sample":"queued_interrupted_pending_wait","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":130,"set":{"handoff.wait.reason":"evidence_failed"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":false,"ackPromoted":false,"resubmit":false}}, + {"name":"valid_ack_profile","sample":"receipt_satisfies_wait_without_injection_claim","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":0,"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":true,"resubmit":false}}, + {"name":"ack_wrong_exit","sample":"receipt_satisfies_wait_without_injection_claim","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"ok":false},"expected":{"wireAccepted":false,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"unsupported_preserves_injection","sample":"delivered_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"handoff.state":"delivered","handoff.wait.status":"unsupported","handoff.wait.reason":"evidence_unsupported","handoff.ack.status":"unsupported"},"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"interrupted_preserves_injection","sample":"delivered_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":130,"set":{"handoff.state":"delivered","handoff.wait.status":"stopped","handoff.wait.reason":"stopped_by_operator"},"expected":{"wireAccepted":true,"nativeEvidencePreserved":true,"handoffValidated":true,"ackPromoted":false,"resubmit":false}}, + {"name":"wrong_runtime_agent_profile","sample":"queued_explicit_ack_timeout","optIn":true,"explicitWait":true,"waitFor":"acknowledged","exit":1,"set":{"target.agent":"claude"},"expected":{"wireAccepted":false,"nativeEvidencePreserved":false,"handoffValidated":false,"ackPromoted":false,"resubmit":false}} + ], + "framing": { + "privateInput": "raw UTF-8 bytes including all whitespace and LF; <=4096", + "handoffSubtree": "compact ensure_ascii=false UTF-8 encoding of handoff object only; <=8192", + "optInOuter": "raw complete per-destination stdout frame including whitespace and LF; <=1048576", + "optInFanout": "at most32 destinations before effect and aggregate stdout <=34603008 bytes; no normalization of ordinary legacy mode", + "lexicalScope": "new-schema subtrees only; legacy native sibling numeric tokens follow their existing rules", + "oversizedHandoff": "preserve independently valid matching native snapshot; no ACK promotion and no resubmission" + }, + "receiptSchema": { + "schemaVersion": 1, + "kind": "receipt", + "required": [ + "schemaVersion", + "kind", + "ledgerEpoch", + "correlationId", + "targetGeneration", + "receiptId", + "capability" + ], + "unknownKeysAllowed": false, + "capabilityInput": "private_stdin_only", + "capabilityEncoding": "base64url_unpadded_43_chars", + "persistRawCapability": false, + "issuer": "compatible_receipt_only_handler", + "assurance": "token_possession", + "duplicateProof": "original epoch/correlation/generation/receiptId plus constant-time original capability hash proof", + "duplicateExpiryException": "only return retained already committed receipt; deny revoked/quarantined/detail-expired authority", + "commitClassificationAtomically": ["assurance", "late", "originatingWaitOrdering"], + "recipientNativeStorageMayPersistCapability": true, + "independentAgentAuthentication": false + }, + "operatorConfirmationSchema": { + "required": [ + "schemaVersion", + "ledgerEpoch", + "correlationId", + "targetGeneration", + "confirmed" + ], + "confirmed": true, + "unknownKeysAllowed": false, + "input": "private_metadata_stdin_only", + "assurance": "operator_confirmed", + "eventSemantics": "new current attestation, not retroactive receipt timing proof", + "unprovableOriginatingWaitOrdering": {"exit": 1, "reason": "receipt_order_unprovable", "ackPromoted": false} + }, + "freezeGates": [ + "TS exact commit review", + "prepare/confirm/epoch/wait identity amendments", + "collector bootstrap and restart continuity", + "intact-history restoration limitation", + "bounded resource cleanup platform validation", + "strict opt-in SSH preservation", + "token-aware JSON numeric validation", + "no live/runtime claims" + ] +} diff --git a/test/fixtures/handoff-v1.md b/test/fixtures/handoff-v1.md new file mode 100644 index 0000000..5f691e5 --- /dev/null +++ b/test/fixtures/handoff-v1.md @@ -0,0 +1,203 @@ +# Handoff v1 contract — design proposal v0.2 + +## Status and provenance + +This is an unimplemented design for Python #181 and the TypeScript 0.4.0 design review. It is not a frozen protocol, a release promise, or evidence of live ACK support. Merge of this document does not authorize runtime implementation, publication, native submissions, or operational changes. The fixture contains synthetic examples, not recorded deliveries. + +The published Python reference is [v1.0.3](https://github.com/abruption/session-peer/tree/0d252550ab40c26d1ac4a19193df6ca2f84d830b). The ordinary output source is [adapters](https://github.com/abruption/session-peer/blob/0d252550ab40c26d1ac4a19193df6ca2f84d830b/session_peer_core/adapters.py), [Codex](https://github.com/abruption/session-peer/blob/0d252550ab40c26d1ac4a19193df6ca2f84d830b/session_peer_core/codex.py), [SSH](https://github.com/abruption/session-peer/blob/0d252550ab40c26d1ac4a19193df6ca2f84d830b/session_peer_core/ssh.py), and [output](https://github.com/abruption/session-peer/blob/0d252550ab40c26d1ac4a19193df6ca2f84d830b/session_peer_core/output.py). The [shared fixture](https://github.com/abruption/session-peer/blob/main/tests/fixtures/handoff-v1.json) separates normalized legacy examples from proposed opt-in examples. Runtime reference and design fixture pins are separate: TypeScript's published historical reference is not silently changed to an unshipped Python version. + +## Evidence and compatibility + +Submission is acceptance by the native transport; delivery is evidence that the exact original user-message ID was injected into the exact original target generation. Acknowledgement is an explicit correlated receipt, not completion, responsibility acceptance, or proof that the model read every byte. Queue deletion, process exit, turn completion, reply silence and a successful status query are not ACKs. Unknown native turn states never become completed. + +Ordinary send output stays runtime-specific. Python Claude success omits status, submitted and consumptionConfirmed; Codex queued success includes submitted true and consumptionConfirmed false. Legacy unknown with submitted false is not universal no-effect proof. Do not add missing false/null fields, reinterpret optional absence, overwrite consumptionConfirmed, or change ordinary exits. Normalized fixture values hide identities only; each example records provenance and is not an exhaustive output schema. + +Opt-in adds one handoff object to each destination result under the existing outer schemaVersion 1. There is no fanout-level handoff. Explicit waiting can change outer ok and exit only: it preserves the native status, target, queueId, submitted, consumptionConfirmed and all independently valid submission evidence. The receiver of a result must use its ORIGINAL request context, not an untrusted returned field, to select this validation mode AND the runtime/agent native profile. Only an original explicit-wait request plus a fully validated handoff, positive native profile and matching original target/home/context permits a failed-wait exception. Timeout, failure and post-submission unsupported use ok false/exit 1; interruption of a pending wait uses ok false/exit 130. Receiving handoff alone never enables either exception in an opt-out request. Best-effort observation failure cannot change native submission success. Invalid/oversized handoff cannot erase a separately validated, correctly targeted native snapshot or promote an ACK; explicit waiting fails validation without resubmitting. A wrong native target/home or altered consumption fact is not independently valid native evidence. A valid ordinary opt-out success remains valid with unrequested handoff ignored. + +Positive native profiles are separate from the common handoff submission enum. Do not infer positive Python Claude submission from outer status/submitted fields that its native success does not emit, and never add those fields merely to satisfy a guard. The opt-in adapter must retain its validated pre-wait native success snapshot; a consumer validates that profile and exact original context rather than trusting handoff.submission alone. + +| Original runtime / agent | Positive native snapshot | Target/context matching | +| --- | --- | --- | +| Python / Claude | status, submitted and consumptionConfirmed absent; existing positive socket-write target fields retained | original resolved target pid/name under Python's existing rules | +| TypeScript / Claude | status posted, submitted true, consumptionConfirmed false; queueId absent | resolved pid; agent may be absent, name may be null under the existing TS schema | +| Python / Codex | status queued, submitted true, consumptionConfirmed false; native queueId and codexHome retained | original thread and canonical home/context under Python's existing rules | +| TypeScript / Codex | status queued, submitted true, consumptionConfirmed false; native queueId and codexHome retained | UUID comparison remains case-insensitive; agent may be absent; canonical home/context must match | + +This table defines synthetic positive-profile fixtures, not an exhaustive runtime envelope or permission to relax each runtime's existing validator. A Codex snapshot with posted status or consumptionConfirmed true is invalid. Target comparison is profile-aware, not raw dictionary equality: permitted optional absence and null values stay permitted, while wrong agents, ids/pids or canonical homes fail. Canonical home matching is an additional proposed handoff gate where a runtime's current SSH validator does not yet enforce it, not a claim of shipped protection. Native snapshot validation is independent of whether a handoff is present or valid. + +## Public schema + +The following grammar is normative for this proposal. All objects are closed except the runtime-specific outer envelope. A question mark means optional and absent when unavailable; nullable fields are explicit below. Conditional rules follow the grammar. + +```text +handoff.schemaVersion = 1 +handoff.correlationId = UUID-v4 lowercase canonical +handoff.ledgerEpoch = UUID-v4 lowercase canonical +handoff.state = validated | refused | submitted | delivered | acknowledged | unknown | timed_out_unknown +handoff.submission.status = not_attempted | submitted | refused | unknown +handoff.observation.status = not_requested | pending | observed | unsupported | failed +handoff.observation.injectionObserved = boolean +handoff.observation.clientUserMessageId? = safe-id +handoff.observation.turn? = {id: safe-id, status: running | completed | failed | interrupted | unknown} +handoff.ack.status = not_requested | pending | acknowledged | unsupported +handoff.ack.assurance? = token_possession | operator_confirmed +handoff.ack.receivedAtUtcMs? = safe-integer +handoff.ack.late? = boolean +handoff.wait.for = none | delivered | acknowledged +handoff.wait.status = not_requested | pending | satisfied | timed_out_unknown | stopped | unsupported | failed +handoff.wait.operationId? = UUID-v4 lowercase canonical +handoff.wait.deadlineAtUtcMs? = safe-integer +handoff.wait.reason? = insufficient_budget | deadline_before_effect | evidence_unsupported | evidence_failed | history_unavailable | stopped_by_operator | invalid_handoff +handoff.targetGeneration = safe-generation | null +handoff.decisionOwner = sender_operator +handoff.retry = {allowed: false, reason: receiver_dedup_unavailable} +handoff.nextActions = unique subset of [keep_waiting, reconcile, stop_waiting] +``` + +Delivery requires observed injection plus an exact clientUserMessageId and non-null original targetGeneration. Turn evidence requires observed injection; a turn is separate from receipt state. Receipt acceptance does not manufacture injection evidence. An acknowledged state requires acknowledged ack with assurance, receipt time and late flag; other ACK statuses omit those fields. Submitted, delivered and acknowledged states require submitted submission. Validated requires not_attempted; refused requires refused; unknown requires unknown. Timed-out state permits submitted or unknown, never refused/not_attempted. + +Each explicit wait has an operationId and deadline, even pre-effect refusal. No-wait records have neither. Satisfied delivered waits require injection evidence; satisfied acknowledged waits require valid ACK. Stopped/failed/unsupported waits preserve known effect evidence. nextActions depend on a live validated channel and retained history: unsupported or expired channels never offer keep_waiting. stop_waiting ends observation only; it does not cancel native work. All native local/SSH results forbid retry. No resend_same_id action exists in v1; correlation and user approval alone do not establish safe resend. Receiver dedup (#182) is not a prerequisite for implementing this no-resend contract. + +## State and exit tuples + +Absent means a legacy field remains absent. Native denotes an unchanged per-runtime snapshot. The same table applies to local and SSH, not to an unimplemented paired-device transport. + +| Mode / evidence | state | submission | wait status | outer status / submitted | ok / exit | +| --- | --- | --- | --- | --- | --- | +| Ordinary send | absent | absent | absent | native / native or absent | native | +| Opt-in dry-run | validated | not_attempted | not_requested | native / native or absent | true / 0 | +| Required channel unavailable before effect | refused | refused | unsupported | native if present / no synthetic field | false / 1 | +| Budget exhausted definitely before effect | refused | refused | failed | native if present / no synthetic field | false / 1 | +| Known queued, best-effort observer fails | submitted | submitted | not_requested | queued / true | true / 0 | +| Known queued, explicit ACK deadline expires | timed_out_unknown | submitted | timed_out_unknown | queued / true | false / 1 | +| Positive native profile, channel becomes unsupported after submission | submitted or delivered | submitted | unsupported | native / native or absent | false / 1 | +| Pending explicit wait interrupted by operator | submitted, delivered or unknown | submitted or unknown | stopped | native / native or absent | false / 130 | +| Injection known, ACK deadline expires | timed_out_unknown | submitted | timed_out_unknown | native / native or absent | false / 1 | +| Effect may have occurred, no receipt | unknown | unknown | not_requested | native / native or absent | false / 1 | +| Valid receipt satisfies explicit wait | acknowledged | submitted | satisfied | native / native or absent | true / 0 | +| Successful status query of prior timeout | timed_out_unknown | submitted | timed_out_unknown | absent / absent | true / 0 | +| Successful status query of stopped wait | retained state | retained submission | stopped | absent / absent | true / 0 | + +Invalid flags, invalid timeout lexemes, missing targets and malformed IDs retain usage/no-target exit 2 and may have no JSON/handoff at all. No synthetic correlation ID or legacy false/null is invented for such failures. Interrupting a pending explicit send/wait operation, including SIGINT, terminally records wait stopped with reason stopped_by_operator without cancel/resend. If complete structured output is available, that operation returns ok false/exit 130 while preserving known native/submission/injection facts; SSH accepts this exception only for the original explicit-wait request and a validated stopped wait with matching target/home/bindings. A deliberate operator stop of a pending wait follows the same rule, without adding a new API spelling. Already-terminal waits are immutable: an interrupt cannot rewrite a committed timeout, satisfaction or other terminal result to stopped. A successful status query of that stopped record uses ok true/exit 0. Partial/no interruption output preserves independently known evidence and otherwise uncertainty, never synthetic false fields, fallback or resend. A successful status query reports query success, not acknowledgement. + +## Capabilities and bootstrap + +Every capability is a future acceptance gate, not current product support. A route must validate the receipt consumer, producer and immutable ORIGINAL target generation independently before advertising ACK support. Null generation cannot mint receipt authority or support required delivered/acknowledged wait; that requirement refuses before effect with wait unsupported. A failed bootstrap is unsupported, not permission to try generic message delivery. Newly discovered current generation never substitutes for the original binding. Target restart prevents new submission/rebinding; it does not by itself invalidate a delayed receipt bound to the original generation. A validated original tuple and original capability prove delegated token possession, not original agent/model identity. A successor's new generation is rejected. A successor or same-user reader with a copied original tuple AND original capability is indistinguishable under token_possession assurance; do not claim that this scheme excludes that replay by authenticating model identity. + +| Route / observer | injection observation | receipt ACK | +| --- | --- | --- | +| Same-machine private collector plus compatible producer | Codex only after version/home/writer/ID validation | supported after private bootstrap | +| Explicitly trusted reverse SSH to installed compatible receipt handler | separately validated Codex route | supported after handler/configuration validation | +| Source-streamed SSH without compatible handler installation | separately validated Codex route | unsupported | +| Claude native observer | unsupported by default | compatible explicit receipt producer only | +| Paired device, Relay, Side Session, wake | outside initial route scope | unsupported | + +Required delivered or acknowledged wait refuses before effect when its channel is unsupported. Best-effort correlated send may submit with observation/ack unsupported, without minting an unused secret capability. No collector calls delivery, wake, model prompts or approvals. Native app-server fallback to the queue is allowed only before the original native submission may have begun; post-attempt ambiguity and observer failures never trigger another submission. Version, platform, home and original writer identity checks are mandatory. No transcripts or arbitrary reply bodies are scanned. + +## Receipt wire and lifecycle + +Receipt v1 is separate from Reply-To v1, whose parser and permissions are unchanged. A receipt handle is a non-secret local selector, not a URI or authority. The actual bearer capability travels only through authorized private effect input and private handler stdin. The no-secret guarantee covers sender/collector argv, URI, environment, ordinary JSON, diagnostics, error/ACK results and their own raw journal/persistence. It does NOT cover the intended recipient's native queue, history or transcript: native effect input may be retained there outside CLI control, and same-user readers may acquire delegated receipt-only authority. token_possession is not independent agent authentication. Our observer still never reads transcripts. Revocation, the 24-hour TTL and atomic first-receipt acceptance bound new receipt authority, not the recipient's storage lifetime. + +```json +{ + "schemaVersion": 1, + "kind": "receipt", + "ledgerEpoch": "11111111-1111-4111-8111-111111111111", + "correlationId": "22222222-2222-4222-8222-222222222222", + "targetGeneration": "fixture-generation", + "receiptId": "33333333-3333-4333-8333-333333333333", + "capability": "" +} +``` + +The capability is 32 cryptographically random bytes encoded as 43 unpadded base64url characters. The example placeholder is not valid wire input. The protected collector stores only a SHA-256 capability hash bound to epoch, correlation and ORIGINAL generation; constant-time verification is required for every handler request, including read-only duplicates. Atomic durable first-receipt commit includes receipt ID/binding, proven classification, assurance, late flag, collector receipt time and the wait-order/deadline facts that justify it. State and relevant wait transitions commit consistently with this record, not through separate later writes. Wrong IDs, wrong token, successor generations, conflicting receipts, expired uncommitted authority and invalid frames cannot advance state. ACK arrival time is collector-assigned; caller timestamps are not authority. + +The collector is a user-owned process independent of the sender conversation. Files/directories are private 0600/0700; endpoints are local owner-checked private IPC or an explicitly configured trusted reverse SSH handler. No public listener, auto SSH key installation, profile changes, transcript reading, secret export or generic CLI exec is permitted. Restart recovers committed receipt hashes and classified receipts; lost raw capabilities are never regenerated for an attempted intent. Capability lifetime is 24 hours from intent commit and ends with detail expiry, revocation, quarantine or first valid receipt. The sole expiry exception is read-only retrieval of the SAME already committed receipt: require original epoch, correlation, generation, receiptId AND constant-time proof of the original capability hash. TTL expiry/first-receipt spending does not permit accepting a new receipt or creating/promoting state. Revocation, quarantine, missing/expired detail or a wrong token deny even this exception. Identifiers alone confer no generic status/query permission. Authorized sender-operator status queries use a separate owner-checked local interface. + +Manual producer is the proposed no-submission handoff confirm operation. It requires the exact epoch/ID/ORIGINAL generation and a local sender-operator assertion of an observed explicit correlated reply; stdin receives metadata only, not transcript/body. It records operator_confirmed, never token_possession. Confirmation is a NEW current attestation/event, not retroactive proof of an old receipt's arrival time. It may become acknowledged only if originating wait ordering/late classification is provable, for example from durable terminal wait facts. Otherwise refuse confirmation with ok false, exit 1 and fixed reason receipt_order_unprovable; preserve the original pending/unknown ACK and original wait, do not commit an accepted ACK or invent a late boolean. An already committed valid receipt is never downgraded or replaced by this failed confirmation. The token-possession producer must be an installed compatible receipt-only handler reading private input; it cannot execute arbitrary commands. Both producers remain implementation acceptance gates. No assurance is advertised until its producer is validated. + +## Sender ledger, freshness and retention + +A single private, serialized durable ledger owns a random epoch. Its target-bound prepared intent is committed and synced before effect; once an effect-intent fence is committed, only one native attempt is authorized in that intact epoch. Crash after that commit, including a crash before effect actually starts, recovers as unknown and never submits again. Binding includes agent, destination, home/writer identity, generation and a protected internal payload digest; the digest is not public output. Conflict or target restart never silently rebinds the intent. + +Fresh IDs are generated and reserved by automatic preparation or by the new no-effect handoff prepare operation. Caller-supplied unknown UUIDs are refused before effect, because freshness cannot be proved. A supplied known prepared ID must match its exact binding and be unattempted. A supplied attempted ID is query/reconcile only, never another send. Ordered fanout generates and durably associates one intent per destination once; callers cannot reuse one ID for multiple destinations. Reconciliation never regenerates IDs or capabilities. + +Limits per epoch are 10,000 retained intent/fence records and a 32 MiB journal, whichever fills first. Admission reserves bounded storage for terminal records and one receipt before effect, rather than filling capacity after submission. No fence eviction/LRU is allowed. Detail retention is 30 days from preparation, capability lifetime 24 hours, maximum 64 waits per intent; expired detail becomes a compact target-bound fence tombstone retained for the entire epoch. Tombstones count toward quota. wait quota exhaustion refuses a new wait without changing native effect facts. Pruning details never restores submission authority; quota exhaustion refuses preparation/effect until explicit operator archival, not automatic reset. + +Missing, corrupt, expired-detail or known-restored history yields unknown for status/reconcile, with retry forbidden. A known tombstone retains its original epoch and target binding: it can return a normal handoff with state/submission unknown, no ACK/injection claim and no keep_waiting. Missing/corrupt ledger or an unknown ID cannot recover that context and MUST return the separate query-error envelope below, without handoff or a fabricated epoch. An existing ledger is never recreated automatically; first use requires explicit local initialization and a fresh epoch. Known restoration quarantines all old intents, disabling effects and receipt acceptance until reconciliation of retained evidence. New epochs require explicit operator initialization and generated fresh IDs; they make no claim about old requests. No local-only design can reliably detect an unmarked rollback or cross-machine ledger clone: fencing guarantees are limited to proven intact, retained, non-rolled-back history. External monotonic anchors/clone-proof storage are outside v1; this limitation must remain visible, not hidden behind a universal exactly-once claim. + +The proposed query error uses exit 1. Its handoffQuery is a closed schema with the fields shown; context is ledger_missing, ledger_corrupt or id_unknown. The caller's syntactically valid correlationId is echoed, not treated as proof of history. No ledgerEpoch, handoff, top-level status/submitted/consumptionConfirmed or synthetic native snapshot is added. Outer host/command are the queried destination and handoff, with reason handoff_history_unavailable. This is an opt-in query result, not a change to ordinary legacy errors. + +```json +{ + "schemaVersion": 1, + "ok": false, + "host": "fixture", + "command": "handoff", + "reason": "handoff_history_unavailable", + "handoffQuery": { + "schemaVersion": 1, + "correlationId": "22222222-2222-4222-8222-222222222222", + "status": "unknown", + "context": "ledger_missing", + "retry": {"allowed": false, "reason": "history_unavailable"} + } +} +``` + +## Wait history and late receipts + +Each wait has a distinct operation ID, goal, monotonic deadline and immutable terminal result. Status returns the most recently started wait for that intent (serialized sequence order), or not_requested if none; a future separate wait-history API is not implied. Current receipt/state can advance independently. Thus a late ACK can set current state to acknowledged while the returned originating wait remains timed_out_unknown. It never rewrites that wait to satisfied. + +A receipt is late if accepted at or after the originating send wait's observation deadline; if there was no send wait, compare with the earliest explicit wait. With no wait, late is false. A later wait can be satisfied by an already accepted late receipt; the ack late flag remains true. Receipt acceptance and deadline transitions are serialized against the same monotonic clock while active, and their proven ordering/classification is durably committed atomically. A fully committed valid ACK restores its original classification, assurance, late and immutable wait facts: a changed restarted clock alone NEVER downgrades it. Pre-commit crash or otherwise uncommitted/unclassified timing evidence cannot become acknowledged or supply a synthetic late flag. Preserve it as unclassified evidence, with required wait unsatisfied and existing pending/unknown ACK unchanged. Manual confirmation is a new event under the proof-or-refusal rule above, never retroactive rewriting. UTC timestamps are diagnostics only. Repeated waiting makes no new native submission. + +## Budgets and bounds + +Default budget is 30 seconds per destination; CLI accepts only ASCII decimal integers 1 through 60 without leading zeros, signs, whitespace, decimal points or exponents. New flags do not change legacy parsing. The total monotonic budget starts before setup, preflight, preparation, effect and observation. Reserve exactly 5 seconds within it for cleanup; budgets at or below 5 seconds refuse before effect with insufficient_budget. Observation/effect cutoff is start plus budget minus 5 seconds; cleanup cannot extend start plus budget. A wait deadline diagnostic describes this cutoff, not a renewed remote budget. + +SSH passes only remaining duration and keeps the original caller deadline; remote handlers cannot reset the budget or use UTC to authorize effect. If the remaining total budget is at or below cleanup reserve, refuse before effect. At the cutoff boundary use now >= cutoff as expired; a receipt committed strictly before cutoff may satisfy that wait, one at/after cutoff is late. Once effect may occur, timeout is unknown; no retry/fallback or new submission is allowed. Ordered fanout gives each destination its own bounded budget, not one host's repeated budget. Implementations that cannot guarantee bounded cleanup/owned resources cannot advertise that route. + +```text +safe-integer = JSON integer token, 0..9007199254740991; booleans/fractions/exponents rejected +safe-id = 1..128 UTF-8 bytes, no U+0000..001F or U+007F..009F, valid Unicode scalars +safe-generation = 1..256 UTF-8 bytes, same control/scalar rule +receipt/confirmation frame = raw strict UTF-8 JSON input, at most 4096 bytes including LF/whitespace +public handoff size = compact UTF-8 encoding of the handoff subtree, at most 8192 bytes +opt-in destination stdout frame = at most 1048576 bytes including LF/whitespace +opt-in fanout = at most 32 destinations and 34603008 total stdout bytes including LF/whitespace +duplicate JSON keys, unknown nested keys, NaN/Infinity and trailing data rejected +turn/native IDs are opaque; only correlation/epoch/wait/receipt IDs use UUID-v4 syntax +``` + +JSON number tokens must be validated before lossy parsing; lexical 1.0 and 1e0 are rejected even if a JavaScript Number equals 1. The lexical restriction applies ONLY to the handoff/handoffQuery subtree and private receipt/confirmation schemas, not unrelated legacy outer fields. Keep existing legacy parsing and duplicate-key/nonfinite rules; a legacy field does not become invalid just because a handoff request exists. Hand-off subtree sizing uses canonical compact encoding (no optional whitespace, UTF-8 scalar characters not ASCII escapes, separators comma/colon); subtree whitespace in the enclosing wire counts toward the outer frame limit instead. Private receipt/confirmation raw input includes every byte, including optional trailing JSON whitespace and LF; trailing non-whitespace data is rejected. No extra newline allowance exists. The destination/aggregate limits apply only to new opt-in mode; refuse excessive destination count before any effect and do not change ordinary opt-out wire/output limits. Cap stdout incrementally before allocation; bounded aggregate formatting is compact JSON plus optional LF. When an optional subtree is malformed/oversized inside a bounded complete outer frame, retain independently validated native evidence, reject ACK/explicit wait and never resubmit. If the outer frame limit is exceeded, do not partially parse it into success; retain only already independently validated evidence and otherwise return unknown. Bounds are checked before persistence, not after truncation. Sender/collector ordinary stdout, diagnostics, errors/ACK results and own journal never contain capability or payload digest; they do not read recipient transcript/body. Bounded metadata observation must not hide a body-returning API behind output filtering. + +## Proposed CLI and zero-submit operations + +These are proposed spellings, not commands available in published Python. Preparation binds the same target/message input as send without submission; confirmation accepts metadata-only private stdin. Explicit local ledger initialization is a separate opt-in setup prerequisite, never an automatic recovery action. + +```text +handoff init +handoff prepare --to TARGET --message-file PRIVATE_FILE +send --to TARGET --message-file PRIVATE_FILE --correlation-id PREPARED_UUID +send --to TARGET --request-ack +send --to TARGET --observe-delivery +send --to TARGET --wait-for delivered|acknowledged --wait-timeout SECONDS +handoff status --correlation-id UUID +handoff wait --correlation-id UUID --wait-for delivered|acknowledged --wait-timeout SECONDS +handoff confirm --receipt - +ack --receipt - +``` + +Without correlation-id, request-ack/observe-delivery/wait-for automatically reserve a fresh intent; otherwise ordinary send stays unchanged. A correlation-only send uses best-effort evidence and no explicit wait. Status, wait, confirm, ack, init and prepare make zero native submissions. Receipt selectors do not contain secrets. Confirmation stdin contains schemaVersion, ledgerEpoch, correlationId, targetGeneration and a caller assertion confirmed=true; it rejects extra fields/body/capability and records operator_confirmed only. Flag incompatibilities and unsupported route requirements are checked before durable effect intent. Remote setup and host options must follow existing SSH trust restrictions, not arbitrary receipt routing. + +ACK evidence also requires a non-null original targetGeneration and submitted submission; once a valid receipt is accepted, current state is acknowledged. Observed injection always implies submitted submission. A supplied prepared ID must not belong to a prior real send invocation, including a terminal pre-effect refusal; dry-run never consumes that first-submission authority. Dry-run is incompatible with request-ack, observe-delivery and wait-for (usage exit 2); correlation-only dry-run uses an already prepared ID. + +If a restarted collector cannot prove monotonic capability lifetime continuity, it expires unused new-receipt authority conservatively rather than extending its TTL or regenerating it. Valid committed receipt classification/assurance/late remains recoverable; authenticated same-receipt read-only duplicates follow the explicit expiry exception. For a new wait on an already attempted intent, insufficient budget fails only that wait and preserves submission/injection facts; it does not relabel the original intent refused. The reconcile action means status/evidence comparison, not a new submission or an implicit repair command. + +## Freeze and implementation gates + +This proposal requires TypeScript review of its exact commit and fixture before freezing. The new prepare/confirm spellings, ledgerEpoch and wait operationId fields, 5-second cleanup reserve, numeric token validation and restoration limitations are explicit v0.2 amendments, not already accepted or shipped capabilities. + +The fixture must cover per-runtime optional absence, request-context-gated explicit wait timeout with queued true, forged opt-out handoff, wrong native target, authenticated/wrong-token/expired duplicates, committed/pre-commit/unknown-order receipt recovery, manual-confirm refusal, original/new/null generation, no secret reflection, missing-context query errors versus known tombstones, wait history, missing/restored/expired/full ledger, custom unknown IDs, deadline/framing boundaries, malformed schemas and SSH preservation. Strict SSH opt-in validation must preserve a valid native snapshot even if handoff is rejected; no legacy Claude reconstruction or generic failure path may discard it. Complete responses survive transport timeout/nonzero exits only through the same strict, ORIGINAL-request-aware parser; partial frames remain unknown. + +Before implementation acceptance: validate producer bootstrap and collector ownership/restart, durable intent and capacity reservation, cleanup under each supported platform/version/home, metadata-only Codex observation, strict local/SSH tuples and zero-submit status/wait/ack/confirm operations. No native/live evidence is supplied by this design PR. Side Session and wake experiments are separate future work, not prerequisites. Paired-device/Relay dedup and operator monitoring are not silently added to v1. diff --git a/test/fixtures/handoff-v1.pin.json b/test/fixtures/handoff-v1.pin.json new file mode 100644 index 0000000..3868b95 --- /dev/null +++ b/test/fixtures/handoff-v1.pin.json @@ -0,0 +1,67 @@ +{ + "status": "design_frozen_unimplemented", + "proposalVersion": "0.2", + "pythonRepository": "abruption/session-peer", + "pythonDesignPR": 261, + "pythonDesignCommit": "99d8ec3855d57da34a6fa789f3a375820a8fbe85", + "pythonRuntimeVersion": "1.0.3", + "pythonRuntimeCommit": "0d252550ab40c26d1ac4a19193df6ca2f84d830b", + "document": { + "path": "handoff-v1.md", + "upstreamPath": "docs/contracts/handoff-v1.md", + "sha256": "46a953010c49214bd8f969412f26713384fbe09a0ba78ebf0fef75da40d0d889" + }, + "fixture": { + "path": "handoff-v1.json", + "upstreamPath": "tests/fixtures/handoff-v1.json", + "sha256": "963b94e78f510c2535e405813558275d512a5bfe6bbd99ffdf0994b4d2e6e441" + }, + "runtimeImplemented": false, + "jointFreezeApproved": true, + "openReviewGates": [], + "previousCandidateCommit": "b369cef5bd7be433e28d515cce2f44d3bbf20f0b", + "reviewedAmendments": [ + "duplicate-receipt-original-hash-authentication-after-expiry", + "atomic-classified-receipt-recovery-and-unprovable-order-refusal", + "immutable-original-generation-and-copied-token-assurance-limit", + "native-input-retention-and-controlled-secret-output-boundary", + "missing-context-handoffQuery-without-invented-epoch", + "subtree-raw-outer-and-fanout-frame-boundaries", + "request-gated-ssh-wait-result-and-native-target-validation", + "original-request-per-runtime-native-profiles", + "post-submission-unsupported-wait-tuple", + "interruption-130-and-status-query-operation-tuples" + ], + "tsDesignReviewStatus": "approved_design_only", + "tsDesignApprovedAtUtc": "2026-10-06T12:28:28.864449+00:00", + "tsDesignApproval": { + "commit": "99d8ec3855d57da34a6fa789f3a375820a8fbe85", + "documentSha256": "46a953010c49214bd8f969412f26713384fbe09a0ba78ebf0fef75da40d0d889", + "fixtureSha256": "963b94e78f510c2535e405813558275d512a5bfe6bbd99ffdf0994b4d2e6e441", + "scope": "normative_design_and_synthetic_fixture_only", + "remainingNormativeFindings": [], + "runtimeImplementationApproved": false, + "mergeOrPublicationApproved": false + }, + "jointDesignFreeze": { + "status": "approved_design_only", + "scope": "normative_design_and_synthetic_fixture_only", + "sourceCommit": "99d8ec3855d57da34a6fa789f3a375820a8fbe85", + "documentSha256": "46a953010c49214bd8f969412f26713384fbe09a0ba78ebf0fef75da40d0d889", + "fixtureSha256": "963b94e78f510c2535e405813558275d512a5bfe6bbd99ffdf0994b4d2e6e441", + "pythonAcceptanceRecord": "https://github.com/abruption/session-peer/pull/261#issuecomment-6016310362", + "tsAcceptanceCommit": "63473fb80936b0085e08039997ac39f7158a58dd", + "sourceBytesRegenerated": false, + "runtimeImplementationApproved": false, + "mergeOrPublicationApproved": false + }, + "publishedPythonReview": { + "version": "1.1.0", + "commit": "8ca77cf31254c8287a2593fe484bb30a7dba2727", + "documentSha256": "46a953010c49214bd8f969412f26713384fbe09a0ba78ebf0fef75da40d0d889", + "fixtureSha256": "963b94e78f510c2535e405813558275d512a5bfe6bbd99ffdf0994b4d2e6e441", + "reviewScope": "published_document_and_fixture_byte_identity_only", + "sharedFixtureReplaced": false, + "runtimeParityEstablished": false + } +} diff --git a/test/fixtures/python-reference.pin.json b/test/fixtures/python-reference.pin.json new file mode 100644 index 0000000..c6f4c7b --- /dev/null +++ b/test/fixtures/python-reference.pin.json @@ -0,0 +1,11 @@ +{ + "repository": "abruption/session-peer", + "version": "1.1.0", + "commit": "8ca77cf31254c8287a2593fe484bb30a7dba2727", + "scope": "ordinary-list-discovery-projection", + "files": { + "session_peer.py": "d36d83f3d6f59eb20c958aac530ca6d5d988a99f6ceabf2704c7c51db1fe524f", + "session_peer_core/cli.py": "024965a5b84e5f09495aac902f91419008a96ea6ea9766149e27fd43b943e8c5", + "tests/fixtures/compatibility-v1.json": "afd5a0b37562bb6a81afa9c20628bfae0cfe4d1caf9e88db402005fc42aabcd7" + } +} diff --git a/test/handoff-contract.test.ts b/test/handoff-contract.test.ts new file mode 100644 index 0000000..852191b --- /dev/null +++ b/test/handoff-contract.test.ts @@ -0,0 +1,190 @@ +// Exact candidate pins and synthetic examples only; no receipt, observer or native engine. +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; + +type Pin = { + status: string; proposalVersion: string; pythonRepository: string; pythonDesignPR: number; + pythonDesignCommit: string; pythonRuntimeVersion: string; pythonRuntimeCommit: string; + document: { path: string; upstreamPath: string; sha256: string }; + fixture: { path: string; upstreamPath: string; sha256: string }; + runtimeImplemented: boolean; jointFreezeApproved: boolean; openReviewGates: string[]; + jointDesignFreeze: { status: string; scope: string; sourceCommit: string; + documentSha256: string; fixtureSha256: string; pythonAcceptanceRecord: string; + sourceBytesRegenerated: boolean; runtimeImplementationApproved: boolean; mergeOrPublicationApproved: boolean }; + +}; +type Handoff = { + schemaVersion: number; state: string; submission: { status: string }; + observation: { status: string; injectionObserved: boolean }; + ack: { status: string; late?: boolean }; wait: { status: string }; + retry: { allowed: boolean; reason: string }; nextActions: string[]; +}; +type Example = { + name: string; mode: string; exit: number; + result: { ok: boolean; status?: string; submitted?: boolean; consumptionConfirmed?: boolean; handoff: Handoff }; +}; +type Fixture = { + schemaVersion: number; proposalVersion: string; status: string; + provenance: { pythonRuntimeVersion: string; pythonRuntimeCommit: string; exampleKind: string }; + legacySnapshots: { name: string; result: Record; mustRemainAbsent: string[]; noEffectProof?: boolean }[]; + accepted: Example[]; + queryErrors: { name: string; exit: number; result: Record & { + ok: boolean; reason: string; handoffQuery: { status: string; context: string; retry: { allowed: boolean; reason: string } }; + } }[]; + requestAwareWireCases: { name: string; optIn: boolean; explicitWait: boolean; exit: number; expected: { + wireAccepted: boolean; nativeEvidencePreserved: boolean; handoffValidated: boolean; ackPromoted: boolean; resubmit: boolean; + } }[]; +}; +const file = (path: string) => readFileSync(new URL(`./fixtures/${path}`, import.meta.url)); +const pin = JSON.parse(file('handoff-v1.pin.json').toString('utf8')) as Pin; +const fixture = JSON.parse(file('handoff-v1.json').toString('utf8')) as Fixture; +const example = (name: string) => { + const value = fixture.accepted.find(x => x.name === name); + assert.ok(value, `missing shared design example: ${name}`); + return value; +}; + +test('handoff candidate document and fixture match one explicit upstream design commit', () => { + assert.equal(pin.pythonRepository, 'abruption/session-peer'); + assert.equal(pin.pythonDesignPR, 261); + assert.match(pin.pythonDesignCommit, /^[a-f0-9]{40}$/); + assert.equal(pin.document.path, 'handoff-v1.md'); + assert.equal(pin.fixture.path, 'handoff-v1.json'); + for (const entry of [pin.document, pin.fixture]) { + assert.match(entry.sha256, /^[a-f0-9]{64}$/); + assert.equal(createHash('sha256').update(file(entry.path)).digest('hex'), entry.sha256); + } + assert.equal(pin.pythonRuntimeVersion, '1.0.3'); + assert.equal(pin.pythonRuntimeCommit, '0d252550ab40c26d1ac4a19193df6ca2f84d830b'); + assert.equal(fixture.provenance.pythonRuntimeCommit, pin.pythonRuntimeCommit); + assert.equal(fixture.provenance.pythonRuntimeVersion, pin.pythonRuntimeVersion); +}); + +test('joint design freeze preserves exact historical synthetic bytes and denies runtime authority', () => { + assert.equal(pin.status, 'design_frozen_unimplemented'); + assert.equal(pin.jointFreezeApproved, true); + assert.equal(pin.runtimeImplemented, false); + assert.equal(fixture.schemaVersion, 1); + assert.equal(fixture.proposalVersion, pin.proposalVersion); + assert.equal(fixture.status, 'draft_unimplemented'); + assert.equal(fixture.provenance.exampleKind, 'synthetic_normalized_not_live'); + assert.deepEqual(pin.openReviewGates, []); + assert.equal(pin.jointDesignFreeze.status, 'approved_design_only'); + assert.equal(pin.jointDesignFreeze.scope, 'normative_design_and_synthetic_fixture_only'); + assert.equal(pin.jointDesignFreeze.sourceCommit, pin.pythonDesignCommit); + assert.equal(pin.jointDesignFreeze.documentSha256, pin.document.sha256); + assert.equal(pin.jointDesignFreeze.fixtureSha256, pin.fixture.sha256); + assert.equal(pin.jointDesignFreeze.pythonAcceptanceRecord, + 'https://github.com/abruption/session-peer/pull/261#issuecomment-6016310362'); + assert.equal(pin.jointDesignFreeze.sourceBytesRegenerated, false); + assert.equal(pin.jointDesignFreeze.runtimeImplementationApproved, false); + assert.equal(pin.jointDesignFreeze.mergeOrPublicationApproved, false); + const document = file(pin.document.path).toString('utf8'); + assert.match(document, /not a frozen protocol/); + assert.match(document, /No native\/live evidence/); +}); + +test('shared golden examples preserve submission, legacy absence and terminal wait facts', () => { + for (const snapshot of fixture.legacySnapshots) { + for (const key of snapshot.mustRemainAbsent) assert.equal(Object.hasOwn(snapshot.result, key), false); + } + const unknown = fixture.legacySnapshots.find(x => x.name === 'legacy_unknown_false_not_no_effect'); + assert.ok(unknown); assert.equal(unknown.result.submitted, false); assert.equal(unknown.noEffectProof, false); + const timeout = example('queued_explicit_ack_timeout'); + assert.equal(timeout.exit, 1); assert.equal(timeout.result.ok, false); + assert.equal(timeout.result.status, 'queued'); assert.equal(timeout.result.submitted, true); + assert.equal(timeout.result.consumptionConfirmed, false); + const observer = example('queued_best_effort_observer_failed'); + assert.equal(observer.exit, 0); assert.equal(observer.result.ok, true); assert.equal(observer.result.submitted, true); + const late = example('late_receipt_preserves_timeout_history'); + assert.equal(late.result.handoff.state, 'acknowledged'); assert.equal(late.result.handoff.ack.late, true); + assert.equal(late.result.handoff.wait.status, 'timed_out_unknown'); + const dry = example('dry_run_preserves_codex_false'); + assert.equal(dry.result.handoff.submission.status, 'not_attempted'); + assert.equal(dry.result.handoff.observation.injectionObserved, false); + for (const value of fixture.accepted) { + assert.equal(value.result.handoff.retry.allowed, false); + assert.equal(value.result.handoff.retry.reason, 'receiver_dedup_unavailable'); + assert.equal(value.result.handoff.nextActions.includes('resend_same_id'), false); + } +}); + +// Expected-contract assertions only; these do not execute a query or wire handler. +test('amended missing-context examples never invent a ledger epoch or native snapshot', () => { + assert.equal(fixture.queryErrors.length, 3); + for (const value of fixture.queryErrors) { + assert.equal(value.exit, 1); assert.equal(value.result.ok, false); + assert.equal(value.result.reason, 'handoff_history_unavailable'); + assert.equal(value.result.handoffQuery.status, 'unknown'); + assert.ok(['ledger_missing', 'ledger_corrupt', 'id_unknown'].includes(value.result.handoffQuery.context)); + assert.equal(value.result.handoffQuery.retry.allowed, false); + assert.equal(value.result.handoffQuery.retry.reason, 'history_unavailable'); + for (const key of ['handoff', 'ledgerEpoch', 'status', 'submitted', 'consumptionConfirmed']) { + assert.equal(Object.hasOwn(value.result, key), false); + } + assert.equal(Object.hasOwn(value.result.handoffQuery, 'ledgerEpoch'), false); + } +}); + +test('amended declared wire cases require original opt-in and native-target evidence', () => { + assert.equal(fixture.requestAwareWireCases.length, 39); + for (const value of fixture.requestAwareWireCases) { + assert.equal(value.expected.resubmit, false); + if (!value.optIn) { + // A valid legacy success remains acceptable; unrequested metadata never + // enables a failed-wait tuple or authenticated handoff/ACK evidence. + if (value.exit !== 0) assert.equal(value.expected.wireAccepted, false); + assert.equal(value.expected.handoffValidated, false); + assert.equal(value.expected.ackPromoted, false); + } + if (value.name.includes('wrong_native_target')) { + assert.equal(value.expected.nativeEvidencePreserved, false); + assert.equal(value.expected.wireAccepted, false); + assert.equal(value.expected.ackPromoted, false); + } + } +}); + +// These are pinned example invariants, not executions of the proposed operations. +test('final candidate distinguishes native profiles, unsupported waits and interruption results', () => { + const pythonClaude = example('python_claude_explicit_ack_timeout'); + assert.equal(pythonClaude.exit, 1); assert.equal(pythonClaude.result.ok, false); + for (const key of ['status', 'submitted', 'consumptionConfirmed']) { + assert.equal(Object.hasOwn(pythonClaude.result, key), false); + } + assert.equal(pythonClaude.result.handoff.submission.status, 'submitted'); + const typescriptClaude = example('typescript_claude_explicit_ack_timeout'); + assert.equal(typescriptClaude.result.status, 'posted'); assert.equal(typescriptClaude.result.submitted, true); + assert.equal(typescriptClaude.result.consumptionConfirmed, false); + const unsupported = example('queued_post_submission_unsupported'); + assert.equal(unsupported.exit, 1); assert.equal(unsupported.result.ok, false); + assert.equal(unsupported.result.status, 'queued'); assert.equal(unsupported.result.submitted, true); + assert.equal(unsupported.result.handoff.wait.status, 'unsupported'); + const interrupted = example('queued_interrupted_pending_wait'); + assert.equal(interrupted.exit, 130); assert.equal(interrupted.result.ok, false); + assert.equal(interrupted.result.status, 'queued'); assert.equal(interrupted.result.submitted, true); + assert.equal(interrupted.result.handoff.wait.status, 'stopped'); + const query = example('stopped_status_query'); + assert.equal(query.exit, 0); assert.equal(query.result.ok, true); + assert.equal(query.result.handoff.wait.status, 'stopped'); + for (const name of ['codex_posted_is_invalid', 'codex_consumption_overwrite', 'wrong_codex_home', + 'interrupted_wrong_exit_one', 'interrupted_wrong_exit_zero', 'interrupted_wrong_wait_reason']) { + const value = fixture.requestAwareWireCases.find(x => x.name === name); + assert.ok(value, `missing final design vector: ${name}`); + assert.equal(value.expected.wireAccepted, false); assert.equal(value.expected.resubmit, false); + } +}); + + +test('published Python checkpoint cannot silently replace the frozen fixture or claim runtime parity', () => { + const review = (JSON.parse(file('handoff-v1.pin.json').toString('utf8')) as { publishedPythonReview: Record }).publishedPythonReview; + assert.equal(review.version, '1.1.0'); + assert.equal(review.commit, '8ca77cf31254c8287a2593fe484bb30a7dba2727'); + assert.equal(review.documentSha256, pin.document.sha256); + assert.equal(review.fixtureSha256, pin.fixture.sha256); + assert.equal(review.sharedFixtureReplaced, false); + assert.equal(review.runtimeParityEstablished, false); + assert.equal(review.reviewScope, 'published_document_and_fixture_byte_identity_only'); +}); diff --git a/test/updates.test.ts b/test/updates.test.ts index 9a692be..9796fb8 100644 --- a/test/updates.test.ts +++ b/test/updates.test.ts @@ -159,7 +159,7 @@ test('update --check reports npm dist-tags, writes a private atomic cache and le assert.equal(v.command, 'update'); assert.equal(v.ok, true); assert.equal(v.package, 'session-peer'); assert.equal(v.current, '0.3.6'); assert.equal(v.latest, '0.3.7'); assert.equal(v.channel, 'latest'); assert.equal(v.distTag, 'latest'); assert.equal(v.source, 'npm_registry'); assert.equal(v.status, 'update_available'); assert.equal(v.outdated, true); assert.equal(v.updated, false); - assert.equal(v.referenceVersion, '1.0.2'); assert.equal(v.cache, 'written'); + assert.equal(v.referenceVersion, '1.1.0'); assert.equal(v.cache, 'written'); // The test runs from a checkout; installed-path guidance is covered above and in package-smoke. assert.equal(v.managedBy, 'source'); assert.equal(v.updateCommand, null); assert.equal(v.skillsManagedBy, 'separate');