Commit Graph

7438 Commits

Author SHA1 Message Date
ruv 0cbdaa9597 docs(x-gateway): GitHub-style discovery metadata on GET / (rebased onto 0.5.0)
Rebased #3270 onto the live 0.5.0 gateway (was based on 0.4.0; prod moved to
0.5.0 via the channels/seraphina PRs). Scoped to the root `GET /` service-info
JSON — the "x.ruv.io metadata" rUv asked to make self-explaining — since 0.5.0
already taught the registry resource + docs about channels (#3263).

- server.mjs `GET /`: add tagline/description/model/quickstart/channels{public,
  private,isolation}/invites/cli/docs (additive — canonicalRelay, gatewayPubkey,
  relay, authNote, resources all preserved for NIP-42). 0.5.0 -> 0.5.1.
- gateway.test.mjs: assert the new discovery metadata + canonicalRelay/resources
  preserved; version 0.5.1. 16/16 pass.
- CLI syntax matches the shipped surface (federation channel --action create/grant,
  federation invite --uses --ttl). Honest ADR-386 boundary stated (private channels
  isolate content, not membership).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-09-10 12:06:06 -04:00
rUv a64f8b1ad8 feat(x-gateway): declare default channels so a quiet one is still discoverable (#3274)
channel_list reports what has been published to recently. That is right for
finding activity and wrong for onboarding: a channel nobody posted in today does
not exist as far as a newcomer can tell, so everyone starts in the flat firehose
and the channels stay empty. The emptiness was self-sustaining.

Four declared channels, always listed with their purpose even at zero messages:
announce for releases and status, help for anyone stuck, claims so cross-host
ownership has one home, showcase for what people built. Kept deliberately small.
A directory of plausible-sounding empty rooms costs a newcomer more attention
than no directory, because it makes them guess which ones are alive; the listing
now sorts active channels first and marks the declared ones.

They are also published in ruv://federation/registry, so the set is discoverable
without calling a tool, and named in channel_list's own description.

Second fix in the same area: a malformed `c` tag value was being reported as a
channel. My own tag probe left `ruflo-probe-c` sitting in the directory looking
like a real room. Listings now drop anything that is not a well-formed pub:/prv:
id.

Verified live on 0.5.0 (revision ruflo-x-gateway-00010-nh4): all four appear,
seeded so each opens with something to read, probe traffic gone, 16/16 tests.


Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 15:32:01 +00:00
rUv cda8b04f6a chore(release): 3.41.1 — memory path identity and Seraphina truncation (#3273)
Two fixes, both found by someone hitting them rather than by us.

#3196: an explicit database path now selects that database. The bridge cached one
registry globally, so the first caller decided the file for the whole process and
every later caller's --path or CLAUDE_FLOW_DB_PATH was accepted and then silently
ignored. A CLI write and an MCP write landed in two files, both reporting success,
and neither interface could see the other's rows. The 'memory list' command also
stopped
printing a confident total for one store while rows sat unread in its sibling —
it now names the store it did not read. Reported downstream by pacphi/agentic-kit#213.

#3267: Seraphina no longer passes a truncated reasoning dump off as coordination
guidance. The routed model spent its whole token budget reasoning and never
emitted its JSON, and the empty result read as "the swarm needs nothing" — the
one conclusion a coordinator must never reach by accident.


Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
v3.41.1
2026-09-10 14:39:52 +00:00
rUv 2b40aee077 fix(seraphina): stop reporting a truncated reasoning dump as coordination guidance (#3267)
Seraphina returned HTTP 200 and a well-shaped object with zero proposals, zero
risks, and a `guidance` string that was the model thinking out loud mid-sentence.
Measured on the live gateway: output_tokens 2000, reasoning_tokens 2000,
stop_reason max_tokens. The tier had resolved to a reasoning model whose reasoning
tokens are billed against max_tokens, so the entire budget went to private
reasoning and the JSON answer was never emitted.

The dangerous part was not the truncation, it was that it looked like success. An
empty proposals array reads as "the swarm needs nothing", which is the one
conclusion a coordinator must never reach by accident.

Two changes. The budget goes to 8000 so there is room to answer after reasoning.
And extractJson now reports whether it actually parsed, so askSeraphina can return
an explicit `degraded` result naming the cause and a retry hint instead of passing
raw reasoning off as guidance.

Verified live before and after: before, stop_reason max_tokens with 0 proposals
and reasoning text as guidance; after, stop_reason end_turn on
anthropic/claude-sonnet-5 with 4 proposals, 4 risks and real guidance.
Gateway suite 14/14, including two new guards — one that a truncated reply is
marked degraded with empty guidance, one that a well-formed reply is untouched.
Deployed as ruflo-x-gateway-00009-qxb.


Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 14:15:20 +00:00
rUv e6f116b5c0 fix(memory): honour an explicit database path, and stop counting one store as if it were all of them (#3196) (#3271)
* fix(memory): honour an explicit database path, and stop counting one store as if it were all of them (#3196)

Two writes, both reporting success, landing in two files neither interface could
see whole. Reproduced on 3.41.0 in a clean directory:

  .swarm/memory.db            CLI-written key 1, MCP-written key 0
  .swarm/agentdb-memory.db    CLI-written key 0, MCP-written key 1

The cause is a cache, not a path calculation. getRegistry() kept one global
instance, so the first caller to touch the bridge decided the file for the whole
process; every later caller's dbPath was accepted and then silently ignored,
because the function returned the existing instance without ever comparing
paths. `--path` and CLAUDE_FLOW_DB_PATH have always advertised that they select
a database. Now they do: the cache is keyed by resolved path, so two paths in one
process are two registries, and a relative path and its absolute form are one.

The second half is worse than the split and is fixed separately. `memory list`
printed "Showing 1 of 1 entries" — not a warning, not a partial result, a
confident total describing one file as though it described the memory. A
downstream integrator measured 51 rows in one store and 16,671 in its sibling,
with 50 keys existing only in the smaller one. Silence would have been
recoverable; a wrong total is not, because nothing prompts anyone to look
further. The CLI now names the store it did not read, and how to read it.

Deliberately NOT done: merging the two files. #2786 gave AgentDB its own
plaintext file so native better-sqlite3 does not open an encrypted memory.db,
where it fails with "file is not a database" and silently disables the learning
system. Unifying the path would re-break encryption at rest. Both corpora stay,
and both stay readable.

The sibling count is read-only, opens nothing it cannot open read-only, and
returns null when the file is missing or unreadable — an unreadable store is not
evidence of an empty one, so the CLI stays silent rather than claim zero.

Verified against the original reproduction: the MCP-written rows are now
disclosed by name and count after the total. Three regression tests cover
sibling identity, path resolution, and silence on an unreadable store.

Reported-by: pacphi (pacphi/agentic-kit#213)
Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* fix(memory): make the bridge test seam override every path, not two guessed keys

Keying the registry cache by path broke #2968's fixture. That test installs a
fake registry through __setMemoryBridgeRegistryForTests and then calls
bridgeStoreEntry with its own temp dbPath, so an override installed under the
two keys the seam could guess was never consulted: the call resolved a third
path, missed the cache, and tried a real init instead of the fake.

A test seam means 'use this registry', not 'use it if the caller passes the path
I assumed'. It is now a separate override that getRegistry() returns first, kept
deliberately outside the path cache so the two concerns cannot collide again.

Verified: #2968 and the #3196 identity tests pass together, and the bridge dbPath,
durability, purge and row-visibility suites are unchanged.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 14:14:53 +00:00
ruv cb10bf617e chore(release): 3.41.0 — open swarm federation CLI/MCP, channels, Seraphina
@claude-flow/cli, claude-flow, ruflo -> 3.41.0 (lockstep).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_015y2UjPSd3VX1CYnPstZJ5D
v3.41.0
2026-09-10 07:59:09 -04:00
rUv 9576a1032b docs(federation): teach the skills and gateway docs about channels (#3263)
ADR-386 shipped the capability but the guidance still describes the old world:
the open-federation skill has no channels at all and never names the canonical
relay, and the gateway README and plugin manifest list a tool surface that is now
three tools short.

open-federation gains a Channels section with the two visibilities, the CLI verbs,
and the three things worth saying before anyone trusts a private channel: metadata
is not hidden, there is no revocation, and losing the key file loses the channel.
It also records the tag trap — buzz-relay enforces NIP-29 group membership on `h`,
so an h-tagged event publishes and then cannot be read back, which is why ruflo
channels use `c`.

claims gains a short section on scoping a claim stream to a channel, with the
caveat that matters: a claim nobody outside the channel can read cannot arbitrate
against one made outside it, so swarm-wide ownership stays on the open stream.

Verified: marketplace validation passes over all 39 plugins, the manifest audit
exits 0, and both skills still parse their frontmatter.


Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 04:06:41 +00:00
rUv 90bff10f15 feat(x-gateway): public and private swarm channels (ADR-386) (#3261)
Coordination on x.ruv.io was one flat topic: every member read every message,
and there was no way to keep a stream separate — or confidential — without
standing up another relay tenant.

Channels are one tag on the existing events, in two visibilities:

  public   c=pub:<name>      plaintext, any relay member reads it
  private  c=prv:<16 hex>    NIP-44 v2 ciphertext, k=enc hides the message type

The private channel key is generated client-side and cached at
~/.ruflo/channels.json (0600). Access is granted by sealing that key to a
member's pubkey over ECDH and publishing it as a ChannelGrant only they can
open. The gateway holds no channel keys and cannot decrypt — a hosted gateway
that encrypted on your behalf would be a custodian of every private channel on
the service, which is the thing worth not building.

Not hidden, and said so in the ADR: the relay sees that a channel exists, its
opaque id, who published and when. There is no revocation either; removing
someone means rotating the channel.

The channel tag is 'c', not the obvious 'h'. buzz-relay already implements
NIP-29-style server-side group membership on : an h-tagged event publishes
fine and the matching REQ comes back CLOSED "restricted: not a channel member",
so the author cannot read back their own message. Measured every other
single-letter tag against the live relay — c/d/g/l/m/r/x/y/z are all indexed and
unrestricted.

Surfaces: gateway channel_list / channel_sync / channel_publish (public only,
admin-gated) + ruv://swarm/channels; client x_federation_channel_{create,grant,
accept,publish,read,list} and 'ruflo federation channel --action ...'.

Verified: gateway 12/12, CLI 11/11, and a live two-key E2E on wss://relay.ruv.io
— grantee reads the plaintext, an outsider key cannot decrypt, the ciphertext
does not contain the secret, and the gateway's own channel_sync returns
encrypted:true. Deployed as ruflo-x-gateway-00008-s6n.


Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 03:53:36 +00:00
rUv 6f29819fe6 fix(metaharness): bump the darwin pin to 0.10.2 so pin-drift and clean-install go green on main (#3262)
main is red on two required gates: @metaharness/darwin is declared ~0.9.0 while
npm latest is 0.10.2, so check-metaharness-pins reports STALE. The drift predates
#3256 — it merged with these gates already failing — but it is on main now.

Checked the bump rather than assuming it: unpacked both tarballs and compared.
0.9.0 and 0.10.2 ship the same bin (metaharness-darwin), the same exports (. and
./gepa), and the same gepa symbols this repo imports (loadCand6Genome,
CAND6_GENOME_PATH). Nothing we call was removed.

MH_DARWIN_PIN moves with it, since the mechanical oracle shells out to
npx @metaharness/darwin@<pin> and an out-of-range pin is its own drift failure.
pnpm lockfile regenerated in the same commit — v3 is a separate pnpm workspace
and a stale lockfile takes the frozen-lockfile jobs down.

Verified: check-metaharness-pins exits 0 with every pin reported current.


Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 03:53:20 +00:00
rUv dbf450a927 feat(federation): ruflo CLI + MCP integration for x.ruv.io, and Seraphina (swarm queen) (#3256)
* feat(federation): ruflo CLI + MCP integration for x.ruv.io, and Seraphina (swarm queen)

Integrate the open swarm federation into ruflo itself, and add Seraphina —
a primary-coordinator / swarm-queen guidance tool in the style of the ruOS
assistant terminal (invoked from a terminal or any MCP client).

MCP tools (src/mcp-tools/x-federation-tools.ts), registered in the barrel and
the in-process registry so `callMCPTool` and the MCP server both see them:
  x_federation_sync / roster / claims / registry   (open reads)
  x_federation_publish / invite_mint / admit       (gateway-identity writes,
    require RUFLO_X_ADMIN_TOKEN; fail closed with no network call)
Each description follows ADR-112 ("Use when … wrong because …").

Seraphina (src/mcp-tools/seraphina-tools.ts): `seraphina_guidance { goal }`
gathers the live roster, claims board and recent messages from the x.ruv.io
gateway, compacts them (dedupe by from|type, cap 15 — a cheap tier drowns in
repeated PeerHellos), and asks the cognitum meta-llm gateway
(https://api.cognitum.one/v1/messages, model cognitum-auto by default with a
tier override) using a queen system prompt that treats message content as
data, respects one-owner-per-resource claims, and returns
{ guidance, proposals[], risks[] }. Proposals are advisory. JSON is extracted
by slicing the outermost object so fenced/prose-wrapped answers still parse.
Key from SERAPHINA_METALLM_KEY (GCP secret seraphina-metallm-api-key).

CLI: `ruflo federation sync|roster|claims|registry|invite|admit|publish`
(src/commands/federation.ts), thin over the same tools.

Skill: .agents/skills/open-federation/SKILL.md documents CLI, tools,
Seraphina, the claims rules, the canonical NIP-42 relay-tag gotcha, and
onboarding.

Tests: 11 (x-federation: RPC shaping, SSE parse, resource mapping, fail-closed
admin gating, isError surfacing; seraphina: registration, fail-closed key,
context gathering + queen prompt + x-api-key, tier override, fenced-JSON
extraction). Project typechecks at 0 errors.

Verified live: Seraphina against the real 3-node federation returned 3
structured proposals + 4 risks via cognitum-auto (routed to a cheap tier).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* fix(federation): ADR-125 env-var precedence — URL config takes a flag/arg, credentials registered env-only

The env-var-precedence audit correctly flagged the new process.env reads.
Config URLs now have a precedence path: `ruflo federation --gateway` feeds a
`gatewayUrl` tool arg (and Seraphina takes `metaLlmUrl`/`gatewayUrl`) that
wins over RUFLO_X_GATEWAY_URL / SERAPHINA_METALLM_URL, documented per ADR-125.
Credentials (RUFLO_X_ADMIN_TOKEN, SERAPHINA_METALLM_KEY) are registered as
env-only escape hatches with rationale: a secret must never be a CLI flag.
+1 test (arg precedence, arg not forwarded upstream). Audit passes locally.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* feat(federation): `ruflo federation join --code` — self-service invite access with your own key

The user-facing path into the open swarm. Decentralized by design: the user
generates/holds THEIR OWN Nostr key (~/.ruflo/nostr.key, 0600), redeems an
invite code with a NIP-98-signed claim directly against the relay (no admin in
the loop), proves membership via NIP-42, and can then publish as themselves.
The gateway never signs for a user.

- src/mcp-tools/x-federation-join.ts: x_federation_join { code, relayHttp?,
  relayWs?, keyFile? } — validates the code shape before any network call,
  loads/creates the key, NIP-98 claim, NIP-42 verify, returns pubkey + role.
  ADR-125: args take precedence over RUFLO_X_RELAY_HTTP / RUFLO_X_RELAY_WS /
  RUFLO_NOSTR_KEY_FILE.
- CLI: `ruflo federation join --code v2.…`
- nostr-tools added as an optionalDependency (secp256k1/Schnorr is not in
  node:crypto); the tool degrades with an install hint when absent. pnpm
  lockfile regenerated in this PR (frozen-lockfile CI).
- 4 tests: 0600 key create/reuse, valid NIP-98 header (kind 27235, verifies,
  bound to url+method+payload hash), malformed code rejected pre-network,
  ADR-112 description. Suite: 16/16; tsc 0; env-var audit passes.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* fix(federation): default relay -> wss://relay.ruv.io; join test tolerates missing nostr-tools

- x-federation-join defaults now point at the canonical relay.ruv.io host (Cloud Run
  domain mapping for buzz-relay); the raw run.app host stays routable for old clients.
- Validate the invite-code shape before the optional-dependency check so a bad code
  fails fast whether or not nostr-tools is installed.
- The root Test Suite runs the CLI tests via root npm ci, which never installs the
  CLI's optionalDependencies: the crypto cases are it.skipIf(!nostr-tools) so the
  file no longer trips the CI test ratchet.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 02:51:40 +00:00
rUv 6396ff636c feat(x-gateway): open Nostr swarm federation gateway — MCP + ruv:// + ws proxy (deployed at x.ruv.io) (#3255)
* feat(x-gateway): MCP + ruv:// gateway for open Nostr swarm federation

New plugin `ruflo-x-gateway` — the service behind x.ruv.io. An MCP server
(Streamable HTTP at /mcp) that exposes ruflo swarm FEDERATION and CLAIMS over
an open, membership-gated, SIGNED Nostr relay (buzz-relay).

Why Nostr: every coordination message is a signed Nostr event, so authorship is
cryptographically verifiable and anyone the relay admits can participate — open
but secure. The relay gates membership + NIP-42 auth; no pre-pinning needed.

Surface:
- Tools: federation_identity / federation_join / federation_publish /
  federation_sync, claims_issue / claims_release / claims_status.
- Resources: ruv://federation/registry, ruv://swarm/roster, ruv://claims/board.
- src/nostr-federation.mjs: NIP-42 authenticated connect, signed publish, and
  verified fetch of #t=ruflo-swarm coordination events.
- src/server.mjs: node http server routing /, /health, /mcp (stateless
  Streamable HTTP), plus the ruv:// resources.
- Dockerfile for Cloud Run; persistent Nostr identity at /data (0600).

Smoke-tested locally: server starts, /health + / respond, POST /mcp tools/list
returns the tool set over SSE. Federation tools require relay membership (by
design) — deployment wires membership + DNS (x.ruv.io) as follow-up.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* feat(x-gateway): stable identity via RUFLO_NOSTR_KEY_HEX (GCP secret) + read-only-FS fallback

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* feat(x-gateway): v0.2.0 — ws proxy, admin-gated writes, invite/admit tools, tests

Security model made explicit: /mcp is public, so every tool that writes
using the GATEWAY's own identity (join, publish, claims_issue/release,
invite_mint, admit) now requires `adminToken`, checked constant-time and
fail-closed (no configured token => all writes denied). Reads and ruv://
resources stay open. Users publish with THEIR OWN keys via invite->claim.

- src/ws-proxy.mjs: transparent WebSocket proxy so wss://x.ruv.io (and
  /relay) fronts the Nostr relay; 500-conn cap, 502/503 on failure.
- src/relay-admin.mjs: mintInvite (NIP-98 POST /api/invites) and
  admitMember (NIP-43 kind 9030) — the gateway holds relay admin role, so
  the owner key never leaves GCP.
- src/security.mjs: per-IP token-bucket rate limit (60/min), 256KB body
  cap enforced before buffering, security headers, timingSafeEqual admin.
- src/claims.mjs: owner-per-resource reducer extracted for testing.
- src/server.mjs: createGateway() factory (testable), stateless MCP.
- test/gateway.test.mjs: 8 tests — claims rules, gating, rate limit, body
  cap, NIP-42 against a mock relay (verifies the signed challenge), routes.

Verified live before this commit: gateway pubkey admitted + promoted to
relay admin; invite minted and a fresh key self-joined via claim and
passed NIP-42 auth; deployed gateway publishes/reads over the relay.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* fix(x-gateway): surface canonicalRelay — relay verifies NIP-42 relay tag strictly

Empirical: auth through the wss://x.ruv.io proxy is ACCEPTED when the client
signs relay=<canonical relay URL> and REJECTED (verification failed) when it
signs relay=wss://x.ruv.io. Expose canonicalRelay + authNote at GET / and in
ruv://federation/registry so clients sign the right tag.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* ci: retrigger test-suite (ratchet flake; main green at ac08e4e02)

* feat(x-gateway): v0.3.0 — Seraphina swarm-queen guidance tool (admin-gated, cognitum meta-llm)

seraphina_guidance reads the live roster/claims/recent from the relay, compacts
them (dedupe by from|type, cap 15), and asks https://api.cognitum.one/v1/messages
with cognitum-auto (tier override). Admin-gated because it spends meta-llm
budget. JSON extracted by slicing the outermost object so fenced answers parse.
Key from SERAPHINA_METALLM_KEY. +1 test (9/9).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* ci(ratchet): baseline ruflo-x-gateway node:test file (own deps; runs via plugin npm test)

Root vitest sweeps plugins/**/test/*.test.mjs and cannot resolve the gateway's
deps (nostr-tools, ws, MCP SDK live only in the plugin's own node_modules), so
the ratchet flagged it on 3 consecutive runs — deterministic, not a flake.
Follow the existing convention (17 plugin tests already baselined, e.g.
plugins/ruflo-adr). The real runner is `npm test` in the plugin (9/9 pass).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* fix(x-gateway): v0.3.1 — security hardening + relay-connection optimization

Security review findings (live battery all green; static gaps fixed):
- publish(): bound msgType ([A-Za-z0-9_-]{1,64}) and payload (<=32KB) before signing
- rate limiter: evict idle buckets (10m) and cap the map (10k) — unbounded IP churn
  could grow memory without bound
- ws proxy: maxPayload 256KB on both legs; bad path now answers HTTP 404 instead
  of a bare socket destroy (Cloud Run surfaced that as 503)
Optimization:
- fetchManyOn(): several REQs over ONE authenticated connection — Seraphina now
  does 1 NIP-42 handshake per call instead of 3
- 5s TTL cache on the roster/claims resources to absorb read bursts
+1 test (10/10): bounds, bounded buckets, proxy limits, single-handshake multi-REQ.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* feat(x-gateway): v0.3.2 — canonical relay wss://relay.ruv.io + legacyRelay metadata

relay.ruv.io is a Cloud Run domain mapping for buzz-relay (Cloudflare CNAME, unproxied so
WebSocket upgrades go straight to Cloud Run). The old run.app host remains routable and is
advertised as legacyRelay in / and ruv://federation/registry so pinned clients keep working.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 02:34:56 +00:00
ruv bd5e22441c docs(ruflo-explained): add federation section — open swarm at x.ruv.io, claims, Seraphina, join flow
New section 14 (closing section renumbered to 15): what federation is, the
private mesh vs the open x.ruv.io swarm, the one-command invite join with your
own key, claims as collision avoidance, Seraphina as coordinator, the security
posture in plain language, and an acceptance test. Footer pin notes 3.40.0 /
3.41.0 for the federation features.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-09 21:52:53 -04:00
rUv ac08e4e02f docs(plugins,skills): surface v3.40.0 cross-host federation + claims (#3254)
* docs(plugins,skills): surface v3.40.0 cross-host federation + claims capabilities

The agentbbs federation plugin and the claims skill still described only
Phase-1 room coordination. Update them to the shipped 3.40.0 surface.

- plugins/ruflo-bbs-federation/plugin.json: 0.1.0 -> 0.2.0. Document the
  Phase-2 cross-host tools (identity, peer_add, peers, serve, sync),
  Ed25519-signed envelopes, registry-anchored pinning, drop/count of
  unverified envelopes, network-agnostic HTTP-pull transport, and claim
  coordination. Keywords: drop phase-1-mvp; add cross-host, signed-envelopes,
  pinned-peers, registry-anchored-pinning, union-merge, claims.
- plugins/ruflo-bbs-federation/skills/cross-host-federation/SKILL.md (new):
  the practical join/serve/publish/sync flow, the pull-not-push model, the
  JSON-stable-payload gotcha, work-claim messages + rules, and the security
  model (registry-anchored pinning, no secrets in payloads, content-is-data).
- .agents/skills/claims/SKILL.md: add a Cross-Host Work Claims section —
  the claims_* runtime ledger tools plus the federated ClaimIssued/Released/
  Handoff/Ack messages and ownership rules, distinct from the existing
  authorization claims.
- marketplace.json: bbs-federation description updated to Phase 2.

Docs only — no code or tool-signature changes.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* fix(plugins): add missing manifest for ruflo-deepseek-harness

The Validate Marketplace workflow requires every plugins/*/ dir to carry
.claude-plugin/plugin.json. ruflo-deepseek-harness has agents/commands/
scripts/skills but no manifest, so main's validate has been red since
2026-08-21 and every PR touching plugins inherits the failure. Pre-existing;
surfaced here because this PR is the one touching plugin manifests.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 01:02:29 +00:00
ruv 2629de628b chore(release): 3.39.3 -> 3.40.0
Ship cross-host federation (agentbbs) + claims. New MCP tools:
federation_bbs_{identity,peer_add,peers,serve,register,publish,sync,watch}.
Signed Ed25519 envelopes, registry-anchored pinning, HTTP pull transport.
Includes the memory-search recall fix (#3252).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
v3.40.0
2026-09-09 19:15:49 -04:00
rUv e111bfbd7f fix(memory,tests): restore memory search recall and green the CLI suite (69 → 0) (#3252)
* fix(memory,tests): restore `memory search` recall and green the CLI suite (69 → 0)

The CLI suite had 69 failing tests across 50 files. Root-causing each rather
than adjusting expectations turned up one real product regression, two real
bugs, and a set of tests asserting things about the machine instead of the code.

PRODUCT REGRESSION — `memory search` recalled nothing (re-break of #2558)

`memory search` returned zero results for content that matched word for word,
while store/list/retrieve worked. Bisected to the threshold default:

  * 2026-07-04 #2558 restored recall, designed around a 0.3 threshold. Its
    fusion scores a full-coverage keyword hit as
    0.6*max(0,semantic) + 0.4*lexical, so with a non-positive cosine — routine
    for a one-word query — a PERFECT keyword match tops out at exactly 0.40.
  * 2026-07-26 #2790 set the CLI default to 0.7. Above 0.4, keyword recall is
    mathematically unreachable, so #2558 silently regressed.

Instrumented the live path to confirm before changing anything:
  key=note/alpha terms=["connectivity"] cov=1 bm25=0.029 sem=-0.883
  lex=1.000 score=0.400 thr=0.7      → dropped
Default restored to 0.3 at both sites, with the ceiling documented so it is not
raised past 0.4 again. Verified end to end: a shared keyword recalls all three
entries, a unique keyword recalls only its own. This was broken on main too.

REAL BUG — anchor containment rejected any project under a symlink

containedPath() compared a realpath'd root against a NON-realpath'd candidate,
so on macOS (/tmp → /private/tmp) every project looked like an escape. Now
compares like with like. Both guarantees re-verified by probe: ../ traversal,
deep traversal, absolute-outside and symlink-escape all still rejected;
symlinked-root spelling now accepted.

REAL BUG — a test that could not fail

funnel.test.ts asserted `elapsed < 100ms` as a proxy for "no network call" —
its own comment conceded a future fetch() would still pass. It also failed
intermittently (118ms) under full-suite CPU contention. Replaced with an
actual assertion on fetch/http.request/https.request, patched via the CJS
copies (ESM namespaces are non-writable). Negative-controlled: the technique
observes a real call and restores cleanly.

TESTS THAT MEASURED THE MACHINE, NOT THE CODE

  * 51 wasm tests asserted `available === true` for optional packages that were
    simply not installed, gated only on `process.env.CI`. Now also gated on real
    availability. The 7 tests in those files that need no WASM were lifted into
    their own blocks so they keep running rather than being skipped along with
    the rest — no coverage traded away for a green tick.
  * 4 agenticow tests lacked the `skipIf(!havePkg)` guard their 4 siblings had;
    in degraded mode the verb is inert and returns before validation.
  * policy-runtime hashed the raw tmpdir path while the source hashes the
    CANONICAL one, so it looked for a trust anchor that is never written there —
    and its cleanup was deleting a nonexistent path, leaking real anchors into
    ~/.config on every run.
  * sona's provider allowlist predated the ruvector / wasm-embedder /
    @claude-flow/embeddings backends.
  * ruvector/index asserted vi.mock satisfies a *dynamic* import() of a bare
    specifier. It does not under vitest 4 — the specifier is rewritten to
    '/@id/@ruvector/core' and fails with ERR_MODULE_NOT_FOUND. Now pins the
    contract the try/catch actually provides: unloadable → false, never throws.

TIMEOUTS — the "flakiness" was a fixed 5s budget

Six memory/intelligence files failed with "Test timed out in 5000ms", never an
assertion. They initialise a real ONNX embedder and SQLite; alone they are
fast, but the full suite saturates every core (~440% CPU). Because it depended
on scheduling, a different file failed each run. Raised testTimeout/hookTimeout
to 30s. (Process-level isolation was tried first and ruled out — forks changed
nothing, which is what identified the cause as time, not shared state.)

Verified: 3 consecutive full runs at 0 failed / 3572 passed on the feature
base, and 0 failed / 3537 passed on this branch's base.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* fix(tests): make ruvector availability assertion environment-independent

CI caught what my first pass missed. The rewritten test asserted
`isRuvectorAvailable()` resolves to `false`, but whether the dynamic
`import('@ruvector/core')` resolves is environment-dependent:

  - under vitest's module runner a bare-specifier dynamic import is
    rewritten to '/@id/@ruvector/core' and fails (ERR_MODULE_NOT_FOUND)
    → false → my `toBe(false)` passed locally
  - a normal install where the package resolves → true → `toBe(false)`
    fails, which is exactly what the CI test-ratchet flagged

The prior `toBe(true)` had the same defect in the other direction. Pin
the invariant that holds in BOTH environments instead — the only thing
the try/catch actually promises: it resolves to a boolean and never
throws. No specific resolution outcome is asserted.

Verified 33/33 locally; the sibling "should return boolean" test already
covers the type, so this now documents the no-throw contract distinctly.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-09 23:07:32 +00:00
rUv 72df96c297 feat(agentbbs): cross-host federation — signed envelopes, pinned peers, union merge (#3251)
* feat(agentbbs): cross-host federation — signed envelopes, pinned peers, union merge

Phase 1 gave every host a local append-only room log and derived roomId
deterministically from the room label, so two hosts that register #sales
already compute the same roomId without talking. This adds the layer that
actually moves envelopes between them.

Design. A room log is an append-only set of immutable envelopes, so reconciling
two hosts is a set union — there is no conflicting write to arbitrate and
nothing for a consensus round to decide. Union is commutative, associative and
idempotent, so sync is order independent and safe to retry. That is a
grow-only set keyed on envelopeId, and it is cheaper and less failure-prone
than the Byzantine agreement the plugin README gestures at.

What union does not give you is authenticity: if any peer can inject, the merge
faithfully replicates forgeries. So each host now holds a persistent Ed25519
identity (Phase 1's key was ephemeral per process, which no peer can pin),
every published envelope is signed, and a receiver verifies against the key it
pinned at peer-add time rather than one carried in the envelope — otherwise an
attacker signs with their own key and claims any origin.

Transport is pull-based HTTP: peers poll GET /agentbbs/v1/rooms/:roomId/
envelopes?since=N. Pull needs no inbound connectivity, tolerates a node being
offline, and leaves ingest volume under the receiver's control; push would make
every node an unauthenticated write target. The server binds 127.0.0.1 unless
a bindHost is passed explicitly, and exposes no route that mutates state.

Bounds are enforced receive-side where a sender cannot negotiate them away:
envelopes per sync, bytes per envelope, bytes per response, peer count, and a
hop limit that is incremented on merge so a cycle terminates. hops is excluded
from the signed material, since it is mutated in transit by design.

Five tools: identity, peer_add, peers, serve, sync.

Tested: 35 new tests, most of them the trust boundary — forged payload,
forged origin, key substitution, unpinned signer, cross-room injection, replay,
oversize, hop exhaustion, traversal at the HTTP boundary, and private key never
served. Convergence and idempotence are asserted against two real nodes over a
real socket, not mocks. Verified end to end with three independent nodes:
all three converge on the same envelope set, a second round merges nothing,
and an unpinned node's injection is rejected.

The Phase 1 structural test pinned the surface at exactly 4 tools; it now pins
9 and names both groups, so adding a tool stays a deliberate contract change.
Full package suite: 68 failures against 70 on main, +35 passing — no
regressions.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* ci(agentbbs): stop rule 2 firing on the federation wire namespace

ADR-164 rule 2 exists to stop `agentbbs` becoming a mandatory dependency: a
static `import ... from 'agentbbs'` is forbidden, and any other mention has to
sit behind loadAgentbbs / a dynamic import. Its catch-all is a substring test
for "agentbbs" anywhere in the file.

agentbbs-federation.ts trips that catch-all without importing anything. Its
only remaining mentions after comment-stripping are string literals that never
reach a module resolver: the '/agentbbs/v1/...' HTTP route prefix and the
'agentbbs:<kind>:' hash domain-separators.

Adds those two shapes to the existing benign-pattern strip list. The
static-import check is untouched and remains the real gate.

Verified this narrows the check rather than loosening it, by running the
workflow's own rule-2 body verbatim against fixtures:
  - the real federation module            -> passes (false positive gone)
  - `import { x } from 'agentbbs'`        -> still caught
  - unguarded runtime reference           -> still caught
  - guarded `await import('agentbbs')`    -> passes

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67

* ci(agentbbs): feed rule 2 via a quoted heredoc so bash stops mangling it

The rule 2 fix in fda44cd1f passed locally but still failed in CI. Cause:
the check runs as `node -e "..."` — a bash double-quoted string — so bash
rewrites the script before node ever parses it:

  - `\\?` collapsed to `\?`, turning the intended optional-backslash
    lookahead into a lookahead for a literal '?'. It could never match
    `agentbbs/v1`, so the strip was a no-op and rule 2 kept firing.
  - backticks in the explanatory comments were command substitution, so
    bash actually executed `export { agentbbsTools } from ...` etc. and
    spliced their (empty) output back into the script. Pre-existing since
    #2503 — visible as `import: command not found` noise in every run.

Fix the boundary rather than re-escaping: feed the script through a
QUOTED heredoc (`node - <<'RULE2'`), which bash passes through verbatim.
Regex backslashes and comment backticks now reach node as written. Also
drops backslashes from the new lookaheads so the pattern is robust even
if the step is ever converted back to an inline string.

Verified by executing the step's actual `run:` block through `bash -c`
(parsed out of the YAML), not a hand-retyped copy — that gap is what hid
the bug. Negative controls, all through that same path:

  static import of agentbbs ............................. CAUGHT
  unguarded runtime ref (deps['agentbbs']) .............. CAUGHT
  wire route + real unguarded ref (bypass test) ......... CAUGHT
  guarded dynamic import ................................ pass
  wire route only ('/agentbbs/v1/...') .................. pass
  hash domain only ('agentbbs:node:') ................... pass
  escaped-slash route (/agentbbs\/v1\/rooms/) ........... pass
  real tree ............................................. pass

The static-import check is untouched and remains the real gate.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-09 21:54:14 +00:00
ruvnet e855ae2e4e chore(release): 3.39.2 -> 3.39.3
Ships #3250: the agentbbs MCP tools gated on `await import('agentbbs')`, but the
published package is a CLI-only launcher with no importable entry point, so all
four federation_bbs_* tools reported degraded regardless of install state.
Replaced with a subprocess probe.

Includes the follow-up hardening: the probe's shell is now gated to win32
instead of always on. The probed binary comes from AGENTBBS_BIN, and under a
shell that argument is command-interpreted, so a crafted value executed a
trailing command (verified locally). POSIX now spawns without a shell.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
v3.39.3
2026-09-09 16:26:04 -04:00
rUv 07f1cf3a3f fix(agentbbs-tools): detect the real CLI-only agentbbs package instead of a nonexistent JS import (#3250)
* fix(agentbbs-tools): detect the real CLI-only agentbbs package instead of a nonexistent JS import

The published `agentbbs` package (0.2.1) is a CLI-only launcher — a `bin`
script with no importable JS entry point — that downloads/builds a Rust
binary and shells out to it. loadAgentbbs()'s `await import('agentbbs')`
could therefore never resolve, even with the CLI correctly installed and
on PATH, so all 4 federation_bbs_* tools reported degraded:true
unconditionally regardless of install state.

Replace the dynamic import with a subprocess presence probe
(agentbbsCliAvailable(), `agentbbs --version`), matching how the rest of
the codebase treats optional CLI tools. shell:true is required for the
probe to find npm's Windows .cmd shim (child_process does not consult
PATHEXT otherwise).

Making the probe actually succeed surfaced two further pre-existing bugs
in code that was previously always skipped:
- vitest.config.ts externalized @noble/ed25519 alongside genuinely-optional
  deps (agentic-flow, agentdb, ...), but it is an always-installed hard
  dependency; externalizing it broke dynamic-import resolution under Vite's
  SSR transform.
- federation_bbs_human_join dynamically imported @noble/ed25519 a second
  time (getSigningKey() already does), which is now deduplicated behind one
  memoized loadEd25519() loader.

All 12 tests in agentbbs-tools.test.ts now pass, including the 4
happy-path tests that were previously always skipped.

* fix(agentbbs-tools): gate the probe shell to win32 instead of always on

`shell: true` was unconditional, and the probed binary comes from
`process.env.AGENTBBS_BIN`. Under a shell that argument is command-interpreted,
so `AGENTBBS_BIN='agentbbs --version; touch /tmp/PWNED'` executes the trailing
command. Verified locally: the file is created with `shell: true` and the spawn
fails with ENOENT once the shell is gated off.

This is the case browser-tools.ts already warns about in the comment beside the
same pattern -- "if user-controlled args are ever added, escape them before
spawn" -- and `shell: process.platform === 'win32'` is the convention already
used at browser-tools.ts:49, commands/init.ts:856 and :1194, and
init/helpers-generator.ts:1459. Unconditional `shell: true` appeared nowhere
else in the package.

The Windows behaviour the original was reaching for is unchanged: cmd.exe still
resolves the `agentbbs.cmd` shim via PATHEXT. POSIX simply stops interpreting
the override. Added windowsHide to match the sibling call sites.

Verified the fix still does what it set out to do: with the CLI resolvable the
suite is 12/12 and the four previously-always-skipped happy-path tests execute,
with the shell disabled on POSIX.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-09 20:14:19 +00:00
rUv 498a238799 fix(security): make the fast-uri CVE fix actually reach shipped packages (#3222) — 3.39.2 (#3247)
* fix(security): make the fast-uri CVE fix actually reach shipped packages (#3222)

3.39.1 bumped fast-uri only in the ROOT package.json. Post-publish validation
caught that this never reached users: a fresh `npm install ruflo@3.39.1`
still resolved fast-uri@3.1.5, squarely inside the advisory range
(GHSA-5jgf-p345-68v8, high, CVSS 7.5, >=3.1.3 <3.1.6).

Root cause: v3/@claude-flow/cli/package.json carried its own hard pin
"fast-uri": "3.1.5" in dependencies, and that is the package that actually
ships. The root bump did not override it -- the #2112 lesson recorded in
CLAUDE.md, that root overrides do NOT propagate to the published ruflo
wrapper, applies to the CLI package too.

- v3/@claude-flow/cli: fast-uri 3.1.5 -> ^3.1.6 (the shipping dependency)
- ruflo: add "fast-uri": ">=3.1.6" override so the wrapper cannot regress
  independently of the CLI

Verified by fresh-installing the published tarball rather than trusting the
lockfile: that is what surfaced the gap in the first place.

Refs #3222, #2112

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk

* fix(ci): refresh v3 pnpm lockfile for the fast-uri bump

The 3.39.2 fast-uri change touched v3/@claude-flow/cli/package.json but only
the npm lockfile was regenerated. v3/ is a separate pnpm workspace and CI runs
pnpm install --frozen-lockfile with working-directory: v3, which rejected the
mismatch (ERR_PNPM_OUTDATED_LOCKFILE) and cascaded into 34 failing checks --
every one an install failure, not a real regression.

Diff is 6 lines, fast-uri only: specifier 3.1.5 -> ^3.1.6, resolved 3.1.7.
Verified pnpm install --frozen-lockfile now exits 0.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
v3.39.2
2026-09-09 13:29:58 +00:00
rUv 40878cca35 dream(swarm): #3242 fix AgentPool health checks that could never detect a dead agent (evaluated, ACCEPT-scoped) (#3243)
* 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>
2026-09-09 13:25:33 +00:00
rUv d55b1bfeac fix(memory): stop reporting a gated native bridge as the active backend (#3228) + fast-uri CVE (#3245)
* fix(memory): stop reporting a gated native bridge as the active backend (#3228)

On Windows the #3024 kill-switch makes getRegistry() return null, so every
memory read/write silently falls back to the sql.js store (memory.db) instead
of the AgentDB corpus (agentdb-memory.db) the bridge would have opened. The
reporter measured the consequence: a canary store/retrieve round-trip SUCCEEDS
because both halves use the same unintended file, while an existing live-store
key returns found:false against 31,673 real rows.

The status surface hid the substitution. getHNSWStatus() gated its bridge
branch on whether the bridge MODULE was loaded -- which it is, even when the
registry is gated off -- so describeBackend() printed "sqlite (bridge,
brute-force cosine)" for a store the bridge never touched.

Gate on whether the bridge is permitted to be the active path rather than on
whether the module resolved. shouldDisableNativeBridge() is sync and a pure
function of platform + env, so it is a faithful discriminator and needs no
warm registry. describeBackend() now names the gate and its reason, so an
upgrade that changes which file receives writes says so.

This does not fix the opt-in allocation abort (#2948) or reroute the fallback
to the AgentDB corpus; both need Windows validation. It makes the silent case
loud, which is what the report asks for first.

Also bumps fast-uri 3.1.5 -> ^3.1.6 (resolves 3.1.7), clearing
GHSA-5jgf-p345-68v8 (high, host confusion via skipped IDN canonicalization)
and the ajv high that inherited it. Same change as #3222 by @aeonframework,
applied here because CI was never authorized to run on that PR.

Refs #3228, #3222, #2948, #3024

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk

* chore(release): 3.39.0 -> 3.39.1

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01JJnDvpYYk2rTvjkudQUoCk
v3.39.1
2026-09-09 12:55:02 +00:00
ruv e341ec8c4a chore(release): 3.38.23 -> 3.39.0
Ships the ADR-322C receipt number-domain fix (#3229 / #3235).

MINOR rather than patch: receipt output changes shape (fractional
candidatePolicy values become scale-12 decimal strings) and
policySchemaVersion moves v1 -> v2, so pre-fix receipts refuse promotion
with 'policy schema changed'. Not MAJOR because no CLI surface or MCP tool
signature changed.

Existing signed receipts stay valid as history and are deliberately NOT
migrated — re-encoding changes the content, hence the ID, hence invalidates
the signature. Re-establish a champion through the explicit reset path,
which already requires confirmation and a recorded reason.

Validated end to end against the built dist before publishing: a receipt
carrying the exact candidatePolicy from #3229 encodes to
{"alpha":"0.3","subjectWeight":1,"mmrLambda":"0.5","bodyWeight":"1.5",
"typePenaltyFactor":"0.5"}, contains zero fractional JSON numbers anywhere
in the payload, and verifies VALID.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01J2s8oQsAZjJtJ6inoYRFPj
v3.39.0
2026-09-08 12:51:23 -04:00
rUv ea2df4726c fix(flywheel): encode candidatePolicy fractions as decimal strings (#3229) (#3235)
`ruflo metaharness flywheel run` wrote receipts whose candidatePolicy
carried binary floats — {"alpha": 0.3, "mmrLambda": 0.5} — contradicting
the contract each receipt claims to satisfy:

  witness-receipt-contract.md rule 2  "Fractional values are decimal
                                       strings, never binary floats"
  conformance-checklist A3            "Every fractional value is a
                                       canonical decimal string"
  ADR-322C L22                        "Signed policy fractions use
                                       schema-quantized decimal strings"

The spec's own example strings it: {"hnswEf": 128, "hybridWeight": "0.65"}.
Confirmed in the PUBLISHED 3.38.23 artifact, not just source:
dist/src/services/harness-flywheel.js:219 `candidatePolicy: candidate`.
Two releases shipped after the 3.38.21 report without fixing it.

Root cause was two-part, and the second half is what let it survive:

1. Producer: `candidate as unknown as Record<string, unknown>` — a DOUBLE
   cast (a single one will not compile) that erased RetrievalConfig's
   `number` fields and laundered floats into the receipt.
2. Verifier: `assertJsonValue` accepted any finite non--0 number. That was
   its entire number rule, so ruflo verified its own non-conforming
   receipts while autogenous's stricter verifier correctly rejected them
   (ruvnet/autogenous#15). The JSON Schema pins `decimalString` on ten
   named fields and cannot express it inside `candidatePolicy`, which is
   opaque by design — so the rule lived only in prose.

Fix:
- `encodePolicyFractions` encodes non-integers to scale-12 decimal strings
  and leaves integers as JSON numbers, matching the contract's example.
  Recurses through nested objects and arrays.
- `policyCandidateId` encodes at the hashing boundary, so the content ID is
  always over the canonical form. Encoding is idempotent, so
  policyCandidateId(raw) === policyCandidateId(encoded) and `verify` can
  still recompute the ID from the payload.
- `assertReceiptNumberDomain` enforces the rule at the RECEIPT boundary, on
  produce (before signing) and on verify. This is the half that closes the
  class: without it the next writer drifts again invisibly.
- The producer's type-erasing double cast is gone.
- policySchemaVersion v1 -> v2, because fixing the encoding changes the
  candidate content ID. Pre-fix receipts now refuse promotion with an
  ACCURATE 'policy schema changed' instead of a confusing 'stale baseline'
  hash mismatch. Signed receipts are deliberately NOT migrated: re-encoding
  changes the content, hence the ID, hence invalidates the signature — a
  migration would mean minting new receipts claiming to be old ones. They
  stay valid as history and unpromotable; the champion is re-established
  through the existing confirm-and-reason reset path.

Scope note worth keeping: a first attempt put the rule inside
`canonicalizeJcs` and broke the proposer envelope and the promotion ledger,
which share that canonicalizer and which the contract does not govern. The
full suite caught it; a test now pins that the shared canonicalizer stays
permissive.

Mutation-verified: disabling the encoder fails 16 tests, disabling the
enforcement fails 1, and dropping the boundary encoding in
policyCandidateId fails 13.

Measured A/B on one installed+built tree — strictly better, nothing broken:
  baseline  40 files / 83 tests failed, 2801 passed
  with fix  39 files / 82 tests failed, 2811 passed
The remaining failures are @ruvector/*-wasm optional native deps absent on
this host, present identically on origin/main.


Claude-Session: https://claude.ai/code/session_01J2s8oQsAZjJtJ6inoYRFPj
2026-09-08 16:46:28 +00:00
ruvnet a295c68703 docs: replace RuFlo Explained visuals with the source renders
Swaps the LinkedIn-derived stills for the original Codex-generated renders
found in ~/.codex/generated_images. 1400x788 rather than LinkedIn's 800x450
re-encode, and taken from the PNG masters instead of a first frame lifted out
of a 100-frame GIF.

The store held 24 renders for 14 chapters -- three concepts had a second take
and there were three unused billboard plates -- so chapter assignment was
resolved by perceptual matching against the published stills rather than by
filename, timestamp or eyeball. Every chapter matched at MAE 1.6-2.2 against a
runner-up of 17-23, then was verified visually against its caption.

2.8 MB for the set.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-07 20:11:14 -04:00
ruvnet 4f77741d91 docs: add RuFlo Explained guide with chapter visuals, link from README
The 14-chapter guide (originally published on LinkedIn) covering what RuFlo is,
how the model / skill / MCP / runtime pieces differ, setup for Claude Code,
Codex, Claude Desktop, ChatGPT and Grok, a first bounded project review, cost,
and what to verify yourself.

Full text preserved: all 14 sections, 6 "Try it" and 7 "Success check"
callouts, prompts and commands verbatim, pinned to ruflo@3.38.23.

Chapter visuals recovered from the published article. The originals are
100-frame animated GIFs at 5-7 MB each -- ~82 MB for the set, which is not
reasonable permanent git weight -- so each chapter carries a static first-frame
still instead (1.1 MB total). The animated versions remain in the linked
article. Chapter order was verified visually against each caption rather than
assumed from DOM or upload order.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-07 20:07:15 -04:00
ruvnet 7d76560a10 Revert "docs: add Cognitum Media showcase and link it from the README"
This reverts commit 674581294b.
2026-09-07 19:19:39 -04:00
ruvnet 674581294b docs: add Cognitum Media showcase and link it from the README
A two-minute broadcast master produced end to end by AI, in which the creative
models were never allowed to publish it. It earns a place in Ruflo's docs
because it demonstrates the pattern Ruflo is built around -- generation
authority and promotion authority are different things -- applied somewhere
other than a codebase. The run terminates at `awaiting_approval` with
`publication_authorized: false`, the same shape as a witness receipt carrying
`authority: 'none'`.

Walkthrough covers all twelve scenes with a still, narration line, style,
timing and transition for each, plus the governance envelope, the consent model
(four presenter variants inheriting one attestation), and the declared audio
mastering targets.

Numbers were verified against the artifacts rather than copied from the
production notes: all six SHA256SUMS check out, and ffprobe independently
confirms codec, resolution, frame rate, sample rate, channels, duration and
byte size. Two caveats from the source are preserved rather than rounded away
-- the cost is a bounded estimate, not an invoice, and captions are a sidecar,
not burned in.

One rendering defect is documented rather than cropped out: scene 9's headline
clips to "Creative does not mean loos". At 28 characters it is the longest in
the production and every other panel is <=25, pointing at fixed-width
truncation in the overlay renderer.

Assets: 12 scene stills (1280px JPEG) + poster + contact sheet, 3.4 MB total.
The 84 MB master is deliberately not committed -- it is referenced, not vendored.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01Ff2xRKvYrqXJhefvcapfE1
2026-09-07 19:08:01 -04:00
ruvnet 384b923986 chore(release): 3.38.21 -> 3.38.23
Patch release covering the six commits landed since 3.38.21:
- #3221 gate EnhancedModelRouter's forwarded modelId on tier match
- #3169 wire embedding-cosine into SmartRetrieval's MMR step
- #3184 wire real cold-start measurement, drop the fabricated V2-vs-V3 benchmark
- #3177 bound two ReDoS patterns, stateless PII regexes, O(P) confidence pass
- #3204 fix two stale main-tree Test Suite failures
- #3203 add v3/@claude-flow/mcp to the root npm workspaces

3.38.22 is intentionally skipped: @claude-flow/cli's publish for that version
stuck in npm's "staged" state (published umbrellas, cli 404) and the registry
refused to republish over it even after unpublish, so all three packages moved
to .23 together to restore version lockstep.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01Ff2xRKvYrqXJhefvcapfE1
v3.38.23
2026-09-07 13:41:18 -04:00
rUv 6acc689150 dream(intelligence): #3220 gate EnhancedModelRouter's forwarded modelId on tier match (evaluated, ACCEPT-scoped) (#3221)
Merged after independent re-verification: 109/109 CI green, the earlier '168 failures' finding traced to an unbuilt-sibling-package local artifact (unrelated to changed files), and a subsequent CI flake confirmed unrelated via clean re-run. Overriding the PR's self-merge gate on explicit user (ruvnet) authorization.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013XaRQntabVwGT9SvnW8cKK
2026-09-07 10:57:05 -04:00
rUv 277c7bc03a dream(memory): #3168 wire embedding-cosine into SmartRetrieval's MMR step (evaluated, ACCEPT) (#3169)
* dream(memory): wire embedding-cosine into SmartRetrieval's MMR step (evaluated, ACCEPT)

smart-retrieval.ts's MMR diversity re-ranking used token-Jaccard text
overlap as its "similarity to already-selected" term, even though
memory-initializer.ts's searchEntries() already computes real
ONNX-embedding cosine similarity for the primary relevance score and
discards it before returning. 2025-2026 production practice (LangChain's
reference maximal_marginal_relevance, Qdrant's native Mmr query shipped
Sept 2025, Weaviate's MMR reranker shipped in 1.37) universally uses
embedding-cosine for this term; token overlap misses low-token-overlap
paraphrases embeddings correctly flag as near-duplicates.

Threads the embedding through: searchEntries()'s two live compute sites
(RaBitQ rerank, brute-force SQL) now return it instead of discarding it;
the two CLI/MCP bridge mappers (memory-tools.ts, commands/memory.ts) pass
it through; SearchCandidate gains an optional `embedding` field;
mmrRerank uses cosine when both candidates have one, falling back to
token-Jaccard otherwise (existing callers/tests without embeddings are
unaffected). controller-registry.ts's separate toCands()/applyMMR path
had the same gap for a different reason (embedding available on
r.entry.embedding but never copied) — fixed the same way.

Evaluated via deterministic Vitest, zero LLM calls, $0 cost. New
discriminating test: a low-token-overlap paraphrase (near-1.0 cosine to
the seed) vs. a genuinely different topic (near-0 cosine). Baseline
(git stash-isolated): picks the paraphrase second (Jaccard can't tell
"low overlap because paraphrase" from "low overlap because unrelated").
Candidate: correctly picks the different topic second. Full
@claude-flow/memory suite: 460/461 passing identically with/without the
candidate (1 pre-existing chmod-based environmental failure, same class
documented since 2026-08-15). tsc --noEmit: zero new errors (verified
@claude-flow/cli's 455 pre-existing errors, from unbuilt workspace
packages, are identical with and without this diff).

Darwin: skipped, scope mismatch (correctness/similarity-metric fix with
a discriminating unit test, not a continuous parameter with a
gold-labeled corpus).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01Qho5K4p5ACBt4yzCWjkBGe

* dream(memory): address review remediation — split CI, real benchmark, hardening

Response to ruvnet's REJECT review on #3169:

1. Split — dream-cycle-backlog-guard.yml moved out to #3205; this branch
   now carries only the MMR fix, rebased onto main (which already
   includes the merged #3203 CI fix).

2. Real benchmark (new src/mmr-benchmark.test.ts): 18-doc/6-topic labeled
   corpus (1 canonical + 2 low-token-overlap paraphrases per topic), run
   through the actual smartSearch/mmrRerank code, swept across mmrLambda
   in {0.3, 0.5, 0.7, 0.9} rather than one favorable value. Honest
   finding: at diversity-heavy lambda, candidate's recall/nDCG drop
   sharply vs. baseline, because cosine correctly suppresses same-topic
   near-duplicates that Jaccard fails to detect at all (and so
   "accidentally" keeps full recall). At smart-retrieval.ts's own default
   (lambda=0.7), recall@6/nDCG@6 are identical to baseline (1.000/0.777
   both) while topic-diversity improves 0.472->0.667 and ground-truth
   duplicate-rate drops 0.289->0.200. Reports latency (sub-ms both paths
   at this corpus size) and a memory-overhead estimate.

3. Malformed/dimension-mismatch: found and fixed a real bug while writing
   these cases. A NaN/Infinity-poisoned embedding component reached
   cosineSimilarity unguarded; the poisoned NaN score meant `mmr > bestMmr`
   was never true (NaN comparisons are always false in JS), which could
   silently break MMR's selection loop and under-fill results below the
   requested limit. Added isWellFormedEmbedding() so malformed/empty
   embeddings fall back to token-Jaccard, same as absent/mismatched ones.
   3 new cases (NaN/Infinity, empty array, explicit dimension-mismatch)
   plus a test mirroring the two real CLI/MCP output whitelists
   (memory-tools.ts:559-569, commands/memory.ts:633-639) to assert
   `embedding` cannot appear in either shape.

4. Determinism: new test runs the same input through smartSearch 5 times
   concurrently, asserts byte-identical result ordering every time,
   including a genuine same-embedding tie.

Full @claude-flow/memory suite: 465/466 (1 pre-existing, unrelated
environmental failure). tsc --noEmit: zero errors.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01Qho5K4p5ACBt4yzCWjkBGe

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-05 11:57:23 -04:00
rUv 33a2c3ed07 dream(performance): #3183 wire real cold-start measurement, remove fabricated V2-vs-V3 benchmark (evaluated, ACCEPT-scoped) (#3184)
* docs(dream-cycle): backfill LEDGER rows for 2026-08-24..09-03

Ledger-append step silently failed again for 10 consecutive nights
(3rd occurrence of this failure class, first flagged 2026-08-19).
Verified via git ls-remote + GitHub MCP issue/PR search before
concluding this (all 10 nights ran to completion: real branch, issue,
and draft PR each) rather than inferring failure from a sparse table.
2026-08-20..08-23 is a separate, confirmed-genuine 4-night gap where
the pipeline did not run at all.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01YQUz3bkMpqgtLXgkYa1LFF

* dream(performance): wire real cold-start measurement, remove fabricated V2-vs-V3 benchmark (evaluated, ACCEPT-scoped)

cli-cold-start.bench.ts's own measureColdStart() (a real,
spawn-based process timer) was never called anywhere in the file.
Every reported number instead came from setTimeout() delays,
including a "V2 vs V3 Speedup: 5.00x" that was guaranteed by
construction (setTimeout(100) vs setTimeout(20), no code between
them) regardless of any real behavior.

- Export measureColdStart(), add runRealColdStartMeasurement() and
  CLI_BIN_PATH; wire the real spawn-based measurement into the suite.
- Remove the fabricated V2-vs-V3 comparison outright (no V2 binary
  remains to honestly compare against).
- Add a deterministic Vitest regression test (4 assertions); fails
  against baseline (functions not exported, fabricated pattern still
  present), passes against candidate — confirmed via git-stash
  isolation. Full @claude-flow/performance suite: 99/99 passing.
  tsc --noEmit: clean.
- Real measured number: node bin/cli.js --version, 5 runs, mean
  54.65ms (min 51.94 / max 57.92) — well under the 500ms target.
  Caveat disclosed in-code: --version is the one subcommand that
  succeeds without a built dist/ in this worktree; this is not a
  full-CLI cold-start measurement.
- Correct the two stale "Achieved" claims this fed in
  ADR-STATUS-SUMMARY.md's Performance Targets table (CLI Startup,
  and the already-known-false HNSW 150x-12,500x figure one row up).

Darwin: skipped, scope mismatch (dead-code-wiring fix, not a
tunable parameter). Flywheel: no signed bundle, deterministic
Vitest evidence retained instead. Follow-ups documented in tonight's
gist: same anti-pattern in agent-spawn/mcp-server-init/cli-warm-start
bench files (not touched, kept to one conceptual change); a Raft
election safety gap and a plugin supply-chain enforcement gap found
by tonight's scan roles (hive-mind, security) but out of scope for
the performance DEEP surface.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01YQUz3bkMpqgtLXgkYa1LFF

* docs(dream-cycle): append 2026-09-05 ledger row

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01YQUz3bkMpqgtLXgkYa1LFF

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-09-05 11:38:56 -04:00
rUv 4dcfa0fadd fix(aidefence): bound two ReDoS patterns, stateless PII regexes, O(P) confidence pass (#3177)
* fix(aidefence): bound two quadratic patterns, make PII regexes stateless, hoist the indicator count out of the per-match loop

Three findings from the 2026-09-04 upstream AIDefence review (ruvnet/midstream
PR #105, corpus of 55 bypass / 30 legitimate inputs), fixed here in the
vendored detector that the aidefence_scan MCP tool runs:

- ReDoS: `\[\[.*?\]\]|<<.*?>>|\{\{.*?\}\}` and `\bDAN\b.*\bmode\b|…` were
  measured at 1.3–1.4 s and 0.65–1.0 s per 100 KB adversarial input. Bounded
  to one line / 200 chars; the regression test requires a full detect() under
  200 ms on 100 KB of `[[` and of `DAN …` repeats.
- Stateful PII: PII_PATTERNS carried /g (and /gi) but were only used with
  .test(); a shared RegExp's lastIndex made detectPII alternate true/false
  across calls on the same service instance. Flags dropped. The email class
  `[A-Z|a-z]` typo (matched `|` as a TLD char) is corrected to `[A-Za-z]`.
- O(P²): calculateConfidence re-tested every pattern for every match. The
  indicator count is now computed once per detect() and passed in; the boost
  semantics are unchanged (test asserts the multi-indicator input scores at
  least the single-indicator one).

Tests: 19 passed (16 existing + 3 regressions).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013PKv3picsfzoQJDKLLW7Rt

* fix(aidefence): bound the email PII regex (RFC 5321 lengths) and pin it with a timing test

Review of this PR (2026-09-05 mission, agent pr-review-ruflo) found the
fix left a worse quadratic behind: the email PII pattern's unbounded
`[A-Za-z0-9._%+-]+@` local part took 3.8 s on 'a.' × 50 000, and
detect() calls detectPII on every scan — the same aidefence_scan path
this PR hardens. Bounded to RFC 5321 lengths ({1,64} local, {1,253}
domain, {2,24} TLD); a regression test runs three 100 KB dotted/dashed
inputs under the 200 ms budget and checks a real address and a
limit-length one are still found.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019xHM4rAH4aaShb4DTr1n6s
2026-09-05 11:37:11 -04:00
rUv 66e4a12366 test/hooks: fix two stale main-tree Test Suite failures exposed by #3203 (embedding width; hook-handler exports) (#3204)
* test(context-hook): assert the hash embedding width is EMBEDDING_DIM (384), not the legacy 768

tests/context-persistence-hook.test.mjs still expected createHashEmbedding
to return 768 values; the hook has defaulted to 384 (the ONNX
all-MiniLM-L6-v2 width, EMBEDDING_DIM) since February so hash-fallback
blobs stay comparable with ONNX vectors. The mismatch was hidden while
the Test Suite job died at ETARGET (fixed in #3203) and surfaced on the
first PR runs after it (#3177, #3184). Assert EMBEDDING_DIM and pin it
to 384; fix the stale "768-dim" docstring on the blob store.

node --test tests/context-persistence-hook.test.mjs: 66 pass, 0 fail.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019xHM4rAH4aaShb4DTr1n6s

* fix(hooks): restore hook-handler's require.main guard and test exports dropped by the July helper sync

tests/hook-handler-runwithtimeout.test.cjs failed with "runWithTimeout is
not a function" on the first Test Suite runs after #3203 (#3177, #3184).
cb1e93e8d (2026-06-15) had guarded the hook dispatch behind
`require.main === module` and exported { runWithTimeout,
INTELLIGENCE_TIMEOUT_MS } for that test; the 2026-07-04 helper sync
(a5f86ad0a) replaced both copies of hook-handler.cjs with the packaged
build and dropped the guard and the exports, so require()-ing the helper
ran main() (stdin read + process.exit) and exposed nothing.

Both copies (repo dogfood + v3/@claude-flow/cli) restored identically;
direct invocation still prints usage and exits 0. The helpers manifest
is re-signed at publish (prepublishOnly), as the guard workflow expects.

node --test tests/hook-handler-runwithtimeout.test.cjs
  tests/context-persistence-hook.test.mjs: all pass, 0 fail.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019xHM4rAH4aaShb4DTr1n6s

* chore(helpers): re-sign helpers manifest after hook-handler.cjs test-surface restore

The packaged hook-handler.cjs changed in this branch (require.main guard +
runWithTimeout exports), so helpers.manifest.json still carried main's hash
and helper-signing.test.ts ("hashes match the actual shipped helper files")
failed in Test Suite (ubuntu-latest), run 33972961703. Re-signed with the
ruflo helpers key via RUFLO_HELPERS_SIGNING_SECRET; no other file changed.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_019xHM4rAH4aaShb4DTr1n6s
2026-09-05 11:15:02 -04:00
rUv efadf92b97 fix(ci): add v3/@claude-flow/mcp to the root npm workspaces so npm ci resolves the unpublished alpha.10 pin (#3203)
Root package.json and v3/@claude-flow/cli/package.json pin
@claude-flow/mcp@3.0.0-alpha.10 (since 16b0dc4d5, release 3.38.17), but
the registry only has 3.0.0-alpha.1/7/8/9, and v3/@claude-flow/mcp was
the one bundled package not listed in the root workspaces (codex,
plugin-agent-federation and security are). Root `npm ci` therefore hit
the registry and failed with ETARGET on main and on every PR since
2026-08-22, before any test ran.

Listing the package as a workspace member makes npm resolve it locally
(lockfile entry: `"node_modules/@claude-flow/mcp": {"resolved":
"v3/@claude-flow/mcp", "link": true}`), matching how the other three
bundled packages already resolve. No version change; the pin stays
alpha.10 and the tarballs still bundle it.

Found by the 2026-09-05 PR-review mission (agent pr-review-ruflo):
verified in main's own Test Suite job and in 3 red jobs on #3177.


Claude-Session: https://claude.ai/code/session_019xHM4rAH4aaShb4DTr1n6s
2026-09-05 10:26:44 -04:00
ruv db4991967c chore(release): 3.38.20 -> 3.38.21
Publishes the #3155 fix (fix(memory): stop seeding the bridge's
ControllerRegistry with the sql.js dbPath, PR #3156) and the CI-fixing
PR #3059 (agentic-flow-agent duration-assertion flake) to npm.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_011N1hncQ1p4pVt15q2VqaQD
v3.38.21
2026-09-02 09:40:25 -04:00
rUv b3db018011 fix(test): stop asserting strict-positive task duration in agentic-flow-agent tests (#3059)
CI's test ratchet flagged agentic-flow-agent.test.ts as an unexpected new
failure (run 32091740244, same commit fa13ee4a that passed the day before).
result.duration is Date.now()-delta over a ~1ms setTimeout; under coarse CI
clock resolution the two samples can land in the same millisecond, making
duration legitimately 0. Relax the two affected assertions from
toBeGreaterThan(0) to toBeGreaterThanOrEqual(0).
2026-09-02 09:35:21 -04:00
rUv 4d0134e59b Merge pull request #3156 from ruvnet/fix/mcp-http-bridge-rehydrate-3155
fix(memory): stop seeding the bridge's ControllerRegistry with the sql.js dbPath
2026-09-01 22:00:47 -04:00
ruv d413297dde fix(memory): stop seeding the bridge's ControllerRegistry with the sql.js dbPath
Fixes #3155. `initializeMemoryDatabase()` passed its just-resolved
sql.js-facing `memory.db` path straight through to
`activateControllerRegistry()` -> `bridge.getControllerRegistry(dbPath)`.
That seeded the process-wide ControllerRegistry singleton with `memory.db`
as AgentDB's own native better-sqlite3 database, instead of the dedicated
`agentdb-memory.db` sibling `getAgentDbPath()` exists specifically to
provide (#2786).

Because this activation only runs on a database's first-ever init (an
already-initialized `.swarm/` skips it via the #1791.6 idempotent no-op
branch), a bridge started against a brand-new directory would activate
against `memory.db` and work fine for that process's lifetime. A
restarted bridge process, whose `.swarm/memory.db` already exists, would
skip that call entirely, and its first real bridge operation would
activate the registry against `agentdb-memory.db` instead (via the
`dbPath || getAgentDbPath()` fallback in memory-bridge.ts). That file
never received the earlier writes, so every read after a restart came
back `found:false` / an empty list, silently, with no error anywhere.

`activateControllerRegistry()` no longer forwards the sql.js dbPath to
the bridge at all — it calls `bridge.getControllerRegistry()` with no
argument, so both the init-time warm-up and every later bridge call
(bridgeStoreEntry, bridgeGetEntry, ...) resolve the same file via
`getAgentDbPath()`, regardless of whether this is a fresh directory or a
restart against an already-initialized one.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01MND33UDrtWPRH851k9w8M3
2026-09-01 21:40:17 -04:00
Dragan Spiridonov 29f048fc3b Merge pull request #3133 from ruvnet/fix/meta-proxy-stale-owner
fix(proxy): transactionally activate the effective daemon
2026-08-30 16:47:11 +03:00
Dragan Spiridonov 0a5182b427 fix(ci): align CLI workspace lockfile 2026-08-30 15:44:49 +02:00
Dragan Spiridonov 36740e4304 fix(proxy): verify effective daemon on install 2026-08-30 15:27:37 +02:00
ruv d33ef4bf8a feat(plugin): add ruflo-music plugin for Cognitum Music (cogmusic MCP)
Wraps the cogmusic MCP server (music.cognitum.one, MiniMax-Music3) as a
Ruflo plugin: 2 agents (music-composer for lyrics/prompt writing,
music-producer as the pipeline entry point), 7 skills mapped onto the 6
cogmusic MCP tools plus a one-time connect/setup skill, a /music
dispatcher command, and an ADR documenting the PAT auth model,
audio_url delivery pattern, and disclosed reliability history.

First plugin in this marketplace whose skills reference MCP tools from
a server other than ruflo-core (mcp__cogmusic__*) — documented
explicitly in ADR-0001 as a deliberate, live-verified deviation from
the CLI-wrapping convention every sibling plugin follows.

Verified: 10/10 structural smoke checks, all cross-file relative links
resolve, all six referenced MCP tool names match the real cogmusic
surface exactly.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01G3Fkc9qcZ2AkwGPce83yTa
2026-08-27 16:45:36 -04:00
ruv e21aa352fd chore(release): 3.38.19 -> 3.38.20
Publishes PR #3092 (fix(statusline): stop pinning intelligence to a
hardcoded 0%).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01BGiC4SoXiGcUHxs4TsFCeh
v3.38.20
2026-08-24 15:45:21 -04:00
rUv a0b39b9dd0 fix(statusline): stop pinning intelligence to a hardcoded 0% (#3092)
Closes #3091.

`intelligencePct` had exactly one assignment in the whole file, inside
`buildLocalFallback()`. Everywhere else it was only read. So the segment
showed either whatever the CLI returned or a literal 0 -- and since
`applyLocalOverlays()` repairs adrs/agentdb/tests/hooks/integration/security
from disk but never touched `system`, any CLI failure left the brain at 0%
sitting beside segments that were still live. Nothing distinguished the two,
so an absent measurement rendered exactly like a real one.

That is reachable from ordinary conditions, not just exotic ones. Both
candidates fail together on a normal setup: Claude Code's plugin marketplace
git-clones the repo with no install step, so `bin/cli.js` exists while
`@claude-flow/cli-core` does not (the case resolveCliBinCandidates() already
documents), and the npx backstop dies with `npm error Invalid Version:` in any
npm-workspaces root holding a member with no version field.

Three changes:

- `getLocalIntelligence()` joins the overlay set, deriving the percentage from
  `.claude-flow/neural/patterns.json` (project, then home) with the formula the
  sibling statusline.js already used. A CLI value greater than zero still wins.

- `countPatternsCached()` keys the parse on the store's mtime+size. The store
  is routinely large -- 14.5MB / 1277 patterns on the machine this was
  diagnosed on -- and the statusline re-renders every prompt, so parsing it
  each time was not an option. Measured: 57.2ms cold, 0.2ms warm.

- Unknown now renders as `—` rather than `0%`, and the fallback constant is
  `null` instead of `0`. A metric that silently pins to zero when a subprocess
  dies is indistinguishable from a metric that is genuinely zero, which is what
  made this read as a display bug rather than an outage.

This is the same failure `getLocalAgentDB()` already carries a comment about --
"the statusline showed Vectors 0 despite thousands of real vectors" -- in a
different segment.

Applied to both committed copies. The CLI package's copy is the generator's
single source of truth (#2679) and the one the drift guard byte-compares, so
editing it keeps generator output and artifact in step by construction; the
repo-root copy is CRLF and its endings are preserved.

Verified by execution, both copies:
  1277-pattern store -> 100%   (previously 0%)
  no store anywhere  -> —      (previously 0%)


Claude-Session: https://claude.ai/code/session_016QSCkKnxDjqU49NVVpWMK5
2026-08-24 15:30:24 -04:00
ruv 3c99b1c84a fix: repin root claude-flow package.json's @claude-flow/mcp (hygiene)
Same defect as v3/@claude-flow/cli's package.json, already fixed in
68a9c9f6a: pinned to an unpublished 3.0.0-alpha.10 instead of the real
published ^3.0.0-alpha.9. Harmless in practice (bundled via
bundleDependencies + workspace link, verified npm install works either
way) but wrong to leave uncorrected. No republish needed for this alone.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_018ZkgSrVRWNMcXmaFiMZ7Z8
2026-08-22 12:40:28 -04:00
ruv 68a9c9f6a4 chore(release): 3.38.18 -> 3.38.19 (supersedes broken 3.38.17/3.38.18)
A subagent I spawned earlier (intended only to relay a status message)
went out of scope and independently ran its own full fix/verify/publish
cycle in parallel with mine on this same checkout, racing my edits. Between
its publish and a set of stale-cached `claude-flow daemon` background
processes self-healing package.json mid-write (#3005), @claude-flow/cli
was published twice in a row (3.38.17, 3.38.18) with a broken
`@claude-flow/mcp` dependency pin, and 3.38.18 additionally shipped
entirely missing the `@claude-flow/memory` dependency. claude-flow and
ruflo were published at 3.38.18 too, inheriting the same defect.

3.38.19 is the verified-correct cut: @claude-flow/mcp repinned to
^3.0.0-alpha.9 (published, has the #2990 fixes), @claude-flow/memory
present at ^3.0.0-alpha.23 (has the #2977 fix). Deprecating 3.38.17/3.38.18
on npm as a follow-up; this commit does not touch the doc/metadata files
that raced independently of the version bump.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_018ZkgSrVRWNMcXmaFiMZ7Z8
v3.38.19
2026-08-22 12:35:58 -04:00
ruv 127a6546c4 chore(release): 3.38.17 -> 3.38.18, @claude-flow/memory 3.0.0-alpha.22 -> alpha.23
3.38.17 was published by a concurrent session mid-race with a stale
package.json (the @claude-flow/mcp pin had been reverted to the broken
3.0.0-alpha.10 by a background helper-refresh process just before publish;
see #3005). npm forbids republishing over an existing version, so cutting
3.38.18 with the verified-correct pin instead of trying to fix 3.38.17.

Also discovered @claude-flow/memory is NOT in @claude-flow/cli's
bundleDependencies (unlike @claude-flow/mcp/codex/security/plugin-agent-federation)
-- it's resolved from the registry via a normal ^3.0.0-alpha.22 semver range,
and that exact version is what's currently published, without the #2977 fix
from 8a8500194. Bumping @claude-flow/memory to 3.0.0-alpha.23 and re-pinning
@claude-flow/cli to ^3.0.0-alpha.23 so the fix actually reaches users --
otherwise 3.38.18 would ship pointing at an unfixed memory dependency.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_018ZkgSrVRWNMcXmaFiMZ7Z8
v3.38.18
2026-08-22 12:27:01 -04:00
ruv c313276442 docs: correct MCP-server-registration claims, refresh catalog/helpers metadata (retry)
Same content as b5b12a751, which only landed the two new proven-config files
-- the other six staged files dropped out of that commit, most likely a git
index race from a concurrent session against this same checkout. Re-applying
now; working tree already held the correct content.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_018ZkgSrVRWNMcXmaFiMZ7Z8
2026-08-22 12:21:54 -04:00
ruv b5b12a751a docs: correct MCP-server-registration claims, refresh catalog/helpers metadata
- README (root + ruflo/): the "no MCP server on plugin-only install" claim
  was inaccurate for ruflo-core, which ships its own .mcp.json; correct the
  comparison table and the plugin-install walkthrough to reflect the real
  mcp__plugin_ruflo-core_ruflo__* tool names. Fix the `claude mcp add`
  example to use the server name actually registered (claude-flow).
- catalog-manifest.json: regenerate (agents 164->165, current gitSha).
- helpers/.helpers-version, helpers.manifest.json, statusline.cjs,
  .proven-config-version, proven-config.json: refresh to match current
  release metadata (forward version bump, not a revert).

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_018ZkgSrVRWNMcXmaFiMZ7Z8
2026-08-22 12:20:34 -04:00
ruv 16b0dc4d5b chore(release): 3.38.16 → 3.38.17
Fixes #2990's root cause at the publish layer: v3/@claude-flow/cli and the
claude-flow umbrella both pinned @claude-flow/mcp to 3.0.0-alpha.10, a
version that was never published (npm registry tops out at alpha.9). Since
@claude-flow/mcp is bundled (bundleDependencies) via a pnpm workspace link,
this didn't affect the monorepo build, but it broke plain `npm install`
against the published packages and left package.json/pnpm-lock.yaml
out of sync with what's actually resolvable. Repinned to ^3.0.0-alpha.9
(the real latest, which already carries the protocolVersion-string fix)
and regenerated v3/pnpm-lock.yaml accordingly.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_018ZkgSrVRWNMcXmaFiMZ7Z8
2026-08-22 12:18:30 -04:00