Sans-I/O rewrite: pure state machine + wire codec + async/embedded drivers - #92
Open
al8n wants to merge 129 commits into
Open
Sans-I/O rewrite: pure state machine + wire codec + async/embedded drivers#92al8n wants to merge 129 commits into
al8n wants to merge 129 commits into
Conversation
…s in legacy/ workspace
Add buffa-build codegen (proto/serf/v1/messages.proto → OUT_DIR/serf_wire_generated.rs),
the typed UserEventMessage + LamportTime, the user_event_{to,from}_pb bridge, and two
round-trip tests (happy-path + missing-ltime rejection). Build and test both green.
Adds `serf-proto::framing`: - `MessageType` enum with 10 variants (Leave/Join/PushPull/UserEvent/ Query/QueryResponse/ConflictResponse/Relay/KeyRequest/KeyResponse) + `Unknown(u8)` for forward compatibility; tag bytes 1–10 match the legacy serf-core constants. - `encode_message(ty, &impl buffa::Message) -> Result<Vec<u8>, FrameError>`: writes `[tag][LEB128 body_len][buffa body]` into a single allocation. - `decode_message(&Bytes) -> Result<(MessageType, Bytes, usize), FrameError>`: zero-copy body slice via `Bytes::slice`, returns bytes consumed. - `FrameError` covers Empty / Incomplete / VarintOverflow / FrameTooLarge / Decode. All four items re-exported from the crate root. 6 framing tests + 2 pre-existing messages tests — 8 total, all green.
…roto + bridge
Adds the four foundation field types:
- QueryFlag: bitflags! u32 (ACK | NO_BROADCAST); no standalone proto, u32 wire
- Coordinate: typed + pb::Coordinate (repeated double portion, error/adjustment/height)
- Tags: typed + pb::Tags (map<string,string> entries via buffa HashMap)
- Filter: typed enum + pb::Filter (oneof NodeIdList|TagFilter, boxed by buffa)
Bridges: coordinate_{to,from}_pb, tags_{to,from}_pb, filter_{to,from}_pb.
Tests: 14 new round-trip tests (22 total), all green.
…esponseMessage Add proto definitions, typed generics, and bridge conversions for the three membership messages. Node-id (I) and Node<I,A> are embedded as opaque `bytes` encoded via memberlist_proto::Data / DataRef, mirroring the bridge pattern from memberlist-proto. ltime is proto3 `optional uint64` (required on decode). Round-trip tests use I=SmolStr, A=SocketAddr.
…ssage - Make Filter generic over I: Id(Vec<I>) with where-clause Data bounds; default I = SmolStr preserves backward compatibility. - NodeIdList proto field changed from repeated string to repeated bytes; each I encodes via memberlist_proto::Data (mirrors the Node bytes pattern). - Add QueryMessage<I,A> and QueryResponseMessage<I,A> typed structs with ack()/no_broadcast() helpers; Duration stored as uint64 nanos on the wire. - Add pb::QueryMessage and pb::QueryResponseMessage to messages.proto with field numbers matching the legacy serf-core tag constants. - Add filter_to_pb/filter_from_pb bridge generics (now return Result); add query_to_pb/query_from_pb + query_response_to_pb/query_response_from_pb. - Export all new types and bridge functions from lib.rs. - Update existing Filter tests for new Result-returning bridge API; add 8 new query tests covering roundtrip, filters, and required-field rejection. - All 36 tests pass (cargo test -p serf-proto, exit 0).
…elayMessage PushPullMessage<I>: 3 lamport clocks (ltime/event_ltime/query_ltime) required-on-decode; status_ltimes as repeated NodeStatusTime (map<bytes,...> forbidden in proto3); left_members as repeated bytes; events as repeated UserEvents. UserEvent + UserEvents batch added as separate typed + proto + bridge types. KeyRequestMessage: optional bytes key (None = list-keys). KeyResponseMessage: result + message + repeated bytes keys + optional primary_key. KeyResponse<I> (machine aggregation) intentionally NOT ported per spec. RelayMessage<I,A>: destination Node<I,A> via Data bytes; payload carried verbatim. 54 tests pass (24 new).
- Add `src/any/mod.rs`: `AnyMessage<I,A>` enum (one variant per MessageType)
with `decode(buf: &Bytes) -> Result<Self, DecodeError>` that dispatches on
the framing tag byte → buffa decode → bridge → typed variant.
`DecodeError` wraps `FrameError`, `BridgeError`, `Buffa { tag }`, and
`UnknownTag(u8)`. KeyRequest/KeyResponse variants and DecodeError::UnknownTag
arms are gated on `any(aes-gcm, chacha20-poly1305)` with matching doc(cfg).
- Add `src/any/tests.rs`: end-to-end `encode_message → AnyMessage::decode` round-
trip for every message type (11 plain + 2 encryption-only). Unknown tag and empty
buffer error cases. No-encryption guard for key tags → UnknownTag.
- Re-export `AnyMessage` and `DecodeError` from `lib.rs`; add `pub mod any`.
- Sweep minors:
- `data_to_bytes`: delegate to `Data::encode_to_bytes()` (eliminates local reimpl).
- `data_from_bytes`: widen signature from `&Bytes` to `&[u8]`; all call sites
coerce via Deref unchanged.
- `KeyResponseMessage`: add `#[derive(Default)]` (fixes clippy::new_without_default
+ clippy::derivable_impls; `new()` delegates to `Self::default()`).
- Full gate: all 6 commands (build/test/clippy × plain/encryption) RC=0.
63 tests plain, 69 tests encryption.
Pure Sans-I/O serf state machine over memberlist_proto::Endpoint: membership FSM, three Lamport clocks, user events, queries/responses/relay, conflict and key-management interceptors, push-pull anti-entropy, Vivaldi coordinates, and snapshot replay.
Modernize the coverage nightly-toolchain step (drop the deprecated actions-rs/toolchain action) and quote the tarpaulin output-dir path.
…inators A narrow pub(crate) trait the serf core uses to reach a memberlist reliable coordinator (queue, send, snapshot, push-pull, leave, poll), implemented for the Stream (tcp/tls) and QUIC coordinators. Adds the tcp/tls/quic feature forwards to serf-proto.
…ver the Reliable seam
serf_proto::Endpoint drops its inner memberlist Endpoint and reaches the transport through a disjointly-borrowed &mut impl Reliable; a transitional StreamEndpoint { core, transport } runs the composed tick and preserves the test surface.
…reamEndpoint Wraps the real reliable coordinator (tcp/tls) plus the serf core; the coordinator owns the stream lifecycle and sieves DialRequested internally, so serf observes only RemoteStateReceived. The composed handle_timeout keeps the before_inner, transport, after_inner order.
Add the docsrs doc(cfg) attribute to the feature-gated impl blocks so docs.rs renders the gate badges, and tighten the composed-timeout doc comment.
The no-transport build exposes only wire/data/config/snapshot types; the whole members module (the membership FSM state) is gated on any(tcp, quic).
…a seam set_tags encodes the local tag map into the node meta, re-advertises it via the coordinator through the new Reliable::update_meta seam, and refreshes an already-present local member's tags so tag-filtered local queries observe them immediately. Both super-machines forward it. handle_node_update no longer marks local-state dirty for a NodeUpdated, since a member's tags and address are not part of the push-pull snapshot.
…f drivers serf-driver holds the membership SerfSnapshot view, the common driver error payloads, and the observation byte-weight helper — the runtime-independent pieces both serf-compio and serf-reactor will need. Mirrors memberlist-driver; the run loops, channel/cell substrate, and delegate dispatch live per-runtime.
Expose the Sans-I/O accessors a runtime driver needs to pump the StreamEndpoint and QuicEndpoint super-machines: start_scheduling, start_push_pull, handle_message, gossip_mtu, max_stream_frame_size, local_id, members_snapshot, and encrypt_gossip/decrypt_gossip, plus members_snapshot on Endpoint. FSM and wire behavior are unchanged.
JoinFailed was never wired into a SerfError variant and has no consumer under the dispatch-only Join semantics the drivers use: the Join command is dispatched into the machine, not awaited for a seed-contact result.
The compio runtime driver (io_uring on Linux, polling/kqueue on macOS/BSD, IOCP on Windows) for serf's Sans-I/O SWIM machine, mirroring memberlist-compio: TCP, TLS-over-TCP, and QUIC reliable coordinators over the shared UDP gossip plane, gossip encryption (AES-GCM and ChaCha20-Poly1305), DNS/getifs/OS address resolvers, serde and clap option layers, and the Serf handle with observable event and observation drop counters. A single-owner select loop per transport drives the endpoint super-machine and republishes a lock-free snapshot. When a suspicion deadline is already past, the drain reaps the proactor with a zero-timeout poll before re-checking the socket, so a buffered Ack resolves its probe before suspicion fires: a backend-portable empty observation on a completion-based runtime where no synchronous check exists.
…atermark-guarded leave (#65)
…nts, options The agnostic-generic reactor-driver foundation for serf's Sans-I/O machine (Send/Arc/agnostic R, flume everywhere, no cmd_fairness_budget knob); ports serf-compio's command/delegate/error/events/options into the reactor Send model, mirroring memberlist-reactor's shape. Compiles --no-default-features as the foundation; the tcp/quic-gated driver and transports follow.
Agnostic-generic reactor substrate: Arc<Shared> with a Mutex<VecDeque<Command>> + stored Waker command queue (no channel), ArcSwap snapshot, drop counters, and teardown latch; a Send Transport trait over R plus agnostic TCP (bind + ephemeral-port retry) and the resolver infra. The stream pump is an impl Future::poll driving serf-proto's StreamEndpoint (readiness recv-loop -> drain_surfaces -> single inline handle_timeout; NO poll_with(ZERO) drain) with the readiness bridge task; ports serf-compio's serf-logic (PendingJoin await-result join, per-exchange ignore_old, events->observation) into the reactor model mirroring memberlist-reactor. The handle carries construction + command dispatch; ergonomic constructors and tests follow.
…e TCP tests Add ergonomic Serf::tcp / tcp_with_rng over the generic Serf::new::<TcpTransport>, plus src/snapshot.rs and the snapshot public read-forwarders (members/local_member/state/advertise_node/advertise_address/local_id/default_query_*/remove_failed_node*) porting serf-compio's semantics. Add the real-node TCP suite on tokio (2-node join/converge/user_event/query/leave-LeftCluster/shutdown) proving the reactor stream driver works end-to-end; mirrors memberlist-reactor's harness.
…e before the timer The readiness pump capped the UDP recv (and bridge-inbound) at iter_drain_cap but still ran the single handle_timeout and the join/leave deadline reaps, so a due timeout could fire before a ready pre-deadline Ack / ExchangeCompleted / LeftCluster queued behind the cap was drained (false suspicion / spurious JoinAllFailed / LeaveTimeout under bursts). It keeps memberlist-reactor's bounded per-poll recv cap but now gates the single handle_timeout site and the deadline reaps on quiescence (!more), so they run only once the socket is drained to Poll::Pending and drain_surfaces has no more ready work. Regression with iter_drain_cap=1.
… for liveness drain_surfaces is now a fixed-point (repeat the ordered pass while any surface made progress), so a later surface feeding an earlier one no longer reports false quiescence — the withheld Close and the KeyRequest respond_key gossip are drained the same poll. The timer + deadline reaps fire at quiescence OR after a bounded number of capped-poll deferrals (TIMER_DEFERRAL_LIVENESS_BOUND), so a sustained ingress flood can no longer starve failure-detection / join-leave-query deadlines, while staying non-premature (FIFO drains the pre-deadline backlog first). Regressions: continuous-flood liveness, the fixed-point withheld-Close late surface, and the kept non-premature completion-behind-cap case.
…orcement Ports serf-compio's two encrypted regressions onto the reactor tcp real-node suite: a shared-keyring 2-node cluster joins and converges over the AEAD-sealed gossip plane, and a disjoint-keyring pair must NOT exchange membership — the negative case proves the cfg-gated encrypt_gossip/decrypt_gossip hops are real AEAD enforcement, not an identity transform. Gated #[cfg(encryption)].
The reactor exported an async MergeDelegate that nothing ever invoked — by the time any event surfaces, the inner machine has already applied the push/pull merge, so a driver-side async gate can never veto anything. The machine's own synchronous MergeDelegate predicate is the real admission point: it runs inline for EVERY push/pull merge (a join and a periodic anti-entropy refresh alike, deliberately tighter than the reference implementation's join-only gate), and a vetoed peer set is never applied. Both composed serf endpoints now forward set_merge_delegate to the inner machine, every handle constructor accepts an optional boxed predicate (installed by each transport's run body exactly like the reconnect delegate), and the inert async trait and its noop impl are retired in favor of re-exporting the machine trait — an application needing async policy resolves it ahead of time and answers from the resolved state. An e2e pins the wiring: a recording delegate on the joined node observes at least one notify_merge carrying the joining peer's state. (Consultation is the stable assertion — a merge-only veto is transient by design, since a rejected peer can still be admitted moments later through gossip Alives, as in the reference implementation.)
The machine has owned the snapshot record format and the replay fold since the port, but no driver ever touched a file — snapshot_path was a silent no-op and rejoin_after_leave was inert. The driver now owns the file end to end: a SnapshotOptions constructor argument names the path (and compaction threshold), the Serf constructor reads and decodes it so a corrupt record fails construction loudly (SerfError::SnapshotOpen; a truncated tail from a crash mid-append is tolerated and repaired to a whole-record boundary), the transport body replays the records — honoring rejoin_after_leave at the Leave marker — and hands the result to Endpoint::load_snapshot, which recovers the clock floors and re-dials the recovered peers through the normal push/pull machinery. The pump appends at its event chokepoint: Alive/NotAlive per surfaced member event, the advancing clock floors, and the Leave marker on LeftCluster; appends are buffered and flushed per batch, and past the threshold the file is compacted to the live alive-set + clocks via a sibling temp file and an atomic rename. Coverage: five snapshotter units (reopen replay, the leave gate both ways, torn-tail tolerance + repair, corrupt-middle refusal, compaction identity) and two e2e scenarios on both runtimes — an abruptly-killed node reboots from its snapshot and rejoins with NO join call, and a cleanly-left node stays solo on the default posture but recovers its membership when rejoin_after_leave opts in.
…tion Additive changes ride proto3 field semantics (new optional fields default on old nodes; unknown message tags are consumed and dropped); breaking changes ship as a new cluster generation fenced by the cluster label, cut over blue/green; the delegate surface is compile-time API versioned by semver. No in-band version negotiation exists by design — the stance that replaces the legacy protocol_version and delegate_version knobs.
…ence Compaction preserved only the clocks and live alive-set, silently erasing a clean-leave marker: a churned or low-threshold file rewritten after leave() would auto-rejoin on the default posture. The writer now tracks clean-left state (set by the Leave append, cleared by membership activity, recovered from the tail on reopen) and re-emits the marker LAST in the compacted file, which therefore replays exactly like the sequence it replaces under both rejoin postures — pinned by a threshold-1 compaction test both ways, plus the post-leave-activity case. The compacted replacement is now written, synced, and OPENED before the rename, so no fallible operation remains after the swap — a rename that succeeded can no longer pair with a failed reopen leaving the writer on the unlinked inode. (Also fixes a bug the rework surfaced: truncate+append is a rejected OpenOptions combination, so the previous chain silently fell to the grown-file branch.) The keyring file's replacing temp inode is born owner-only (0600 on Unix), so a rotation can never widen a restrictive mode under a permissive umask — pinned by a Unix permissions test. And keyring_updated no longer touches storage inline on the pump: rotations hand off to a dedicated persistence thread through an unbounded channel (rare, small payloads), keeping the callback non-blocking as the delegate contract requires. The merge-veto docs now state the boundary plainly: a push/pull filter, not admission control — a rejected peer can still enter through gossiped Alives, exactly as in the reference implementation.
…ponses, leave-last snapshot order Keyring persistence: each rotation writes through an exclusively-created (create_new), owner-only, OS-entropy-named sibling temp — key bytes can no longer land in a pre-existing inode or behind a planted symlink — and construction sweeps the legacy fixed-name temp and abandoned temps. keyring_updated now returns a persistence acknowledgement: the pumps park a rotated op's key response until it resolves, folding a failure into the response (result = false carrying the error) exactly as the reference implementation folds a keyring-file write error, bounded by the requester's response deadline, with the re-poll cadence folded into the idle-arm timer target. FileKeyringDelegate acknowledges from its persistence worker, and dropping the delegate joins that worker after it drains, so a shutdown cannot discard a rotation the wire already carries. Snapshot: both pumps append the clock floors BEFORE the leave marker, so a clean shutdown ends the file at the Leave record — the terminal shape compaction preserves and replay expects (a trailing clock record would resurrect floors the default no-rejoin posture zeroes). The snapshotter pins original-versus-compacted replay equivalence under both postures and the leave-gate e2e asserts the on-disk tail shape. serf-embedded: the key-management ops pass relay_factor through (the plain forms pin 0, _with variants expose it), restoring the encryption-feature build the widened endpoint signatures had broken.
…tion; make the rename durable A destination whose own extension is tmp is its with_extension image, so the construction sweep was deleting the persisted keyring itself — and under the previous implementation such a destination was written in place, so the file can hold the only copy of legitimate key material. The sweep now skips the legacy path when it equals the destination, and the random-suffix matcher accepts only the exact .name.16-hex.tmp shape so an operator's own sibling files are never this delegate's to delete. A completed rename is not crash-durable until the directory entry is: the acknowledgement — which releases a successful key response to the cluster — now follows a sync of the containing directory, so a crash after the response cannot revert the node to the old ring while peers believe the rotation durable. On non-Unix platforms std cannot open a directory handle; durability is left to the filesystem's metadata journaling there. A rotation into a directory that cannot be synced acknowledges failure. KeyringPersistence is must_use so a future call site cannot silently drop the acknowledgement.
…Delegate Unix-only The legacy-sweep guard compared paths lexically, so a destination named with an uppercase TMP extension differed from its lowercase with_extension image while aliasing the same file on the case-insensitive filesystems that are the default on macOS and Windows — construction could still delete the only persisted keyring. The guard now treats any ASCII case of the tmp extension as aliasing the destination and skips the sweep; the regression rotates into a .TMP-named destination and proves load returns it after reconstruction. The acknowledgement contract is rename durability, and no safe standard API can flush a directory entry on Windows — rather than acknowledge a rotation a power loss could revert, the turnkey delegate is now compile-time Unix-only (the trait and acknowledgement types stay portable; a Windows application implements KeyringDelegate with a platform-durable strategy). The file-backed rotation e2e is gated accordingly.
… names A symlinked destination defeats every name-based alias guard: a configured ring.current pointing at a file that lives at the legacy temp name has an extension that never case-folds to tmp, so the sweep unlinked the symlink's target — the only persisted keyring — and left the destination dangling. Every sweep candidate (the legacy fixed name and the random-suffix temps alike) is now gated on resolved device/inode identity against the destination: a candidate reaching the destination's storage — through lexical identity, a case-folding filesystem, or a symlink on either side — is never touched, a dangling link or a provably distinct file is swept, and unresolvable identity conservatively skips. This subsumes the extension case-fold guard and restores hygiene for the mixed-case corner it over-skipped. The regression seeds a ring at the legacy-named path, links the configured destination to it, and proves construction-then-load preserves the keyring.
Sequential identity observations race a concurrent rotation: for a candidate whose NAME can itself name the destination — lexically, or equal under the ASCII case folding that aliases names on case-insensitive filesystems — a rename landing between the two metadata reads makes the identities differ and authorizes unlinking the freshly persisted keyring. remove_file unlinks a name, so for those shapes no point-in-time identity observation can ever make the unlink safe: they are refused unconditionally, before any filesystem access. The identity gate remains for genuinely distinct names, where the unlink cannot remove the destination's entry and resolved device/inode still guards the symlink and hard-link shapes. The regression widens the observation gap through a test seam, lands an atomic replacement mid-check exactly as a rotation would, and proves construction never unlinks the destination.
…n extensions ASCII case folding is not the complete filename-alias relation on every supported filesystem: case-insensitive HFS+ ignores certain Unicode scalars when comparing names, so a destination extension carrying one — t<ZWJ>mp, say — aliases the generated legacy image while passing both name guards, reopening the mid-check deletion race there. The legacy image differs from the destination only in its extension, so the sweep now runs only when that extension is provably distinct from tmp: pure ASCII and not ASCII-case-folding to tmp, or absent entirely (the image then appends four non-ignorable characters no folding can absorb); any non-ASCII scalar conservatively refuses. Random-suffix candidates need no classifier — their names carry a dot prefix and 16-hex infix the destination's name does not, an excess of non-ignorable ASCII no alias relation can erase. The classifier regression covers the tmp case folds, an HFS+-ignorable scalar, and a non-ASCII extension; the behavioral regression proves an unprovable extension keeps its legacy sibling on every filesystem — exactly the file that IS the persisted keyring where the alias folds.
…ke-driven farewell completion The secret-key codec re-export rode a cipher-only cfg while the bridge module it re-exports from needs a transport: a coordinates-plus-cipher build dangled. The gate now mirrors the module's full compound condition. The QEMU firmware harness is workspace-excluded with its own lockfile, which still pinned memberlist from before the farewell-compound seam — farewell_capacity did not exist there. Re-pinned to current main. The farewell epoch test demanded send completion on the single poll after eligibility, but a fresh socket's writable readiness may not have reached the reactor yet on a loaded runner — the send legitimately returns Pending and production advances on the registered writable wake. The completion half now drives bounded wake iterations; the epoch-gate assertions are unchanged.
The first full-matrix coverage run showed the fold work exercised almost entirely through the tcp suite: the quic pump's snapshotter, key-response parking, and replay threading — and the tls constructor's snapshot threading — had no end-to-end coverage at all. Mirror the leave-gate snapshot scenario (including the on-disk tail-shape assertion) onto the quic driver, add a quic file-backed rotation proving the parked response routes within the query window with the key already durable, and run the leave-gate scenario over tls. Unit coverage for the remaining dark arms: every settle outcome of a parked key response (pending, persisted, failed, worker-vanished), the nameless-destination input error, the snapshot coordinate builders and accessors, and the quic endpoint's operator forwarders (health score, merge-delegate install, coordinate resets).
…out of serf-reactor The file snapshotter and the keyring-file persistence engine are pure std file mechanics with no runtime coupling, and the compio driver needs both for operator parity — one shared implementation beats a fork of the hardened logic (exclusive owner-only temps, the identity-gated stale-temp sweep, the directory-synced rename the acknowledgement waits on, the compaction clean-leave gate). They now live in serf-driver alongside the shared key-management apply logic, together with the KeyringPersistence/KeyringPersistRx/KeyringPersistError acknowledgement contract; the snapshotter opens from a bare path plus threshold so it carries no per-crate options type. serf-reactor keeps its public surface through re-exports, with FileKeyringDelegate now a thin wrapper that turns the engine's acknowledgement into KeyringPersistence::Pending. The engine and snapshotter test suites move with the code; the reactor keeps delegate-level round-trip, drop-flush, and first-boot tests.
… machine's merge predicate keyring_updated now returns the shared KeyringPersistence acknowledgement (default Durable, so observation-only delegates are unchanged): a rotated op's key response is parked until the acknowledgement resolves and a persistence failure folds into the response exactly as the reference implementation folds a keyring-file write error, bounded by the requester's response deadline. Both pumps reap parked responses ahead of every output drain (so a sent response flushes in the same pass) and fold the re-poll cadence into their timer targets only while something is parked; the settle logic itself is the serf-driver implementation the reactor already runs. FileKeyringDelegate arrives as a thin wrapper over the shared engine, Unix-only per its durability contract. The async MergeDelegate is retired: it had no call site — the machine merges push/pull state before any event surfaces, so a driver-side veto can never run — and its replacement is the machine's own synchronous push/pull filter, re-exported with docs stating exactly what it bounds (one exchange's bulk admission, not durable exclusion).
Serf::new gains the two remaining construction hooks: merge_delegate installs the machine's synchronous push/pull filter into the endpoint each transport builds, and snapshot opens the shared file snapshotter BEFORE any socket binds — a corrupt snapshot refuses construction loudly — with the decoded records riding the runtime bundle into T::run, which replays them under the serf option rejoin_after_leave and hands the writer to the pump. Both pump loops append at their event chokepoint: member records, then the advancing clock floors, with the clean-leave marker written last so a clean shutdown ends the file at the Leave record (the terminal shape compaction preserves and replay expects), flushing — and compacting past the threshold — before the drain returns. SnapshotOptions joins the driver options (constructor argument, not a runtime knob) and the snapshot-open failure surfaces as SerfError::SnapshotOpen.
Both pumps now attach the driver-side live readings when publishing the membership snapshot — health score, broadcast queue depth, the encryption flag, and (under the coordinates feature) the local Vivaldi coordinate and reset count — and the handle reads them lock-free: stats(), encryption_enabled(), health_score(), and coordinate(). cached_coordinate rides a read-only command answered from the endpoint's coordinate cache in every lifecycle state, with the shutdown drain failing a parked probe like any other queued command.
Mirror the reactor's scenario classes onto the compio driver: the snapshot leave-gate (on-disk tail ends at the Leave record; the default posture starts fresh on restart while the opt-in posture rejoins), the recording merge predicate consulted on a join push/pull with the joining peer's state, the operator aggregate and coordinate surfaces on the handle, and a file-backed rotation whose parked response is still collected within the query window with the key already durable when it arrives.
…ures The hoist moved the persistence-failure warnings — snapshot append, flush, compaction, and keyring-write — behind serf-driver's tracing feature, which neither runtime crate's tracing feature forwarded: an operator enabling tracing on serf-reactor or serf-compio would have compiled none of the only diagnostics for silently dropped snapshot records or unpersisted rotations. Both manifests forward it now, and each runtime carries a wiring test asserting serf-driver's TRACING_WIRED probe so a dropped forward fails loudly instead of silencing telemetry.
The runtime crates' all-targets clippy gates deny assertions on constants, which the wiring tests' runtime asserts tripped. The pin now const-evaluates inside each test, which both satisfies the lint and strengthens the check: a dropped serf-driver/tracing forward fails every tracing-featured compile of the test target outright rather than waiting for the test run.
…ve, rejoin, join-cancel (#85)
…update, query filter (#87)
…e ports + a test-only MessageDropper (#91)
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #92 +/- ##
===========================================
+ Coverage 70.10% 95.19% +25.08%
===========================================
Files 42 94 +52
Lines 5402 18899 +13497
===========================================
+ Hits 3787 17990 +14203
+ Misses 1615 909 -706
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
main's only divergence is dependabot CI-action bumps (actions/checkout 6→7, actions/cache 5→6, codecov-action 6→7). Bumps to workflows this branch still carries merge cleanly; the bumps to the four pre-Sans-I/O workflows this branch replaced with the per-driver ones — ci.yml, coverage.yml, net.yml, fuzz.yml — resolve to the deletion, since those workflows are gone here and the new ones already run on the same action versions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.