Skip to content

feat(handoff): add fenced local receipt, queue and wait runtime candidate - #290

Merged
abruption merged 34 commits into
mainfrom
abruption/v110-handoff-181-20261009
Oct 9, 2026
Merged

abruption merged 34 commits into
mainfrom
abruption/v110-handoff-181-20261009

Conversation

@abruption

@abruption abruption commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

Add an opt-in Handoff v1 runtime candidate: a same-machine POSIX Claude receipt route, correlation-only local Codex submission, and single-destination POSIX source-streamed SSH correlation-only Claude/Codex submission, with bounded message stdin. This is runtime implementation, not another design-only PR. #181 remains open because the remaining acceptance gates below are not implemented.

Related: #181. Frozen design PR #261 and prerequisites #283–#289 are now merged through main 75cfb4f4a1778cce664ae1d6dc3c14d6edcf6b22. Final candidate 7f38b63290199a8928beb7220278869ddcee4d88 integrates that exact main, preserving both argument parsers, selected Codex storage guard, peer framing, native generation checks, connection-bound SSH identity, wake-chain safeguards, passive shorthand and protected setup. The user authorized normal protected merge and version preparation, not publication or operations.

Companion documentation coordination: session-peer-skill PR #30. Published Python 1.0.4 and its existing skill snapshot do not implement this new handoff runtime or new framing contract; no runtime release or global skill installation is part of this PR.

  • Explicit owner-only ledger initialization/preparation; serialized durable intent/effect fences, bounded records/bytes/waits, retained target-bound tombstones, missing/corrupt context errors and known-restoration quarantine.
  • Actual private Unix collector and installed private-stdin receipt producer: receipt-only capabilities, hash-only retention, constant-time duplicate proof, original generation/clock binding, early receipt retention, atomic classification and immutable waits.
  • Claude preserves its absent legacy status/submitted/consumptionConfirmed fields. Codex preserves the actual queued/true/false profile, matching native queue ID and canonical home/context, with null generation and no receipt capability or observer claim.
  • A strict complete matching Codex queue receipt survives shutdown timeout, abnormal exit, SIGINT or diagnostic overflow. Partial/wrong-target/malformed/invalid-UTF-8/stdout-overflow evidence stays unknown, with no second submission. Interrupted version preflight never proceeds to queue.
  • Without an original explicit wait, a known native submission stays successful if subsequent history storage fails, while unprovable handoff/ACK is omitted. Required waits cannot invent a result. Failed stored native profiles are closed and corrupt values are never reflected in status.
  • Dry-run checks the immutable original Claude PID/generation or Codex home/writer context; changed targets refuse without native effect. Public malformed correlation IDs are usage errors before input/discovery/ledger access.
  • One original total budget starts before setup/input; five seconds are reserved inside it. Owned process/pipe operations use bounded byte and time budgets. POSIX message stdin is strict UTF-8, at most four million bytes/one million codepoints; stalled input and interruption refuse before intent creation. Ordinary opt-out behavior is unchanged apart from documented prerequisite framing/storage guards.
  • Protected exact isolated Python ACK handler, not an assumed compatible PATH command. Raw capability is private input only, never argv, URI, environment, ordinary output or raw journal. Every external message line uses the shared untrusted-peer frame; final limits include framing and receipt authority.
  • Source-streamed SSH keeps the sender-owned durable fence, original total budget, actual feat(ssh): verify optional host-key pins on the actual connection #287 identity-wrapper path and strict request/native profile validation. No remote ledger, capability or collector is created. Required remote delivered/acknowledged waits refuse before effect; best-effort requests report unsupported. An old boot-bound remote monotonic anchor conservatively debits both round trips; SSH cleanup does not prove remote task cancellation.
  • Structured SSH-self normalization retains prepared local intent/digest and the local message limit. Real SSH retains its 90,000-character limit; metadata JSON frames additionally have a 1 MiB encoded-byte limit. Trusted remote home mapping permits actual canonical/symlink resolution, not a sender-verified inode or atomic incarnation guarantee. Closed connection identity/reply metadata is retained separately; private body/proofs/clock/context are stripped from public results.
  • Four-language candidate documentation; frozen normative document/fixture bytes are unchanged.

