Skip to content

feat: carry a provider fault, and stop forwards faking ordinal 0 - #20

Merged
Kinflou merged 1 commit into
mainfrom
feat/fault-channel
Oct 17, 2026
Merged

Kinflou merged 1 commit into
mainfrom
feat/fault-channel

Conversation

@Kinflou

@Kinflou Kinflou commented Oct 17, 2026

Copy link
Copy Markdown
Member

Follows ComlineProject/runtime#11, which added a fault channel: a third
response shape for a provider that failed where the schema declares no error
for it. The simulator acts as both provider and client, so it needs both ends.

The channel

  • WireFraming::encode_response_fault — rendered per framing, as the runtime
    does it: the datagram framing's third tag, JSON-RPC's reserved
    -32603 Internal error.
  • jsonrpc_decode_response no longer does e.code as u16 unconditionally.
    Only a code inside the ordinal space is a raised schema error; the reserved
    negative codes are JSON-RPC's own. -32603 as u16 was silently becoming
    ordinal 33205, and -32601 ("method not found") ordinal 33039.
  • BehaviorKind::Fault — "Fault (provider failed)". Unlike Raise it applies
    to any non-one-way function whether or not it declares errors, which is the
    whole point: a function with no ! previously had no way to fail at all.
  • CallResult::Fault, SimOutcome::Fault, and fault: true on FrameDetail
    so the inspector shows a fault instead of reporting the frame as undecodable.

The bug it fixes

A forward whose upstream timed out, or returned a body that would not decode,
replied to the outer caller with ordinal: 0 and a {"error": "…"} body. That
is a schema error: the outer client decodes it as whichever error happens to
hold ordinal 0 and hands its caller a wrong answer rather than a failure. Those
now fault.

Two sites still fabricate ordinal: 0 and are left alone deliberately:
begin_forward's configuration refusals ("no connection X", "forwarding
cycle", "X has no send") and the disabled-script stub. Each carries a message
the user needs, and a fault is opaque by design — there is nowhere else in the
engine to put the text. They want a diagnostic sink, which is its own change.

Multi-`!`

Needed no change. FnShape::throws was already a Vec<ThrowShape> and
Raise's config names an ordinal, so every declared error is reachable; the
default seeds the first in declaration order. Pinned by
`a_function_that_throws_several_errors_can_raise_any_of_them`.

Verification

135 tests pass, clippy and fmt clean. New tests: a fault round-trips over every
framing in TRANSPORT_MATRIX; a reserved JSON-RPC code is not read as an
ordinal; JSON-RPC's fault frame is byte-checked; a forwarded fault stays a
fault; the inspector renders one.

Note for the playground

call_result_json now emits {"status":"fault"} and a frame detail can carry
fault: true. The playground UI needs a case for each (a status chip and a
frame badge) — not touched here.

The runtime grew a fault channel: a third response shape for a provider
that failed where the schema declares no error for it. The simulator is
a provider, so it needs to be able to produce one, and a client, so it
needs to be able to read one.

- `WireFraming::encode_response_fault` — the datagram framing's third
  tag, JSON-RPC's reserved `-32603`.
- `jsonrpc_decode_response` no longer does `e.code as u16`
  unconditionally. Only a code inside the ordinal space is a raised
  schema error; the reserved negative codes are JSON-RPC's own, and
  `-32603 as u16` was silently becoming ordinal 33205.
- `BehaviorKind::Fault` — "Fault (provider failed)". Unlike `Raise` it
  applies to any non-one-way function, declared errors or not: a
  function with no `!` previously had no way to fail at all.
- `CallResult::Fault` / `SimOutcome::Fault`, and `fault: true` on
  `FrameDetail` so the inspector shows it rather than reporting the
  frame as undecodable.

It also fixes a real bug. A forward whose upstream timed out or returned
an undecodable body replied to the outer caller with `ordinal: 0` and a
`{"error": "..."}` body — a *schema* error, which the outer client
decodes as whichever error happens to hold ordinal 0. That is a wrong
answer dressed as a failure. Those now fault.

Multi-`!` needed no change: `FnShape::throws` was already a `Vec` and
`Raise`'s config names an ordinal, so any declared error is reachable.
There is a test pinning that.

135 tests pass; clippy and fmt clean.
@Kinflou
Kinflou merged commit aa82ce0 into main Oct 17, 2026
1 check passed
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