fix(bridge): a refusal is not an effect, so it does not spend the key (Phase 16a) #25
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/events-p16a-walk"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Protocol 7, amended in place — the shape 12b already used. The at-most-once store had two cases and needed a third.
The defect
A handler that threw keeps its key and stores the failure. Correct: it may have applied half of itself, and that is the failure this file exists to prevent.
A handler that returned had its reply stored and replayed for ever.
There was no case for a handler that ran to completion and deliberately refused — and every refusal on this plane is a guard: a missing
runId, an unknown item, a cap, a rate limit, a write that failed and left the value alone. Nothing happened. Remembering the refusal froze it as the key's answer, so a refusal that waiting fixes could never be retried past.uo.world.saveis the case the acceptance walk found it with, and the module states the expectation it could not meet:Six attempts over four minutes — three automatic, an operator retry, three more — every one answering the same frozen sentence:
The number cannot age, because it is the first reply being replayed. A step's idempotency key is one value for the life of the step, so the operator's retry control could not escape it either. Proof it was a replay and not the truth: the same call under a fresh key answered live — first
world.save.ok started:true, later "the last save was 26 seconds ago", a number that moves.The fix
A refusal releases the key. The refusal is still emitted to the caller, which is what ends that attempt; it is simply not remembered as this key's answer.
A refusal is recognised by its
kindending in.error— matched on the suffix, so a handler family added later is covered without anyone remembering to extend a list.bridge.erroris excluded deliberately: that is the reply this file writes when a handler threw, which is exactly the case whose key must be kept. Both completion paths are covered, including a deferred handler that refuses after its timer has armed.The rule this rests on
The shard cannot verify "nothing happened"; it takes the
.errorkind as the claim. So it is written down as a rule handlers must keep:Audited across
BridgeWorld,BridgeOneShots,BridgeLeasesandBridgeParticipationbefore making the change: everyErr(reqId, …)call site is a pre-mutation guard, or follows aWritethat failed and left the value alone.Verification
On a real shard, built offline and confirmed live (
Scripts.dllrebuilt with the server stopped, 0 warnings):So the at-most-once guarantee is intact exactly where it matters, and only the case that never had an effect is released.
Status()gains arefused=counter.Pairs with
Module-uo#33,website#198anddocs#(Phase 16a).🤖 Generated with Claude Code
https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4