Type

  • feat
  • fix
  • docs
  • chore / refactor / test / perf

Checklist

  • Live local/SSH agent delivery (not performed; owned offline fixtures only).
  • Target/context, dry-run, pre-effect refusal and unknown-no-retry fixtures.
  • Conventional Commits.
  • Scope and limits documented in four languages.
  • No credentials, real session identifiers or raw receipt capabilities in the diff.

Verified against

Final integrated candidate 7f38b63290199a8928beb7220278869ddcee4d88 after all prerequisite merges:

  • Canonical generator, whitespace, clean worktree and frozen hashes: pass.
  • Exact final documentation: 54 tests pass, 2.357 seconds.
  • Full strict extras discovery with fresh artifact verification enabled: 1,088 tests, 1,073 passed and 15 explicitly skipped, 106.739 seconds; exit 0 (CPython 3.13.7, existing read-only extras environment; owned fixture processes only). Asyncio slow-callback diagnostics were observed; no ResourceWarning/assertion failure was observed in this run. Platform/live skips remain unverified.
  • Fresh wheel/sdist build and four exact artifact-content tests pass. Each was independently installed with Relay/MCP extras into a new private environment; isolated -I imports/version, both parser contracts, setup/wake/handoff flags, passive shorthand resources, empty-home refusal, initialized empty-home JSON list and pip check pass. No model/native session is started.
  • Local, unpublished version-1.0.4 wheel SHA-256: 2c91632576c631a31b13811fdb3236177f36804a629c7adec1e84a6dfd5ed760; sdist: ac95838fa1af4c298e2655c23c4ef5820eb4d3dc6d6c8f857e80d561e6c848e1. These are test artifacts, not published release files or a version bump.
  • Exact final head 7f38b63290199a8928beb7220278869ddcee4d88: all 28 checks including the release gate succeeded in CI run 37891144424. The PR is ready for review under normal protections; GitHub's review/comment APIs have returned no automatic review findings at the reporting snapshot. Earlier-head results below are historical, not final acceptance.
  • The first integrated CI run at a12a37b failed one Ubuntu 3.13 positive early-receipt fixture, which used a six-second total leaving only one second for setup/effect. Its recipient timed out and the sender conservatively refused; the original inner reason was not captured, so this is not a uniquely proven CI root cause. Final 7f38b63 changes only tests: positive fixtures request ten seconds (five-second effect budget), all waits remain bounded, fixed stage/native-error diagnostics omit body/capability, and an actual expired one-second-cutoff test proves refusal before socket creation. All generation/early-receipt/privacy assertions and production deadlines remain unchanged. Both positive paths passed five repeated runs (10 tests, 3.272 seconds).

Earlier implementation evidence:

