* dream(swarm): #3242 fix AgentPool health checks that could never detect a dead agent
performHealthChecks() stamped lastHeartbeat = now on every "still healthy"
tick, and updateAgentHeartbeat() (the only other legitimate way to advance
that field) has zero callers anywhere in v3/ — so the check's own tick was
the sole thing keeping every pooled agent's heartbeat fresh, meaning
timeSinceLastActivity could never exceed one health-check interval and
replaceUnhealthyAgent() could never fire for any agent, in any pool, ever.
Fixes the self-stamp; matches the correct pattern already implemented in
UnifiedCoordinator.checkHeartbeats() in the same package. New deterministic
test file (fake timers): baseline fails 2/3 tests, candidate passes 3/3,
223/223 full package suite, tsc clean.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01TAzfKzdv9uRVjKt5TeBgkv
* dream(swarm): fold in adversarial-critique caveats, re-verify, ACCEPT-scoped
Independent critic (separate context) confirmed the fix and re-derived
everything itself (zero-callers grep, full suite, tsc, stash-isolated
baseline/candidate). Two disclosed, non-blocking caveats folded into the
gist: (1) @claude-flow/swarm is not currently a dependency of the shipped
@claude-flow/cli, so tonight's fix is real within the swarm package's own
UnifiedSwarmCoordinator/tests but has no reachable path through the actual
CLI today; (2) health still optimistically recovers +0.1/tick inside the
grace window even with zero real heartbeats (pre-existing, out-of-scope,
mirrors UnifiedSwarmCoordinator's own no-tick-recovery design). Also fixes
a cosmetic class-name typo in the source comment (UnifiedCoordinator ->
UnifiedSwarmCoordinator). Re-ran full suite (223/223) and tsc (clean) after
the comment edit; recomputed the witness stamp over final content.
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01TAzfKzdv9uRVjKt5TeBgkv
* dream(swarm): update LEDGER.md for #3242/#3243
Records tonight's row plus verified GitHub state for the trailing nights
(MCP tools, not inferred) and the 2026-09-08 gap note (same
ledger-rows-only-reach-main-via-merge structural issue tracked since
2026-08-19).
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01TAzfKzdv9uRVjKt5TeBgkv
---------
Co-authored-by: Claude <noreply@anthropic.com>
* dream(swarm): recover 5 missing live ledger rows (2026-08-14..18)
The v2 live-entries section of docs/dream-cycle/LEDGER.md was never
appended to for 5 consecutive nights despite each night running to
completion (branch + draft PR + issue verified to exist for all 5 via
git ls-remote and GitHub search before concluding this, per the ledger's
own anti-inference rule). Backfilled directly from the PRs/issues.
Root cause of the append gap not diagnosed tonight; flagged in the
gist/issue as a candidate finding for a future automation/meta scan.
* dream(swarm): bound MessageBus retry attempts, fix unreachable message.failed
Given a subscriber whose callback always throws, MessageBus.handleDeliveryError()
incremented the queue entry's attempts counter but addToQueue() always
constructed a fresh entry with attempts:0 on re-queue, discarding it. Retries
were unbounded (96-98 invocations / 0 failures observed in 500ms, git-stash-
verified pre-fix) instead of capped at config.retryAttempts, and the
message.failed branch was unreachable.
Fix: addToQueue() takes an optional existingAttempts parameter; the retry
path in handleDeliveryError() now threads entry.attempts through instead of
resetting it. Fresh-message call sites are unaffected (default 0).
Candidate evaluated: exactly 3 invocations / 2 retries / 1 failure, stable
thereafter. 220/220 tests passing (2 new, 0 regressions). Independently
re-verified by an adversarial critic (fresh session, no authoring context):
CONFIRMED-WITH-CAVEATS — fix and scope match the claim; flagged (and now
disclosed in-code) a separate pre-existing, unfixed broadcast-retry queue-key
bug this diff does not touch.
Full research/evaluation writeup: docs/dream-cycle/dream-gist-2026-08-19.md
* dream(swarm): append 2026-08-19 ledger row
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(security): ADR-165 Phase 1 — close all critical CVEs, refresh registry, add CI gate
Closes ADR-165 Phase 1. Both workspaces pass `npm audit --audit-level=critical`.
## Before (2026-06-29)
| Workspace | Critical | High | Moderate | Total |
|-----------|----------|------|----------|-------|
| Root | **1** | 6 | 31 | 38 |
| v3 | **4** | 33 | 57 | 97 |
## After
| Workspace | Critical | High | Moderate | Total |
|-----------|----------|------|----------|-------|
| Root | **0** | 0 | 31 | 31 |
| v3 | **0** | 27 | 58 | 88 |
All 5 critical advisories closed (vitest GHSA-5xrq, handlebars prototype-pollution,
protobufjs constructor-pollution, plus 2 more in v3). 12 of 39 high advisories
closed via overrides; the remaining 27 v3 highs are deeper transitive chains
that would require breaking-change major bumps — flagged for ADR-165 Phase 2.
## Changes
### Root workspace overrides (package.json)
- `vitest`: `^1.0.0` → `^3.2.6` (closes critical GHSA-5xrq CVSS 9.8)
- `hono`: `>=4.11.4` → `>=4.12.25` (closes 6 hono advisories)
- `undici`: `>=7.18.0` → `>=8.5.0` (closes 7 undici advisories — bumps the
stale pin that ADR-165 flagged as still-in-vulnerable-range)
- `vite`: `>=6.4.6` → `>=8.0.16` (closes 2 vite advisories)
- NEW `@grpc/grpc-js`: `>=1.14.4`
- NEW `form-data`: `>=4.0.6`
- NEW `http-proxy-middleware`: `>=3.0.7`
- `@hono/node-server`: `>=1.19.10` → `>=1.19.14`
### v3 workspace overrides (v3/package.json + pnpm.overrides)
- `vitest`: `^4.0.16` → `^4.1.0` (and `@vitest/coverage-v8` matched)
- Top-level + pnpm overrides: `handlebars: >=4.7.9`, `protobufjs: >=8.6.0`
- 27 sub-package `package.json` updated to `vitest ^4.1.0`
### CVE registry refresh (v3/@claude-flow/security/src/CVE-REMEDIATION.ts)
Rewritten from 5 stale Jan-2026 entries to 16 total: 15 fixed, 1 open
(ADR165-OPEN-01 — PII pipeline wiring per ADR-164 §6.1 not yet on
dispatch-layer). `SECURITY_SUMMARY` is now computed dynamically from the
registry; `validateRemediation()` correctly returns `allFixed: false`
with 1 issue. ADR-165 §5 flagged this as THE Phase 2 priority — now closed.
### New CI gate
`.github/workflows/cve-audit.yml`:
- `audit-root` — `npm audit --audit-level=critical` in root, blocking
- `audit-v3` — same in v3, blocking
- `audit-high-report` — warn-only summary on high-severity drift
- Triggers: PR, push to main, daily cron
### ADR-165 §9 evidence ledger addendum
Captures AFTER-remediation `npm audit --json` metadata for both workspaces.
The diff between BEFORE (already in §9) and AFTER makes Phase 1 verifiable.
Also CORRECTED one prior surprise in §9: the `validate-input.ts` in
`mcp-tools/` is NOT a 9-line shim. It's 269 lines of real implementation.
(That was an inaccurate findings note from the initial audit.)
### §7.3 research items resolved
- `protobufjs` v3 chain confirmed gone after override pins
- `handlebars` v3 chain confirmed not user-reachable on the workflow path
(toolchain-only; override sufficient)
- `validate-input.ts` confirmed real implementation, not a shim (registry
entry corrected)
- CORS surface check: no `cors()` / `app.use(cors` call sites — the hono
CORS-wildcard advisory is not exploitable on the current server
- ADR-164 §6.1 PII pipeline NOT wired in current agentbbs dispatch —
recorded as ADR165-OPEN-01 (Phase 3 work)
## Gates (all PASS)
- `npm audit --audit-level=critical` root: exit 0 (was non-zero)
- `npm audit --audit-level=critical` v3: exit 0 (was non-zero)
- vitest suites: 105/109 pass (4 skip-conditional — same as before)
- ADR-112 audit: 352/352 tools with guidance
- business-pods smoke: 11/11 PASS
- agentbbs smoke: 8/8 PASS
- agenticow smoke: 8/8 PASS
- cli build: clean (tsc)
## Phase 2+ deferred
The 27 v3 high-severity advisories that remain are transitive chains with
no patched-in-range version. ADR-165 §6 Phase 2 picks these up after the
`cve-watch.yml` automation in §6 lands (NEW PR — not in scope here).
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01WbBa4nccx5aGhXphoHxcni
* chore(lock): regen v3/pnpm-lock.yaml after override updates (PR #2508 CI fix)
PR #2508 added handlebars + protobufjs to v3 overrides + bumped vitest pin,
but v3/pnpm-lock.yaml wasn't regenerated. CI surfaced
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on every install step.
Same pattern as the previous lockfile-drift fixes on PR #2500 and #2503.
* fix(ci): cve-audit.yml jq-only + regen witness manifests for #1609 marker drift
Two PR #2508 CI failures fixed:
1. Static-regression-guard tripped on cve-audit.yml line 52: the static
YAML strict-parser sees `if high > 0:` inside the `run: |` block as
a YAML block-mapping key indicator. Refactored all 3 inline-python
blocks to use jq (preinstalled on ubuntu-latest) — no embedded
`if x > 0:` lines, so the YAML guard passes.
2. Witness marker drift smoke flagged fix-marker #1609 — the Phase 1
vitest bump (4.0.16 → 4.1.0) drifted the cited marker string in
v3/@claude-flow/aidefence/package.json. Updated `marker` field in
all 3 platform manifests + `desc` to reference ADR-165 Phase 1, then
re-signed via plugins/ruflo-core/scripts/witness/regen.mjs.
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 1 — pluggable ConsensusTransport + Ed25519 message signing
First concrete piece of ADR-095 G2 (hive-mind multi-process). The
consensus protocols (raft/byzantine/gossip in @claude-flow/swarm) used a
local EventEmitter for *everything* — both observability events
("leader.elected", "consensus.achieved") AND inter-node messages
(append-entries, vote requests, pre-prepare/prepare/commit). The latter
never crossed a process boundary: a node "sent" a message by emitting it
locally and synthesizing the peer's reply inline. That's the single-
process limitation #G2 names.
## What lands
`v3/@claude-flow/swarm/src/consensus/transport.ts`:
- `ConsensusTransport` interface — `send` (request-response), `broadcast`,
`onMessage`, `peers`, `close`. Separates the inter-node-message
dimension from the observability-event dimension (events stay on each
protocol's own EventEmitter).
- `LocalTransport` + `LocalTransportRegistry` — in-process registry,
the default. Matches current single-process behavior so nothing breaks.
Supports optional Ed25519 signing + per-sender monotonic-seq replay
defense when keypairs are configured.
- Real Ed25519 helpers via Node's built-in `crypto` (no new deps):
`generateNodeKeyPair`, `signMessage`, `verifyMessage`,
`canonicalizeForSigning` (deep-sorted-key JSON — deterministic across
hosts), `messageDigest`.
- Exported from `consensus/index.ts`.
## CI guard (the user's ask: "include ci guards for new capabilities")
`plugins/ruflo-core/scripts/test-consensus-transport.mjs`, wired as a step
in the `mcp-roundtrip-smoke` job:
- Static: required exports present in swarm dist; **no `return true`
signature-stub regression** (the original #G2 bug — `verifySignature()`
returned true unconditionally).
- Behavioral: `LocalTransport` round-trip (send → peer handler → reply);
Ed25519 verify is real (true for right key; false for wrong key /
tampered payload / missing sig). 30s watchdog so CI never hangs.
## Tests
14 new tests in `__tests__/consensus-transport.test.ts` (sign/verify
round-trip, tampered-payload rejection, wrong-key rejection, fail-closed
on missing sig, canonicalization stability, LocalTransport send/broadcast/
peers/timeout/closed-peer, signed-transport round-trip, unsigned-rejected,
seq monotonicity). 186/186 swarm suite passes. Build clean.
## Remaining for G2 (subsequent commits)
- FederationTransport adapter (ADR-104 WS wire)
- Wire raft/byzantine/gossip to accept an injected ConsensusTransport
- Replace byzantine-coordinator verifySignature()→true with real verifyMessage
- Per-strategy correctness: BFT f<n/3 vote counting; Raft term/log replication
- Failure-injection tests
ADR-095 G2 status section updated.
Tracks #1872
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 2 — wire ByzantineConsensus to ConsensusTransport + sha256 digests
Second piece of ADR-095 G2. ByzantineConsensus now accepts an optional
`transport: ConsensusTransport` in its config:
- When set, PBFT messages (pre-prepare / prepare / commit) actually go
over the transport (signed if the transport has a keypair) instead of
being `emit`ted into the void. Inbound transport messages are routed
back into `handlePrePrepare` / `handlePrepare` / `handleCommit`.
- When unset, behavior is unchanged: messages emit as `message.broadcast`
/ `message.sent` events for an external relay layer (the legacy
single-process path). The `emit`s also stay when a transport IS wired,
so observers don't break.
- Transport broadcast failures are non-fatal at the protocol layer — the
proposal simply times out, which is the correct failure mode (lose
liveness, never commit incorrectly).
Also: `computeDigest` was a 32-bit string-hash "for demonstration" —
replaced with sha256. PBFT relies on the digest uniquely identifying the
request being agreed on; a 32-bit hash has trivial collisions. Removed
the toy hash entirely (no test depended on it).
Tests: 3 new in `__tests__/byzantine-transport.test.ts` — emit-only when
no transport (legacy unchanged); broadcasts go over transport AND emit
when wired (and digests are 64-hex sha256); inbound transport messages
route into the PBFT handlers (a pre-prepare in → a prepare out over the
transport). 189/189 swarm suite passes. consensus-transport CI guard
green. Build clean.
Remaining for G2: same wiring for raft.ts + gossip.ts; FederationTransport
adapter; per-strategy correctness (BFT f<n/3 vote counting — the current
`2f+1` thresholds are right but a single node fabricates the quorum
locally; Raft term/log replication); failure-injection tests.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 3 — wire RaftConsensus to ConsensusTransport with real receiver rules
Third piece of ADR-095 G2. RaftConsensus now accepts an optional
`transport: ConsensusTransport`:
- Sender side: `requestVote(peerId)` / `appendEntries(peerId, entries)`
go over the transport as real request-response RPCs (RequestVote /
AppendEntries) instead of mutating the local fake-peer map. On a
higher-term reply, the node steps down (§5.1).
- Receiver side (`handleInboundRaftMessage`): proper Raft rules —
• RequestVote: grant iff `term >= currentTerm` AND (haven't voted this
term OR voted for this candidate) AND candidate's log is at least as
up-to-date (lastLogTerm/lastLogIndex comparison). Adopts a higher
term, resets the election timer on grant.
• AppendEntries: reject if `term < currentTerm`; else become follower,
reset the election timer, append entries by index, advance
`commitIndex` to `min(leaderCommit, lastLogIndex)`.
- Legacy no-transport path unchanged (single-process in-memory peer map).
This is the "Raft term/log replication correctness" piece from the ADR.
(Full prevLogIndex/prevLogTerm conflict resolution is the next refinement;
the current append-by-index is correct for the heartbeat + sequential-
append cases the consensus engine actually exercises.)
Tests: 5 new in `__tests__/raft-transport.test.ts` — vote granted for an
up-to-date candidate; second different candidate denied (vote-once-per-
term); stale-term candidate denied; behind-log candidate denied; valid
AppendEntries accepted + commitIndex updated; stale-term AppendEntries
rejected with the follower's term reported. 194/194 swarm suite passes.
Build clean. consensus-transport CI guard green.
Remaining for G2: same wiring for gossip.ts; FederationTransport adapter
(ADR-104 WS wire); BFT f<n/3 vote-counting correctness; failure-injection
tests; cross-host validation; build/publish.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 4 — FederationTransport (ConsensusTransport over the ADR-104 WS wire)
Fourth piece of ADR-095 G2 — the multi-host transport option.
`v3/@claude-flow/swarm/src/consensus/federation-transport.ts`:
- `FederationTransport implements ConsensusTransport` — backed by the
federation plugin's ADR-104 WS wire (agentic-flow/transport/loader).
- Structural dependency, not a package dep: takes an
`AgenticFlowTransportLike` instance by injection (the `loadQuicTransport`
result satisfies it) plus consensus-level metadata (this node's id, how
to address peers, who the peers are). @claude-flow/swarm stays zero-dep;
the wiring layer (hive-mind + federation) supplies the transport.
- Layers request-response on top of fire-and-forget WS delivery via
per-message correlation ids + a pending-replies map + per-call timeout.
Uses ADR-104 stream-mux (`streamId='ruflo-consensus'`) so consensus
traffic doesn't interleave with application traffic.
- Optional Ed25519 signing — outbound signed, inbound verified against the
sender's published pubkey (resolvePeerPublicKey), per-sender monotonic-
seq replay defense. Fail-closed: unverifiable inbound is dropped.
- Exported from `consensus/index.ts` (+ `AgenticFlowTransportLike` /
`FederationTransportOptions` types).
Tests: 7 new in `__tests__/federation-transport.test.ts` using a mock
in-memory mesh — send→handler→reply correlation; broadcast to all peers;
no-address rejection; send timeout; peers() excludes self; signed
round-trip (seq increments, signature present); unsigned inbound dropped
at a signing-enabled node (sender's send times out, handler never runs).
201/201 swarm suite passes. Build clean. consensus-transport CI guard
extended to assert FederationTransport is exported — green.
Remaining for G2: wire gossip.ts to the transport; BFT f<n/3 vote-
counting correctness (real vote aggregation across distinct senders, not
a locally-fabricated quorum); failure-injection tests; cross-host
validation (mac↔ruvultra over tailscale, root); build/publish; then merge.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* feat(swarm): ADR-095 G2 step 5 — wire GossipConsensus to transport + BFT f-from-cluster-size
Fifth piece of ADR-095 G2 — completes the protocol-wiring trio and adds
the BFT fault-tolerance correctness fix.
## GossipConsensus transport wiring
- `GossipConfig.transport?` — when set, `sendToNeighbor` actually sends
the gossip message over the transport (signed if signing is on) instead
of mutating the local fake-neighbor map. Inbound gossip routes into the
merge logic via `handleInboundGossipMessage`, deduped by message id.
- Legacy no-transport path preserved.
## BFT fault tolerance correctness
- `f` (`maxFaultyNodes`) was hardcoded to 1 in the config default, so a
7- or 10-node cluster ran with the same `2f+1=3` quorum as a 4-node one
— wrong. Now `byzantineF()` derives `f = floor((n-1)/3)` from the actual
cluster size (n = self + known peers), clamped to ≥1. `config.maxFaultyNodes`,
when set, acts as an *upper cap* (never exceed what the operator declared
the cluster can tolerate). The three `2f+1` quorum checks (vote /
handlePrepare / handleCommit) now use the cluster-derived `f`.
- Dropped the `?? 1` config default so an operator who doesn't pass
`maxFaultyNodes` gets the cluster-derived behavior, not a silent cap of 1.
## Tests
- 3 new gossip-transport tests (messages go over transport with hops/path
incremented; inbound deduped by id; non-gossip ignored).
- 5 new byzantine-f tests (f scales 4→1, 7→2, 10→3; explicit cap honored;
no-cap follows cluster size; 3-node clamps to 1).
- 210/210 swarm suite passes. Build clean. consensus-transport CI guard green.
ADR-095 G2 status section updated — all three protocols now wired,
FederationTransport landed, Ed25519 sig verify real, BFT correctness done,
Raft receiver rules done. Remaining: failure-injection tests, cross-host
validation, merge.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
* test(swarm): ADR-095 G2 step 6 — failure-injection tests (f<n/3 BFT, f<n/2 Raft)
Sixth piece of ADR-095 G2 — the failure-injection coverage the ADR calls for.
## BFT — prepare/commit quorum under f-bounded faults
`injectQuorum()` builds one ByzantineConsensus with a configured cluster
size (so byzantineF() derives f = floor((n-1)/3)), feeds it a pre-prepare
(creates the proposal), then N distinct external prepares + M distinct
external commits, and reports whether `consensus.achieved` fired. Because
the node contributes 1 prepare + 1 commit of its own, 2f external messages
of each kind hit the 2f+1 quorum.
- 4-node (f=1): 2 ext prepares + 2 ext commits → reached; 1 ext commit → NOT
- 7-node (f=2): 4 + 4 → reached (quorum 5); 3 ext commits → NOT
- 10-node + maxFaultyNodes:1 cap → f stays 1, 2 ext commits suffice
## Raft — election under vote majority/minority
`tallyVotes()` drives a RequestVote round from a candidate to all voters
over LocalTransport; silenced voters never reply (their handler is a no-op)
so the candidate's `send` to them times out → no vote. Tallies grants
including the self-vote.
- 5-node, all reply → 5 votes (≥ majority 3) → wins
- 5-node, 2 silent → 3 votes (= majority) → wins
- 5-node, 3 silent → 2 votes (< majority) → loses
(The full multi-node PBFT cascade over the synchronous LocalTransport is
order-sensitive and flaky as an integration test — the direct
message-injection form above tests the threshold property robustly. A
real-WS cross-host cascade test belongs in the federation cross-host
validation step, not the unit suite.)
218/218 swarm suite passes. Build clean.
This completes the per-strategy correctness + failure-injection items.
Remaining for G2: cross-host validation (mac↔ruvultra over tailscale,
root); merge PR #1905.
Tracks #1872 · branch feat/adr-095-g2-hive-mind-ws-transport
Co-Authored-By: RuFlo <ruv@ruv.net>
---------
Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
Automatic checkpoint created by Claude Code
- Branch: v3
- Timestamp: 2026-01-07T19:04:42Z
- Changes: 19 file(s)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Updated standard-checkpoint-hooks.sh to accept both JSON and plain paths
- Fixed pre-edit and post-edit functions to detect input format
- Fixed v3 CLI hook flags (-f -> --file, -c -> --command, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>