fix(spawn): require replay-resume server proof
Outcome
- Opt external service-caller spawns into Relaycast's explicit
relay.spawn.invoke.logical-replay-resume/1contract. - Require both exact server-owned proofs before accepting a strict-invoke or replay response.
- Replay the original logical request with the same idempotency key and no materialized input, strict-invoke fallback, or legacy fallback.
- Leave session callers on their existing
/spawn/invokerequest shape.
This is stacked on #2119 (fix/r1-durable-invoke-intent) and depends on Relaycast #644 plus successor #647. It must not merge or release before the server contract is published.
Verification
Exact head: a649079fd2cc02e691ee2469ca863115085de345
The Rust test binary was built with:
$ CARGO_TARGET_DIR=/tmp/relay-r1-durable-intent/crates/relay-node/target cargo test --lib --no-run
warning: `agent-relay-node` (lib test) generated 1 warning
Finished `test` profile [unoptimized + debuginfo] target(s) in 49.09s
Executable unittests src/lib.rs (.../agent_relay_node-0e80f58e8ac12e9f)
The affected caller module was then run serially to avoid interference from unrelated day-old parallel copies of this same module on the shared host:
$ agent_relay_node-0e80f58e8ac12e9f 'relay::spawn::control::tests::callers::' --test-threads=1
running 29 tests
...
test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 2374 filtered out; finished in 0.40s
$ CARGO_TARGET_DIR=/tmp/relay-r1-durable-intent/crates/relay-node/target cargo check --lib
Checking agent-relay-node v0.1.0 (...)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.50s
$ cargo fmt --all -- --check
# exit 0, no output
$ git diff --check
# exit 0, no output
Mutation evidence
Removing replay-capability validation from the shared strict receipt validator:
$ agent_relay_node-0e80f58e8ac12e9f an_external_caller_requires_the_exact_replay_resume_proofs --nocapture --test-threads=1
test relay::spawn::control::tests::callers::an_external_caller_requires_the_exact_replay_resume_proofs ... FAILED
called `Result::unwrap_err()` on an `Ok` value
test result: FAILED. 0 passed; 1 failed
After byte-for-byte restoration:
test relay::spawn::control::tests::callers::an_external_caller_requires_the_exact_replay_resume_proofs ... ok
test result: ok. 1 passed; 0 failed
Reverting the external logical schema to the old lookup-only /1 value:
$ agent_relay_node-0e80f58e8ac12e9f a_retry_after_restart_replays_the_exact_request_without_rechecking_the_target --nocapture --test-threads=1
test relay::spawn::control::tests::callers::a_retry_after_restart_replays_the_exact_request_without_rechecking_the_target ... FAILED
assertion `left == right` failed
left: ... Internal ... "Agent Relay returned an unreadable native spawn result."
right: ... StoreFailed ... "the receipt ledger is unavailable"
test result: FAILED. 0 passed; 1 failed
After byte-for-byte restoration, the complete 29-test caller module passed as shown above.
Boundary
This proves the client request/receipt contract locally. It does not claim public-artifact or live Desktop/remote-node acceptance; those remain after human merges, official workflow releases, dependency pins in Flows #707, and Snow Lunch's independent E2E observation.