jcp-gateway: Close the flow loop from tool output to sealed verdict #42

Closed
dcharlot wants to merge 3 commits from jcp-gateway-flow-relay into jcp-receipt-flow-seal

2026-07-22

Transaction Science
jcp-gateway: Close the flow loop from tool output to sealed verdict
The pieces exist; this wires them into the request path so provenance actually
propagates across a session. The relay gains two phases around the existing
authorize/meter/charge. Before authorizing, it folds the argument's flow label
from the tool outputs admitted earlier this session — the scan finds any verbatim
carry — and after a successful call it admits the result as a labelled, untrusted
source, so a later call carrying its bytes is folded against it. Derived taint now
outlives the single call that produced it, which is the whole point.

Authorization runs against those facts (authorize_flow), so a tainted argument
above the grant's ceiling is denied at the gate before the upstream is contacted
and before any debit — zero joules, the tool-poisoning invariant extended to
derived taint. The charge debits the tainted sub-budget too when the call was
tainted, and the receipt is sealed with the flow verdict: jcp/3 when tainted,
byte-identical jcp/2 when clean, so a non-flow deployment is unaffected. The
monitor's own evaluation energy is metered on the same hardware meter and sealed
as its own field, never as tool cost.

The result frame is Trust::Data, so mcp_result_frame goes live from the gateway
(it was written for exactly this and never called): an injected instruction in a
tool result still cannot exercise a capability, and now the result's provenance
also flows forward. The witnesses are retained by id so an auditor can fetch one
and re-derive a sealed verdict offline.

set_flow_ceiling attaches a FlowBelow ceiling to a tool by re-issuing its grant;
enable_high_water turns on the coarse session floor; forbid_flow installs an
enforcement-point taint floor for a tool.

The capstone test is the attack the feature exists to stop, end to end: read an
attacker email (allowed, its output admitted), then try to send a body carrying
the secret verbatim — the send is denied tainted-flow at the gate, costs zero
joules, seals a jcp/3 receipt whose flow block reads "above", and leaves a witness
that verify_witness re-checks offline. A clean send still goes through, and the
whole jcp/2 + jcp/3 log verifies as one chain. The four pre-existing gateway
tests pass unchanged — a clean call is byte-identical to before.

Final increment of the FL-2 plan. The branch merges the jcp-flow crate (#39) that
the relay depends on; once the stack lands on main this rebases to the gateway
diff alone.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-22 07:54:58 -04:00
Transaction Science
jcp-flow: Compute propagated provenance from tool bytes
The flow lattice says what a label is; this crate computes one from real bytes.
As a run reads tool outputs and derives new values, FlowStore tracks where each
value's bytes came from and folds a derived value's label from the ancestors it
was actually computed from — so authorization can be conditioned on where an
argument came from, not merely on its literal content, the gap the one-bit Trust
tag cannot express.

Two properties make this a reference monitor rather than a heuristic. The carry
detector is an exact blake3 window index, deliberately not a rolling hash: each
W-byte window is hashed independently, so two values share a fingerprint exactly
when they share a verbatim run, with no carry state to get wrong and identical
fingerprints on every platform. And every verdict emits a self-contained,
content-addressed Witness — the ancestor cut, the taint edges, and the folded
label — so a third party re-folds it offline to re-derive the verdict rather than
trusting it; a receipt commits only its id.

label_payload folds over four sources, each an edge kind the witness records:
verbatim carry the scan found (Witnessed, proven by grams), a registered
sensitive term appearing verbatim (Witnessed, for secrets below the window
floor), a caller declaration (Declared — or top, fail-closed, if that parent was
evicted), and in HighWater mode the coarse session floor (Ambient). Every
collection is BTree, so a witness content-addresses deterministically.

Ten tests carry the laws L17-L20 and the residuals as executable claims: verbatim
carry yields a witnessed edge; a sub-window unregistered token yields nothing (the
residual, pinned so it is never mistaken for a guarantee); a registered term of
any length is caught; verify_witness re-folds the cut and catches a label tampered
to look clean; a declared-but-lost parent fails closed to top; and — the attack
this exists to stop — a summary carrying an attacker email verbatim does not sit
below a principal-only ceiling, while HighWater still taints a laundered payload
the scan alone cannot see.

Increment 2 of the FL-2 plan; the runtime gate, jcp/3 receipt seal, and gateway
relay follow. The receipt-stream replay (the eight-check chain walk) lands with
the receipt integration, where jcp/3 receipts exist to walk.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-22 02:09:29 -04:00