Exact local candidate 8ec6cf98a357f72df787c3d4fec5fede3224a57c; macOS arm64/Python 3.14.8, owned temporary SQLite databases, inboxes, pipes, streamed fake SSH and fake CLI processes only. No actual agent/model send, installation or service change. Included #286 follow-up and its already-reviewed owned restarted-inbox and unsupported Codex generation regression prerequisites. An initial combined test failed because the restarted-inbox prerequisite fixture was absent; including it fixed the confirmed test setup error. Final 8ec6cf9 adds only the approved missing generation regression to the independently reviewed runtime head 80f0acc.

  • Strict focused handoff/process/stdin/remote/native-generation suites at runtime head 80f0acc: 182 pass, 21.484 seconds. At exact final head, affected generation suites: 21 pass, 0.930 seconds.
  • python3 -m unittest discover -s tests/docs at exact final head: 54 pass, 2.131 seconds.
  • Generator --check, git diff --check, clean branch and frozen hashes: pass.
  • Full strict discovery at runtime head 80f0acc in a private packaging-only environment: 837 tests, 807 passed and 30 explicitly skipped, 72.142 seconds; exit 0. The final test-only addition was subsequently rerun in the affected suites, not attributed to that earlier full result. Asyncio slow-callback diagnostics were observed, not ResourceWarning failures. Optional Relay/MCP dependencies and platform/live integrations are not inferred from skipped tests.
  • Exact preceding e808df9848af7a708b84b8722798b02a5248c5ae received three bounded read-only reviews: 160 focused tests, an independent 29 remote tests, and another independent 70 remote/backend/process tests all passed. Subsequent changes preserve local SSH-self limits with two regression tests and integrate independently approved feat(identity): pin Claude sends to observed process generations #286 changes. These reviews do not approve issue closure, release, live agents or unsupported routes. No external model CLI or actual model/agent test was used.
  • Exact 80f0acc follow-up received root and independent read-only scoped acceptance, including independently run 55 and 35 selected tests. The final 8ec6cf9 approved test-only addition changes no runtime or frozen source.
  • Previous PR head 02d84c533946b4903884ff8fcb21e06274b582b2 passed all 24 CI jobs, including macOS Python 3.9 clock fixtures. Its test-only slow partial-frame correction accepts expected BrokenPipe closure without masking aggregate deadline renewal. New-head CI/review must complete; previous results are not current-head acceptance.
  • Exact final head 8ec6cf98a357f72df787c3d4fec5fede3224a57c: all 24 current-head checks succeeded, including the release gate, in CI run 37876295009. This is CI acceptance, not a release or live-agent/remote-host test.

Darwin uses typed mach_absolute_time/mach_timebase_info throughout sender/journal/collector, avoiding CPython 3.9's process-local monotonic epoch. Native adapter duration translation samples native first/shared second and does not renew the budget; continuity is namespaced. Source evidence: CPython 3.9.25 commit 0bbaf5de9744ae1acea3e2c9ad2257d1cc68e847, Python/pytime.c. Local Python 3.9 is unavailable; actual macOS 3.9 acceptance comes from CI, not a local claim.

Positive protected-handler fixtures use an owned 0700 wrapper delegating to the test interpreter; this does not certify unsafe hosted toolcache permissions. Production script/interpreter protections remain unchanged.

Frozen document SHA-256: 46a953010c49214bd8f969412f26713384fbe09a0ba78ebf0fef75da40d0d889.
Frozen fixture SHA-256: 963b94e78f510c2535e405813558275d512a5bfe6bbd99ffdf0994b4d2e6e441.
Synthetic frozen-contract tests check schema/vectors, not real engine acceptance.

Notes / remaining acceptance gates

#181 remains open. Codex ACK and body-free precisely correlated injection observation, Windows bounded native pipes/process cleanup, paired devices, installed reverse-SSH receipt bootstrap and opt-in fanout are not implemented. Source-streamed SSH correlation-only submission is implemented with owned offline fixtures, not live host/native-agent acceptance. Public remote no-effect preparation tooling remains incomplete. Required unsupported evidence refuses before effect. Canonical thread/home/writer context is not atomic native incarnation or consumption proof; #284's effective native configuration/shared-daemon proof boundary remains open.

Cross-boot retention review, restoration/reconciliation CLI and stale collector lifecycle tooling remain. Known restoration has an explicit quarantine API; unmarked rollback/cloning cannot be reliably detected. Native filesystem/process identity/fsync calls cannot be forcibly cancelled: bounded socket/process budgets are not universal hard-real-time guarantees.

Receipt authority is token possession, not model identity or tool approval. The intended recipient's native storage may retain it; same-user readers may acquire that authority outside our control. No transcript polling, turn-completion ACK inference, automatic reply/retry/fallback, operational change, version bump, tag or publishing.

@abruption abruption changed the title feat(handoff): add fenced local receipt and wait runtime candidate feat(handoff): add fenced local receipt, queue and wait runtime candidate Oct 9, 2026
Base automatically changed from abruption/handoff-v1-design-181-20261006 to main October 9, 2026 04:59
@abruption
abruption marked this pull request as ready for review October 9, 2026 06:01
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-09T06:11:30.986920Z 7f38b63 Draft marked ready
🔒 Security Review ✅ Completed 2026-10-09T06:12:28.806858Z 7f38b63 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@abruption
abruption merged commit 3b32879 into main Oct 9, 2026
28 checks passed
@abruption
abruption deleted the abruption/v110-handoff-181-20261009 branch October 9, 2026 06:03
@abruption abruption mentioned this pull request Oct 9, 2026
6 of 8 tasks

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f38b63290

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1034 to +1036
text = read_message(args)
binding, generation, _ = handoff_binding(args, text)
epoch, record = ledger.prepare(binding, generation, native_context=handoff_native_context(args))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the body before reserving a prepared intent

