open-standards/jouleclaw/jouleclaw-rs/crates/jouleclaw-cascade/tests
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science fce6e6eb98 cascade: the egress contract — per-resolution, and never stronger than evidence
Closes §5 step 4, the last item in the blueprint's build sequence. This is the
gap the frontier survey filed as "nobody has shipped a privacy router": a system
that decides between on-device and remote execution and never tells the user
which side their request landed on.

## The design turns on the default

`Egress::Undeclared` is the default, and everything else follows from it. A tier
that has not declared where its work goes is recorded as undeclared — never as
"nothing left the device". Not knowing and knowing nothing happened are
different facts, and only the second is a privacy claim.

    Undeclared        no declaration; NOT a claim, and not publishable
    None              resolved on-device
    AttestedEnclave   left the device to a peer that attested itself
    ThirdParty        left the device unattested — named, counted, and
                      honestly marked unverifiable

`is_publishable()` refuses `Undeclared`, so a receipt cannot be built from a
resolution nobody measured. `Undeclared` and `None` both report zero bytes,
which is precisely why the class must be read rather than the byte count — a
test pins that.

## The runtime stamps it, not the tier

`Tier::egress()` is a defaulted trait method, matching the existing `coord()`
precedent so legacy tiers keep compiling. But the tier does not write the value
into its own `Answer`: the runtime reads the declaration and stamps it onto the
trace it returns. A tier therefore cannot forge the claim by construction, and
cannot silently omit it either.

Both dispatch paths needed the stamp on the *runtime's* trace rather than the
tier's, because both return `Answer { trace, ..answer }` — the spread discards
the tier's copy. That is exactly the sort of thing a test catches and a design
review does not; `a_local_cache_hit_is_recorded_as_local` failed on the first
attempt for this reason.

L0 is the one tier in the base cascade entitled to claim `None`: a hit is served
from this process's own memory.

## Placement

The record lives on `ExecutionTrace`, which already derives `Default` and has a
single literal construction site — so the change lands in 4 files and 201 lines
with no churn. Putting it on `Answer` would have required touching 12 struct
literals plus test files; an earlier attempt at that also tripped `cargo fmt`,
which reformatted 22 unrelated files because the crate was never rustfmt-clean.
Reverted, and the field moved to where it fits.

The dependency direction is preserved: the cascade gains its own egress concept
and takes no dependency on AR-1. Mapping `Egress` onto an Action Receipt's
egress binding is the envelope's job, as it should be.

108 tests green in jouleclaw-cascade.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
(cherry picked from commit 95f1c00238)
2026-08-28 08:44:08 -04:00
..
r1_cascade.rs cascade: the egress contract — per-resolution, and never stronger than evidence 2026-08-28 08:44:08 -04:00
r7_budget.rs jouleclaw Phase 2: cascade + loader-gguf + pack + history + wire 2026-05-26 11:55:46 -04:00
r14_three_gaps.rs jouleclaw: workspace audit — zero warnings, zero errors, 3096/3096 tests 2026-06-03 09:35:09 -04:00
r37_closed_calibration_loop.rs jouleclaw Phase 2: cascade + loader-gguf + pack + history + wire 2026-05-26 11:55:46 -04:00
r46_escalation_gate.rs jouleclaw-cascade + -stack: wire calibrated EscalationGate into the cascade walker 2026-06-01 22:41:20 -04:00