Commit Graph

395 Commits

Author SHA1 Message Date
rUv e477d3a5aa Verify SDK progress notification contract 2026-09-11 22:11:43 +00:00
rUv a692d5ef83 Test incremental guidance and streaming failure paths 2026-09-11 22:10:51 +00:00
rUv 24bb42afca Wire opt in MCP guidance progress and cancellation 2026-09-11 22:10:50 +00:00
rUv 001b7d75de Wire opt in MCP guidance progress and cancellation 2026-09-11 22:10:49 +00:00
rUv 1e897c02aa Implement bounded guidance stream decoding 2026-09-11 22:10:35 +00:00
rUv 31b3e6a69f Add Seraphina streaming 2026-09-11 22:10:23 +00:00
rUv 005a0ed25e chore(x-gateway): 0.7.1 — version label for the claims TTL fix (#3293)
Claude-Session: https://claude.ai/code/session_015y2UjPSd3VX1CYnPstZJ5D
2026-09-11 08:25:03 +00:00
rUv 4bb9ca182d fix(x-gateway): claims reducer honours ttlSeconds — expired leases free the resource (#3292)
Found by an external node's failure-path probes on the live swarm (T1 expired-lease,
T5 disconnected-worker): reduceClaims recorded ttlSeconds but never applied it, so a
worker that claimed and then vanished held the resource forever, and the docs' promise
that a timed-out claim frees itself was false.

- Expiry is evaluated against the next event's timestamp while reducing (deterministic
  replay: a later ClaimIssued after expiry wins) and against `now` for the final ledger
  (a disconnected owner's lease lapses without a release).
- Claims without ttlSeconds never expire (unchanged).
- Ledger entries now carry `expiresAt` (ISO) so clients can show remaining lease.
- Tests: ttl expiry lets a later claim win; live lease is kept; disconnected worker
  frees; no-ttl never expires; releasing an expired claim is a no-op.


Claude-Session: https://claude.ai/code/session_015y2UjPSd3VX1CYnPstZJ5D
2026-09-11 08:12:32 +00:00
rUv 8485302f10 feat(x-gateway): 0.6.1 — bound Seraphina by budget instead of an admin token (#3275)
* feat(x-gateway): 0.6.1 — Seraphina bounded by budget, not by an admin token

Squashes what is actually running on x.ruv.io so git and production agree.

Seraphina was gated alongside the write tools, but it is not like them: it reads
the roster, the claims board and recent messages, asks a model, and returns
advice. It writes nothing and carries no authority. The gate answered the wrong
question — the exposure is model spend, and spend is bounded with a budget, not
a password.

The practical cost was worse than a wrong abstraction. A browser cannot hold a
bearer secret, so a published UI was either locked out or tempted to ship
RUFLO_ADMIN_TOKEN to the client — the same token that mints invites and publishes
as the gateway identity. The safe-looking option was the catastrophic one.

Seraphina now answers with no token, bounded by a shared daily cap and a
per-client hourly cap, and anonymous callers cannot select the high or ultra
tiers. An admin token lifts both. Every write tool keeps its gate; verified live
that claims_issue, federation_invite_mint, federation_publish, federation_admit
and channel_publish all still refuse an anonymous caller.

Not included, deliberately: a tool to relay member-signed events through the
gateway. It was built, tested against the live relay, and removed. buzz-relay
refuses any EVENT whose pubkey differs from the NIP-42 authenticated connection,
so a gateway cannot publish on another identity's behalf — and it does not need
to. Members already publish as themselves through the wss://x.ruv.io proxy,
verified end to end. Shipping a tool that always fails would be worse than none.

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

* feat(x-gateway): serve onboarding guidance, and deliberately not key generation

The registry said "generate a Nostr keypair" and stopped, and it never named the
two identities in play. That gap was not theoretical: a capable agent inspected
the surface, found only gateway-signed publishing, and concluded ordinary members
could not publish from a dashboard at all. They can. It just was not written
anywhere they could reach.

federation_onboarding and ruv://federation/onboarding now answer it, open, no
token. The guide leads with the distinction that causes the confusion — your key
signs for you, the gateway's key signs for the service, and the gated tools are
gated precisely because they speak as the service. It carries the five steps, the
four things never to do, and the three traps this service has actually taught us:
sign the NIP-42 relay tag with the canonical URL even when proxied, the relay
binds publishing to the authenticated connection, and the channel tag is `c` not
`h`.

It does NOT generate keys, and that is the point rather than an omission. A
service that mints your keypair and returns the secret has seen your secret, and
becomes custodian of every identity it "helped" — the same custody mistake as
putting an admin token in a browser, inverted. The guide ships the code so the
caller runs it locally and the key never crosses the wire.

One test note worth keeping: the first version of the safety assertion regex-
matched for "send your secret" and failed on the guide's own "Never send your
secret key" line. It is now structural — no field may be named for secret
material — because a string search cannot tell an instruction from its negation.

Verified live on 0.7.0 (ruflo-x-gateway-00015-qj7) with an anonymous call. 20/20.

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

* fix(ci): register the gateway's credential and budget knobs, and skip test/ like tests/

The ADR-125 precedence audit failed the gateway PR, and it was right to look —
but for two reasons that are both about the audit, not the code.

RUFLO_ADMIN_TOKEN was not registered as an escape hatch even though its sibling
RUFLO_X_ADMIN_TOKEN is, with the same reasoning: a secret must never be a CLI
flag, where it lands in shell history and process listings. The gateway is a
long-running service with no typed command surface at all, so there is no
invocation to attach a flag to. Same for the two Seraphina budget knobs added in
#3275.

The other half was a naming gap. SKIP_DIRS already excludes `tests` and
`__tests__` because the audit is about production precedence, not test setup —
but the flagged lines were in `plugins/ruflo-x-gateway/test/`, singular, which
was not in the set. Two `process.env.X = 'test-admin-token'` assignments inside a
test fixture were reported as undeclared production reads.

Verified: the audit now exits clean on this branch.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013u4pmL9ZUAXb6usVQgNo67
2026-09-10 19:20:21 +00: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 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 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 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 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 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 0f3c45101b fix: address #3045 #3064 #3065 + new ruflo-deepseek-harness plugin (3.38.13) (#3078)
Bug fixes
- #3045 statusline.cjs: getGitInfo() runs the 5-command git chain per render;
  on large repos with concurrent Claude Code sessions this queued subprocesses
  faster than they finished (reporter observed hundreds of orphan children +
  load-avg in the hundreds). Added a per-cwd tmp file cache with 5s TTL —
  dirty status still feels live, pileup is bounded.

- #3064 hooks post-task: the narrow ad-hoc regex /^[a-zA-Z0-9_-]+$/ silently
  dropped every colon-namespaced plugin agent (ruflo-core:reviewer,
  feature-dev:code-explorer, ...) — i.e. every Claude Code plugin agent. The
  canonical validateIdentifier() upstream already allows ':' and '.', so the
  redundant regex is removed. Regression test locks in 4 agent-shape cases,
  proven to catch the bug: 2 pass / 2 fail on revert, 4 pass with fix.

- #3065 harness-gepa SKILL.md: unquoted colon in `description` broke YAML
  parsing in `npx skills add`. Quoted the description; rephrased the bare
  "(default:" to avoid the leading colon.

New plugin: ruflo-deepseek-harness
- plugins/ruflo-deepseek-harness/: sibling to ruflo-metaharness (ADR-150
  shape). Two skills: `deepseek-chat` (non-reasoning) and `deepseek-reason`
  (surfaces reasoning_content separately). Reads DEEPSEEK_API_KEY from env;
  degrades gracefully (exit 0 with `{status: 'degraded', reason, hint}`)
  when the key is missing or the API is unreachable. `--alert-on-error`
  flag opts into hard exit 1 for CI gates. Smoke-tested locally.

Release
- Bump @claude-flow/cli, claude-flow, ruflo: 3.38.12 → 3.38.13 (PATCH:
  bug fixes; the new plugin is scaffolding under plugins/ and not part of
  the npm-published CLI packages).

Not fixed
- #3051 memory_store tags: current source's memory_store handler passes
  tags straight through to storeEntry; a maintainer already verified live
  round-trip works on fa13ee4ad. Reporter has not yet supplied the
  ruflo/@claude-flow/cli version that hosted the affected MCP server,
  so the affected release path is unknown. Left as-is pending that reply.


Claude-Session: https://claude.ai/code/session_0118jMsYhwHD5dx2vStENsEB
2026-08-21 10:10:13 -04:00
rUv f9537a240b fix(ruflo-adr): skip .brain when walking for ADRs (#2911) (#2994)
* fix(ruflo-adr): skip .brain when walking for ADRs (#2911)

ruvnet-brain clones ~50 external repos under .brain/repo/clones/,
many carrying their own docs/adr/. Without .brain in SKIP_DIRS,
findAdrs() picked up hundreds of foreign ADRs alongside the
project's own — and since .brain sorts before docs, the project's
ADRs landed dead last in the walk order.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ruflo-adr): smoke step 22 should not hard-code the test-file count

Was `-eq 3`, breaking every time a new *.test.mjs file is added to
__tests__/ even when the new tests themselves pass. `-ge 3` keeps the
"regression tests exist and run" guarantee without re-breaking on
growth — caught by this PR's own new test file (skip-brain-dir-2911).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-08-12 15:31:06 -04:00
rUv 8e62a8bd8d fix(plugins): pin ruflo-core MCP launch path + register 3 missing marketplace plugins (#2975)
ADR-382 Part A:

- plugins/ruflo-core/.mcp.json launched the MCP server via a bare
  `npx -y @claude-flow/cli@latest`, which always re-resolves to the
  newest registry version regardless of any local `npm install
  @claude-flow/cli` a user separately pinned. Add scripts/mcp-launch.cjs,
  a small launcher mirroring hook-handler.cjs's resolveCliBinForHook()
  (candidate list + dist/src/index.js existence guard against a
  source-only marketplace checkout), and point .mcp.json at it via the
  documented ${CLAUDE_PLUGIN_ROOT} substitution. Falls back to the
  original npx @latest invocation only when no local install resolves.

- .claude-plugin/marketplace.json listed 35 plugins while plugins/ has
  38 directories; register the 3 missing ones (ruflo-agntcy,
  ruflo-bbs-federation, ruflo-business-pods), descriptions sourced from
  each plugin's own .claude-plugin/plugin.json.

Ref: ADR-382, #2971
2026-08-11 18:25:49 -04:00
rUv 0b3cfb77d6 MetaHarness hardening: repair the dependency contract + strict sequential promotion evidence (#2956)
* feat(metaharness): repair dependency contract + strict sequential promotion evidence

Item 1 — dependency & compatibility repair (the contract was silently broken):
- @metaharness/darwin ^0.8.3, @metaharness/flywheel ^0.1.10, and
  @metaharness/radio ^0.1.0 are now explicit optionalDependencies of
  @claude-flow/cli (optional PEER deps are never auto-installed, so a clean
  ruflo install shipped with zero MetaHarness packages on disk)
- check-metaharness-pins.mjs now searches dependencies, optionalDependencies,
  AND peerDependencies; UNDECLARED and PEER-ONLY (for installable pins) are
  fatal drift instead of silently passing; radio added to the watch list;
  --require-installed asserts the real published symbol contracts
  (RefineMutator, withSequentialEvidence, RadioBus, ...)
- new scripts/metaharness-clean-install-test.mjs: installs the declared
  ranges into a pristine temp dir and asserts every advertised export;
  wired as a MANDATORY clean-install job in metaharness-ci.yml
- doctor: new "MetaHarness declared packages" check FAILS (not warns) when a
  declared optional dep does not resolve at runtime; --component metaharness
  now runs upstream + declared-deps + integration checks
- MH_DARWIN_PIN bumped 0.8.0 → 0.8.3 in lock-step with the declared range

Item 2 — strict promotion evidence at the transaction authority:
- receipts now carry task-level pairedOutcomes (taskId + per-task baseline/
  candidate scores) behind heldOutDeltas; verifyFlywheelReceipt refuses rows
  that cannot reproduce their aggregate; evaluateFlywheelCandidate populates
  them; pre-existing receipts still verify byte-identically
- new flywheel-sequential-evidence.ts: anytime-valid e-process over
  discordant paired outcomes (testing-by-betting) composed with per-candidate
  alpha allocation (alpha_k = alpha_total * 6/(pi^2 k^2)), so the family-wise
  false-promotion probability across an ADAPTIVE candidate stream is bounded
  by alpha_total = 5%
- promoteFlywheelCandidate requires paired evidence by DEFAULT — aggregate-
  only receipts are refused, never silently downgraded (the upstream
  withSequentialEvidence fallback hole); explicit
  --allow-aggregate-evidence / allowAggregateEvidence escape hatch for
  pre-upgrade receipts; alpha spend is persisted per receiptId in the
  transaction state (looking spends alpha; retries reuse their index)
- acceptance test: 1,000 null-improvement streams (20 adaptive candidates x
  40 worst-case discordant pairs) — measured family-wise false promotion
  0.6%, within the <=5% budget

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

* chore: gitignore node-compile-cache build artifact

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

* fix(ci): satisfy #2561 guard, dep-overlap audit, and smoke contract for metaharness optional deps

Three CI failures from the dependency-contract repair, three fixes:

- tilde-pin @metaharness/{darwin,flywheel,radio} (~0.8.3 / ~0.1.10 / ~0.1.0)
  per the ADR-150 anti-caret rule enforced by smoke step 17z73 — upstream is
  unstable, tilde absorbs patches but never minors
- remove the three from peerDependencies/peerDependenciesMeta: an entry in
  BOTH optionalDependencies and peerDependencies crashes npm 11.x arborist
  on dedupe (#1147/#2018 dep-overlap audit); optionalDependencies alone is
  the declaration that actually installs
- update the #2561 cold-startup guard per its own escape clause: budget
  8 → 10 and drop @metaharness/darwin from the forbidden list — that entry
  dated from the pre-0.8 heavy-tree era; darwin@0.8.3 is 1.8M unpacked with
  ZERO dependencies and no lifecycle scripts (all three packages combined:
  2.3M, 753ms cold install into an empty dir, measured 2026-08-10; the
  clean-install CI job re-verifies on every pin change)
- smoke.sh 17h now accepts the componentMap ARRAY form
  ('metaharness': [checkMetaharness, ...]) introduced with the
  declared-packages doctor check

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

* fix(ci): remove apostrophe that broke the single-quoted guard script

The #2561 guard runs as `node -e '...'` inside bash; an apostrophe in a
comment ("guard's") terminated the quoted string and bash tried to execute
the next // comment line (exit 126, '//: Is a directory'). Verified by
executing the extracted run block end-to-end: all three checks OK, exit 0.

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

* feat(flywheel): ADR-381 — alpha-stream governance, reset epochs, accept/v2+seq

Completes the sequential-evidence governance PR #2956 opened:

ADR-381 (new) records the decisions:
- the ADR-322 promotion ledger IS the alpha stream (per project root) —
  receipt lineageIds default to fresh UUIDs and cannot scope the control
- evidence epochs: resetSequentialEvidence({confirm, reason}) archives the
  spend into an append-only sequentialResets audit trail, EXPIRES every
  outstanding evaluated receipt (fresh-data enforcement — old-epoch
  evidence cannot be replayed against a reopened budget), and increments
  evidenceEpoch; surfaced as `flywheel evidence-reset --reason … --confirm`
  (CLI) and the metaharness_flywheel MCP op, both behind the same policy
  gate as promotion
- accept/v2+seq: the ADR-176 generations loop now decides each bundle with
  a third conjunct — the e-process over the bundle's own embedded per-task
  holdout at alpha_k for its position in the attempts stream — with the
  full verdict recorded in the bundle and independently replayed by
  verifyReceiptBundle (v1 bundles keep v1 semantics; versions pin per
  bundle); flywheelStatus surfaces next test index / threshold / minimum
  pairs / remaining budget so exhaustion reads as plateau, not mystery
- two-layer pre-flight: evaluateFlywheelCandidate annotates (never blocks)
  when the promotion holdout cannot clear the next threshold on a perfect
  sweep; promoteFlywheelCandidate refuses size-inviable receipts BEFORE
  allocating an alpha index — sample size is ancillary, so the refusal
  looks at no evidence and spends no budget

Tests: reset semantics (archive/expire/epoch/fresh-promote), alpha-free
size refusal vs alpha-spending e-process refusal, v2 promote/reject/replay
including tamper + index-shopping detection, v1 compat, helper math
anchors (min pairs = 9 at test 1), budget monotonicity. 82 tests green
across the eight affected suites; tsc build clean.

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

* fix(flywheel): close 3 concurrency/epoch gaps in ADR-381 sequential evidence

Code review of this PR found three real correctness bugs that can silently
violate the <=5% family-wise false-promotion guarantee that is the PR's own
core deliverable:

- flywheel-transaction.ts: resetSequentialEvidence's "fresh data only"
  guarantee only expired receipts already registered at reset time; a
  receipt whose evidence predates a reset but is registered afterward was
  silently admitted into the new, cheaper epoch (index shopping). Now
  tracks evidenceEpochStartedAt and promoteFlywheelCandidate refuses any
  receipt whose payload.issuedAt predates it, before allocating an alpha
  index.

- harness-flywheel-generations.ts: the daemon generations loop computed its
  sequential-evidence testIndex via an unlocked loadAttempts(root).length+1
  read before appending. Two overlapping runFlywheelGeneration calls on the
  same root could be assigned the same test index and spend the same
  alpha_k twice. The read-index -> build-bundle -> append critical section
  now runs under the same O_EXCL lock pattern flywheel-transaction.ts
  already uses.

- harness-flywheel.ts: evaluateFlywheelCandidate's sequentialPreflight
  (backing the `promotable` flag) was computed from an unlocked snapshot
  taken before the async retrieval/scoring work, so it could go stale by
  the time promoteFlywheelCandidate allocates the real index under its own
  lock. Moved to the latest possible read (after receipt registration) to
  minimize the window; documented as advisory, since promoteFlywheelCandidate
  remains the sole authority.

Added a regression test for the epoch-boundary refusal and a concurrency
test proving two racing generations get distinct sequential test indices.

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-10 11:32:40 -02:30
rUv 30d49c6fe2 fix(agntcy): pin @agntcy/slim-bindings to the confirmed-working alpha (#2888)
The SLIM maintainers confirmed (agntcy/slim#1916) they've moved off
uniffi-bindgen-react-native onto @ubjs/core/@ubjs/node (compiled output,
not raw TypeScript) in the alpha dist-tag (2.0.0-alpha.4+), not yet
promoted to latest.

Verified live: a real server bring-up + client connect + graceful
shutdown against @agntcy/slim-bindings@2.0.0-alpha.5 succeeds under
plain Node with zero errors — no ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.
Verified detectAgntcyRuntime() itself (not a mock) now returns
configured: true when RUFLO_AGNTCY_SLIM_ENDPOINT is set, exactly as its
existing graceful-degradation design predicted it would once the
upstream bug was fixed — zero logic changes needed to that function.

Pinned to the exact alpha version (not a caret range — deliberate for
a pre-release channel) rather than latest, until the fix is promoted.
Added detectAgntcyRuntime()'s optional packageName parameter so the
"package genuinely not installed" fallback path stays covered by a
real regression test now that the real package resolves.

pnpm-lock.yaml regenerated (scoped to this one dependency).
2026-07-31 10:11:41 -04:00
rUv 791d24b36f ADR-380 correction: real @agntcy/slim-bindings package + 2 upstream bugs filed (#2880)
* fix(agntcy): correct SLIM package name to the real @agntcy/slim-bindings

ADR-380's original scaffold guessed a placeholder package name
(@claude-flow/agntcy) and concluded no SLIM SDK existed anywhere — that
check only tried guessed names. The real SLIM Node.js bindings are
published as @agntcy/slim-bindings (v1.4.1 stable), documented for exactly
this plain-Node use case.

It currently fails to load, but for a specific, verified, upstream reason
rather than "doesn't exist": a transitive dependency
(uniffi-bindgen-react-native) ships raw, uncompiled TypeScript as its
package.json "main" with no build output and no exports field — works
inside a bundler (Metro, its primary React Native use case), not plain
Node require/import. Reproduced and confirmed the exact failure
(ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING, not MODULE_NOT_FOUND) via a
real install in isolation. Filed upstream:
- agntcy/slim#1916 (downstream impact — their own README's plain-Node
  example is currently broken)
- jhugman/uniffi-bindgen-react-native#422 (root cause)

detectAgntcyRuntime()'s existing graceful-degradation design already
handles this correctly with zero logic changes needed: its catch-all
branch (anything other than MODULE_NOT_FOUND) surfaces the real error and
falls back to local transport — verified this is exactly what happens
today. Once the upstream packaging bug is fixed, this same code path
starts succeeding with no changes needed here.

Added @agntcy/slim-bindings to optionalDependencies (real package now,
matching ADR-150's optional-only rule ADR-380 §1 follows).

* docs(adr): ADR-380 update section — correct the false 'no packages exist' claim

Documents both real corrections in-place (this repo's convention for a
still-Proposed ADR) rather than silently rewriting history: real
@agntcy/slim-bindings and agntcy-dir packages exist; Directory is now
live-integrated; SLIM is blocked by a specific, filed, upstream packaging
bug, not a missing SDK. Synced to the plugin-bundled copy.

* fix(ci): regenerate v3/pnpm-lock.yaml for the new @agntcy/slim-bindings optionalDependency

'pnpm install --frozen-lockfile' (what CI actually runs) failed after the
previous commit's hand-edit to package.json, since pnpm-lock.yaml was never
regenerated: ERR_PNPM_OUTDATED_LOCKFILE, cascading into ~20 unrelated-looking
CI job failures (every job that installs deps first).

Used a scoped 'pnpm install @agntcy/slim-bindings --lockfile-only --save-optional'
(not a full lockfile regeneration) specifically to avoid an unrelated,
pre-existing issue: @claude-flow/codex is pinned to 3.0.3 in package.json but
only 3.0.2 is actually published, which breaks a full-tree re-resolution but
doesn't block a scoped single-package update. Verified 'pnpm install
--frozen-lockfile' now succeeds cleanly from the correct v3/ workspace root
(the one CI's failing jobs were actually running from — this repo has two
separate pnpm workspaces, root and v3/, and I'd initially checked the wrong
one). As a side effect, the frozen-lockfile install's postinstall step
rebuilt @claude-flow/security's stale dist/, which also cleared the
pre-existing 19 unrelated tsc errors noted throughout this session's earlier
verification passes — tsc --noEmit is now fully clean, not just clean of
agntcy-related errors.
2026-07-31 00:32:45 -04:00
rUv 1e64c2fd3b ADR-378/379/380: npm Trusted Publishing, statusline segments, AGNTCY/Outshift runtime integration (#2879)
* docs(adr): ADR-378/379/380 — npm trusted publishing, statusline segments, AGNTCY runtime integration

Renumbered from the original 322/323/324 (chosen when this branch was
based on an older main) — origin/main has since merged real, Accepted
ADRs at those numbers (322=metaharness-flywheel-integration,
323=typed-memory-provenance, 324=agentic-policy-engine-codex-swarm).
Content unchanged from the original commit, only the numbers and
filenames moved to the next free slot after ADR-377.

ADR-378: npm Trusted Publishing (OIDC) for CI/CD release automation,
replacing the standing NPM_TOKEN class with per-run short-lived
tokens; pairs GCP WIF for the helpers-signing-secret fetch instead of
a static service-account key.

ADR-379: optional context/session/week usage segments and extra
statusline tip lines for .claude/helpers/statusline.cjs, following
the existing CONFIG.hideCost toggle precedent; session/week/effort
default OFF pending a stdin-schema verification spike.

ADR-380: AGNTCY/Outshift runtime integration (SLIM transport, CASA
enforcement, IOC Layer 9 coordination events) as an optional,
removable augmentation per ADR-150's pattern. Companion to metaharness
repo ADR-237 (agent/adr-237-agntcy-outshift-integration branch), which
owns the build-time half (identity, OASF export, observability).

* feat(agntcy): ADR-380 scaffolding — CASA envelope, SLIM/IOC CLI verbs, Rust crate

Renumbered from the original ADR-324 (origin/main has since merged a
real, unrelated, Accepted ADR-324 — see the companion docs commit).
Content and behavior unchanged; only the ADR number/filename and every
in-file reference to it moved to ADR-380.

Implements the buildable-now portion of ADR-380 (AGNTCY/Outshift
runtime integration) as optional, removable augmentation per ADR-150's
pattern. No AGNTCY/SLIM/Outshift npm or crates.io packages exist yet
under any plausible name (verified live) — every network touchpoint is
a clearly-logged stub gated behind explicit config, never a fake
success.

- plugins/ruflo-agntcy/: CASA envelope schema (Zod) + deterministic
  compiler + enforcement gate (checkAuthorization, deny-by-default) +
  bypass-attempt tests; Ed25519-signed CASA decision receipts
  (.swarm/casa-receipts.jsonl); AGNTCY OTel span attribute constants
  this repo owns (coordination.episode, authorization.decision).
- v3/@claude-flow/cli/src/commands/agntcy/: `ruflo transport use slim`,
  `ruflo agent publish`, `ruflo swarm join <namespace>` — wired into
  the top-level command registry and into agent.ts/swarm.ts's existing
  subcommand arrays (merged alongside main's concurrently-added
  pheromoneCommand); all exit 0 with a clear message when AGNTCY/SLIM
  isn't configured (RUFLO_AGNTCY_SLIM_ENDPOINT unset).
- v3/crates/ruflo-agntcy/: Rust CasaEnvelope + check_authorization
  mirroring the TS enforcement logic exactly, LocalTransport (real,
  working, in-process) + SlimTransport stub behind a non-default
  `slim` Cargo feature. Added to the workspace; also fixed a
  pre-existing `cargo check` blocker (v3/plugins/gastown-bridge's
  nested [workspace] table conflicting with the parent workspace) by
  moving it from `members` to `exclude`.

Adversarial security review (as ADR-380 §3 requires before trusting
this gate) found and this commit fixes two real bugs in the first
pass:
- enforce.ts trusted the erased TS type instead of validating its own
  input — a non-array `allow`/`deny` collapsed `.includes()` into
  JS's substring-match overload, and a malformed `now`/`expires_at`
  silently skipped the expiry check instead of failing closed.
  checkAuthorization now runs CasaEnvelopeSchema.safeParse() on entry,
  requires an explicit timezone offset on all timestamps, and treats
  any unparseable timestamp as expired.
- compile.ts's keyword table matched bare "push"/"publish"/"release"/
  "deploy" with no contextual gate, false-permissively granting
  git.push/deployment.create for objectives like "push notification
  integration" or "review the release notes". Patterns now require a
  co-occurring contextual noun (git/commit/branch for push;
  service/app/production/version/etc. for deploy).

Deferred to a follow-up pass (documented, not silently dropped):
plugins/ruflo-agntcy has no package.json yet (this repo's plugins/
ruflo-* convention is Claude Code plugin content, not an npm
workspace package — unlike @metaharness/agntcy's sibling-package
convention on the metaharness side), so no optionalDependencies entry
was added anywhere; adding one now would reference a package shape
that doesn't exist and break installs.

Companion: metaharness repo ADR-237 (agent/adr-237-agntcy-outshift-
integration branch) — build-time half of this integration.

* docs(agntcy): fix stale companion-ADR cross-reference (metaharness ADR-237 -> ADR-240)

Metaharness's own numbering collided too (ADR-237 was already taken by
evals-math-live-gsm8k-fast-domain on their main) and got renumbered to
ADR-240 — this repo's references to it were stale.

* fix(agntcy): add missing allowed-tools frontmatter to agntcy-status skill

CI's fleet-wide SKILL.md frontmatter audit correctly flagged this —
no implicit 'all tools' is allowed. Scoped to Read since this is a
scaffolding stub (not yet implemented) that will only ever need to
inspect local package.json/config state, never execute commands.
2026-07-30 23:05:44 -04:00
rUv fabcc9261a fix(hooks): Codex hooks.json schema + PreToolUse verdict compat (#2857)
Codex's plugin hook-manifest loader accepts only `description` and
`hooks` at the top level and hard-rejects the rest of the manifest on
anything else. plugins/ruflo-core/hooks/hooks.json and
plugins/ruflo-cost-tracker/hooks/hooks.json carried `_note` /
`_platform_note` documentation fields, so a fresh Codex install of
ruflo-core@ruflo (still true as of the currently-published 3.32.38 /
ruflo-core 0.2.5) fails to load the plugin at all with:

  failed to parse plugin hooks config .../hooks.json:
  unknown field `_note`, expected `description` or `hooks`

Fold the doc content into `description` (content preserved, no hook
commands changed) for both marketplace-listed manifests, and add a
strict top-level-key check to
scripts/audit-plugin-hooks-cross-platform.mjs so this class of
regression fails CI going forward. `.claude-plugin/hooks/hooks.json`
is POSIX-only and not marketplace-distributed (not what Codex fetches
for ruflo-core@ruflo); its doc fields are folded too but its
audit-script flags (`_platform`, `_legacy_unaudited_shim`) are kept.

Second, deeper bug once the manifest loads at all: `modify-bash`/
`modify-file` PreToolUse hooks always echo Cursor's
`{"permission":"allow"}` verdict. Codex's own strict output schema
(additionalProperties: false) rejects that shape outright and reports
"hook returned invalid pre-tool-use JSON output" on every single tool
call — verified directly against the real parser
(codex-rs/hooks/src/engine/output_parser.rs,
codex-rs/hooks/src/events/pre_tool_use.rs) and its generated JSON
schema. Empty stdout is the one input Codex's parser treats as
no-opinion/implicit-allow with no error. `isCodexPluginHost()` (added
for #2816, ruflo-core 0.2.5) already detects Codex via its
PLUGIN_ROOT/PLUGIN_DATA env vars and correctly suppresses the verdict
in that case — harden it with a `turn_id`-based fallback (a documented
Codex-only field always present in Codex's PreToolUse input JSON, per
codex-rs/hooks/schema/generated/pre-tool-use.command.input.schema.json)
so detection doesn't depend solely on those env vars being set.

Bump ruflo-core 0.2.5 -> 0.2.6 and ruflo-cost-tracker 0.26.2 -> 0.26.3
so Codex's per-version plugin cache invalidates and re-fetches the
fixed manifest instead of serving a stale cached copy indefinitely.

Fixes #2855, #2856.
2026-07-29 21:36:20 -04:00
rUv 401e02d511 fix: complete reports and consistent initialization for v3.32.37 (#2851)
* fix(metaharness): preserve readiness verdict payloads

* test(metaharness): cover blocked genome verdicts

* fix(adr): parse bullet metadata and relationships (#2659)

* fix(adr): align adr-create with AgentDB schema (#2651)

* fix(adr): make index updates idempotent (#2660)

* fix(memory): bound session-end graph consolidation (#2628)

* fix(memory): align active row visibility (#2652)

* fix(memory): honor database path during init

* fix(hooks): keep all shim fallback tags aligned

* fix(codex): omit unbacked full-template skills

* fix(init): generate complete native dual projects

* test(memory): isolate path and legacy-row regressions

* chore(release): prepare v3.32.37
2026-07-29 15:40:17 -04:00
rUv 67ff9898c0 fix: resolve current runtime and verification defects for v3.32.36 (#2850)
* fix(cli): make runtime status and learning signals truthful

* fix(metaharness): accept padded scan severities

* fix(codex): make core hooks and status skill native

* fix(security): make witness verification hermetic

* fix(metrics): expose grounded learning outcomes

* fix(hooks): deduplicate project and plugin events

* fix(verification): preserve security audit payloads

* fix(runtime): stabilize dual memory skills and MCP schemas

* fix(cli): harden helper signing and reflexion health

* test(release): align helper and container invariants

* chore(release): prepare v3.32.36

* test(funnel): align gates with cold-start seed pool

* fix(ci): document MCP environment precedence
2026-07-29 15:08:51 -04:00
rUv b08246b04a chore(release): bump to 3.32.27 (#2823)
* chore(release): bump to 3.32.27

* chore(release): publish policy security dependency

* chore(release): sign 3.32.27 helper manifest

* ci: harden policy release gates
2026-07-28 22:38:57 -04:00
rUv 495a451cf6 feat: add ADR-324 agentic policy engine (#2822)
* feat(memory): typed provenance in AgentDB (ADR-323)

Corrects and implements the 2026-07-28 dream-cycle research proposal
(PR #2804, closed as superseded) for typed memory provenance in
AgentDB, addressing MemIR/MemSyco-Bench's "provenance-role collapse"
and memory-induced sycophancy findings.

The dream-cycle PR's proposed schema change targeted the wrong table
(`vector_indexes`, which is per-namespace HNSW index metadata, not
per-entry) and its filename collided with the already-merged
`ADR-322-metaharness-flywheel-integration.md` (#2817) — both corrected
here as ADR-323 against `memory_entries`, the real per-record table.

- memory-initializer.ts: `provenance_type` column (user_claim |
  agent_output | system_observation | tool_result | unknown, default
  unknown) on `memory_entries`, migrated for existing DBs via
  `ensureSchemaColumns()`. `storeEntry()`/`searchEntries()` accept and
  validate `provenanceType`/`provenanceFilter`.
- memory-bridge.ts: same wiring for the native better-sqlite3 AgentDB
  v3 path (`bridgeStoreEntry`/`bridgeSearchEntries`).
- commands/memory.ts: `memory store --provenance <type>`, `memory
  search --provenance-filter <types>` (comma-separated, OR semantics).
- mcp-tools/memory-tools.ts + cli-core/mcp-tools/memory-defs.ts:
  `memory_store`'s `provenance_type` param, `memory_search`'s
  `provenance_filter` param (kept in sync across both tool-def sets).

Found and fixed during implementation: an initial design skipped the
RaBitQ/HNSW acceleration paths entirely whenever a provenance filter
was requested (neither carries provenance_type on their candidates).
This reproducibly crashed the CLI process on exit (a libuv assertion
on Windows) after printing correct results. Fixed by never skipping
those paths — RaBitQ now fetches provenance_type in its existing
per-candidate query, HNSW does one batched (namespace, key) lookup
afterward — eliminating the crash across repeated runs. See ADR-323's
"Implementation note" for detail.

7 new end-to-end tests (adr-323-memory-provenance.test.ts), including
a repeated-run regression guard for the crash. Full existing memory
suite re-verified with no regressions (one pre-existing, environment-
specific #2558 test failure confirmed identical against unmodified
main — better-sqlite3's native bridge is unavailable in this dev
environment, unrelated to this change).

Closes the follow-up from dream-cycle PR #2804 (closed as superseded).

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(memory): enforce provenance across retrieval paths

* feat: add agentic policy engine

* fix: align Codex MCP integration

* fix: keep MetaHarness dependencies optional

* fix: declare MetaHarness as optional peers

* fix: sync workspace lockfile

* fix: document policy tool selection guidance

* test: cover governed in-process flywheel

* ci: make MetaHarness absence drill deterministic
2026-07-28 22:12:19 -04:00
rUv 810b13dcd6 fix: tracker-sweep 2026-07-26 (v3.32.10) — 9 bugs + promo seed + follow-ups (#2788)
* fix: tracker sweep 2026-07-26 — 9 bug fixes + promo seed

Closes: #2770 #2774 #2776 #2777 #2781 #2782 #2785 #2786 (Codex follow-up)
Refs:   #2775 (memory upsert semantics)

- Statusline promo row blank on new installs — restored cold-start local
  seed pool that ADR-311 had emptied. Remote Cognitum-served pool
  (funnel.ruv.io/v1/messages) remains authoritative via
  eligibleMessagesFromPools (remote wins by id); seed only fills the
  ≤ 60s cold-start window before first successful remote fetch.
- #2777 — ruflo init no longer imports the entire ruvnet/ruflo repo
  (97 MB, 384 SKILL.md) into .agents/skills/ruflo/. Materializes single
  platform SKILL.md; detects "bloated" prior install and re-materializes.
- #2781 — ruflo-adr adr-index no longer silently drops ADR data: status
  regex accepts "- **Status**: proposed"; single-line and wrapped
  relation lines parse fully; CLI_CORE=1 warns and unifies namespace.
- #2770 — Windows: browser-session MCP tools + two init execFileSync npx
  sites now set shell: process.platform === 'win32' so cmd.exe resolves
  npx.cmd. POSIX behavior unchanged.
- #2782 — WorkerDaemon.saveState + autopilot-state.saveState + appendLog
  no longer race on shared .tmp filename; all three call writeFileAtomic
  (pid + timestamp + random-suffixed temp).
- #2785 — ruflo hooks post-task accepts --task/-t and --store-results
  flags matching CLAUDE.md-documented usage; routing outcomes finally
  persist to the namespace hooks_metrics reads.
- #2786 — AgentDB no longer silently fails to initialize under
  CLAUDE_FLOW_ENCRYPT_AT_REST=1. New getAgentDbPath() returns
  agentdb-memory.db in the same directory as memory.db, so the
  ControllerRegistry native better-sqlite3 opens a distinct file from
  the sql.js CRUD writer's encrypted memory.db.
- #2776 — Statusline security STALE/IN_PROGRESS branches reachable via
  local overlay recomputing freshness on every render from
  .claude/security-scans/scan-*.json. Env: RUFLO_SCAN_STALE_HOURS (24),
  RUFLO_SCAN_PENDING_CAP_MIN (30). STALE renders dim gray.
- #2774 — Codex MCP generator registers dedicated stdio server binary
  claude-flow-mcp instead of the management CLI ruflo mcp start that
  never answers initialize. All 7 wrong-command sites fixed
  (initializer.ts + 6 template sites in generators/config-toml.ts + 2
  in migrations/index.ts + generators.test.ts:653 assertion).
- #2775 — Memory store to existing key no longer dead-ends:
  bridgeStoreEntry uses INSERT ... ON CONFLICT with tombstone
  auto-resurrect; UNIQUE returns typed error instead of null (no more
  misleading demotion into #2735 guard); bridgeDeleteEntry runs
  wal_checkpoint(PASSIVE); CLI memory store --upsert default
  materialized locally (parser.applyDefaults bug).
- Stale ruflo-rebrand assertion in codex/tests/generators.test.ts:192
  updated Co-Authored-By: claude-flow → ruflo-bot.

- #2786 fix-2/3 (hooks_metrics reads dead .claude-flow/memory/store.json;
  bridgeRecordFeedback calls non-existent agentdb API) — architectural
  rewire, needs separate PR.
- #2775 defect #3 root cause (parser.ts applyDefaults doesn't apply
  subcommand-declared defaults) — flagged by agent, other subcommands
  affected too.
- #2775 memory-tools.ts MCP tool schema still defaults upsert:false
  (parity fix flagged).

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(parser): applyDefaults now walks subcommand + command options (#2775 follow-up)

Previously only `this.globalOptions` were walked. Any subcommand-declared
`default: <value>` silently dropped and the action handler received
`undefined`. The immediate victim was `memory store --upsert` (per #2775),
but the bug is generic — every subcommand that relies on a per-flag
default was affected.

Fix: applyDefaults now accepts optional command + subcommand, walks all
three layers narrow-to-broad (subcommand > command > global), and the
first layer to supply a value wins (because it only writes when the flag
is `undefined`, so earlier writers stick).

Parser tests: 52/52 pass. Build: tsc exit 0.

Refs: #2775 (root cause behind the CLI --upsert workaround already in
`commands/memory.ts`; the workaround is now redundant but harmless).

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(mcp): memory_store defaults upsert=true for CLI parity (#2775 follow-up)

The CLI `memory store` command defaults `--upsert=true` (issue #2594, fix
c36cb4d66) so `store → delete → store` on the same (namespace, key)
doesn't trip the `UNIQUE(namespace, key)` constraint against a
soft-deleted row. The `memory_store` MCP tool schema still defaulted
`upsert: false`, so every agent-facing store-to-existing-key hit the
strict-insert path unless the caller explicitly passed `upsert: true` —
different behavior than the CLI documented.

Fix: schema description now states default true, handler reads
`input.upsert !== false` (only explicit `upsert: false` opts out).
Symmetric with the CLI-side workaround in `commands/memory.ts`.

Refs: #2775 (MCP parity follow-up flagged by the sweep agent).

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(changelog): re-scope tracker sweep to 3.32.10 + reflect final commit set

- Rename header 3.28.1 → 3.32.10 (patch on the actual current 3.32.9;
  the original 3.28.1 heading was left over from the stale-base draft
  before the rebase onto origin/main).
- Remove the #2774 bullet from the "Fixed" list — that fix was reverted
  during rebase (upstream d20f1323b superseded it and the reporter's
  diagnosis appears incorrect). Move #2774 to a new "Investigated"
  subsection with the analysis and a note recommending closure.
- Fold the #2775 parser follow-up (parser.ts:applyDefaults walks
  subcommand + command options) and the MCP parity follow-up
  (memory_store default upsert=true) into the #2775 bullet, since
  they're the same defect surfacing at three layers.
- Drop the "stale rebrand test assertion" bullet — that assertion is
  now on origin/main already; no notable delta in this PR.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(hooks): dual-write routing decisions to JSON store for metrics reader (#2786 fix-2)

Previously `hooks_post-task --store-results` wrote routing outcomes to
AgentDB namespace `patterns` via `storeFn`, but the sync reader
`getIntelligenceStatsFromMemory()` only reads
`.claude-flow/memory/store.json`. Result: the "Pattern Learning" and
"Agent Routing" numbers in `hooks_metrics` stayed at zero even when
routing decisions were being recorded.

Fix: after the AgentDB write, mirror the same entry into the JSON store
with `metadata.type = 'routing-decision'` so the reader's filter
picks it up. AgentDB stays authoritative for cross-session retrieval;
the JSON store is the counter surface the sync reader depends on. Both
writes are wrapped in try/catch so a failure at either layer doesn't
break `hooks_post-task`.

Same shape as the existing dual-write pattern for `hooks_post-command`
at line 910-931. Keeps `getIntelligenceStatsFromMemory` synchronous
(would require async ripple through 3 callers otherwise).

Refs: #2786 fix-2 (flagged as architectural by the sweep agent — turns
out to be one bounded additive write, not a rewire).

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(parser): regression coverage for command + subcommand defaults (#2775)

Three new tests in the existing `defaults` describe block:
- command-level `option.default` applies to a flagless invocation
- subcommand-level `option.default` applies to a subcommand invocation
  (this is the exact shape that trapped `memory store --upsert`)
- explicit `--no-upsert` still overrides the default:true

Guards against a re-regression of parser.ts:applyDefaults collapsing
back to only walking `globalOptions`.

52/52 → 55/55 parser tests pass.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(hooks): regression guard for #2786 fix-2 JSON dual-write

New test file `hooks-post-task-routing-dual-write-2786.test.ts` runs
`hooksPostTask.handler` in a temp cwd and asserts that when called with
`storeDecisions=true`:

  - `.claude-flow/memory/store.json` is created
  - It contains a `routing-decision:<taskId>` entry
  - The entry's shape matches what `getIntelligenceStatsFromMemory()`
    filters on (`key.includes('routing') || metadata.type === 'routing-decision'`)
  - `metadata.confidence` maps from `quality` (used to compute avgConfidence)
  - `namespace` is `patterns` (parity with the AgentDB write)

Second test guards backwards compat: when `storeDecisions` is omitted,
no routing-decision entry appears in the JSON store.

Bridge is mocked (the AgentDB path's real behavior is covered elsewhere).

2/2 pass.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(funnel): regression coverage for cold-start seed pool

New test file `funnel-messages-seed-pool.test.ts` asserts the seven
invariants of the 2026-07-26 cold-start pool restoration:
  - MESSAGES is non-empty
  - contains ≥1 disclosure so the disclosure gate can unlock cold
  - every disclosure carries the exact ' · manage: ruflo settings' tail
    (ADR-301 invariant)
  - every seed message passes isValidMessage() (schema / host allowlist
    / control-char strip / 80-col cap)
  - every URL is on the exact-host allowlist (no third-party leaks)
  - contains ≥1 educational (4-in-5 rotation slots)
  - all ids are unique (rotation dedups on id)

7/7 pass. Guards against a re-empty of MESSAGES that would blank the
statusline promo row again on new installs.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(funnel): disclosure gate now consults local seed pool (E2E follow-up)

E2E validation of the 2026-07-26 sweep exposed a gap: on true cold
start (no remote cache, no network), the promo row stayed blank
because `selectDisclosureMessage` only read `getRemoteMessages()` —
my local seed disclosure in messages.ts never reached the disclosure
gate, so the gate stayed `never_seen` forever.

Fix: `getDisclosureMessagePool` now merges local `MESSAGES` with the
remote pool via the same `eligibleMessagesFromPools` helper rotation
uses (remote wins by id). This mirrors the design of the rotation
selector — the seed is a cold-start bootstrap, remote takes over as
soon as its cache populates.

Verified end-to-end: cleared funnel cache + pointed
`RUFLO_FUNNEL_MESSAGES_ENDPOINT` at an unreachable host; statusline
render emits the local disclosure ("Ruflo shows occasional tips and
sponsor notes here · manage: ruflo settings") — exactly the string
seeded in messages.ts (`local.disclosure.v1`).

Refs: 2026-07-26 tracker sweep, promo row cold-start.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-26 15:05:33 -07:00
ruvnet bf8e766875 fix(witness): re-anchor #1862 marker + refresh manifests after #2721
The #2721 hooks.json rewrite legitimately moved the -f/-s CLI-flag
construction logic out of hooks.json and into scripts/ruflo-hook.cjs,
which drifted the #1862 witness marker (it cited the literal bash
string that no longer exists). Re-anchored the marker to the new
location: plugins/ruflo-core/scripts/ruflo-hook.cjs, marker
"['-f', String(file), '-s', 'true']" -- the actual flag-construction
line that replaced the old bash string.

Also did a full witness regen for all three manifests (linux/macos/
windows) against a properly built tree, refreshing the 14 other
entries that had gone stale (sha256 no longer matching current file
content, though their marker text was still verified present --
"drift" per verify.mjs's own terminology, not a real regression: only
"regressed" i.e. marker text actually missing counts as a failure).
Confirmed those 14 are pre-existing on main, unrelated to this PR
(zero diff against origin/main for every file they cite), and were
already recorded as unverified in the previously-committed manifest --
this regen doesn't mask or fix any of them, it just brings sha256
current. All three manifests now show pass=117/drift=0/regressed=0/
missing=0.

(Regenerated with an OS-aware variant of regen.mjs's own algorithm,
since the tool's osDir() stamps whatever OS it's actually run on --
regenerating linux/macos manifests from this Windows machine via the
CLI as-is would have mislabeled their "os" field.)

Also fixed plugins/ruflo-cost-tracker/scripts/smoke.sh's step 28b,
which asserted literal substrings (track.mjs, TRACK_QUIET=1, || true)
directly in hooks.json -- true before #2721, no longer true now that
those live one level down in ruflo-hook.cjs. Updated to verify the
indirection is actually wired (hooks.json references ruflo-hook.cjs,
which references track.mjs/TRACK_QUIET/a resilient always-exit-0 done()).
2026-07-18 19:43:23 -04:00
ruvnet b68ad4ccba fix(plugins): make ruflo-core/ruflo-cost-tracker hooks Windows-native (#2721)
Both plugins' hooks.json wrapped every command in `/bin/bash -c '...'`,
which fails outright on native Windows (no such path) -- Codex/Claude
Code report "PreToolUse hook (failed) -- exit code 1" on every tool
call. The `_platform: posix` / "ruflo init overrides this on Windows"
claim in both files was never actually true: Claude Code merges
plugin-declared hooks additively with any init-generated
.claude/settings.json, it doesn't replace them, and there's no `ruflo
init` step at all in the reported Codex marketplace install flow.

Fix: every hook command is now a `node -e` bootstrap that resolves
plugins/*/scripts/ruflo-hook.cjs from process.env.CLAUDE_PLUGIN_ROOT
inside Node -- no shell env-var expansion (${VAR} vs %VAR%), so the
exact same command string runs unchanged on Windows/macOS/Linux.

ruflo-core's ruflo-hook.cjs (previously a full port of ruflo-hook.sh
that existed on disk but was never referenced by hooks.json) gained:
  - JSON parsing of the hook event from stdin (replaces jq) for
    post-command/post-edit, deriving the same CLI flags the bash
    version computed
  - the PreToolUse permission-allow stdout echo Cursor's stricter
    contract requires (previously only the bash wrapper's trailing
    printf did this)
  - precompact-manual/precompact-auto guidance text (previously plain
    bash echoes, no CLI call)
  - a real Windows shell-quoting fix: shell:true with an args array
    does NOT quote array elements, so "echo hi" silently truncated to
    "echo" and a heredoc's `<<` errored as unexpected -- skip the
    shell entirely for `node` invocations (never a .cmd shim, so
    CreateProcess gets the argv array byte-for-byte)

cost-tracker's existing ruflo-hook.cjs (already correct, just
orphaned) needed no logic changes, only wiring.

Also:
  - corrected the false "_platform_note" claims about ruflo init
    overriding plugin hooks
  - hardened scripts/audit-plugin-hooks-cross-platform.mjs: a
    POSIX-exempt hooks.json now must actually reference its sibling
    .cjs shim, not just have one sitting on disk unreferenced (which
    is exactly the shape cost-tracker shipped in undetected)
  - added windows-latest to the plugin-hooks-smoke CI matrix (it was
    ubuntu/macos-only because the old bash-based hooks.json couldn't
    run on Windows at all) and rewrote test-hooks.mjs to drive hooks.json's
    literal command strings via `shell: true` -- exactly how Claude
    Code/Codex invoke them -- instead of wrapping everything in an
    explicit `bash -c` that could never have caught this bug
  - flagged (not fixed) a separate, currently-published, actively
    maintained plugin package (.claude-plugin/ + plugin/, the older
    "claude-flow" plugin, not listed in the ruflo marketplace) with
    the same underlying bug via jq/xargs pipes instead of bash --
    explicitly marked _legacy_unaudited_shim so the hardened audit
    doesn't silently regress on out-of-scope work

Verified locally on native Windows (this fix's actual target
platform): all 17 ruflo-core hook cases pass, all 3 cost-tracker
cases pass, the existing 12-case smoke-ruflo-hook-cjs.mjs passes
unchanged, both hook-command audits pass clean.

Fixes #2721
2026-07-18 19:05:06 -04:00
ruvnet 1fb874005c test(plugins): preserve standalone MCP catalog assertions 2026-07-16 23:20:35 -04:00
ruvnet 5e66f065e9 test(plugins): align namespace and stable hook shims 2026-07-16 23:14:09 -04:00
ruvnet e332689b8c fix(release): harden hooks, statusline, security, and plugin MCP integration 2026-07-16 23:06:29 -04:00
ruvnet 49b36d7acd fix(cost): preserve memory JSON across Windows npx 2026-07-16 19:35:06 -04:00
ruvnet 25b16cfdf2 test(cost): confirm npx installs in fixtures 2026-07-16 19:18:10 -04:00
ruvnet 72875da937 chore(release): prepare stable Ruflo 3.32.1 2026-07-16 18:58:55 -04:00
ruvnet d20f1323b1 fix(codex): ship stable Windows-safe Ruflo integration 2026-07-16 18:48:10 -04:00
rUv dc015986dc feat(adr): reconcile deleted ADRs — hard-delete primitive + drop-and-rebuild reindex (#2668)
* feat(adr): reconcile deleted ADRs — hard-delete primitive + drop-and-rebuild reindex

Closes #2666. `adr-index` can only add/upsert; deleting an ADR file (or a
relation line from a surviving one) left an orphan row in `adr-patterns`/
`adr-edges` forever, and `adr-verify` certified the result as healthy since
an orphan has no dangling ref and forms no cycle.

Root cause traced into @claude-flow/cli itself, not just this plugin:
`memory delete` only ever soft-deletes (status='deleted'), so the row keeps
occupying its UNIQUE(namespace, key) slot and blocks a later non-upsert
re-store (#2652). `memory cleanup` reaps by age/TTL/quality, not by
"does the source still exist". Neither the AgentDB v3 bridge nor the sql.js
fallback exposed any hard, namespace-scoped delete anywhere in the CLI/MCP
surface. Separately, import.mjs/verify.mjs's spawnSync calls never passed
`cwd`, so ADR_ROOT scanned the right files but could write to the wrong
`.swarm/memory.db`.

CLI (@claude-flow/cli):
- memory-bridge.ts: bridgePurgeNamespace() — real DELETE FROM memory_entries
  WHERE namespace = ? against the live bridge SQLite handle, routed through
  the existing MutationGuard/AttestationLog hooks.
- memory-initializer.ts: purgeNamespace() (bridge-then-sql.js-fallback, same
  shape as deleteEntry) + withMemoryDbLock() (new <dbPath>.lock O_EXCL
  advisory lock, same stale-takeover pattern as global-ai-budget.ts —
  protects against a second concurrent purge/delete on the same file; does
  not close #2621, which needs every writer to adopt the same lock).
- commands/memory.ts: `memory purge --namespace <ns> --force [--dry-run]` —
  requires --namespace explicitly (no default), confirms unless --force.
- package.json: sql.js was already imported directly by
  memory-initializer.ts's fallback path but never declared as a dependency
  — worked by accident under npm's loose hoisting, silently broken under
  this repo's strict pnpm workspace (confirmed: unmodified deleteEntry/
  storeEntry fail identically). Declared explicitly.

Plugin (ruflo-adr):
- scripts/reindex.mjs (new) — purge both namespaces via `memory purge`,
  rescan every ADR currently on disk, store fresh, then re-list and assert
  the count equals files-on-disk (a storedRecords tally can't see a
  concurrent-writer clobber of the purge; a fresh recount can). Exits
  non-zero on failure. Full-namespace wipe rather than a selective orphan
  diff, since adr-edges keys always carry a fresh timestamp-rand suffix and
  are never deduplicated across imports — a selective pass would still
  leave duplicate edges accumulating for ADRs that do still exist.
- scripts/lib/parse-adrs.mjs (new) — findAdrs/parseAdr and sub-parsers
  extracted from import.mjs so reindex.mjs doesn't duplicate ~150 lines of
  dual-format parsing; import.mjs shrank 322 -> 156 lines, same dry-run
  output verified before/after (530 ADRs, same status/relation counts).
- import.mjs/verify.mjs: every npx subprocess call now passes `cwd: ROOT`.
- New `/adr-reindex` skill; adr-index/adr-verify SKILL.md cross-link it.
- ADR-0002 records the decision. plugin.json 0.3.0 -> 0.4.0.

Verified end-to-end against a scratch repo + local CLI build: 2 ADRs
indexed, one deleted from disk, re-run adr-index -> orphan survives
(bug reproduced exactly as filed), run adr-reindex -> orphan gone,
post-condition OK, exit 0.

New tests: __tests__/memory-purge-namespace-2666.test.ts (8 cases) —
namespace-scoped hard delete, cross-namespace isolation, the specific
#2652 regression (non-upsert re-store of a purged key must succeed),
invalid-namespace rejection, lock acquire/release/stale-takeover/
serialization. smoke.sh: 15 -> 21 checks.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): restore sql.js lockfile entry lost during local drift verification

A `git stash` + reinstall done while investigating unrelated pre-existing
pnpm-lock.yaml drift (verifying it wasn't caused by this branch) ran with
@claude-flow/cli/package.json temporarily reverted, regenerating
v3/pnpm-lock.yaml without the sql.js entry the prior commit added.
`git stash pop` restored package.json but not the lockfile state written
while the stash was active, so the previous commit's lockfile silently
lost that entry — CI's `pnpm install --frozen-lockfile` correctly caught
the drift (ERR_PNPM_OUTDATED_LOCKFILE). Re-ran `pnpm install` with
package.json in its correct state; frozen-lockfile install now passes
clean locally, matching CI's exact step.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-14 10:51:11 -04:00
ruv a02b46e4fb fix(verification): unblock ADR-104 and witness source checks 2026-07-09 10:40:08 -04:00
rUv 1d5b06e4a2 fix(federation): unblock main — TS shim types + dispatcher import + witness re-sign (#2604)
* fix(federation): sync shim types + guard optional close() to unblock Build V3

Fixes 7 TS errors on main that block Build V3 across all 3 platforms:
- src/transport/midstream-aware-loader.ts:39-52 — local AgentMessage/
  AgentTransport shim was over-minimized by #2578. Restored required
  {id, type, payload, metadata?} on AgentMessage and made send/onMessage
  2-arg (address, message) to match how plugin.ts actually calls them.
- src/transport/midstream-aware-loader.ts:84 — cast dynamic import()
  through `unknown` since upstream's exported AgentTransport carries a
  richer InboundMessageHandler surface we intentionally hide behind the
  minimal shim.
- src/plugin.ts:481 — this.transport.close is optional (`?`) on the
  shim; guard with optional chaining `close?.()`.

Local tsc build clean after fix.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(federation): dispatcher import + re-sign witness manifests (3.25.3 drift)

Two more preexisting main-red root causes, in the same fix:

(1) inbound-dispatcher.ts:22 still imported `AgentMessage` from
    `agentic-flow/transport/loader` — the exact phantom subpath #2578
    eliminated in midstream-aware-loader.ts. Repointed to the local
    shim. This is the residual site #2578 missed; the graph schema smoke
    (ADR-130 P1) failure was a cascade of this same TS type collision.

(2) Re-signed verification/{linux,macos,windows}/manifest.md.json via
    plugins/ruflo-core/scripts/witness/regen.mjs. The 3.25.3 release
    (PR #2602) edited the ADR-104-transport `desc` string in all 3 per-OS
    manifests but did not re-run regen.mjs, so the stored
    integrity.manifestHash no longer matched sha256(JSON.stringify(
    manifest)) and Witness verify exited 1 across all 3 platforms.
    Manifests now verify cleanly (117 pass, 0 drift, 0 regressed).
    Note: witness/regen.mjs takes --manifest + --history and re-signs
    deterministically from manifest.gitCommit; already wired but the
    3.25.3 release process skipped it.

Local verify: pass=117 drift=0 regressed=0 missing=0 on all 3 OSes.
Local tsc build: clean.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(hooks): PreToolUse hook emits valid JSON verdict (#2613)

Cursor imports third-party Claude Code hooks under its stricter
`preToolUse` contract, which requires stdout to be a valid JSON
permission verdict (`{"permission":"allow"|"deny"|"ask"}`) and
fail-closes on any other text. The prior PreToolUse commands ran the
ruflo hook shim which prints telemetry to stdout — Cursor then blocked
every Bash/Write/Edit tool call across every workspace.

Fix:
1. ruflo-hook.sh (all three copies) — redirect stdout to /dev/null in
   addition to stderr. Claude Code doesn't consume this stdout either,
   so silencing it is a pure cleanup with no functional cost.
2. hooks.json PreToolUse commands — after invoking the shim, always
   `printf` a well-formed `{"permission":"allow"}` verdict. Valid for
   both Claude Code's JSON-hook contract and Cursor's stricter
   preToolUse contract, so telemetry keeps working while nothing is
   blocked. Also guard `${CLAUDE_PLUGIN_ROOT:-}` so an unset env var
   collapses to the safe empty branch instead of a bash parse error.

Simulated Cursor invocation (CLAUDE_PLUGIN_ROOT unset) now emits:
  {"permission":"allow"}

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): indirect optional-dep imports in cli — @ruvector/*, @metaharness/router (#2608)

Same install-safety anti-pattern as #2586's prime-radiant fix, now
surfaced in @claude-flow/cli once plugin-agent-federation's TS2307
stopped masking it (630034ca4 on this branch). Build V3 fails with
"Cannot find module '@ruvector/learning-wasm'" and
"Cannot find module '@metaharness/router'" whenever the optional deps
aren't installed — which is the case in the install-safety /
Build V3 configurations.

Every call site is already runtime-guarded (try/catch, availability
probes). But a literal `await import('@ruvector/learning-wasm')` or a
`typeof import('@metaharness/router')` type expression forces tsc to
statically resolve the specifier at build time, so TS2307 fires when
the package is absent.

Fix: route the specifier through a string-typed variable
(`const pkg: string = '...'; await import(pkg)`) — same idiom as the
optional better-sqlite3 loader and #2586. Runtime behaviour unchanged:
the package still loads when present, still falls back cleanly when
absent.

Also replaced services/ruvector-training.ts's static `import type
{ WasmMicroLoRA, ... } from '@ruvector/learning-wasm'` with local
`type X = any` aliases (mirrors the existing FlashAttention et al.
pattern a few lines below).

Verified: with @ruvector/learning-wasm, @ruvector/attention, and
@metaharness/router hidden locally (matching the CI condition), the
cli package now compiles clean (tsc, 0 TS2307). plugin-agent-federation
remains clean as well.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(smoke): add metaharness optionalDep to ruflo wrapper (ADR-150 constraint #2)

The all-plugins-smoke, metaharness-ci `score`, and no-metaharness-smoke
CI jobs all fail on the same single step (ruflo-metaharness smoke #16):

  → 16. ruflo wrapper has metaharness in optionalDependencies
        (architectural constraint #2) ... FAIL:
        ruflo wrapper missing metaharness optionalDep

ADR-150 §"Architectural constraint" rule #2 requires `metaharness` and
`@metaharness/*` packages to live in `optionalDependencies` (never
`dependencies`) so ruflo remains operational when they're removed. The
CLI already satisfies this; the wrapper package.json had an empty
`optionalDependencies` block.

Pin to `~0.3.1` — the current npm-published `metaharness@latest`, and
the same range family used by CLAUDE.md's `@metaharness/router@~0.3.2`
pin. Wrapper install remains graceful-fail when the optional dep is
absent (that's the whole point of ADR-150's removable rule #1).

Unblocks PR #2604 merge. Only 3 CI failures on the branch — all
the same rule — so this single change flips them green.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(smoke): reconcile ADR-150 rule #2 with #2561 npx-startup budget

Two CI gates were mutually incompatible on this PR:

- ruflo-metaharness smoke #16 (older): asserted \`metaharness\` MUST be
  in \`ruflo/package.json\` optionalDependencies.
- guard #2561 (newer): enforces \`RUFLO_MAX=0\` optionalDependencies
  AND FORBIDs \`metaharness\` specifically, because installing it as a
  ruflo postinstall dep timed out the \`npx -y ruflo@alpha --version\`
  fast-path (measured incident).

Adding \`metaharness\` to ruflo optDeps satisfies #16 but breaks #2561;
removing it satisfies #2561 but leaves #16 red (pre-existing main state).

Fix: correct the smoke #16 rule. ADR-150 §"Architectural constraint"
rule #2 says "when \`@metaharness/*\` or \`metaharness\` appears in a
package's dep graph, it MUST be in \`optionalDependencies\`, never
\`dependencies\`". It does NOT require ruflo to depend on metaharness.
The old check was over-restrictive — it demanded PRESENCE, not just
correct SHAPE-when-present. The new check enforces the actual invariant:
no \`metaharness\` / \`@metaharness/*\` in ruflo's hard \`dependencies\`.
Absence is fine (ruflo delegates to @claude-flow/cli, which keeps
metaharness as a runtime dynamic import via ADR-150 rule #1).

#2561 is stronger evidence (proven cold-npx timeout) than the old #16
assertion, so the correction goes to #16.

Verified locally:
  smoke #16 PASS (new logic)
  guard #2561 PASS

Also reverts the ruflo/package.json optionalDeps change that briefly
tried to satisfy the old #16 rule — the wrapper stays clean.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(init): heal duplicate MCP registrations without breaking plugin refs (#2612)

Users who followed pre-rename setup docs ended up with two independent
MCP server registrations pointing at the same binary — one keyed
`claude-flow`, one keyed `ruflo` — doubling Claude Code's tool-schema
load (~350 duplicated tools) with zero added capability. Bug #2612.

The naive fix (rename the canonical key from `claude-flow` → `ruflo`)
silently re-opens #2206 (closed), because ~166 plugin files across
plugins/**/{agents,commands,skills}/*.md reference tools with the
`mcp__claude-flow__*` prefix. Claude Code derives the tool prefix from
the MCP server key; flipping the key to `ruflo` renames every plugin
tool to `mcp__ruflo__*` and every one of those 166 refs errors with
"Unknown tool".

Correct reconciliation:

  - Canonical MCP key STAYS `claude-flow` (preserves #2206 for the 166
    plugin refs). The binary invoked is `ruflo@latest` — only the
    registration name is legacy.
  - Duplicate is HEALED via `ruflo doctor`, not by renaming: detect
    coexisting `ruflo`-keyed + `claude-flow`-keyed registrations for
    the same binary, warn, and instruct the operator to remove the
    `ruflo`-keyed one.

Changes:

  - src/init/mcp-generator.ts — server key restored to `claude-flow`;
    manual `claude mcp add …` commands restored to name `claude-flow`.
    Comment now explains the #2206/#2612 duality.
  - src/init/executor.ts — writeMCPConfig skip-comment updated to
    reflect that `claude-flow` is canonical and `ruflo`-keyed is the
    legacy duplicate form; existing detection accepts both keys (was
    already correct).
  - src/init/claudemd-generator.ts — generated CLAUDE.md `Setup`
    section restored to `claude mcp add claude-flow -- …`.
  - src/commands/doctor.ts — inherit the concurrent session's
    duplicate-detection logic (rufloLocations / duplicateLocations /
    legacyLocations / currentLocations), but flip which key is
    treated as canonical: `claude-flow`/`claude-flow_alpha` are
    current, `ruflo`/`ruflo_alpha` are legacy. Fix-message now
    references removing the `ruflo`-keyed entry and cites #2206.
  - src/commands/swarm.ts — help output uses `claude mcp add
    claude-flow` again.
  - __tests__/issue-2612-mcp-rename.test.ts — regression guard for the
    duplicate-detection warning; header comment documents the
    canonical/legacy roles matching the flipped semantics.
  - CLAUDE.md / v3/@claude-flow/cli/CLAUDE.md / v3/@claude-flow/mcp/CLAUDE.md /
    v3/implementation/init/API.md — kept the binary-update from
    `@claude-flow/cli@latest` → `ruflo@latest` (correct, this IS the
    post-rename wrapper), but restored the MCP server KEY to
    `claude-flow` in every example line.

Verified:
  - tsc clean (CLI package)
  - all 70 test files / 726 tests pass locally, including:
    - __tests__/init-wizard-bugs.test.ts (#2206 canonical key + #2207 dual-key detection)
    - __tests__/stale-mcp-key-2369.test.ts (10 tests, unchanged)
    - __tests__/issue-2612-mcp-rename.test.ts (new — asserts doctor
      surfaces "Duplicate Ruflo MCP registrations found" with legacy
      `ruflo` + current `claude-flow`)
  - Manual doctor run against a synthetic .mcp.json/.claude.json pair
    surfaces the exact warning + remediation string.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-09 10:16:55 -04:00
rUv 11f34ec947 chore(release): 3.25.3 — 10 fixes + CI guards (#2602)
* fix(ci): neural status ReasoningBank row shows Empty despite persisted patterns (#2575)

Derive the Status cell from the same count Details displays so the label
matches the number (7798 patterns → Active) instead of the stale in-memory
handle.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): default memory store --upsert=true to fix store→delete→store UNIQUE violation (#2594)

memory_entries has UNIQUE(namespace, key) that does not exclude soft-deleted rows,
so a store→delete→store cycle reliably hits UNIQUE constraint failed when --upsert
defaults to false. storeEntry() already honors upsert via INSERT OR REPLACE; flipping
the CLI default closes the footgun without touching schema or plumbing.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): remove reverse-substring collision in GAIA isAnswerCorrect (#2566)

The reverse check normExpected.includes(normModel) scored fragmentary model
answers as correct whenever they normalized to any substring of the expected
answer (e.g. "a" vs "Paris, France" → true), inflating GAIA scores via
normalization collision. ADR-169 R1 forbids this vector. Forward-substring
and numeric-tolerance paths remain.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): skip onnxruntime-node postinstall to unblock v3 memory smoke (#2590)

onnxruntime-node's postinstall fetches a GPU nupkg from nuget.org which
ETIMEDOUTs from GitHub runners, taking pnpm install down. Skip it via
neverBuiltDependencies (CPU prebuilds ship in the npm tarball, so runtime
is unaffected) and pass --ignore-scripts in the memory smoke workflow as a
belt-and-braces safety net.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): drop verifyMemoryInit writeback that races better-sqlite3 handle on Windows (#2596)

sql.js verification holds an in-memory DB copy; writing it back via
atomic rename fights the open better-sqlite3 WAL handle owned by
ControllerRegistry / repairVectorIndexes, producing EPERM on Windows.
Verification is read-only — close and discard the copy instead.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): auto-heal doctor Learning Bridge sidecar on plain run (#2599)

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): prune CLI optionalDependencies to fix cold npx timeout (#2561)

Cold `npx -y @claude-flow/cli@alpha --version` (and the ruflo wrapper)
timed out because npm had to resolve and place ~30 optional deps before
Node ever ran the in-process --version fast-path in bin/cli.js. Trim
optionalDependencies to the 5 actually used by the default CLI path
(agentdb, ruvector, agentic-flow, @claude-flow/memory, @claude-flow/security);
everything else is already gated behind try/require or the plugins-store
lazy-install path. Mirror the pruning in the ruflo wrapper. Add a
pre-warm npm install step in verification-pipeline.yml as defense-in-depth.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): align ruflo-hook.cjs npx fallback with .sh dist-tag (#2600)

The three shipped .cjs Windows shims hardcoded ruflo@latest while the bash
shim used ruflo@alpha, breaking the "mirrors ruflo-hook.sh" contract from
#2132. Point all three .cjs shims at ruflo@alpha and extend the smoke
test with a static parity assertion so future drift fails CI.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): replace phantom agentic-flow/transport/loader marker in ADR-104 witness (#2578)

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): re-sign + verify helpers manifest in prepublishOnly (#2593)

Manifest drift (intelligence.cjs was bumped in 3.24/3.25 but the manifest
still carried the 3.23.0 hash) triggered writeCriticalHelpers' fail-closed
tamper warning on every CLI run in stamped projects. Root cause: sign-helpers.mjs
existed but was never wired into publish. This wires sign+verify into
prepublishOnly so drift fails the release, not every user.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2590): guard against regression of #2590

Adds a CI guard step to the memory-smoke job that fails fast if either
part of the #2590 fix regresses:
  1) v3/package.json drops "onnxruntime-node" from pnpm.neverBuiltDependencies
  2) the memory-smoke pnpm install loses --ignore-scripts

Either regression would let onnxruntime-node's postinstall fetch a GPU
nupkg from nuget.org and ETIMEDOUT on GitHub runners, taking pnpm install
down as it did on main.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2594): guard against regression of #2594

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2561): guard against regression of #2561

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2593): guard against regression of #2593

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2596): guard against regression of #2596

Adds a vitest regression test asserting verifyMemoryInit() does not
modify the on-disk DB file — snapshots bytes+mtime before, calls verify,
asserts unchanged after. Fails if the sql.js writeback is re-added
(bytes change from re-serialization, mtime bumps), passes on the fix.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2566): guard against regression of #2566

Locks in the removal of the reverse-substring branch in
isAnswerCorrect() (v3/@claude-flow/cli/src/benchmarks/gaia-agent.ts).
The reverse rule `normExpected.includes(normModel)` scored fragmentary
model answers (e.g. "a") as correct against any longer expected answer
that contained them ("Paris, France"), inflating GAIA scores via
normalization-collision — the vector ADR-167/169 R1 forbid.

Verified: FAILS if the reverse-substring branch is reintroduced,
PASSES on the current fix.

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2578): guard against regression of #2578

Co-Authored-By: RuFlo <ruv@ruv.net>

* test(2599): guard against regression of #2599

Co-Authored-By: RuFlo <ruv@ruv.net>

* chore(release): 3.25.3 — 10 fixes + CI guards

Bumps @claude-flow/cli, claude-flow (umbrella), ruflo (wrapper) to 3.25.3.
Regens v3 pnpm-lock.yaml after optionalDependencies pruning (#2561) and
neverBuiltDependencies additions (#2590).

Fixes included:
- #2561 npx cold-install timeout — pruned CLI optionalDependencies
- #2566 GAIA isAnswerCorrect reverse-substring collision
- #2575 neural status ReasoningBank Empty vs 7798 patterns display
- #2578 ADR-104 phantom agentic-flow/transport/loader witness marker
- #2590 CI Node24/ubuntu memory smoke — onnxruntime-node postinstall block
- #2593 helpers.manifest.json auto-refresh — verify + sign in prepublishOnly
- #2594 memory store UNIQUE violation — flip --upsert default to true
- #2596 memory init Windows EPERM — drop sql.js writeback race
- #2599 doctor Learning Bridge — self-heal via recordMemoryPackagePath
- #2600 Windows shim dist-tag parity — align @alpha across all shims

CI guards added per fix.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-07 22:19:06 -04:00
rUv cd8a9c0988 feat: memory distillation self-learning loop (ADR-174) + page-agent browser intent (ADR-175) (#2570)
* feat(memory): distillation service — mine memory_entries into intelligence substrate (ADR-174 M0+M1)

Root cause the goal-planner surfaced: the daemon's consolidate worker
(worker-daemon.ts:1443) is a STUB writing hardcoded zeros — 6000+ commits
recorded to memory_entries but never distilled. reasoning_patterns /
episodes / pattern_embeddings / causal_edges were all empty (hence 🧠 0%).

M0 (safety) + M1 (service): runDistillation() mines memory_entries into
episodes → reasoning_patterns (+pattern_embeddings) → causal_edges.
- $0: reuses structured-distill.ts extractor + the embeddings already on
  each row; no LLM, no re-embedding.
- Incremental: distill_state rowid cursor per namespace; never rescans.
- Non-destructive: only inserts into the empty target tables; never
  touches memory_entries. Per-batch transactions.
- Safe on the just-recovered DB: quick_check gate skips corrupt DBs;
  better-sqlite3 optional (silent no-op).
- Provenance (ADR-171): feedback outcomes = oracle:test-exec (promotable);
  everything else = proxy:structural (written, NEVER promoted).

Measured on a copy of the real 7,900-entry DB: 7,899 → 4,260 patterns
(182 promoted, 4,078 proxy), 4,259 embeddings, 4,258 edges; source
unchanged; dry-run wrote nothing; 2nd run processed 0. 6 tests green.

ADR-174 records the decision, root cause, param surface, self-opt plan,
alternatives, rollback. Follow-ups on branch: M2 CLI, M3 daemon (replace
the stub), M4 self-optimization, M5 platform default.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(memory): distill CLI, daemon auto-distill, self-optimization (ADR-174 M2+M3+M4)

M2 — CLI: 'memory distill run|status|config' consuming the frozen M1
runDistillation. Full param surface: --dry-run, --namespace, --batch-size,
--dedup-distance, --max-entries, --since, --budget-usd, --judge (fable
gated on budget per ADR-172), --aggressive/--conservative presets,
--config, --db. status reports substrate counts + cursor + provenance
breakdown; degrades gracefully. 12 tests.

M3 — daemon: replaced the runConsolidateWorker STUB (the root cause — it
wrote hardcoded zeros) with a real runDistillation call every 30-min tick,
bounded to 1000 entries/tick (cursor drains a backlog across ticks, stays
well under the 16-min worker timeout). Real DistillReport written to the
metrics file. Opt-out via --no-distill / RUFLO_DAEMON_NO_DISTILL and the
existing 'daemon enable -w consolidate --disable'. Never throws (corrupt/
skipped surfaced, not fatal). 7 tests. The loop is now self-sustaining.

M4 — self-optimization ('ruflo tuning ruflo'): tuneDistillation grid-
searches {batchSize, dedupDistance, promoteThreshold} on ISOLATED copies,
scored by MRR@10/recall@10 on a rowid-based held-out split (train/held-out
disjointness asserted; held-out scored once). Source checksum unchanged.
Honest measured result on the real 7.9k-entry corpus: winner batchSize=200
dedupDistance=0.2 promoteThreshold=execution+corroborated, held-out MRR@10
0.753 vs 0.749 baseline — roughly on par (the substrate/training value is
the payoff, not raw retrieval uplift; measured-not-marketing). 11 tests.

Combined M1+M2+M3+M4: 30 tests green.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(browser): page-agent natural-language intent layer — browser_act (ADR-175)

Adds an in-page NL intent tool on top of the selector-based browser tools:
browser_act injects page-agent (Alibaba, MIT) into the agent-browser-driven
page and runs 'execute(task)' — one natural-language call instead of manual
selector orchestration.

Verified against the real page-agent@1.11.0 (ESM-only, split @page-agent/*
packages) — three load-bearing deviations handled:
- import('page-agent') throws 'window is not defined' in Node even when
  installed → availability probed via require.resolve (path only, no eval),
  like browser-session-tools.ts.
- the injectable IIFE auto-constructs a second PageAgent pointed at Alibaba's
  PUBLIC test endpoint (data leak on every call) → stripped before injection
  (stripDemoAutoInit).
- execute() returns { success, data, history } → mapped to { result, steps }.

Safety + routing:
- Optional dependency (~1.11.0) + v3 pnpm-lock regenerated same change;
  browser_act returns { degraded:true } (never throws) when absent.
- LLM routed through this CLI's Tier-3 ladder (OPENROUTER/OLLAMA, OpenAI-
  compatible); bare ANTHROPIC_API_KEY degrades (wrong API shape).
- KEY SAFETY: page-agent runs in page context, so the real key never enters
  the injected string — startLocalLLMProxy holds it server-side on a loopback
  http server and injects Authorization itself; page sees 127.0.0.1 + a
  placeholder. Tests assert no key substring in the injection.
- Trajectory → memory namespace 'browser' (feeds ADR-174 distillation).
- AIDefence gates returned content (optional; passes through if absent).

ADR-175 records the decision, the outer-harness/in-page split, constraints,
the 6 follow-on use cases, alternatives, and rollback. 16 tool tests +
145 mcp-client regression tests green; full build clean.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(browser): fail-closed demo-endpoint firewall for page-agent injection

The demo auto-connect (page content → Alibaba's public sandbox at
page-ag-testing-*.fcapp.run) was only neutralized by a best-effort text
strip that fell OPEN — stripDemoAutoInit returned the source unchanged if
the DEMO_MODEL marker moved, silently re-enabling the leak on an upstream
bundle change.

Verified via the real tarball: the demo endpoint lives ONLY in
dist/iife/page-agent.demo.js (6 refs); the library core ESM entry is clean
(0 refs). So no fork is needed — the leak is a property of the demo bundle,
not the library.

Fix: findDemoLeak() firewall + DemoLeakError. buildPageAgentInjection now
scans the stripped bundle for known demo-endpoint signatures
(page-ag-testing, .fcapp.run, DEMO_MODEL) and THROWS if any survive; the
browser_act handler catches it and degrades rather than injecting. This is
version-independent and fail-CLOSED — a moved/renamed marker can no longer
leak, because the guarantee is 'endpoint absent', not 'marker found'.

+4 tests (signature detection, clean-bundle pass, moved-marker refusal,
clean-bundle injection). 20/20 green. ADR-175 updated with the fork-vs-fix
rationale.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(browser): add ADR-112 discoverability guidance to browser_act description

CI failed 4 checks (tool-description audit + 3 smoke contracts that run it):
browser_act was the 1 tool of 361 without a 'Use when …' guidance signal.
The audit regex captures only the FIRST quoted segment of a multi-line
description, so the guidance phrase must lead. Reworded to open with
'Use when …' + 'Prefer … over …' + 'pair with …' + 'Falls back …'.
Audit now 361/361, exit 0.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(memory): M5 — promote M4 winning distill config as daemon default (ADR-174)

The daemon's consolidate worker now runs the M4-tuned config
(dedupDistance=0.2, chosen by the self-optimization grid-search on the real
corpus). Honest note in code + ADR: held-out MRR@10 0.753 vs 0.749 baseline
— on-par, the substrate/training value is the payoff. Overridable per-run
via 'memory distill'. Completes ADR-174 M0-M5.

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs+fix(memory): address ADR-174 review — honest default, edge contract, invariants (ADR-174)

Review fixes:
- STATUS: Proposed → Accepted (M0-M5 implemented+tested, pending #2570 merge).
- TUNED DEFAULT honestly justified: the M4 retrieval delta (0.753 vs 0.749)
  is a TIE, so dedupDistance=0.2 is sold as COMPRESSION, not retrieval lift —
  measured 2,723 vs 4,350 patterns (37% fewer) at 11.6ms/batch, 0 violations.
  Added an explicit promotion rule (retrieval-neutral within 0.002 AND >=15%
  compression AND <250ms/batch AND 0 proxy violations).
- PARAM TABLE / DEFAULT consistency: dedup default is 0.2 everywhere (service,
  CLI, daemon). Presets now bracket it: --aggressive 0.3 / --conservative 0.1.
- CAUSAL_EDGES OVERCLAIM: edges now carry an explicit contract
  (edge_type=cooccurrence, provenance_tier=proxy:structural, confidence=0.3,
  promoted=false) with the rule 'may rank retrieval, must NOT justify
  autonomous action'. Also fixed a latent bug: edges linked epId-1 (assumed
  consecutive rowids) → now the actual previous episode id.
- EMBEDDING 1:1 invariant enforced: a cluster with no parseable vector is
  skipped, so every pattern has exactly one embedding (was off-by-one on a
  malformed source vector).
- ADR adds operational invariants + audit SQL, business-value framing, and an
  acceptance test.

+2 invariant tests (embedding coverage, edge contract). 32 distill tests green.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(hooks): capture edit/task FAILURES, not just successes (ADR-174)

The DB analysis found 898 feedback records — 100% success, 0 failures —
because the post-edit/post-task hooks recorded a hardcoded success:true and
never inspected the tool outcome. With no negative examples the oracle tier
can't teach good-vs-bad, which caps what distillation can learn.

Fix: read Claude Code's PostToolUse tool_response and record the REAL outcome.
- isToolFailure(hookInput): conservative detector (positive error signal only)
  — tool_response.is_error / error / success:false, non-zero Bash exit code,
  or an error string. Exported + unit-tested (5 cases).
- Generated hook (helpers-generator.ts) + the shipped .claude/helpers/
  hook-handler.cjs both inline it: post-edit → recordEdit(file, !toolFailed),
  post-task → feedback(!toolFailed) (feedback() already boosts on success /
  decays on failure — it was just never called with false).
- intelligence.cjs recordEdit(file, success) persists success to
  pending-insights so consolidation/distillation gets the negative example.

The distillation JUDGE (memory-distillation.ts judgeFeedback) already maps
success:false → reward 0 / not-promoted, so the substrate is failure-ready;
this closes the upstream gap that never produced failures.

End-to-end: a failed-edit PostToolUse payload now logs '[LEARN] Edit FAILURE
recorded' through the actual shipped hook; a success stays '[OK] Edit recorded'.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(init): version-stamped auto-refresh of critical helpers — hook fixes reach all users, no re-init (ADR-174)

The propagation gap: Claude Code hooks run the PROJECT-LOCAL
.claude/helpers/*.cjs copies, not the npm package — so npx ruflo@latest
never updated them, and users didn't know to re-run init. Any hook fix
(e.g. the ADR-174 failure-capture change) reached only people who happened
to re-init.

Fix: stamp the critical helpers (hook-handler.cjs, intelligence.cjs,
auto-memory-hook.mjs) with the installed CLI version; on the next CLI
command, if a project's stamp is older than the installed version, silently
re-copy them. New lightweight src/init/helper-refresh.ts (fs/path/module
only at load; lazily imports the heavy generators only on the rare
source-unresolvable fallback). Wired AWAITED into index.ts startup so a fast
command can't exit before the copy lands — fast path is a stamp read +
string compare (sub-ms); the copy runs at most once per version bump. No-op
outside a ruflo project (never scaffolds files in an unrelated dir). Skipped
for init/update. executeUpgrade also stamps so an explicit upgrade is current.

Verified end-to-end: a stale-stamped project with an OLD (no-failure-capture)
hook silently upgrades to the toolFailed logic on a plain 'ruflo --help', and
a current project is a no-op (a marker file survives). +4 tests.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(security): Ed25519 signed provenance gate for the helper auto-refresh (ADR-174)

The auto-refresh copies auto-EXECUTING hook code from the installed package
into projects. npm verifies the tarball at install, not the files on disk
after — a sibling package's postinstall (or disk tamper) could overwrite the
helpers and the refresh would faithfully propagate the malicious code.

Fix (fail-closed): every helper is verified against a ruflo-signed manifest
before install.
- helper-signing.ts: baked RUFLO_HELPERS_PUBKEY + verifyHelpersManifest()
  (native Ed25519, same primitive as rvfa-signing.ts). Fail-closed — any
  bad signature / tampered hash / wrong algorithm → null → refuse.
- scripts/sign-helpers.mjs: publish-time signer; private key from
  $RUFLO_HELPERS_SIGNING_KEY, NEVER committed (kept out of the repo tree).
  Not shipped (scripts/ is narrowed to postinstall.cjs in files).
- helper-refresh.writeCriticalHelpers: verifies the signed manifest + every
  source helper's SHA-256 BEFORE copying anything; a mismatch is refused and
  the project's existing helpers are left intact. The generator fallback
  needs no manifest (it's the CLI's own compiled code = trust root).
- index.ts warns loudly on a blocked refresh (potential tampering signal).

Threat model: closes post-install/on-disk tampering of the helper files; does
not defend a wholesale-compromised CLI (which could swap the baked key too —
but then it owns the binary you run). A CI test ('hashes match shipped files')
fails if a helper changes without re-signing, so a stale manifest can't ship.

Verified end-to-end: a tampered shipped helper → refresh REFUSED (blocked,
project helper untouched); restored → refresh succeeds. +6 signing tests.

Co-Authored-By: RuFlo <ruv@ruv.net>

* chore(security): source helper-signing key from GCP Secret Manager

sign-helpers.mjs now resolves the private key from GCP Secret Manager
(RUFLO_HELPERS_SIGNING_SECRET, via gcloud secrets versions access) as the
preferred source for CI/publish, with the local-PEM fallback
(RUFLO_HELPERS_SIGNING_KEY) for air-gapped signing. Key never committed.
ADR-174 security note updated.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-04 17:10:45 -04:00
rUv d012042800 feat: review-driven upgrades — perf, security, SOTA capabilities (6-agent concurrent implementation) (#2547)
* chore(arch): untrack the prepublish plugin mirror — build artifact, not source

v3/@claude-flow/cli/plugins/ is regenerated by prepublishOnly from
plugins/ruflo-metaharness/ at publish time. Keeping it committed caused
silent drift between publishes (4 files diverged this week alone) and
every source edit required a manual mirror sync. npm pack still bundles
it — the files array + prepublish copy are unaffected by gitignore.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(gaia): submission integrity checklist — fail-closed pre-submit audit (Berkeley RDI hardening)

Adds a 4-part integrity section to the gaia-validate pre-submit gate,
motivated by UC Berkeley RDI's 'Agents' Last Exam' (April 2026), which
gamed all 8 major agent benchmarks to near-100% without solving tasks.

- scripts/gaia-integrity.mjs: (A) answer-key-shaped path reads outside
  the sanctioned dataset dir — FAIL-CLOSED exit 2; (B) eval()/new
  Function()/exec-of-non-literal strings in gaia-bench runner code
  paths — FAIL-CLOSED exit 2; (C) judge-prompt-injection markers in
  produced answers/trajectories — WARN; (D) provenance stamp (results
  + git SHA + dataset content hash) written for gaia-submit to embed.
- --allow-integrity-override suppresses the exit code but records
  'overridden: true' in the stamp — never silent.
- --self-test plants a fake answer-key read, an eval-of-task runner,
  and injection markers in temp fixtures and proves fail-closed/pass/warn.
- gaia-validate.md: new check 7 documenting the checklist + flags.
- gaia-submit.md: package now embeds integrity.json under the Ed25519
  witness manifest (tamper-evident attestation); stale/failed stamps block
  packaging.
- smoke-gaia.sh: 3 new steps (script wired, self-test green, repo scan
  clean); plugin bumped to 0.5.0.

Co-Authored-By: RuFlo <ruv@ruv.net>

* perf(cli): lazy ONNX embedder init + local-first ruvector resolution

Fix 1 — lazy ONNX init (measured, biggest win):
neural-tools.ts had a module-level try/catch with TOP-LEVEL AWAIT that ran
`await import('ruvector')` + `initOnnxEmbedder()` + a probe embed at import
time. Because mcp-client statically imports neural-tools, this added the
full ONNX startup to every CLI command. The block is now a memoized
`ensureEmbeddings()` (module-level promise guard) invoked from
generateEmbedding() and from the three handlers that report the provider
without embedding (neural_compress / neural_status / neural_optimize), so
degraded/fallback semantics and provider reporting are byte-identical.
The probe embed moved inside ensureEmbeddings() — it still guards the
ADR-086 type-load-success-but-runtime-fails trap, but only on first use.

Measured (warm, darwin-arm64):
  node bin/cli.js --help   0.50s -> 0.09s
  node bin/cli.js status   0.64s -> 0.24s
  import(mcp-client.js)    438ms -> 30ms
  "Loading ONNX model" no longer appears on non-embedding commands;
  appears exactly once on first embed (neural_predict / memory search).

Fix 2 — ruvector pin + local-first resolution:
browser-session-tools.ts pinned `npx -y ruvector@0.2.25` while 0.2.27 is
what the package installs, forcing cold npx downloads of a second version
(4 per browser session). The pin is now 0.2.27 and a memoized
resolveLocalRuvectorCli() (createRequire + package.json bin walk) spawns
`node <local-cli>` directly, falling back to the npx pin only when no
local install exists.

While verifying the 0.2.27 subcommand surface (required before bumping),
found the trajectory-* flags passed here were never valid on 0.2.25 OR
0.2.27 (`--session-id/--task/--args/--verdict` vs the real
`--context/--agent`, `--action/--result`, `--success/--quality`) — every
trajectory call exited with commander "unknown option", same bug class as
the #2015 `rvf create --kind` fix documented in this file. Flags are now
mapped correctly; browser_session_record and browser_session_end verified
end-to-end (rvf create + trajectory-begin/step/end + rvf compact all pass).

Fix 3 — lazy heavy MCP tool groups: profiled and NOT converted.
Per-module timed dynamic imports of all 36 tool modules show neural-tools
was 469.6ms and every other group is 0.3-5.5ms (hooks-tools 3.5ms,
agentdb-tools 2.5ms, embeddings 0.9ms, wasm-agent 0.6ms). After fix 1 the
whole registry builds in ~30ms; converting handler dispatch to lazy
import() would add risk for <6ms of residual win, so mcp-client.ts is
intentionally untouched.

Validation:
- tsc build clean
- plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs: 171/171 passed
- memory search / neural predict still work; ONNX loads lazily then

Co-Authored-By: RuFlo <ruv@ruv.net>

* security(deps): lift OTel floor-became-ceiling pins — core >=2.8.0, otlp family >=0.220.0

The exact pins @opentelemetry/{core,resources,sdk-trace-base}@1.25.1 and
the otlp exporter/transformer family@0.52.1 (root + ruflo wrapper
overrides) were added in #1997/#2112 as a 'fewer-knobs-for-arborist'
defense against the npm arborist Invalid Version crash (empty-version
placeholder from optionalDependencies/peerDependencies overlap). They
were never API-compat pins — but they became a ceiling that forces the
tree onto @opentelemetry/core <2.8.0, which npm audit flags for
GHSA-8988-4f7v-96qf (unbounded memory allocation in W3C Baggage
parsing), and they blocked every upstream fix.

Convert to floors while keeping the lockstep families coherent:
  - stable line:   core / resources / sdk-trace-base  ->  >=2.8.0
  - otlp line:     sdk-node, exporter-prometheus, the three
    exporter-trace-otlp-* packages, otlp-exporter-base,
    otlp-grpc-exporter-base, otlp-transformer  ->  >=0.220.0
(sdk-node@0.220.0 declares exactly core@2.9.0 + otlp@0.220.0, so the
floors resolve to one mutually consistent set; verified in both the
root lockfile and a fresh ruflo/ resolution: core 2.9.0 everywhere,
otlp 0.220.0 everywhere.)

The arborist defense is preserved — every package in both families is
still forced to a single resolution via overrides; the override set is
now broader (whole otlp family at root too), which gives arborist fewer
ambiguous nodes than before, not more.

Also adds the exporter family overrides to the root manifest so root
and the published ruflo wrapper stay aligned (#2112 lesson: root
overrides do not propagate to the published wrapper).

Validation:
  - root:  npm audit --omit=dev  30 moderate -> 0 vulnerabilities
  - ruflo: npm install --package-lock-only succeeds, 0 vulnerabilities
  - runtime probe: new NodeSDK({}) via @opentelemetry/sdk-node@0.220.0
    with core@2.9.0 loads and exposes start(); OTLPTraceExporter loads

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

* security(deps): npm audit fix on v3 workspace lock — grpc-js, hono/node-server, brace-expansion

Plain `npm audit fix --omit=dev --package-lock-only` (no --force) on the
v3 npm workspace lockfile. Clears the three targeted advisories:

  - @grpc/grpc-js        1.14.3 -> 1.14.4   GHSA-5375-pq7m-f5r2 (+GHSA-99f4-grh7-6pcq)
  - @hono/node-server    1.19.9 -> 1.19.14  GHSA-wc8c-qw6v-h7f6 (+GHSA-92pp-h63x-v22m)
  - @isaacs/brace-expansion  5.0.0 removed/deduped  GHSA-7h2j-956f-4vf2

Audit summary: 82 vulnerabilities (2 low, 56 moderate, 24 high)
            -> 55 vulnerabilities (50 moderate, 5 high).
The remainder needs --force / dependency swaps and is out of scope for
this pass.

Note: the reify was run with the two pnpm-style `workspace:*` devDep
specs (plugin-agent-federation, plugin-iot-cognitum) temporarily
substituted with the registry pins the lockfile already records
(@claude-flow/shared@3.0.0-alpha.8, @claude-flow/security@3.0.0-alpha.10),
because npm's arborist does not support the workspace: protocol; the
manifests themselves are unchanged. The v3 workspace is otherwise
pnpm-driven (`pnpm -r build`).

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

* security(deps): align agentdb floor in @claude-flow/memory — ^3.0.0-alpha.17

Root overrides force agentdb >=3.0.0-alpha.17, but @claude-flow/memory
declared ^3.0.0-alpha.16, so its nested/published tree could still
resolve alpha.16 (the #2112 lesson: overrides do not propagate to
nested or published trees). The v3 pnpm lock indeed had memory pinned
to agentdb 3.0.0-alpha.16 — bumping the package's own floor makes both
branches resolve the same version regardless of the consumer's
override set.

pnpm-lock.yaml is regenerated by `pnpm install`; besides the
agentdb 3.0.0-alpha.16 -> 3.0.0-alpha.17 move under memory, it also
reconciles previously committed manifest bumps (metaharness family)
that had drifted from the stale lock.

Verified: `npm ls agentdb` from root shows a single deduped
agentdb@3.0.0-alpha.17 across direct, @claude-flow/memory,
@claude-flow/neural, and agentic-flow branches.

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

* security(deps): correct FEDERATION_BIND_HOST doc default — 127.0.0.1 (ADR-166 Phase 3d)

The bin.ts header JSDoc still advertised `default: 0.0.0.0`, but the
code (makeContext) has defaulted to 127.0.0.1 since the ADR-166
Phase 3d loopback-by-default hardening. Doc-only change.

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

* fix(memory): drop vitest-4 style vi.fn generic that breaks tsc build

The hoisted vitest 1.x types Mock as Mock<Args extends any[], Returns>,
so the single-generic vi.fn<SearchFn> form fails the package build
(pre-existing baseline failure). Annotate the variable instead — the
inferred mock signature is identical.

Co-Authored-By: RuFlo <ruv@ruv.net>

* perf(memory): batch embeddings in the migration import path

MemoryMigrator.processBatch batched entries (batchSize=100) but then
awaited this.embeddingGenerator(content) ONE ENTRY AT A TIME inside the
loop — N sequential ONNX inferences per batch on the
memory_import_claude hot path.

Now:
- New BatchEmbeddingGenerator type ((contents: string[]) =>
  Promise<Float32Array[]>) accepted as an optional 4th constructor /
  factory param — one true batch call per migration batch when the
  embedder supports it (e.g. bge-embedder's single padded forward pass).
- Fallback: bounded concurrency over the single-text generator
  (MigrationConfig.embeddingConcurrency, default 8 — never unbounded;
  set 1 to restore sequential behavior).
- If the batch call fails, falls back to per-entry embedding so one
  backend hiccup doesn't strand the batch.

Per-entry error semantics preserved: validation failures skip, store
failures fail, embedding failures warn and store the entry without a
vector — one bad entry never fails the batch.

Measured (200 synthetic entries, 5ms simulated single-embed latency):
sequential 1220ms -> concurrency-8 156ms (7.8x) -> true batch 115ms
(10.6x).

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(memory): temporal validity on hierarchical memory (Zep/Graphiti-style)

Facts in hierarchical memory can now carry validity windows and are
INVALIDATED on conflict instead of overwritten:

- New TieredMemoryStore module (@claude-flow/memory) promotes the inline
  controller-registry stub to a first-class, tested store with optional
  validFrom / validUntil (ISO) + supersededBy fields.
- store accepts supersedes=<entryId|key>: the old entry is stamped
  validUntil=now + supersededBy=<newId> and archived — never deleted.
- recall filters temporally-invalid entries (expired, superseded, or
  not-yet-valid) by default; includeExpired=true is the audit escape
  hatch.
- Backward compatible: entries without the fields behave exactly as
  before (always valid); legacy store(key,value,tier) / recall(query,
  topK) call shapes unchanged; same-key stores still overwrite; the
  bridge's real-vs-stub detection signal (getStats+promote) is
  preserved.
- CLI bridge passes the new optional fields through
  bridgeHierarchicalStore/-Recall. On the native agentdb
  HierarchicalMemory backend, validFrom/validUntil ride in metadata and
  are filtered at recall; supersedes is reported as unsupported there
  (no public update API) rather than silently dropped.
- MCP inputSchemas for agentdb_hierarchical-store (validFrom,
  validUntil, supersedes) and agentdb_hierarchical-recall
  (includeExpired) expose the fields to agents, with ISO-8601
  validation.

Tests: supersede flow (including same-key supersede preserving
history), expired + not-yet-valid filtering, includeExpired escape
hatch, legacy entries untouched, unparseable timestamps fail open.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(routing): confidence-gated tier escalation — hooks_model-verify ($0 post-generation verifier)

ADR-026/143 classify BEFORE generation only. This adds the 2026-SOTA
post-generation gate: attempt the cheap tier, verify the output with
CHEAP structural signals (no LLM call), escalate only on failure.

- src/ruvector/output-verifier.ts — pure verifier: empty/truncated
  output, refusal patterns, degenerate repetition, unbalanced
  delimiters, and a real syntax parse for code/JSON tasks (TypeScript
  transpileModule diagnostics / JSON.parse, graceful degradation when
  typescript is absent). Returns {confident, reasons[], suggestedTier,
  suggestedModel, escalate}; ladder haiku→sonnet→opus, tier 2→3.
- hooks_model-verify MCP tool (hooks-tools.ts, surgical) — wraps the
  verifier and feeds the verdict into the SAME learning stream as
  hooks_model-outcome (ModelRouter.recordOutcome: success when
  confident, escalated when not) so the Thompson priors learn which
  task shapes the cheap tier fails on. A dedicated tool (not a verify
  mode on hooks_model-outcome) because verify is a mid-loop decision
  point returning a verdict the agent acts on, while outcome is a
  terminal write — the sequence is route → generate → verify →
  (escalate) → outcome.
- __tests__/output-verifier.test.ts — refusal/truncation/parse/
  escalation-ladder coverage + MCP handler tests (18 tests).

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

* feat(federation): A2A Agent Cards — spec-1.0 discovery adapter over the bespoke federation schema

Implements the Agent2Agent (A2A, Linux Foundation) Agent Card layer as an
ADAPTER over the existing federation registration/discovery shapes. Cards
only — A2A Tasks/messaging are deliberately out of scope this iteration.

Spec: A2A Protocol 1.0.0 (a2a-protocol.org; schema source of truth
specification/a2a.proto in a2aproject/A2A). Required AgentCard fields per
spec 4.4.1: name, description, supportedInterfaces, version, capabilities,
defaultInputModes, defaultOutputModes, skills. Well-known discovery path
per 8.2 + IANA registration: /.well-known/agent-card.json.

- src/a2a/agent-card.ts: toAgentCard() maps FederationManifest -> spec-
  compliant card (agent types -> AgentSkills; federation endpoint -> open-
  form RUFLO-FEDERATION AgentInterface; bespoke identity rides in a spec
  4.6 extension urn:ruflo:federation:manifest:v1 for lossless round-trip);
  validateAgentCard() enforces the 1.0 required-field shape;
  fromAgentCard() maps a remote card into a FederationNode — ALWAYS
  TrustLevel.UNTRUSTED (cards are self-asserted metadata; trust is earned
  via the normal handshake + TrustEvaluator path).
- src/a2a/well-known.ts: minimal node:http endpoint serving GET/HEAD on
  the exact well-known path with spec 8.6 Cache-Control + ETag/304.
  Binds 127.0.0.1 by default and REFUSES non-loopback binds without an
  explicit allowNonLoopback opt-in (ADR-166 posture preserved — no
  loosening of federation bind or auth).
- src/a2a/consume.ts: fetchAgentCard (injectable fetch, timeout, size cap,
  structural validation) + consumeAgentCard registering the peer into
  DiscoveryService so A2A peers appear in federation discovery.
- DiscoveryService: new 'a2a-card' discovery mechanism +
  registerExternalPeer() (refresh-not-replace preserves trust state).
- plugin.ts: opt-in wiring via config.a2aCard/a2aCardPort/a2aCardHost;
  bind failure degrades gracefully; server closed on shutdown.
- 40 new unit tests incl. golden-file card fixture
  (__tests__/fixtures/a2a-agent-card.golden.json), foreign-card mapping,
  round-trip, loopback guard, and a live serve->consume e2e over HTTP.
- plugin.test.ts: fixed pre-existing stale assertion (10 -> 11 CLI
  commands; 'federation trust elevate' was added without updating the
  count) — baseline suite failed before this change.

Build clean; 600/600 tests green.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(intelligence): execution-state-tree retrieval prototype (MAGE-style positional recall)

Research basis: MAGE (arXiv 2606.06090) — semantic-similarity retrieval
fragments decision trajectories on long-horizon tasks; retrieving by
POSITION in a hierarchical execution-state tree (root→current path)
preserves coherence.

- src/ruvector/trajectory-tree.ts — TrajectoryTree prototype:
  trajectory-start opens a node under the session root (nesting under
  the deepest open trajectory), steps append children, end closes and
  pops the current position. recallPath({sessionId, depth}) returns
  the exact root→current path + recent siblings — no embedding search.
  Best-effort JSON snapshot at .claude-flow/intelligence/
  trajectory-tree.json, ALONGSIDE existing trajectory storage (no
  migration). Limitations documented in the module header.
- hooks-tools.ts (surgical): non-fatal tree mirrors in
  hooks_intelligence_trajectory-start/step/end (+ optional sessionId
  param on start), and an opt-in strategy:"state-tree" on
  hooks_intelligence_pattern-search (+ sessionId/depth params).
  Default strategy stays "semantic" — zero behavior change unless
  opted in.
- __tests__/trajectory-tree.test.ts — synthetic 3-level trajectory
  path assertions, nesting/pop, sibling window, persistence
  round-trip, and default-strategy-untouched checks (12 tests).

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

* fix(metaharness): kill third-party @latest, extract shared _invoke.mjs, derive smoke counts

Three of the four converged security/perf/arch review fixes:

1. SECURITY (HIGH) + PERF — _harness.mjs no longer shells to
   `npx -y metaharness@latest` (a compromised upstream publish would have
   executed arbitrary code on user machines, and @latest forced a registry
   check per call). Resolution is now: (a) locally installed metaharness
   satisfying the PINNED range ~0.3.0 (walk-up node_modules), then (b) a
   one-time `npm install --prefix ~/.ruflo/metaharness-cache-0.3.0`, then
   plain `node <abs-path-to-cli>` spawns — zero network per call. Both bins
   (metaharness + harness) are read from the resolved package.json bin map.
   The {stdout, stderr, exitCode, json, degraded, reason, durationMs}
   contract and ADR-150 graceful degradation are preserved; timeouts now
   report 'metaharness-timeout' (matching _darwin/_redblue) instead of being
   conflated with 'metaharness-not-available'.
   Measured: `score.mjs --path .` completes in ~0.35s via the cache.

2. ARCH — new scripts/_invoke.mjs consolidates the copy-pasted plumbing:
   DEGRADED_RX (superset incl. `npm ERR`), classifyDegraded (keeps the
   -timeout vs -not-available distinction), injectJson, parseTrailingJson
   (the LAST-{...}-block variant from _darwin — the old _harness first-block
   parse was a live bug), ensureCachedInstall (generalizes gepa/_redblue,
   cache-dir naming kept compatible so existing caches keep serving),
   importOptionalLibrary, findLocalPackageDir, makeDegradedEmitter.
   _harness/_darwin/_redblue/gepa are now thin adapters with IDENTICAL
   exported signatures; their genuinely-different parts stay (redblue's
   node-direct isMain workaround, darwin's async streaming for evolve,
   harness's dual binaries). test-graceful-degradation.mjs gains
   RUFLO_METAHARNESS_CACHE_BASE / RUFLO_METAHARNESS_SKIP_LOCAL seams so the
   ADR-150 drill still exercises the degraded path on warm-cache machines.

3. ARCH — smoke.sh derives EXPECTED_TOOLS / EXPECTED_SUBS once from
   metaharness-tools.ts / metaharness.ts and replaces the 7 independently
   hardcoded literals (15 at :750/:829/:2046, 13 at :725/:851, 16 footnote,
   15 success-pattern). Cross-surface value preserved: independent surfaces
   (CLAUDE.md catalog, test-mcp-tools' own `tools.length === 15` literal,
   footnote appends, runScript refs) are compared against the derived count,
   never a surface against itself; a >=15/>=13 floor step catches
   extraction-regex rot.

Validation: smoke.sh 122/122, test-graceful-degradation 16/16,
test-mcp-tools 171/171.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(metaharness): add --diagnose GEPA failure diagnosis to evolve

GEPA's key trick — natural-language failure diagnosis from execution traces
feeding the next mutation, not just scalar fitness — scoped modestly into
the evolve wrapper as an opt-in flag.

`evolve.mjs --diagnose` now appends a `diagnosis` section to the emitted
JSON after the run completes: losing/failed variants' transcripts are run
through darwin's gepa `analyzeTranscript` + `classifyFailure` ops (via the
shared importGepa resolver, moved to _darwin.mjs because gepa.mjs runs
main() on import) and aggregated into failure classes + counts + dominant
class per variant.

Upstream shape findings (verified against @metaharness/darwin@0.8.0 with a
tiny mock-sandbox evolve):
- `metaharness-darwin evolve --json` emits a TEXT leaderboard — stdout
  carries no JSON and no transcripts (r.json has always been null here).
- Per-variant run records live at <repo>/.metaharness/runs/<id>.json as
  sandbox exec traces ({taskId, exitCode, stdout, stderr}) — NOT gepa
  {actionRaw, obs} transcripts.
Diagnosis therefore layers: gepa-shaped transcripts when a run record
embeds them (agent sandbox / future upstream) → champion-transcript
fallback → `diagnosis: {available: false, reason, traceSummary}` with a
mechanical per-variant tally. Never fails the run — internal errors
degrade to {available: false, reason: 'diagnosis-failed: ...'}.

Both paths exercised end-to-end: mock evolve yields the traceSummary
fallback; a seeded gepa-shaped run record yields
{available: true, scope: 'losing-variants', failureClasses:
{exploration-loop: 1, edit-mechanics: 1}, dominantClass: 'exploration-loop'}.

Documented in skills/harness-evolve/SKILL.md.

Co-Authored-By: RuFlo <ruv@ruv.net>

* Revert "feat(gaia): submission integrity checklist — fail-closed pre-submit audit (Berkeley RDI hardening)"

This reverts commit af185fcccd.

* fix(hooks): ADR-112 guidance clause on hooks_model-verify description

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): retune #1921 fix-invariant to the >=2.8.0 OTel override floor

The PR deliberately raised the @opentelemetry/core override from the exact
1.25.1 pin to a >=2.8.0 floor (OTel 2.x audit fix), but the #1921 invariant
in audit-fix-invariants.mjs still asserted the old pin substring, failing
the 'Hook-command install safety (#1921)' job. Per the audit's own guidance,
the invariant is updated to point at the new location of the fix — the
override entry itself — preserving the regression guard (arborist Invalid
Version placeholder) rather than deleting it.

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

* fix(ci): register RUFLO_METAHARNESS_CACHE_BASE as env-var escape hatch

The metaharness _invoke.mjs consolidation introduced cacheBaseDir(), whose
RUFLO_METAHARNESS_CACHE_BASE read is a CI/test seam for relocating the
~/.ruflo pinned-cache root in smoke tests — there is no CLI-flag surface in
plugin scripts to take precedence. Register it in KNOWN_ESCAPE_HATCHES per
ADR-125/ADR-130 remediation Option B, unblocking the 'Plugin package
install-safety (#1902/#1903/#1904)' job.

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

* fix(ci): linear-time dash trim in A2A slugify — resolves js/polynomial-redos

CodeQL flagged /^-+|-+$/g in slugify() (agent-card.ts:281) as polynomial
ReDoS: the anchored quantifier backtracks O(n^2) on adversarial all-dash
card names, and card names are remote library input. Replace the trim regex
with an index-scan (charCodeAt) that is strictly linear. Behavior verified
equivalent against the old implementation over edge cases plus 5000-input
fuzz; package typechecks and all 600 tests pass.

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

* fix(ci): no-metaharness drill uses the _invoke.mjs test seams

The pinned-cache resolution defeats the drill's registry-blackhole absence
simulation (metaharness resolves locally / from the warm ~/.ruflo cache).
RUFLO_METAHARNESS_SKIP_LOCAL=1 + an empty RUFLO_METAHARNESS_CACHE_BASE make
'absent' mean absent again. Reproduced locally: exit 0 + degraded:true.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-03 16:52:09 -04:00
rUv e0d0af1cef feat(gaia): pre-submission exploit audit + signed attestation (ADR-167) (#2543)
* feat(gaia): pre-submission exploit audit + signed attestation (ADR-167)

Motivated by UC Berkeley RDI's April-2026 finding that all 8 major agent
benchmarks were gamed to ~98-100% without solving a single task (GAIA leaked
~98% of answers via public answer DBs + normalization collisions; o3/Claude-3.7
monkey-patch graders in 30%+ of runs per METR).

Signing proves transport-integrity; this audit proves earning-integrity. The
audit report is deterministic ($0, offline, pure-core) and is registered as an
ADR-103 witness fix marker so its sha256 + "clean": true marker are signed INTO
manifest.md.json.

- ADR-167 (v3/docs/adr): threat model, check catalogue mapped to RDI/METR
  vectors, manifest-attestation design, honest harness-gap limitations.
- gaia-audit.mjs: 7 pure checks (answer-leakage, no-work, oracle-leakage,
  grader-isolation, normalization-collision, voting-disclosure, split-integrity);
  CLI exit 0 clean / 1 CRITICAL / 2 usage. Schema-blocked checks return skip
  with a harness_gap note, never a false pass.
- Tests: 27 passing; clean + dirty fixtures triggering every vector.
- Wired --audit into /gaia validate; pre-sign gate into /gaia submit
  (refuses CRITICAL fail unless --allow-dirty) + gaia-submission skill.
- Bump ruflo-workflows 0.4.0 → 0.5.0.

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

* docs(adr): ADR-168 — GAIA harness evidence recording (implements ADR-167 §7 forward contract)

Co-Authored-By: RuFlo <ruv@ruv.net>

* docs(adr): ADR-169 — benchmark reporting integrity standard (strict-EM headlines, view-labeled scaling, disclosed gaps)

Codifies the discipline the FRAMES ablation self-audit measured against the
Berkeley RDI vectors. Completes the integrity family: ADR-167 audits the
submission, ADR-168 records the evidence, ADR-169 governs how numbers are
reported.

Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(gaia): port static source-scan checks (AUD-8/9/10) into gaia-audit

Ports the unique, non-overlapping checks from the reverted #2547 duplicate
gaia-integrity.mjs into gaia-audit.mjs as a new static source-scan family,
matching ADR-167's check-registry / status / harness_gap design:

- AUD-8 answer-key-reads (CRITICAL, fail-closed): scans runner sources +
  produced artifacts for answer/gold/solution/ground-truth-shaped paths
  referenced outside the sanctioned dataset dir(s).
- AUD-9 dynamic-eval (CRITICAL, fail-closed): static scan of the gaia-bench
  runner sources for eval()/new Function()/exec-family calls with a non-literal
  first arg. Preserves the #2547 false-positive refinements (multi-line aware,
  RegExp.prototype.exec excluded, fixed-string commands + comments ignored) —
  passes clean against ruflo's own multi-line `gcloud secrets … execSync`.
- AUD-10 judge-injection (WARN): scans the agent's PRODUCED answer/outputs for
  judge-directed prompt-injection markers. Distinct from AUD-3 oracle-leakage
  (input-side gold-answer scan), so both run.

Unlike AUD-1/3/4 these need no trajectory instrumentation — they enforce today
and skip honestly (never false-pass) when the source population is absent
(standalone/ejected run or --skip-source-scan). The #2547 provenance stamp and
--allow-integrity-override are NOT ported: #2543's signed attestation and the
`/gaia submit --allow-dirty` gate already own provenance + override, so the new
checks flow through the same attestation.clean/strict_clean path.

CLI adds --repo-root, --dataset-dir (repeatable), --skip-source-scan.
Fixtures: planted evil runner (eval + interpolated exec), planted answer-key
read, judge-injection results. Tests: 44 green (17 new). Docs: ADR-167 §3/§7
check table, gaia-validate.md, gaia-submission SKILL.md.

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

* fix(ci): advance ruflo-workflows smoke version assertion to 0.5.0

The AUD-8/9/10 port bumped plugin.json to 0.5.0 but the plugin's own
smoke step 1 still asserted 0.4.0 — the single failing step behind both
smoke-all and the meta-smoke leg of smoke-without-metaharness on #2543.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-07-03 16:46:31 -04:00
rUv ae9bba523e feat(metaharness): learn + gepa integration — metaharness@0.3.0 / darwin@0.8.0 (15 MCP tools) (#2540)
* fix(mcp-bridge): streamable-HTTP session cleanup — DELETE + Mcp-Session-Id + 202 (#2425)

Closes #2425. Cherry-picked from djimit's commit b905c09f4 with
extensions: applied symmetrically to BOTH bridges (per ADR-166's
"both bridges move together" rule), and added regression coverage
in the ADR-166 security lock so this can't silently regress.

Streamable-HTTP clients (Codex/RMCP) send `DELETE /mcp` with an
`Mcp-Session-Id` header at shutdown. Previously the bridge returned
`Cannot DELETE /mcp` which surfaced as noisy client-side cleanup
failures after otherwise-successful MCP calls.

Changes (both bridges — ruflo/src/mcp-bridge/index.js AND
ruflo/src/ruvocal/mcp-bridge/index.js):

- Stable per-process `MCP_SESSION_ID = randomUUID()` echoed as the
  `Mcp-Session-Id` header on every `/mcp*` response
- `DELETE /mcp` and `DELETE /mcp/:group` → 204 No Content (idempotent)
- `DELETE` added to `Access-Control-Allow-Methods`; `Mcp-Session-Id`
  added to `Access-Control-Allow-Headers` (both compatible with the
  ADR-166 CORS allowlist wiring — allowlist controls Origin, not
  Method/Header)
- `notifications/initialized` responses now `202 Accepted` with empty
  body per the MCP streamable-HTTP spec (was returning a jsonrpc
  envelope which some clients rejected as unexpected)

Regression locks (extends ADR-166 §6 acceptance):
- test-security-lock.js — 2 new static checks (#7 delete handler,
  #8 Mcp-Session-Id header) × 2 bridges = 16/16
- test-runtime-security.mjs — 2 new runtime checks per bridge (R6
  DELETE→204 + header echoed; R7 notifications/initialized→202 empty)
  = 20/20 total green

Interaction with ADR-166 auth: DELETE flows through `app.use(requireAuth)`
same as the rest of `/mcp*`. When `MCP_AUTH_TOKEN` is set, DELETE without
Bearer returns 401 (verified in R2 by contrast; DELETE with Bearer
returns 204 verified in R6). Local-only deployments with no token remain
zero-friction (the middleware is a no-op).

Credit: @djimit for the original patch (branch
fix/mcp-session-cleanup-forkbase on djimit/claude-code-flow, commit
b905c09f42).

Co-Authored-By: djimit <djimit@users.noreply.github.com>
Co-Authored-By: RuFlo <ruv@ruv.net>

* feat(metaharness): learn + gepa integration — metaharness@0.3.0 / darwin@0.8.0

Version pins (optionalDependencies, both @claude-flow/cli + ruflo wrapper):
- @metaharness/darwin  ~0.3.1 → ~0.8.0  (GEPA engine + cand-6 genome)
- metaharness          ~0.2.6 → ~0.2.8
- @metaharness/redblue ~0.1.1 → ~0.1.4  (+ versioned cache dir — pin bumps
  now invalidate stale installs instead of serving 0.1.1 forever)
- @metaharness/kernel  ~0.1.0 → ~0.1.2

New surfaces (13 → 15 MCP tools, 11 → 13 CLI subcommands):
- metaharness_learn / `ruflo metaharness learn` — upstream ADR-235 GEPA
  learning run; $0 dry-run default, --run to spend, structured
  checkout-required payload when the metaharness clone is absent
- metaharness_gepa / `ruflo metaharness gepa` — darwin GEPA library
  surface (genome|validate|render|analyze); gepaOptimize stays
  library-only (in-process evaluator can't cross subprocess boundary)
- skills: harness-learn, harness-gepa

Fixes found during validation:
- gepa.mjs treats ERR_PACKAGE_PATH_NOT_EXPORTED (stale pre-0.8.0 darwin
  in ancestor node_modules) as recoverable → falls back to versioned cache
- smoke.sh contract counts advanced (scripts 13→15, tools 13→15,
  subcommands 11→13, CLAUDE.md doc counts)

Validated: tsc clean; test-mcp-tools 171/171 across all 15 tools; darwin
0.8.0 evolve/bench/security-bench verbs proven through wrappers; redblue
0.1.4 attack preview proven via new cache path.

Co-Authored-By: RuFlo <ruv@ruv.net>

---------

Co-authored-by: djimit <djimit@users.noreply.github.com>
2026-07-03 14:17:09 -04:00
rUv 19e4277e12 feat: ADR-164 agentbbs business autopilot — Phases 1-4 (4 MCP tools, 7 pods, http_fetch, atomic budget tracker) (#2503)
* docs(adr): ADR-164 — AgentBBS federated business-management autopilot

Draft ADR proposing agentbbs@0.1.0 integration as a federation-peer kind +
business pod scaffolding. Covers the 4 new MCP tools, 7 domain pod templates
(sales/marketing/finance/ops/support/hr/exec), upstream changes required in
both ruflo and ruvnet/agentbbs, 5-phase rollout, and 7 honest risks.

Builds on the optional-dep + graceful-degraded pattern shipped today in PR
#2500 (agenticow) and ADR-150 (metaharness).

Status: Draft. Implementation gated on upstream confirmation of agentbbs
persistence semantics + latency baseline.

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

* docs(adr): amend ADR-164 + add ADR-164.1 (budget atomicity)

ADR-164 amendments (Draft v2, 1193 → 1412 lines, +219 net):
  - Rewrite agentbbs maturity assessment — it's a 13-crate Rust workspace
    with 7 existing CI workflows + 30+ release tags + existing federation
    crate, NOT a 16h-old greenfield. The npm package is the launcher only.
  - §3.2.4 — two-phase auth: 15-min token is handshake-only;
    long-lived stream sessions persist past expiry until idle-close.
  - §3.5.4 — new "Founder-bootstrap trust elevation" admin escape hatch
    so #exec cross-pod synthesis works on Day 1 without 500-interaction wait.
  - §4.4 — Ops pod gets aidefence_*, terminal_execute, agent_execute,
    new http_fetch tool, cloud-provider MCP servers; concrete bench scenario.
  - §5.1.8 — new MCP tool `http_fetch` (URL allowlist, timeout, size cap).
  - §5.2 — survey-first upstream changes; many requirements may already
    exist in crates/agentbbs-federation and crates/agentbbs-mcp.
  - §7.3 + §9.5 — forward-reference to ADR-164.1 for race resolution.
  - §9.1 — risk #1 rewritten: real risks are npm/Rust version drift, FSL
    license implications, cargo first-run requirement, MCP server
    compatibility.

ADR-164.1 (new, 724 lines):
  - Atomic reserve-and-commit token bucket against AgentDB-style
    SQLite store with BEGIN IMMEDIATE serialization.
  - Schema: bbs_budget_reservations + bbs_budget_rooms tables.
  - Three-op API: reserve / commit / release with discriminated returns.
  - 5 concurrency tests (vitest-implementable) closing the race window.
  - Migration plan: feature-flagged rollout behind
    CLAUDE_FLOW_BBS_ATOMIC_BUDGET, default flip after 1 week zero incidents.
  - p99 < 5ms target with ~1000 reserves/sec throughput ceiling honestly
    documented (partition-per-pod as the scale-out path, not replacing
    SQLite).

Both Draft status. Phase 1 implementation gated on these.

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

* docs(adr): ADR-164.1 amendments — close the Expired Commit Leak

Peer review (2026-06-29) identified three issues:

1. CRITICAL — Expired Commit Leak (§8.1, §5.3, §4.1)
   Original draft rejected commit() of an expired reservation, so the
   actual API spend went unrecorded. A high-latency loop could silently
   burn unbounded $ past the monthly cap. Fixed: commit() now ACCEPTS
   the spend, transitions state to new 'committed_post_expiry' value,
   records actual_usd, returns { ok: true, warned: 'COMMIT_AFTER_EXPIRY' },
   and emits a reservation.committed_post_expiry audit envelope.

2. SQLite locking clarification (§3.2 _lock_bump field note)
   BEGIN IMMEDIATE itself acquires the RESERVED lock; _lock_bump is now
   documented as belt-and-suspenders + code-review visibility, not the
   primary serialization primitive.

3. Per-pod reservationExpiryMs (§3.2 expires_at field note)
   Default 60s is too short for local large-model pods. Pod templates
   may now set reservationExpiryMs in [5_000, 300_000] ms; ceiling
   defends against unbounded runaway.

Plus new Test 6 in §9: load-bearing proof that COMMIT_AFTER_EXPIRY
captures the real spend and the room's subsequent gate-check reflects it.

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

* feat(mcp): Phase 1 — agentbbs integration scaffold (4 MCP tools)

Implements ADR-164 §3.2 + §5.1 with the same architectural pattern as
PR #2500 (agenticow): optional-dep + graceful-degraded fallback.

New MCP tools (4):
  - federation_bbs_register     register a BBS room as a federation peer
  - federation_bbs_publish      publish a typed envelope to a room
  - federation_bbs_watch        poll recent envelopes (streaming = Phase 4)
  - federation_bbs_human_join   single-use Ed25519 token + web/SSH handshake

Phase 1 scope (scaffolding the surface, not the wire):
  - loadAgentbbs() checks importability; degrades to {degraded:true,
    reason:'agentbbs-not-found'} when missing — never throws
  - Validation runs BEFORE the optional-dep check so path-traversal /
    label / msgType rejection is deterministic regardless of install state
  - Backing store: local JSON-lines in <basePath>/room-<id>.jsonl +
    rooms.json registry. The Rust agentbbs-federation crate will replace
    this with signed wire envelopes in Phase 2.
  - Ed25519 keypair is per-process ephemeral; real federation keypair
    wiring is Phase 2 (documented inline at the call site).

Gates (all PASS):
  - Build: clean (v3/@claude-flow/cli npm run build)
  - Vitest: 8 pass + 4 skip-conditional (agentbbs binary not built locally)
  - ADR-112 audit: 349/349 tools with Use-when guidance (was 345; +4 new)
  - Bash smoke: 8/8 (end-to-end register→publish→watch cycle via dist handlers)

Plus:
  - .github/workflows/no-agentbbs-smoke.yml — CI guard mirroring
    no-metaharness-smoke.yml; asserts agentbbs stays in optionalDependencies
    and runtime drill exits 0 with --ignore-optional install
  - plugins/ruflo-bbs-federation/.claude-plugin/plugin.json — plugin metadata

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

* docs(adr-164): replace §5.2 hypotheticals with concrete agentbbs survey findings

Phase 1 survey of ruvnet/agentbbs @ ca3c6e0 resolved each compatibility
check in §5.2 against the live upstream source. Six "VERIFY FIRST / if-
exists / if-missing" branches are replaced with definite answers:

- §5.2.1 FederationPayload — already covers ruflo's needs (4 variants,
  Ed25519-sealed); map publish→ReplicateMessage, register→AnnounceBoard.
  No upstream PR.
- §5.2.2 MCP tool registration — static 4-tool surface (list_boards,
  read_board, post_message, search_memory). Re-scoped from "web UI
  rendering" to the actual MCP shape concern. Gap #1 filed for missing
  create_board tool. No upstream PR for Phase 1.
- §5.2.3 Subscription/streaming — genuinely missing (subscribe=false
  hardcoded), but matches ruflo's Phase 1 polling design. Gap #2 filed
  for Phase 4 streaming. No upstream PR for Phase 1.
- §5.2.4 Integration seam — surprise finding: agentbbs already drives
  `npx ruflo federation` via RufloAdapter (other direction). Ruflo's
  CLI now has a contract obligation to keep init/join/status alive.
- §5.2.5 Durable retention — postgres backend exists in CI but
  `agentbbs mcp` defaults to MemoryStore; gap #3 filed for Phase 3.
- §5.2.6 CI regression coverage — no existing end-to-end agentbbs↔ruflo
  test. Closed by ruvnet/AgentBBS#3 regression guard (PR opened upstream
  in this same Phase 1 cycle).

The regression guard at ruvnet/AgentBBS#3 asserts the four MCP tool
names, four FederationPayload variants, and three RufloAdapter
subcommand names continue to exist on every agentbbs PR + nightly cron.

Cross-ref:
- Upstream PR: https://github.com/ruvnet/AgentBBS/pull/3
- Survey commit: ruvnet/AgentBBS@ca3c6e0

* feat(pods): Phase 2 — sales pod end-to-end (dry-run + validator + smoke)

Implements ADR-164 Phase 2: scaffold the first concrete business pod
(sales) end-to-end with a typed validator, a tick runner, and full smoke
coverage. Dry-run mode is the Phase 2 default; --live mode exits 3 with
a "Phase 3" message until Managed Agent + claude-p wiring lands.

New surface (~1,597 LOC):
  v3/@claude-flow/cli/src/business-pods/pod-schema.ts   — PodTemplate type
                                                        + validatePodTemplate()
                                                        + KNOWN_AGENT_TYPES mirror
  v3/@claude-flow/cli/src/mcp-tools/business-pod-tools.ts — 1 new MCP tool
                                                            (business_pod_validate)
  plugins/ruflo-business-pods/
    .claude-plugin/plugin.json
    templates/sales.json                                — Phase 2 sales pod template
                                                          (matches ADR-164 §4.1 +
                                                          §3.3 schema)
    scripts/pod-tick.mjs                                — dry-run tick runner +
                                                          file-based budget ledger
                                                          (TODO(adr-164.1): swap to
                                                          atomic SQLite tracker)
    scripts/pod-tick.test.mjs                           — 15 node:test tests
    scripts/smoke.sh                                    — 8-step smoke contract
    skills/pod-sales/SKILL.md
  .github/workflows/business-pods-smoke.yml             — CI gate

Gates (all PASS):
  - Build: clean
  - vitest business-pod-tools: 19/19 PASS
  - node:test pod-tick: 15/15 PASS
  - ADR-112 audit: 350/350 with guidance (was 349; +1 new tool)
  - business-pods smoke: 8/8 PASS
  - agentbbs regression smoke: 8/8 PASS (no regression from Phase 1)
  - agenticow regression smoke: 8/8 PASS (no regression from PR #2500)

Honest Phase 2 deviations:
  - Schema validator duplicated TS / JS (single canonical TS in cli/src/,
    JS copy in pod-tick.mjs so script runs without prebuilt CLI). Phase 3
    consolidates via shared agents/registry.ts.
  - Budget ledger is file-based JSON stub; ADR-164.1 atomic SQLite
    tracker swap deferred to Phase 3.
  - agentbbs subprocess wiring not invoked; envelopes go directly to
    the Phase 1 JSONL backing store (matches ADR-164 §5.2.2 phase plan).

Phase 3 follow-ups inline-marked TODO(adr-164.1) / TODO(phase-3).

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

* feat(pods): Phase 3 — remaining 6 pods + domain-affinity router policy

Implements ADR-164 Phase 3: templates for marketing/finance/ops/support/hr/exec
+ the @metaharness/router domain-affinity policy hook.

New templates (6) at plugins/ruflo-business-pods/templates/:
  marketing.json   SOC2, $40/mo, cloud-tolerant
  finance.json     GDPR, $80/mo, local-pinned, expiryMs=180_000 (3min)
  ops.json         SOC2, $50/mo, local-pinned, synthetic-HTTP bench scenario;
                   TODO(phase-3-http-fetch) for the unbuilt http_fetch tool
  support.json     SOC2, $40/mo, cloud-tolerant
  hr.json          GDPR, $30/mo, local-pinned (employee PII = finance-grade)
  exec.json        GDPR, $60/mo, local-pinned, references §3.5.4
                   founder-bootstrap trust elevation for share-context on Day 1

New code:
  v3/@claude-flow/cli/src/business-pods/domain-affinity-policy.ts (86 LOC)
    - selectAgentBackend(pod): {backend, reason}
    - rules: preferLocalExecution=true → local-stdio
             else if budget.monthlyUsd >= 50 → cloud-managed
             else → remote-peer
  v3/@claude-flow/cli/src/mcp-tools/business-pod-tools.ts (+107 LOC)
    - new MCP tool: business_pod_route_backend
    - shared loadTemplate(input: object | string) helper

Schema mirror in lockstep: added `base-template-generator` to KNOWN_AGENT_TYPES
in BOTH TS (pod-schema.ts) and JS (pod-tick.mjs) copies.

Gates (all PASS):
  - Build: clean
  - vitest business-pod-tools: 44/44 PASS (was 19; +18 Phase 3 cases)
  - node:test pod-tick: 21/21 PASS (was 15; +6 happy-path tests)
  - ADR-112 audit: 351/351 with guidance (was 350; +1 new tool)
  - business-pods smoke: 11/11 PASS (was 8; +3 Phase 3 steps)
  - agentbbs regression smoke: 8/8 PASS
  - agenticow regression smoke: 8/8 PASS

Phase 4 follow-ups (inline TODOs):
  - http_fetch MCP tool implementation (referenced by ops.json)
  - @metaharness/router wire point in policy-engine.ts
  - ruflo federation trust elevate ... --to TRUSTED CLI escape hatch
  - Live-mode dispatch (Managed Agent + claude -p wiring)
  - Atomic SQLite budget tracker swap (ADR-164.1)

Per ADR-164 §3.4, all rules codified verbatim; smoke step 11 + 4 vitest cases
assert what the rules produce per template. Live mode still exits 3 with
"Phase 4" message — Phase 3 is templates + validator + routing-policy only.

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

* feat: Phase 4 — http_fetch + atomic budget tracker + federation trust elevate

Three Phase 4 deliverables landing per ADR-164:

A. http_fetch MCP tool (ADR-164 §5.1.8)
   - 1 new tool (#352) for ops-pod's synthetic-endpoint bench
   - Security-default-rejects: private IPs (RFC-1918, localhost), auth
     headers (Authorization/Cookie/X-Auth-*) unless env vars opt-in
   - URL allowlist + timeout + response-size cap (256 KB default, 1 MB max)
   - 25/25 vitest pass (URL allowlist, timeout abort, truncation,
     auth-header rejection, happy path against node:http mock)

B. Atomic SQLite budget tracker (ADR-164.1 §3-§5)
   - bbs_budget_rooms + bbs_budget_reservations tables
   - reserve/commit/release with BEGIN IMMEDIATE + _lock_bump
   - committed_post_expiry state machine (Expired Commit Leak fix from
     peer review 2026-06-29) implemented
   - expires_at clamped to [5_000, 300_000] ms per-pod override
   - Behind CLAUDE_FLOW_BBS_ATOMIC_BUDGET=1 — file-based stub remains
     the default until next flip; pod-tick.mjs migration deferred to
     Phase 5
   - 15/15 vitest pass (Tests 1, 2, 3, 6 from ADR-164.1 §9, skipping
     Test 5 clock-skew which would require system-clock manipulation)
   - reservationId is randomUUID v4 (ADR-164.1 §3.2 notes v7 is purely
     aesthetic — v7 generator would add a transitive dep we don't need)

C. ruflo federation trust elevate CLI (ADR-164 §3.5.4)
   - New subcommand: bypasses 500-interaction TRUST_TRANSITION_THRESHOLDS
   - Mandatory --reason + --audit flags (both required by spec)
   - Refuses unknown peer node-ids
   - Reuses existing trust_level_changed audit envelope with
     metadata.tag='bootstrap_elevation' + metadata.operatorBypass=true
     (cheapest path — keeps SEVERITY_BY_EVENT_TYPE / CATEGORY_BY_EVENT_TYPE
     stable rather than introducing a new event-type that needs propagation
     through downstream consumers)

Gates (all PASS):
  - Build: clean (tsc)
  - vitest http-fetch + bbs-budget-tracker: 40/40 PASS (25 + 15)
  - vitest business-pod-tools regression: 44/44 PASS (Phase 3 still green)
  - ADR-112 audit: 352/352 with guidance (was 351; +1 http_fetch)
  - business-pods smoke: 11/11 PASS
  - agentbbs smoke: 8/8 PASS
  - agenticow smoke: 8/8 PASS

Phase 5 follow-ups (explicit deferrals):
  - Live-mode dispatch (pod-tick --live still exits 3)
  - Sweeper timer integration site (daemon worker, not pod-tick)
  - Spend-reporter audit adapter (write to federation_spend namespace
    BEFORE BEGIN IMMEDIATE per ADR-164.1 §6.1)
  - pod-tick.mjs migration from file-based to atomic tracker
  - ACL gate on `federation trust elevate` (currently consistent with
    `federation evict` / `reactivate` — no founder-only crypto check yet)
  - http_fetch wiring into pod-tick live-mode for ops bench

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

* chore(lock): regen v3/pnpm-lock.yaml with agentbbs entry (PR #2503 CI fix)

Same fix as we did on the agenticow PR #2500 push — pnpm install at root
regenerates pnpm-lock.yaml (the npm workspace), but the real workspace is
v3/pnpm-workspace.yaml and its sibling v3/pnpm-lock.yaml. CI uses
--frozen-lockfile so the v3 lockfile must be in sync with v3/@claude-flow/cli's
package.json (which declares agentbbs: ~0.1.0 as optional).

* fix(ci): re-export-tolerant agentbbs guard + skip business-pods smoke under --ignore-optional

PR #2503 CI surfaced 2 ADR-164 issues:

1. no-agentbbs-smoke Rule 2 false-positive on mcp-tools/index.ts.
   The static-grep treated `export { agentbbsTools } from './agentbbs-tools.js';`
   as an unguarded agentbbs reference. The agentbbsTools symbol itself
   contains the loadAgentbbs() guard — the index.ts re-export is benign.
   Fixed: strip re-export + spread + import-of-agentbbsTools patterns + all
   comments before the unguarded-reference check.

2. ruflo-business-pods smoke 9/11 under no-metaharness-smoke's runtime
   drill (which does `npm install --ignore-optional`). Without optional
   deps, vitest may be absent AND the cli dist may not exist. Steps 4
   (vitest) and 11 (call into dist) now SKIP cleanly when those are
   missing instead of failing — the main test-suite jobs still gate
   the full vitest + build runs.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): business-pods-smoke uses pnpm (root npm chokes on workspace:*)

The root package.json declares some workspace deps with the pnpm `workspace:*`
protocol that npm doesn't support. Symptom: `npm install` step exited with
EUNSUPPORTEDPROTOCOL.

The v3/ tree is a real pnpm workspace (v3/pnpm-workspace.yaml). Switch the
workflow to install + build from there using pnpm + frozen-lockfile, then
fall through to vitest / node:test / smoke contract.

Pattern matches the existing v3-ci.yml install steps.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): pnpm --filter '@claude-flow/cli...' to include workspace deps

The previous `pnpm --filter @claude-flow/cli build` didn't build workspace
dependencies first, so the cli's tsc step failed with TS2307 errors looking
for @claude-flow/memory, @claude-flow/neural, @claude-flow/cli-core types.
The `...` suffix tells pnpm to include all of the cli's workspace
dependencies in the build graph.

Applied to both business-pods-smoke.yml and no-agentbbs-smoke.yml's
runtime drill.

Co-Authored-By: RuFlo <ruv@ruv.net>

* fix(ci): pnpm -r build (whole workspace, dep-first) — pnpm filter can't
discover cross-package dist/ imports

Round 4 surfaced two pre-existing fragilities in the workspace's build wiring:

1. business-pods-smoke: @claude-flow/cli imports from @claude-flow/swarm/dist/
   not via package.json deps but via direct file path. `--filter "@claude-flow/cli..."`
   doesn't traverse those. Fixed: `pnpm -r --no-bail build` builds the whole
   workspace dep-first, with a fallback to a direct cli build.

2. no-agentbbs-smoke runtime drill: `--no-optional` stripped agentdb, which
   is a transitive consumer of @claude-flow/neural and breaks neural's tsc
   even though neural has its own optional-degradation path internally.
   Fixed: install everything, then SURGICALLY rm node_modules/agentbbs only,
   so only the dep we're testing graceful-degradation for is missing.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-06-29 18:28:28 -04:00
ruv a63cdf0526 feat(metaharness): integrate @metaharness/redblue@~0.1.1 — adversarial red/blue LLM testing
ADR-150 sibling-package integration. `@metaharness/redblue` runs an
adversarial red-team → judge → blue-team patch → retest → report loop
against an LLM target you own. Covers OWASP LLM Top-10 / NIST AI RMF
families (prompt injection, tool misuse, data leakage, jailbreaks,
denial-of-wallet). Joins score/genome/mcp-scan/threat-model/oia-audit
as the run-time red/blue layer of the security plugin surface.

Why integrate here vs ruflo-aidefence or ruflo-security-audit:
- aidefence is detection (PII / prompt-injection scan); redblue is
  *generation* — they complement, redblue doesn't replace it
- security-audit is dep CVEs / static scan; redblue is run-time LLM eval
- ruflo-metaharness already carries the @metaharness/* wrapper pattern
  (`_darwin.mjs`, `_harness.mjs`), the locator, the MCP-tool registration
  surface, and the smoke/CI gates — redblue slots in as one more sibling

Surface added:
- `npx ruflo metaharness redblue {init|run|patch|attack|report}` CLI
- `metaharness_redblue` MCP tool (subcommand + family + tests + patch +
  mockJudge + alertOnFail + timeoutMs)
- Skill discoverability via CLAUDE.md (both CLI catalog + MCP tool catalog)
- 11 metaharness subcommands (was 10), 13 MCP tools (was 12)

Safety posture (inherited verbatim from upstream; wrapper does NOT relax):
- upstream `src/config/safety.ts` hard-enforces no real credentials, no
  live external targets (loopback / *.test / *.internal only), no shell,
  no arbitrary network, no eval at config-load time — a config that
  enables any of these errors at load
- `--mock-judge` is the $0 marker-fixture path for CI / offline; the real
  model judge requires `OPENROUTER_API_KEY` and is capped by
  `max_cost_usd` / `max_runtime_minutes` / `max_tests`
- wrapper shells out with `shell: false` argv arrays; never string interp
- ADR-150 graceful-degradation rule #3 — on install/network failure the
  wrapper returns `{degraded: true, reason: ...}` and exits 0 so ruflo
  stays operational

Upstream bug workaround (track for revert):
`@metaharness/redblue@0.1.1`'s CLI bootstrap uses
`isMain = import.meta.url === \`file://\${process.argv[1]}\``. When npx
links `redblue` → `dist/cli/index.js`, argv[1] is the symlink path but
import.meta.url is the resolved real path, so isMain is false and the
binary silently exits 0 with no output, no file, no error. Darwin's CLI
calls `main()` unconditionally and is unaffected. Workaround: install the
package once into `~/.ruflo/redblue-cache` via `npm install --prefix` and
invoke `node <abs_path>/dist/cli/index.js` directly — argv[1] then equals
the real path and isMain becomes true. First install hits npm's cache so
the cold path is ~200ms; warm invocations are ~20ms.

Locator hardening (prevents recurrence of artifact-mirror staleness):
The publish artifact at `v3/@claude-flow/cli/plugins/ruflo-metaharness/`
is regenerated only at `prepublishOnly`. On a dev tree with a fresh
script in source but a stale mirror, the locator would pick the mirror
because `_harness.mjs` exists in both. Locators in `commands/metaharness.ts`
and `mcp-tools/metaharness-tools.ts` now accept an optional `requiredScript`
that narrows the match to dirs containing the dispatched script. The
artifact mirror is also synced with the two new redblue scripts so the
publish artifact stays usable until the next `prepublishOnly`.

Gates updated to lock new state:
- smoke.sh: 9 subcommands in dispatcher (was 8), 11 in description list
  (was 10), 13 MCP tools (was 12), 13 mcp-script-count (was 12), 14
  MCP_SUCCESS_SEMANTIC footnotes (was 13), 13 `success: r.success`
  occurrences (was 12), tilde-pin check covers @metaharness/redblue
- test-mcp-tools.mjs: `tools.length === 13`, `metaharness_redblue`
  enumerated, attack-preview Phase-3 input, 180s timeout (cold-cache npx
  fetch class)
- CLAUDE.md: surfaced under both CLI catalog and MCP-tool catalog (gated
  by smoke 17z56 + 17z57)

Verification:
- `bash plugins/ruflo-metaharness/scripts/smoke.sh` → 120 passed, 0 failed
- `node plugins/ruflo-metaharness/scripts/test-mcp-tools.mjs` → 153 passed, 0 failed
- `node v3/@claude-flow/cli/bin/cli.js metaharness redblue init --out <path>`
  → writes the safety-asserting sample config, exits 0
- attack preview, mock-judge run (3-test → real findings report), and
  CLI dispatcher all exercised end-to-end before commit

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-06-27 14:01:20 -04:00
ruv 12ed081f6b darwin-plugins iter 4 — ruflo-security-audit: --depth full → --depth deep (CLI doesnt support full)
This iter-4 edit was made by the headless agent but its commit step
didnt land in the branch (rate-limit at end of workflow). Cleanly
landing it now alongside the lockfile fix.
2026-06-27 10:12:39 -04:00
ruv 0b5badac7e darwin-plugins iter 3 — ruflo-security-audit: align memory_store namespace to documented security-findings 2026-06-26 20:30:04 -04:00