feat(cluster): RFC 010 c10–c16, follow-ups and Phase 6 (squash of 16ef583..d9c62a8)

Tree snapshot of d9c62a8 (2026-08-18). The 20 source commits between
16ef583 (c9) and d9c62a8 were never pushed and the clone that held them
was lost; this commit carries their combined tree verbatim so the build
history stays auditable from the c1–c9 commits below it. Original
hashes as recorded in the session handoff:

  c10  f03e94d  pid targeting + auto-serialization (RemotePid, D14 name
                on the wire); Phase 3 gate
  c11  7ef4bad  DownReason::Disconnected, wire tag 5
  c12  d124162  remote monitors (Monitor/Demonitor/Down frames)
  c13  9de967b  connection-loss synthesis (A+B: Monitors::teardown +
                unread-command Disconnected); Phase 4 gate
  c14  7e822b7  eager pg eviction (reaper actor, ReaperInboxes)
       dbe1a22  InboundVerdict::label(), trace::Event::ClusterInbound
       31a9877  tests/channel.rs monitor-churn target gated on `go`
       653559e  Discovery::Withdrawn{name, addr}
  c15  b41d76e  distributed pg: Sync on NodeUp, Join/Leave broadcast,
                NodeDown sweep, members_all; PgMsg wire type
  c16  fafa881  pick_any / dispatch_any; Phase 5 complete
  Phase 6 Tier A:
       195c73e  p4  NodeEvent::NodeDown(NodeInfo)
       48fd766  p1  connector Candidate{name, addr, state}
       ce8cf99  p2+p7 conn.rs select arms as Vec<Arm>; Outbound::Drained
       bf24988  p6  RemotePid::from_local -> Option
       9ae0380  p3  PeerStanding{Free, Claimed, Dialing}
       c7d62a1  p11 cluster::Timing knobs, threaded by value
       46f171d  p11 cluster_disconnect un-ignored on SMARM_FAST_TIMING
  Phase 6 Tier B:
       391a9ae  p5  cluster::RemoteDownReason{Local, Disconnected};
                    DownReason::Disconnected removed from core
       7ddd908  p9  pg ctl channel unconditional, one cfg seam at spawn
       d9c62a8      PeerNameMismatch parks the candidate; ClusterDial trace

Verified at d9c62a8: default 361/0, cluster 448/0, clippy --lib on
default / cluster / cluster+smarm-trace, fmt, 10x flake on
cluster_dial_mismatch, 5x on cluster_pg.
This commit is contained in:
claude-asm-audit
2026-08-18 16:00:00 +02:00
parent 16ef583455
commit 4c0e42152f
32 changed files with 4202 additions and 578 deletions
+10 -19
View File
@@ -5,7 +5,7 @@
use smarm::cluster::envelope::{Frame, NodeMeta, RejectReason, PROTO_VERSION};
use smarm::cluster::handshake::{
dial_wins, HelloCtx, Initiator, InitiatorOutcome, Local, Responder, ResponderOutcome,
dial_wins, Initiator, InitiatorOutcome, Local, PeerStanding, Responder, ResponderOutcome,
};
use smarm::pg::Incarnation;
@@ -42,7 +42,7 @@ fn happy_path_establishes_both_ends() {
assert_eq!(hello, hello_from("alpha"), "initiator emits its identity");
let responder = Responder::new(local("beta"));
let (reply, peer) = match responder.on_frame(hello, HelloCtx::default()) {
let (reply, peer) = match responder.on_frame(hello, PeerStanding::Free) {
ResponderOutcome::Accepted { reply, peer } => (reply, peer),
other => panic!("expected Accepted, got {other:?}"),
};
@@ -82,7 +82,7 @@ fn hash_mismatch_rejected() {
incarnation: Incarnation::new(7),
meta: local("alpha").meta,
};
match responder.on_frame(hello, HelloCtx::default()) {
match responder.on_frame(hello, PeerStanding::Free) {
ResponderOutcome::Rejected { reply, reason } => {
assert_eq!(reason, RejectReason::HashMismatch);
assert_eq!(reply, Frame::HelloReject { reason });
@@ -112,7 +112,7 @@ fn proto_version_mismatch_rejected_and_checked_first() {
incarnation: Incarnation::new(7),
meta: local("alpha").meta,
};
match responder.on_frame(hello, HelloCtx::default()) {
match responder.on_frame(hello, PeerStanding::Free) {
ResponderOutcome::Rejected { reason, .. } => {
assert_eq!(reason, RejectReason::ProtoVersion);
}
@@ -123,10 +123,7 @@ fn proto_version_mismatch_rejected_and_checked_first() {
#[test]
fn claimed_name_rejected() {
let responder = Responder::new(local("beta"));
let ctx = HelloCtx {
name_claimed: true,
dialing_this_peer: false,
};
let ctx = PeerStanding::Claimed;
match responder.on_frame(hello_from("alpha"), ctx) {
ResponderOutcome::Rejected { reason, .. } => {
assert_eq!(reason, RejectReason::NameTaken);
@@ -139,7 +136,7 @@ fn claimed_name_rejected() {
fn own_name_offered_rejected_as_name_taken() {
// Self-connect or genuine collision: the responder's own name arrives.
let responder = Responder::new(local("beta"));
match responder.on_frame(hello_from("beta"), HelloCtx::default()) {
match responder.on_frame(hello_from("beta"), PeerStanding::Free) {
ResponderOutcome::Rejected { reason, .. } => {
assert_eq!(reason, RejectReason::NameTaken);
}
@@ -158,10 +155,7 @@ fn hash_checked_before_name() {
incarnation: Incarnation::new(7),
meta: local("alpha").meta,
};
let ctx = HelloCtx {
name_claimed: true,
dialing_this_peer: false,
};
let ctx = PeerStanding::Claimed;
match responder.on_frame(hello, ctx) {
ResponderOutcome::Rejected { reason, .. } => {
assert_eq!(reason, RejectReason::HashMismatch);
@@ -184,10 +178,7 @@ fn dial_wins_is_deterministic_and_antisymmetric() {
fn simultaneous_connect_exactly_one_side_accepts() {
// alpha and beta dial each other at once. Each responder sees the peer's
// Hello while its own dial is in flight.
let ctx = HelloCtx {
name_claimed: false,
dialing_this_peer: true,
};
let ctx = PeerStanding::Dialing;
// On beta: inbound is alpha's dial; alpha < beta, so the inbound wins.
let on_beta = Responder::new(local("beta")).on_frame(hello_from("alpha"), ctx);
@@ -208,7 +199,7 @@ fn simultaneous_connect_exactly_one_side_accepts() {
#[test]
fn tiebreak_loss_only_applies_when_dialing() {
// Same inbound Hello, no dial in flight: plain accept.
let on_alpha = Responder::new(local("alpha")).on_frame(hello_from("beta"), HelloCtx::default());
let on_alpha = Responder::new(local("alpha")).on_frame(hello_from("beta"), PeerStanding::Free);
assert!(matches!(on_alpha, ResponderOutcome::Accepted { .. }));
}
@@ -225,7 +216,7 @@ fn garbage_before_hello_fails_without_reply() {
},
Frame::Demonitor { monitor_id: 3 },
] {
let out = Responder::new(local("beta")).on_frame(frame.clone(), HelloCtx::default());
let out = Responder::new(local("beta")).on_frame(frame.clone(), PeerStanding::Free);
match out {
ResponderOutcome::Failed(f) => assert_eq!(f, frame),
other => panic!("expected Failed({frame:?}), got {other:?}"),