When the private file is empty/whitespace-only or contains between 1,000,001 and 4,000,000 ASCII characters, read_message accepts it and prepare permanently reserves a successful intent, but every subsequent send with that correlation ID fails the check_message validation before reaching ledger.prepare. Because the binding requires the same payload digest and there is no deletion or fence eviction, this produces an unusable intent that permanently consumes ledger quota. Apply the normal message validation before committing the prepared record.

Useful? React with 👍 / 👎.

Comment on lines +930 to +932
supported = (goal == "acknowledged" and record["generation"] is not None
and record["capability"] is not None and not record["capability"]["revoked"])
wait = {"for": goal, "status": initial_status or ("pending" if supported else "unsupported"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Satisfy later waits from an existing manual acknowledgement

When a correlation-only Claude submission is acknowledged through handoff confirm without first minting a capability, the record correctly enters the acknowledged state, but a subsequent handoff wait --wait-for acknowledged is initialized as unsupported because supported requires a capability and ignores record["ack"]. The command therefore exits 1 while returning a handoff whose current state and ACK are already acknowledged; an existing valid ACK should make the new wait immediately satisfied regardless of whether its assurance came from a capability.

Useful? React with 👍 / 👎.

Comment on lines +15 to +19
if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns):
raise handoff_error("unsafe_message_file")
raw = stream.read(4 * MAX_MESSAGE_CHARS + 1)
final = os.fstat(stream.fileno())
if (info.st_size, info.st_mtime_ns) != (final.st_size, final.st_mtime_ns) or len(raw) > 4 * MAX_MESSAGE_CHARS:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recheck owner-only permissions on the opened message file

If the message file is changed from mode 0600 to group/world-readable between handoff_private_stat and either fstat, these comparisons still accept it because they omit st_mode, st_uid, and st_ctime; chmod does not need to change size or mtime. The handoff then reads and submits a file that no longer satisfies the advertised owner-only requirement. Validate the opened and final descriptor as a private owner-owned regular file, rather than checking only the pre-open pathname snapshot.

Useful? React with 👍 / 👎.

Comment thread session_peer_core/cli.py
Comment on lines +148 to +152
if action == "prepare":
sub.add_argument("--to", required=True)
sub.add_argument("--message-file", required=True)
sub.add_argument("--codex-home")
sub.set_defaults(message=None, message_option=None, b64=None, host=[], device=None, target_generation=None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow inactive Codex homes during preparation

For an intentionally inactive Codex thread, ordinary sending requires --codex-home together with --allow-inactive-codex-home, but the handoff prepare parser exposes only the former. Consequently resolve_codex_home always sees the opt-in as false and rejects preparation, so users cannot create the prepared Codex context that a later send with the supported inactive-home flag must match. Add the same explicit inactive-home option to the prepare command.

Useful? React with 👍 / 👎.

Comment on lines +529 to +534
record.update(phase="quarantined", submission="unknown", native=None, ack=None, pendingReceipt=None)
if record["capability"]:
record["capability"]["revoked"] = True
for wait in record["waits"]:
if wait["status"] == "pending":
wait.update(status="failed", reason="history_unavailable")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear satisfied waits when quarantining restored history

When the restored ledger contains an acknowledged intent whose wait is already satisfied, quarantine clears the ACK and native submission facts but leaves that wait unchanged because only pending waits are rewritten. The resulting record loads successfully, yet handoff_public rejects it because a satisfied acknowledged wait no longer has an ACK, so status on this known quarantined record returns a generic invalid-handoff failure instead of the required retained unknown state. Rewrite satisfied waits to a history-unavailable terminal state when removing their supporting evidence.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant