diff --git a/docs/craft/rpcs.md b/docs/craft/rpcs.md index bbcc2e95..0f1700d5 100644 --- a/docs/craft/rpcs.md +++ b/docs/craft/rpcs.md @@ -230,6 +230,10 @@ previously answered as one. it — `craft::wire::op` stops at 14 — so it is deferred at the *wire*, not merely at the transport. It need not ride the same channel as the client plane: these calls are node-to-node, and HomeBlocks may prefer its existing inter-node RPC. See `craft_client/docs/peer-plane.md` for the interface (`craft_peer`) and what remains. + Whatever it ends up being, it **must be timeout-bounded**: the catch-up call site + (`CraftPeerFetcher::fetch_from_peer`, used by `SyncRSCommitLSN` apply) already carries a `timeout_ms` + parameter (`peer_fetch_timeout_ms` in `home_blks_config.fbs`, default 5000ms) for the real transport to + enforce — a peer that misses it is treated as unreachable, same as any other hard failure. Note the asymmetry this creates, because it is easy to get backwards: HomeBlocks is **both ends** of the peer plane (it initiates on RAFT commit *and* serves peers) but **only the far end** of the client plane (it never diff --git a/docs/craft/subtasks.md b/docs/craft/subtasks.md index d5d965e5..7348bdfc 100644 --- a/docs/craft/subtasks.md +++ b/docs/craft/subtasks.md @@ -133,6 +133,7 @@ and enforce single-writer exclusivity without data flowing through the RAFT log. - RAFT entry carries `{rs_commit_lsn, client_token, empty_slots[]}` - **Leader pre-resolution:** before proposing `N`, the leader resolves every unresolved slot ≤ `N`: fetch it from any holder, or record an `Empty` verdict on quorum-lacks evidence (leader counts itself; non-responders never count); it must not propose past an unresolved slot - On apply: verify token; mark `empty_slots` Empty, **discarding any local data held there** (reconciliation); if behind, `fetch_data()` the remaining missing slots from peers; then `commit_lsn = rs_commit_lsn`. **Apply never truncates** and replicas **never declare Empty unilaterally** +- Peer catch-up (`CraftPeerFetcher::fetch_from_peer`) is **timeout-bounded**: every call passes `peer_fetch_timeout_ms` (`home_blks_config.fbs`, default 5000ms); a peer that misses the deadline is treated as a hard failure, same as any other fetch failure (best-effort — `commit_lsn` still advances, unresolved LSNs stay missing). The interface only carries the deadline; enforcing it against a real wire call is S9's (the transport's) - `append(sync_to, client_token)` proposes this entry via RAFT - Triggers: periodic every N LSNs (configurable via `home_blks_config.fbs`, default 128), watchdog, login, **client-requested (after a failed sub-quorum write)** — the client's `Resolve` RPC lands on `CraftReplDev::request_resolution(term, upto)`, which runs this same leader pre-resolution and returns the Empty verdicts ≤ `upto` (`craft::resolution_result`). The client broadcasts it to every member (it cannot know the leader mid-session); a follower returns `NOT_LEADER`