Skip to content

rpc: answer calls pipelined on an already-failed answer with that exception (rebased on L17) - #10

Merged
nullstyle merged 3 commits into
mainfrom
pr8-rebased-on-l17
Jul 31, 2026
Merged

rpc: answer calls pipelined on an already-failed answer with that exception (rebased on L17)#10
nullstyle merged 3 commits into
mainfrom
pr8-rebased-on-l17

Conversation

@nullstyle

Copy link
Copy Markdown
Owner

Supersedes #8 — same single commit, rebased onto main after the receiverHosted lift (f2d89ee) landed. Opened as a new branch rather than force-pushing #8's, which is destructive and was blocked.

#8 could not merge as-is: it conflicted with L17 in mod.zig, the vatc test file, the experimental snapshot, and the e2e driver.

What the rebase had to reconcile

The textual conflict was one comment block in tests/e2e/cpp/l3_vatc_client.cpp, but the real collision was semantic: #8's cross-impl probe assumed the host REFUSES the pipelined-provide scenario, and L17 makes it SERVE.

  • e2e driver: kept rpc: answer calls pipelined on an already-failed answer with that exception #8's stronger probe (a genuinely pipelined call, not whenResolved()) and rewrote the comment for the merged behaviour. It still guards the broken-pipeline rule in the answered direction — this exact wait is what hung before the fix.
  • Unit test: #8's test staged a receiverHosted refusal, which no longer exists. Repointed at a refusal that survives L17 — the site-2 vanished-import shape (CrossPeerProvisionTargetUnavailable). What is under test is the broken-pipeline rule, not which refusal produced it. Also applied L17's probe-lifetime lesson: the staging probes live in the test frame, since the staged question outlives the helper and is cancelled through its ctx at deinit.

Verification

Ablation-proven the teeth survived: disabling the failed_answers lookup reddens both the repointed vatc test (expected .exception, found null — the parked-forever symptom) and #8's own unit test.

Gates: test, test-rpc-peer, check, check-api, api-closure, hardening, test-oom, test-release-fast, fmt-check, docs-smoke, check-generated, the full zig build test -Doptimize=ReleaseSafe (the lane that caught L17's SEGV), and just e2e-l3-vatc 18/18.

Honest gap: the FAILED-answer direction of the broken-pipeline rule no longer has a cross-impl cell, since L17 turned that scenario's refusal into a success. Unit coverage only. Noted in the driver comment.

Closes #8.

🤖 Generated with Claude Code

nullstyle and others added 3 commits July 30, 2026 22:00
…eption

A Call targeting the promisedAnswer of an answer that already returned an
exception queued in pending_promises forever: resolved_answers records
results Returns only, so a failed answer missed there identically to a
still-pending one, and the queued-call drain in sendReturnException runs
before a pipelined call sent in the same burst can arrive. Found on the
wire by the cross-impl L3 lane (pipelined-provide scenarios): the C++
recipient pipelines its Call on the Accept question, the host refuses the
Accept fail-closed, and the Call never got a Return — the recipient hung
on the exactly-one-Return-per-call guarantee.

Record exception Returns in a failed_answers map (owned reason + type,
kept until Finish — the resolved_answers lifecycle), written in
sendReturnExceptionNoDrain, the funnel every exception Return passes
through, so drained pipeline children self-record and transitive late
arrivals are covered. planPromisedTarget consults the record exactly where
it would otherwise queue, and the call is answered immediately with a COPY
of the recorded exception, preserving the retryability signal — the
broken-pipeline behavior of the C++ reference. failed_answers also joins
the inbound answer-id namespace so a stale record can never be replayed
against a reused id, and the record is skipped for loopback and
finished-early answers (no Finish would ever clear it).

Ablation-proven tests pin both shapes: the refused cross-peer Accept with
a pipelined Call (the e2e wire trace, in the vatc suite) and the plain
two-party failed answer (from_peer suite), each asserting reason AND type
survive the copy and that Finish drains the record. The e2e C++ driver now
observes the refusal THROUGH a pipelined call instead of working around
the gap with whenResolved(); the full lane passes against the vendored
C++ reference (18/18), full suite 914/914, Stable API surface unchanged
(experimental snapshot +4 lines).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…remove

Its original staging used a receiverHosted target, which f2d89ee now SERVES, so
the test referenced a constant that no longer exists and did not compile. The
rule under test is the broken-pipeline one -- every Call gets exactly one
Return, and a call pipelined on a failed answer gets a copy of that exception --
not any particular refusal, so it moves to the refusal that survives: a site-2
`.promised` target whose import dies before the Accept (site 2 takes no
Provide-time pin by design).

Also applies the lift's probe-lifetime lesson: the staging probes live in the
test frame, because the staged question is still outstanding when the vat is
torn down and is cancelled THROUGH that ctx pointer.

Ablation: disabling the failed_answers lookup reddens this test with
`expected .exception, found null` -- the parked-forever symptom -- and #8's own
unit test alongside it. The teeth survive the rebase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge artifact, caught by CI's e2e lane. On main the probe was
`whenResolved()`, which settles on the Accept's Return WITHOUT invoking the
capability, so one real call followed and the driver asserted
`localNum.calls == 1`. The rebase correctly kept the stronger probe from the
broken-pipeline work -- a genuinely pipelined `getNumberRequest()`, which is
what exercises the rule -- but L17's count assertion rode along unchanged.

Two invocations is the right expectation now, and both are load-bearing: the
first proves a call pipelined on the Accept question is answered, the second
that the settled capability still routes to B's own cap rather than
host-Carol's.

Lane verified locally: 18/18.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@nullstyle
nullstyle merged commit 721a31d into main Jul 31, 2026
21 checks passed
@nullstyle
nullstyle deleted the pr8-rebased-on-l17 branch July 31, 2026 06:51
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