Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions .trinity/seals/queen_QueenDispatchExit.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
{
"built_by": "t27c-bootstrap@0.5.2+0d573722c",
"built_by": "t27c-bootstrap@0.5.2+e1b4c32a0",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:9d5fb4742395ee3443463b27b5cbf9b4d4e24a4561fc4433210e3c225477b5eb",
"gen_hash_rust": "sha256:1171a040c6c748255961613d0a238c6debd70cee6b47d305d0eab69bbce30be9",
"gen_hash_verilog": "sha256:62fe95606912c285411e434f9b1d427c2f932738ba70059a96bef9057f12741c",
"gen_hash_zig": "sha256:a57022e1dd161143ef6cd379e303852fbda7f23ed292ad1680421a0a0ce65c6b",
"gen_hash_c": "sha256:2c3626742c7e3b35d1017fe866c795f2ad11ef98da00ef5acd0fcd56381adf74",
"gen_hash_rust": "sha256:283d83b27e34e082e497c000eb9b0df7a92beeb208dbf31e9e91514176f97095",
"gen_hash_verilog": "sha256:ad80632bfdf3d74db36766d51f961e34c388f0d4783e08c75e564782727c379b",
"gen_hash_zig": "sha256:d702797b3c2045ca3d0c178608bc6387d377bccbea53bf852746c00b5ed4e34a",
"module": "QueenDispatchExit",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-09T21:26:10Z",
"sealed_at": "2026-10-17T16:11:16Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:ac1f481c62b16c3783ffeb83c682422a0c0fd0498169d8c101db160768913a3b",
"spec_hash": "sha256:2a1b5a1bb24298ee05319cd9458d0a31c4e596a4d87fe9e360fdc93dc9a56037",
"spec_path": "specs/queen/dispatch_exit.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 10,
"total": 10
"passed": 11,
"total": 11,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d",
Expand Down
17 changes: 17 additions & 0 deletions docs/now/2026-10-17-dispatch-exit-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# NOW -- dispatch_exit.t27: a provider refusal never charges the issue (2026-10-17)

## specs/queen/dispatch_exit.t27 (Closes #10008, part of #9976)

- The bee dispatcher as keyed actors (gHashTag/trios `queen-bee-actors.ts`, `TRIOS_QUEEN_DISPATCH=actors`) asks this card whether an end counts against the issue.
- Until now `counts_against_issue(DX_PROVIDER_ERROR)` was true, so three quota refusals in a row (`RUN_LIMIT`) gave the issue up.
- A quota or an overload belongs to the key that served the turn. The next dispatch takes another key, and `key_failover.t27` `charges_issue` (#9993) charges only a bad request. The two cards now agree.
- Unchanged: a bad request (`HC_4XX`) still gives up at once, and a refusal that keeps repeating still ends at `STREAK_LIMIT` (6) with the backoff between tries.
- New test `a_provider_refusal_never_charges_the_issue`: 8 asserts, including that a timeout and an unknown end are still counted runs.
- Railway lab, master de2cff882:
- `t27c test-report`: 11 tests, 0 FAIL, 0 vacuous, 4 invariants.
- `tri mutate spec`: 81 of 84 mutants killed.
- The 3 survivors are equivalent mutants in `restart_wait_seconds`, which this change does not touch: lines 192, 198 and 202 flip `>=` and `>` at a clamp, and at the boundary both branches return the same value (`LEASE_TTL_SECONDS - age` is 0; `wait` equals `BACKOFF_CAP`).
- `dispatch_exit.wasm` (t27c gen-c + zig 0.16.0 cc wasm32-wasi -Oz -DNDEBUG reactor, every fn exported):
- The old card rebuilds to `31c0e524...`, the hash pinned in gHashTag/trios `specs/PIN`, so the recipe is the one in production.
- The new card builds to `b990a601e4c1806095688683e3c25f0b5176124d368dfd258fdcdd7934c56327`, the same hash on two builds.
- The TypeScript hosts read this card from the wasm, so trios takes the change by re-vendoring the `.t27`, the `.wasm` and the `specs/PIN` lines. There is no hand-written code.
26 changes: 24 additions & 2 deletions specs/queen/dispatch_exit.t27
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
; once its lease was taken from it. What is left -- a silent runtime that still holds its lease past
; one TTL without a heartbeat -- contradicts the lease rule itself (a lease expires one TTL after the
; last renewal), so UNKNOWN names an observation that is inconsistent, not a cause nobody looked for.
; #10008 (2026-10-17): a provider error no longer counts against the issue. A quota or an overload
; belongs to the key that served the turn, the next try takes another key, and key_failover.t27
; charges_issue charges only a bad request; this card now agrees with it.
; Not here: the wiring. The host is TypeScript in gHashTag/BrowserOS (queen-dispatch.ts); the test
; `conformance_rows` is one assert per row, inputs to code, so the host can run the same rows. ASCII
; only (L3).
Expand Down Expand Up @@ -145,11 +148,16 @@ pub const BACKOFF_CAP : u32 = 300;
/// Whether this end is a counted run of the issue. An agent runtime is a transient child (actors.t27
/// queen_restart_type): normal and shutdown end it for good and count nothing, a cancel is an
/// interruption (control.t27 cancel_counts_against_issue). A dropped connection is an interruption
/// too: the runtime was alive and the work did nothing wrong. Every other end is a failed run.
/// too: the runtime was alive and the work did nothing wrong. A provider error is the provider's or
/// the key's, not the issue's (#10008): the next try takes another key, and key_failover.t27
/// charges_issue charges only a bad request. A bad request still gives up at once, by its own rule in
/// restart_decision, and a refusal that repeats still ends at STREAK_LIMIT. Every other end is a
/// failed run.
pub fn counts_against_issue(reason: u8) -> bool {
if (reason == DX_NORMAL) { return false; }
if (reason == DX_SHUTDOWN) { return false; }
if (reason == DX_NOCONNECTION) { return false; }
if (reason == DX_PROVIDER_ERROR) { return false; }
return true;
}

Expand Down Expand Up @@ -267,7 +275,7 @@ test the_third_counted_run_gives_up_and_an_interruption_is_not_counted {
assert counts_against_issue(DX_NOCONNECTION) == false;
assert counts_against_issue(DX_KILLED) == true;
assert counts_against_issue(DX_TIMEOUT) == true;
assert counts_against_issue(DX_PROVIDER_ERROR) == true;
assert counts_against_issue(DX_PROVIDER_ERROR) == false;
assert counts_against_issue(DX_UNKNOWN) == true;
assert restart_decision(DX_TIMEOUT, HC_NONE, 1, 1) == RD_LATER;
assert restart_decision(DX_TIMEOUT, HC_NONE, 2, 2) == RD_GIVE_UP;
Expand All @@ -279,6 +287,20 @@ test the_third_counted_run_gives_up_and_an_interruption_is_not_counted {
assert restart_decision(DX_SHUTDOWN, HC_NONE, 9, 9) == RD_NONE;
}

/// #10008: a quota, an overload or a 5xx is the key's, not the issue's. Past RUN_LIMIT counted runs
/// it still waits and tries again; a bad request still gives up at once; a refusal that repeats ends
/// at STREAK_LIMIT like any interruption.
test a_provider_refusal_never_charges_the_issue {
assert restart_decision(DX_PROVIDER_ERROR, HC_NONE, RUN_LIMIT - 1, RUN_LIMIT - 1) == RD_LATER;
assert restart_decision(DX_PROVIDER_ERROR, HC_429, RUN_LIMIT, RUN_LIMIT) == RD_LATER;
assert restart_decision(DX_PROVIDER_ERROR, HC_5XX, 9, STREAK_LIMIT - 1) == RD_LATER;
assert restart_decision(DX_PROVIDER_ERROR, HC_NONE, 0, STREAK_LIMIT) == RD_GIVE_UP;
assert restart_decision(DX_PROVIDER_ERROR, HC_4XX, 0, 0) == RD_GIVE_UP;
assert restart_decision(DX_PROVIDER_ERROR, HC_4XX, RUN_LIMIT, 1) == RD_GIVE_UP;
assert restart_decision(DX_TIMEOUT, HC_NONE, RUN_LIMIT - 1, RUN_LIMIT - 1) == RD_GIVE_UP;
assert restart_decision(DX_UNKNOWN, HC_429, RUN_LIMIT - 1, 0) == RD_GIVE_UP;
}

test an_interruption_that_repeats_also_ends {
assert restart_decision(DX_NOCONNECTION, HC_NONE, 0, STREAK_LIMIT - 1) == RD_LATER;
assert restart_decision(DX_NOCONNECTION, HC_NONE, 0, STREAK_LIMIT) == RD_GIVE_UP;
Expand Down
Loading