Repository navigation
refactor(gateway): call each reducer from its Store in one hop - #645
Conversation
Add `DurableFailure` and `classify`: only a reducer the Module rejected is a Refusal, and every other error is a Transport Loss. Fakes build both through `ReducerCallError::refused` and `transport_lost`. Add `Actor(NonZeroU64)`. `session_actor` takes an `Actor`, the deliberate guid-0 fallbacks call `owner_actor`, and the remaining raw guid call sites go through `actor_or_owner` until their families take an `Actor`. `request_gm_command` now uses `call_reducer!`. The GM handler renders the Refusal reason verbatim and ends the World Session on a Transport Loss. Add Transport Loss to the glossary. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… store file Code motion only. Each `impl <Family> for Coordinator` that still sat in a world handler file moves to `stdb/store/<family>.rs`, together with the inherent reducer calls and Durable Reads only that family calls and the family's Refusal decoder. Inherent methods with other callers (relays, logon, realm-core routing, Transfer intents) stay in `reducers.rs` and `reads/`. Bodies are unchanged. Paths, imports and the visibility of the few helpers two families share are adjusted so the moved code resolves. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Covers the loot window dispatcher, the loot roll and death arms of handle_loot, and HandleLootFake, which serves all three families. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…failures Inline the melee Coordinator wrappers into the trait methods, drop the unused account id and the zero-guid guards, and classify start and stop failures with classify. A Transport Loss now ends the World Session. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…failures Inline the quest Coordinator wrappers into the trait methods, drop the unused account id and the zero-guid guards, and replace the transport-text classifier with classify. A failed interaction-gate read is a Transport Loss. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the single-caller Coordinator wrappers into the VendorActionStore methods, drop the unused account id, take Actor, and classify failures with the typed Refusal and Transport Loss split. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the single-caller Coordinator methods into the AuctionActionStore methods, take Actor in the requests, resolve Hold actors once, and decode Refusals with classify. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the chat inherent wrappers into the Coordinator impl, take Actor on realm_chat, realm_whisper and set_away, and replace the message-text classifier with classify. Add decoder tests for chat_outcome. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… Loss Inline the channel inherent wrappers, take Actor on channel_op, and drop the recoverable read path: a failed target or roster read ends the World Session. Add decoder tests for channel_outcome. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the hold, decide and finish wrappers, drop CharterOwner, and type the test realm's failures. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the guild inherent wrappers, take Actor on guild_op and guild_destroy_charter, extract guild_outcome, and replace is_fatal with classify. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the item Coordinator wrappers into the trait methods and drop the unused account id. The handler answers an internal-error Refusal when the session has no Actor, as the Store did before. The Fake models a Transport Loss with a typed error. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ailures Inline the cast Coordinator wrappers into the trait methods, drop the unused account id and the zero-guid guards, and replace is_transport_failure with classify. Requests the Gateway rejects before the Module (no item target, no Actor) answer the client as a Refusal. A Transport Loss while stopping a ranged loop now ends the World Session. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… an Actor Each family's Coordinator methods now carry the reducer call inside the trait method, the unused account ids are gone, and a Character acts as `Actor`. Handlers resolve the session's Actor once; outside the world the contact ops and the addon bridge drop silently, and a Character delete for guid 0 answers Failed. Decoders classify with `classify`: a Refusal answers the client as before, a Transport Loss ends the World Session. Character create and delete and the addon bridge now end the session on a Transport Loss instead of answering Failed or dropping it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`begin_transfer` takes the Character it freezes as an `Actor`, resolved once from the plan in `run_transfer`, so the owner fallback is gone. The forwarding wrappers whose only caller was the trait method now live inside it; the escrow steps the account-session test drives keep their inherent methods. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ansport Loss Every mail Store step that acts for a Character takes an `Actor`; the trait methods carry the reducer calls, and the owner fallback is gone. Mail ops answer `MailFailure`: a Refusal from a Gateway Gate or the Module answers the client as before, and a Transport Loss ends the World Session. A drive the transport lost mid-escrow, a cache read that failed and an exhausted escrow id range used to answer an internal error and keep the session; they now end it, and the next mailbox visit or login re-drives the fence. The mail Fake answers typed Refusals. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both families are read-only. The trait methods now hold the cache reads that only they called. Neither family has a Durable Request, so neither has a Refusal or Transport Loss answer: a failed read still shows fine weather or skips one Member Stats tick. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the two bank reducer wrappers into the trait, drop the unused account id, and resolve the Character once in the handler. A Refusal answers as before. A Transport Loss now ends the World Session. The buy-slot answer reads the Module's reason, not the reducer error text, so the bracketed code decodes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…failures Inline the four Meeting Stone wrappers into the trait and replace the text match on the transport message with classify. Add a test for the adapter decoder with typed errors. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the taxi wrappers into the trait and remove the zero-guid and owner fallbacks. A Refusal on a query or an activation answers with no packet or the server-error reply. A Transport Loss, or any failure to arm a committed flight, ends the World Session. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the trainer wrappers into the trait and drop the unused account id. Per-action requests (action bar, at-war, talents) log a Refusal and end the World Session on a Transport Loss. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the NPC wrappers into the trait and drop the unused account id. The query handler resolves the Character once. Gossip, bind home, inspect, text emote and area trigger log a Refusal and end the World Session on a Transport Loss. The handler's speech arms and their tests ride in this commit because they share the file. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Inline the speech wrappers into the trait. The GM dot-command keeps two sides: an unknown Account, which the Gateway finds itself, is a Refusal the GM reads as a system line. Realm-core being unreachable is a Transport Loss and ends the World Session. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…inator-one-hop # Conflicts: # gateway/src/world/handle_loot_fake.rs
…mily conversions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drop the temporary owner fallback: the Realm index reducers act for the Character they move, and the GM command takes the Actor its handler resolved. RealmDb for Coordinator moves to stdb/realm_db.rs and calls its Realm-only reducers directly; world_store.rs is gone and reducers.rs keeps only the calls no Store trait owns. item_slot_by_guid loses its unused account id. Session tests assert classify gives TransportLoss instead of matching the transport text. The opcode recipe, architecture doc and Actor glossary entry describe the one-hop shape. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in the Benilla protocol handling and the weak-test removal. Main's new Store methods take the one-hop shape: split_item and destroy_item live in the item family's Coordinator impl, bind_home, reset_talents, set_faction_at_war and talent_reset_cost in the npc and trainer impls. They take an Actor, and InteractionOutcome decodes Refusals through classify. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Code review: Standards and SpecReviewed Most claims in the description are true: StandardsBreaches of documented standards
Smells (judgement calls)
SpecWrong implementation
Missing or partial
Not in the spec
Summary: Standards has 9 findings. The worst is the Actor entry that differs between CONTEXT.md and CORE_TERMS.md, plus the Avoid-word use. Spec has 7 findings. The worst is the rejected taxi ack, which keeps the session with a committed but unarmed flight. Generated by Claude Code |
An activation commits before its reply is acknowledged, so answering a later Refusal with a server error would leave a durable activation and no flight. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
presence::of now wraps a failed absence vouch in PresenceUnknown. Whisper, channel targeted ops, guild invite, petition offer and .guild create answer it as they did before the one-hop change. A real Transport Loss still ends the session. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Gateway handler reached a reducer through three forwarding layers: the family trait, the
WorldStoreadapter block, and an inherent wrapper instdb/reducers.rs. 75 wrappers took an account id they never read, 76 copied a zero-guid guard, and five handlers told a Refusal from a lost connection by matching the text "reducer transport disconnected". That text appears only when the pipe was already down. A connection lost during the call, a 10 s timeout or a send failure counted as a silent Refusal, and the World Session went on.This is phase 4 of #635.
Shape
gateway/src/stdb/store/<family>.rscalls its reducer directly.stdb/reducers.rswent from about 4,950 lines to 113 and keeps only the calls no Store owns.stdb/world_store.rsis gone;RealmDblives instdb/realm_db.rs.Actor(NonZeroU64)replaces(account_id, actor_guid)on every Store method that acts for a Character. The handler resolves it once, and the zero-guid guard exists only inActor::new. Calls that mean the session owner on purpose useowner_actor().classify(&error)returnsRefusal { reason }orTransportLoss. Every handler uses it, and Fakes return typedReducerCallError::refusedortransport_lost.Behaviour change (approved: fail closed)
ReducerCallError::Rejected) is a Refusal. A timeout, a lost or failed send, or a failed cache read is a Transport Loss and ends the World Session, and the client relogs from durable state. Before, vendor, quest, guild, chat, channel, cast, melee, mail, bank, trainer, meeting stone and others swallowed these as a silent Refusal.Tests
stdb/connection.rs, no_account_idingateway/src/stdb, noactor_guid == 0.Glossary: adds Transport Loss; Actor now names the type.
Main's #643 Gateway changes (bind home, talent wipe, at-war, item split and destroy) are merged and expressed in this shape.
Local gates: fmt, clippy with
-D warnings, the five crate test commands and the wasm check pass.🤖 Generated with Claude Code (Claude Opus 5.5 lead, with Opus 5.5 and Sonnet 5.5 subagents)