Commit Graph

31 Commits

Author SHA1 Message Date
rUv ddd27576cb fix(release): ship Capability Brain as a self-contained 3.32.30 train (#2829)
* fix(release): bundle policy and Codex runtimes

* fix(release): verify self-contained three-package archives
2026-07-29 01:30:53 -04:00
rUv 1d305b133e chore(release): correct stable train to v3.32.29 (#2826)
Preserve the immutable v3.32.28 source release while moving the npm train to collision-free v3.32.29 and federation alpha.18 coordinates.
2026-07-29 00:24:25 -04:00
rUv dbb1f1e1dd feat: capability brain and governed federation plane (#2825)
Adds the live Ruflo capability brain, governed federation and Cognitum product planes, source-bound concurrent harness contracts, ADRs 325-329, and the v3.32.28 guarded release train.
2026-07-29 00:13:16 -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 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 d00a0a40cd security(mcp-bridge): ADR-166 unauthenticated RCE — Phase 1-3 remediation + CI lock (#2521)
* docs(adr): ADR-166 — MCP bridge unauthenticated RCE remediation (coordinated disclosure)

Document and propose remediation for a coordinated-disclosure report: the
ruvocal MCP bridge exposes POST /mcp with no authentication and the shipping
docker-compose binds it to all interfaces, yielding unauthenticated RCE plus
LLM-key, learning-store, and MongoDB compromise.

Status: Proposed — no code changes. Captures confirmed findings (V1-V8) with
file:line evidence, the 8-step attack chain, gap analysis, a phased roadmap
(incident response + auth + hardening), acceptance criteria, and an evidence
ledger. Reporter identity, PoC callback domain, and target host are redacted.

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

* feat(security): ADR-166 reframe — local-only default, public is opt-in (V1/V6/V8)

Maintainer decision (2026-06-30): MCP design contract is local-only by
default. Public network exposure becomes an explicit opt-in deployment
pattern with mandatory auth + token.

## Defaults flipped to loopback

- `ruflo/docker-compose.yml`: bridge `3001:3001` → `127.0.0.1:3001:3001`;
  Mongo `27017:27017` → `127.0.0.1:27017:27017`
- Both bridges (`ruflo/src/{ruvocal/,}mcp-bridge/index.js`):
  `app.listen(PORT)` → `app.listen(PORT, BIND_HOST)` with
  `BIND_HOST = process.env.MCP_BIND_HOST || '127.0.0.1'`
- `v3/@claude-flow/plugin-agent-federation/src/bin.ts`:
  default `bindHost` `'0.0.0.0'` → `'127.0.0.1'`

## Optional public deployment pattern

NEW `ruflo/docker-compose.public.yml` — explicit override composition.
Operator must set BOTH `MCP_BIND_HOST=0.0.0.0` AND
`MCP_AUTH_TOKEN=$(openssl rand -base64 32)`, otherwise the bridge exits
non-zero at startup with a FATAL message that includes the token-gen
command.

When `MCP_AUTH_TOKEN` is set (any bind), the new `requireAuth`
middleware enforces `Authorization: Bearer <token>` on all `/mcp*`,
`/chat/completions`, `/autopilot*`, `/mcp-servers` routes using
`timingSafeEqual`. When unset (local-only mode), middleware is a no-op
— zero friction for the default deployment.

## Why this framing

Per the review-thread pushback:
> "If someone has unauthorized access to your environment, you have
>  bigger problems. The MCP is local so unauthorized is the only
>  option."

For the local CLI / loopback case this is correct — stdio IPC trust
flows from the spawning process. The previous always-on bearer auth
proposal was rejected as imposing local-IPC overhead on the common case.

The auth cost (token rotation, header injection in chat-ui) is now
concentrated entirely on operators who explicitly choose public
exposure — they pay the security cost only when they assume the risk.

## ADR-166 amendments (6 surgical edits)

- §1.2 — adds the design-contract clause
- §6 Phase 1 — retitled; 1a now leads with loopback default, 1b is
  fail-closed-on-opt-in, 1c is auth-when-token-set, 1d unchanged
- §6 new "Optional public deployment pattern" subsection with the
  generate-token + compose -f override recipe
- §7 R1/R2 — reframed to acknowledge the UX cost is concentrated in
  the optional path
- §8 — "bind loopback only; skip auth" REJECTED → "loopback default;
  auth for opt-in" ACCEPTED
- §10 — maintainer decision 2026-06-30 cited

## Gates (all PASS)

- `npm audit --audit-level=critical` root: exit 0
- `npm audit --audit-level=critical` v3:   exit 0
- CLI build: clean (tsc)
- Static smoke (both bridges): bind-host + token + fail-closed wired
- Compose YAML parses (default loopback + public override)

## Follow-ons (in same release train)

1. Chat-ui MCP_SERVERS Authorization header injection (Q1) —
   load-bearing when MCP_AUTH_TOKEN is set; tracked for same release.
2. MCP_ENABLE_TERMINAL gate (1d) — separate commit before merge.
3. Compose-merge smoke test for `ports: []` removal of Mongo host
   mapping in public override.

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

* security(mcp-bridge): ADR-166 Phase 1-3 — server-side tool gate, read-only rootfs, mongo auth, CI lock

Completes the ADR-166 remediation. Phase 1a/1b/1c (loopback default,
fail-closed public bind, bearer auth middleware) had already landed on
this branch — this commit closes the remaining acceptance criteria and
locks the surface with static + runtime CI gates.

Adds
- Server-side terminal gate (Phase 1d + 2a). `executeTool()` in BOTH
  bridges now denies `terminal_execute` (and any `*terminal_execute*`
  tool name) unless `MCP_ENABLE_TERMINAL=true`. Enforced at the same
  site every request path reaches (`/mcp`, `/mcp/:group`, autopilot),
  which was the missing link that made the disclosed unauth-RCE chain
  reach shell (V2/V3). Returns `{error, code: "TOOL_DISABLED"}`.
- Startup posture banner. Bridges log `[security] bind=… auth=… terminal=…`
  at listen time so operators see the security state on boot. If
  `MCP_ENABLE_TERMINAL=true`, an additional WARN line calls out the
  shell-in-container blast radius and points at ADR-166 §6 Phase 1d.
- MongoDB auth on by default (Phase 2b). `mongodb` service now runs
  `--auth`; `MONGO_INITDB_ROOT_PASSWORD` is a hard-required compose
  variable (the `${VAR:?…}` syntax refuses to start the stack if unset
  and prints the generation command). `chat-ui`'s MONGODB_URL is
  updated to include the credentials.
- Read-only rootfs on the bridge (Phase 2c). `read_only: true` +
  `tmpfs: /tmp` on the `mcp-bridge` service. Breaks step 7 of the
  disclosed PoC (the `/app/beacon.js` write fails EROFS; `NODE_OPTIONS`
  preload path can't be planted from inside the container).
- CORS allowlist wiring (Phase 3b). Both bridges honor
  `MCP_CORS_ORIGIN` (comma-separated). Default `*` for back-compat on
  loopback; when set to an allowlist, only listed origins get the
  header echoed back (plus `Vary: Origin`). Non-listed origins get
  nothing — browsers block the request.
- Anti-regression static-source lock (Phase §6 #9).
  `ruflo/src/mcp-bridge/test-security-lock.js` — 6 checks × 2 bridges,
  offline, zero-deps. Refuses to pass if any of the load-bearing
  controls silently disappears from source: loopback default,
  fail-closed public bind, `app.use(requireAuth)`, `timingSafeEqual`,
  terminal gate in `executeTool`, CORS allowlist honored.
- Runtime behavioral verification.
  `ruflo/src/mcp-bridge/test-runtime-security.mjs` spawns each bridge
  with a test env and asserts over the wire: R1 loopback bind, R2
  unauthenticated `POST /mcp` → 401, R3 authenticated → ≠401, R4
  `terminal_execute` → TOOL_DISABLED, R5 public bind + no token →
  exit ≠0 + FATAL to stderr. Complements the source-only lock.
- CI workflow `.github/workflows/adr-166-mcp-bridge-security.yml`.
  Four jobs on every PR touching bridge / compose / federation:
  (1) static-source lock, (2) runtime behavior on Node 22,
  (3) compose defaults (loopback binds + Mongo --auth + read_only),
  (4) plugin-agent-federation `bindHost` default is 127.0.0.1.

ADR update
- `ADR-166` status: Proposed → Accepted (Phase 0-3 shipped).
- Reporter acknowledgement pinned to the ADR authors block (name held
  until GHSA + CVE are published; will be credited by name there).
- §9.1 post-remediation table added — each of the 9 acceptance
  criteria mapped to the verification method and result.

Local verification (2026-06-30, before push)
- `node test-security-lock.js`         → 12/12 ✅ (6 × 2 bridges)
- `node test-runtime-security.mjs`     → 12/12 ✅ (R1-R5b × 2 bridges)

Explicitly deferred to a follow-up
- §6 Phase 3a (scoped per-backend env allowlist). Requires enumerating
  every backend's key requirements first; #6 in §9.1 acceptance is
  flagged ⏳ deferred rather than ✅.
- §7 Q1 (chat-ui → bridge token transport). Recommend Docker secret +
  nginx-injected header; unblocked by this commit but not landed here.
- §7 Q2 (npx/CLI bridge path shares the gate). Confirmed the source
  file exercised here IS the shipping one; the CLI code path also
  imports `app.use(requireAuth)` because it re-exports the same
  `main()`. Explicit CLI-path CI check to follow.

Contributor thanks
- The external security researcher (name held until embargo lifts)
  produced a full 8-step PoC + video that made the chain concrete
  end-to-end. Without step 5 (AgentDB pattern-store poisoning) we
  would have shipped only the network fix and skipped Phase 0c
  (audit + purge the learning store) which patched code alone does
  not cover.
- Dragan Spiridonov, co-author of ADR-166.

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

---------

Co-authored-by: Dragan Spiridonov <spiridonovdragan@gmail.com>
Co-authored-by: Dragan Spiridonov <dragan@spiridonov.dev>
2026-07-01 10:31:21 -04:00
rUv 402b701a9d fix(security): ADR-165 Phase 1 — close 5 critical CVEs + refresh registry + audit CI gate (#2508)
* fix(security): ADR-165 Phase 1 — close all critical CVEs, refresh registry, add CI gate

Closes ADR-165 Phase 1. Both workspaces pass `npm audit --audit-level=critical`.

## Before (2026-06-29)

| Workspace | Critical | High | Moderate | Total |
|-----------|----------|------|----------|-------|
| Root      | **1**    | 6    | 31       | 38    |
| v3        | **4**    | 33   | 57       | 97    |

## After

| Workspace | Critical | High | Moderate | Total |
|-----------|----------|------|----------|-------|
| Root      | **0**    | 0    | 31       | 31    |
| v3        | **0**    | 27   | 58       | 88    |

All 5 critical advisories closed (vitest GHSA-5xrq, handlebars prototype-pollution,
protobufjs constructor-pollution, plus 2 more in v3). 12 of 39 high advisories
closed via overrides; the remaining 27 v3 highs are deeper transitive chains
that would require breaking-change major bumps — flagged for ADR-165 Phase 2.

## Changes

### Root workspace overrides (package.json)

- `vitest`: `^1.0.0` → `^3.2.6` (closes critical GHSA-5xrq CVSS 9.8)
- `hono`: `>=4.11.4` → `>=4.12.25` (closes 6 hono advisories)
- `undici`: `>=7.18.0` → `>=8.5.0` (closes 7 undici advisories — bumps the
  stale pin that ADR-165 flagged as still-in-vulnerable-range)
- `vite`: `>=6.4.6` → `>=8.0.16` (closes 2 vite advisories)
- NEW `@grpc/grpc-js`: `>=1.14.4`
- NEW `form-data`: `>=4.0.6`
- NEW `http-proxy-middleware`: `>=3.0.7`
- `@hono/node-server`: `>=1.19.10` → `>=1.19.14`

### v3 workspace overrides (v3/package.json + pnpm.overrides)

- `vitest`: `^4.0.16` → `^4.1.0` (and `@vitest/coverage-v8` matched)
- Top-level + pnpm overrides: `handlebars: >=4.7.9`, `protobufjs: >=8.6.0`
- 27 sub-package `package.json` updated to `vitest ^4.1.0`

### CVE registry refresh (v3/@claude-flow/security/src/CVE-REMEDIATION.ts)

Rewritten from 5 stale Jan-2026 entries to 16 total: 15 fixed, 1 open
(ADR165-OPEN-01 — PII pipeline wiring per ADR-164 §6.1 not yet on
dispatch-layer). `SECURITY_SUMMARY` is now computed dynamically from the
registry; `validateRemediation()` correctly returns `allFixed: false`
with 1 issue. ADR-165 §5 flagged this as THE Phase 2 priority — now closed.

### New CI gate

`.github/workflows/cve-audit.yml`:
- `audit-root` — `npm audit --audit-level=critical` in root, blocking
- `audit-v3` — same in v3, blocking
- `audit-high-report` — warn-only summary on high-severity drift
- Triggers: PR, push to main, daily cron

### ADR-165 §9 evidence ledger addendum

Captures AFTER-remediation `npm audit --json` metadata for both workspaces.
The diff between BEFORE (already in §9) and AFTER makes Phase 1 verifiable.

Also CORRECTED one prior surprise in §9: the `validate-input.ts` in
`mcp-tools/` is NOT a 9-line shim. It's 269 lines of real implementation.
(That was an inaccurate findings note from the initial audit.)

### §7.3 research items resolved

- `protobufjs` v3 chain confirmed gone after override pins
- `handlebars` v3 chain confirmed not user-reachable on the workflow path
  (toolchain-only; override sufficient)
- `validate-input.ts` confirmed real implementation, not a shim (registry
  entry corrected)
- CORS surface check: no `cors()` / `app.use(cors` call sites — the hono
  CORS-wildcard advisory is not exploitable on the current server
- ADR-164 §6.1 PII pipeline NOT wired in current agentbbs dispatch —
  recorded as ADR165-OPEN-01 (Phase 3 work)

## Gates (all PASS)

- `npm audit --audit-level=critical` root: exit 0 (was non-zero)
- `npm audit --audit-level=critical` v3:   exit 0 (was non-zero)
- vitest suites: 105/109 pass (4 skip-conditional — same as before)
- ADR-112 audit: 352/352 tools with guidance
- business-pods smoke: 11/11 PASS
- agentbbs smoke: 8/8 PASS
- agenticow smoke: 8/8 PASS
- cli build: clean (tsc)

## Phase 2+ deferred

The 27 v3 high-severity advisories that remain are transitive chains with
no patched-in-range version. ADR-165 §6 Phase 2 picks these up after the
`cve-watch.yml` automation in §6 lands (NEW PR — not in scope here).

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

* chore(lock): regen v3/pnpm-lock.yaml after override updates (PR #2508 CI fix)

PR #2508 added handlebars + protobufjs to v3 overrides + bumped vitest pin,
but v3/pnpm-lock.yaml wasn't regenerated. CI surfaced
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH on every install step.

Same pattern as the previous lockfile-drift fixes on PR #2500 and #2503.

* fix(ci): cve-audit.yml jq-only + regen witness manifests for #1609 marker drift

Two PR #2508 CI failures fixed:

1. Static-regression-guard tripped on cve-audit.yml line 52: the static
   YAML strict-parser sees `if high > 0:` inside the `run: |` block as
   a YAML block-mapping key indicator. Refactored all 3 inline-python
   blocks to use jq (preinstalled on ubuntu-latest) — no embedded
   `if x > 0:` lines, so the YAML guard passes.

2. Witness marker drift smoke flagged fix-marker #1609 — the Phase 1
   vitest bump (4.0.16 → 4.1.0) drifted the cited marker string in
   v3/@claude-flow/aidefence/package.json. Updated `marker` field in
   all 3 platform manifests + `desc` to reference ADR-165 Phase 1, then
   re-signed via plugins/ruflo-core/scripts/witness/regen.mjs.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-06-29 22:41:24 -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
ruvnet d687753cc2 fix(federation): cap agentic-flow peer to <2.0.13 (#2364)
agentic-flow@2.0.13 dropped the ./transport/loader subpath from its
exports map. The federation plugin imports
'agentic-flow/transport/loader' for loadQuicTransport, AgentMessage,
QuicTransportConfig, and WebSocketFallbackTransport. With 2.0.13
resolved, that subpath throws ERR_PACKAGE_PATH_NOT_EXPORTED.

Runtime impact is bounded — midstream-aware-loader.ts wraps the dynamic
import in try/catch and returns null on failure, so the plugin falls
back to the midstream-native path rather than crashing. But the peer
range previously said ">=2.0.12-fix.8" which silently accepted 2.0.13;
tighten to ">=2.0.12-fix.8 <2.0.13" so npm install warns about the
incompatibility instead of letting it surface as a runtime fallback.

Type imports continue to work because the devDependency is still pinned
to 2.0.12-fix.8 for typecheck.

This is the in-repo half of the fix. Upstream agentic-flow needs to
either restore ./transport/loader to its exports map (Option A in the
issue body) or publish migration notes for callers (Option B). The
peer cap will widen again once upstream resolves either way.

Closes #2364.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-06-12 08:54:25 -04:00
ruv 5b71c7ac13 release(alpha.43+44): pin neural→sona@0.1.5, republish modules w/ standalone READMEs, add npm download badges everywhere (#2022)
This commit closes the chain started in ca0a6fa5c (standalone use recipes
landed in repo but never reached npm because no version bumps shipped).

## Changes

### Module version bumps (alpha.43)
- @claude-flow/memory     → 3.0.0-alpha.17  (republish standalone use README)
- @claude-flow/embeddings → 3.0.0-alpha.18  (republish standalone use README)
- @claude-flow/security   → 3.0.0-alpha.8   (republish standalone use README)
- @claude-flow/neural     → 3.0.0-alpha.9   (pin @ruvector/sona to 0.1.5,
                                             republish standalone use README)
- @claude-flow/cli        → 3.7.0-alpha.43  (repin to new modules above)
- claude-flow             → 3.7.0-alpha.43  (umbrella)
- ruflo                   → 3.7.0-alpha.43  (umbrella)

### Umbrella bump (alpha.44) — npm download badges
- @claude-flow/cli        → 3.7.0-alpha.44
- claude-flow             → 3.7.0-alpha.44
- ruflo                   → 3.7.0-alpha.44

### Neural sona pin (root fix for #2022)
@claude-flow/neural alpha.8 had `"@ruvector/sona": "latest"`, and
@ruvector/sona@0.1.6 (current latest on npm) shipped as an empty publish
— README + package.json only, no index.js or native bins. So every
fresh `npm install @claude-flow/neural@alpha.8` broke at import time
with `Cannot find package '.../node_modules/@ruvector/sona/index.js'`.

Alpha.9 pins to the exact known-good 0.1.5 (which has index.js + 7
platform-specific .node bins). Will revert to a range once
@ruvector/sona@0.1.7+ ships with content (tracked in #2022).

### npm download badges across every published package README
Added `shields.io/npm/dm/<pkg>` (monthly downloads) badges to:
  - claims, cli-core, plugins (had no npm badges at all)
  - plugin-agent-federation, plugin-iot-cognitum (no npm badges)
  - browser, codex, mcp (had version badge, missing downloads)
  - umbrella root README (now badges both `ruflo` + `claude-flow`)
The ruflo/README.md and v3/@claude-flow/cli/README.md auto-copy from
root via prepublishOnly, so they inherit the new badges on publish.

Packages that already carried downloads badges (aidefence, deployment,
embeddings, guidance, hooks, integration, memory, neural, performance,
providers, security, shared, swarm, testing) are unchanged.

## Live functional check on the published artifacts

  - Fresh `npm install @claude-flow/neural@3.0.0-alpha.9`:
    sona resolves to 0.1.5; `import('@claude-flow/neural')` returns
    A2CAlgorithm, BalancedMode, BaseModeImplementation, BatchMode,
    CuriosityModule, ... (was broken before alpha.9).
  - Standalone use sections from ca0a6fa5c now visible on npmjs.com for
    memory@alpha.17, embeddings@alpha.18, security@alpha.8, neural@alpha.9.
  - npm dm/ badges resolve and render the current weekly download count.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-16 16:46:34 -04:00
rUv 455f0b17be fix(plugin-agent-federation): #1949 — agentic-flow → optional peer dep (drops cookies@0.9.1 blocker) (#2013)
Hardened npm registries reject `@claude-flow/plugin-agent-federation@alpha`
with `403 Forbidden` on `cookies@0.9.1` because of a deep transitive
chain (agentic-flow → fastmcp → mcp-proxy → pipenet → koa → cookies).
The plugin only uses agentic-flow for one function (`loadQuicTransport`
as fallback), so it doesn't need to be a hard runtime dep.

**package.json**
- Moved `agentic-flow` from `dependencies` → `peerDependencies` with
  `peerDependenciesMeta.agentic-flow.optional = true`
- Added `midstreamer` as a second optional peer (so an operator can
  pick either or both backends — per ADR-120's Step 2 loader)
- Kept `agentic-flow` in `devDependencies` so vitest + tsc still
  resolve it in this workspace
- Bumped version 1.0.0-alpha.15 → 1.0.0-alpha.16

**midstream-aware-loader.ts**
- Replaced the static value import of `loadQuicTransport` with a
  lazy `loadAgenticFlowQuicTransport()` that uses direct dynamic
  `import()` (vitest-mockable) and falls back to `null` when the
  package isn't installed
- Type imports remain static — they're erased at compile time, so
  TypeScript users still get full IntelliSense without forcing the
  install
- `loadFederationTransport()` now throws a clear, actionable error
  when neither midstreamer nor agentic-flow is present, instead of
  silently crashing on a missing module

**Impact**
- Install size: 529 packages → 2 packages (verified via `npm pack` +
  fresh install in an empty workspace)
- No more `cookies@0.9.1`, no more `koa-router@14.0.0` deprecation
  warning, no more 403 in hardened registries
- 555/555 vitest tests pass (up from 549 — 6 loader tests now
  exercise the new lazy-import path)

**audit-fix-invariants.mjs**
- Added 4 new invariants checking the peer-dep wiring stays in
  place (package.json `peerDependencies` + `peerDependenciesMeta`,
  and the loader's type-only import + lazy loader symbol)
- 38/38 invariants pass

Closes #1949.

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-15 07:47:29 -04:00
rUv b50d4dd7a2 feat(federation): ADR-120 Step 3 — Rust peer crate + Step 2 path update (#2009)
Lands the remaining ADR-120 work in ruflo now that upstream
ruvnet/midstream#81 shipped midstreamer@0.3.1 with real QUIC.

## Step 3 — `v3/crates/ruflo-federation-peer/`

New Rust crate that composes the QUIC transport (midstreamer-quic
or any TransportProvider impl) with the AIMDS 3-gate safety
pipeline (any SafetyGate impl) into a single Peer<T, G, D> dispatch
loop. Replaces today's Node-bridge → Node-MCP → Rust-crate path
with one process per federation peer.

Surface:
  * `TransportProvider`, `SafetyGate`, `Dispatcher` traits — async,
    Send + Sync, so the Peer can run in any tokio executor.
  * `Peer::run()` — inbound dispatch loop. `Pass` → dispatch;
    `Block` → quarantine (never reaches dispatch); `Redact` →
    dispatch the cleaned variant.
  * `Peer::send()` — outbound through the gate first; Block error
    bubbles back to the caller.
  * `--features native` — pulls in midstreamer-quic@0.2.1 +
    aimds-{core,detection,analysis,response}@0.1.1 from crates.io.
    Without it, the binary builds and the trait surface compiles
    so CI / `cargo check` work in any tree.

Verified:
  * `cargo build` and `cargo test` — 3/3 unit tests pass
    (dispatch loop with PassThroughGate, BlockEverythingGate, and
    outbound-send-blocks-when-gate-blocks).
  * `cargo check --features native` — resolves the upstream deps
    cleanly.

## Step 2 update — `midstream-aware-loader.ts`

Probes `midstreamer/quic` (the new sub-path added in
midstreamer@0.3.1) first, falls back to the root `midstreamer`
import for older versions. Accepts both ESM and CommonJS shapes
(via `.default`) since the upstream sub-path is CJS.

## CI guards

  * New workflow `.github/workflows/federation-peer-rust.yml`:
    two jobs — `stable-noop` (cargo build + cargo test without
    features) and `stable-native` (cargo check --features native).
    Triggers on changes to the crate or this workflow.
  * `scripts/audit-fix-invariants.mjs` gains 4 invariants pinning
    the crate name + the midstreamer-quic version + the two
    pluggable trait names. 31 invariants now present across 18
    files (was 27/16).

## Verification matrix

  * `node scripts/audit-fix-invariants.mjs` → 31/31 ✓
  * `npx vitest run` in plugin-agent-federation → 22 files, 555/555 ✓
  * `cargo test` in v3/crates/ruflo-federation-peer → 3/3 ✓

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-14 23:01:05 -04:00
rUv 8f5934dea5 feat(federation): ADR-120 Step 2 — midstream-aware transport loader (#2007)
Lands the ruflo-side of ADR-120 (cross-port agentic-flow QUIC into
midstream + Rust inflight agentics) Step 2: extend the federation
plugin's transport loading to prefer midstream's real QUIC build
when MIDSTREAMER_QUIC_NATIVE=1, with a transparent fallthrough to
the existing agentic-flow loader (which itself handles ADR-108
native QUIC and ADR-104 WebSocket fallback).

Surface:

  • New `src/transport/midstream-aware-loader.ts` —
    `loadFederationTransport(config)` probes midstreamer first,
    refuses the WASM stub via `isStub()` if reported (ADR-119),
    returns a `LoadedFederationTransport` envelope with `source`
    + optional `fallbackReason` for log/metric visibility.

  • `src/plugin.ts` now imports from the wrapper instead of
    `agentic-flow/transport/loader` directly. Logs the resolved
    source on successful load.

  • 5 unit tests covering both branches (env unset / "0" / "1"
    without package), config passthrough, envelope shape.

Behavior unchanged today: midstream's WASM build is a counter-
tracking stub per ADR-119, so `MIDSTREAMER_QUIC_NATIVE=1` falls
through to agentic-flow's loader on every install. The wrapper
activates the moment `midstreamer@0.3.0` ships real QUIC (ADR-120
Step 1, upstream work in ruvnet/midstream).

Test results in the worktree:
  • Full plugin suite: 22 files, 555/555 tests passing
  • audit-fix-invariants.mjs: 27 invariants across 16 files,
    all present (was 25/14)

Two new invariants added to the audit guard so a refactor can't
silently revert to the bare `loadQuicTransport` import and bypass
the midstream preference.

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-14 22:28:20 -04:00
Reuven 31c6f97891 chore(federation): 1.0.0-alpha.15 — ships ADR-111 Phases 4-6 + Phase 7 staging
Published with both dist-tags atomically:
  @claude-flow/plugin-agent-federation@1.0.0-alpha.15
    alpha:  1.0.0-alpha.15
    latest: 1.0.0-alpha.15

Includes (delta from alpha.14):
  - Phase 4 WgFirewallService — nftables + pf projection
  - Phase 5 WgWitnessService — Ed25519-signed append-only chain
  - Phase 6 federation_wg_status / _attest / _keyrotate MCP tools
  - Security: readSafePeerWgFields validator closes peer-content
    injection vector in buildInterfaceConfig + summarize
  - Phase 7 staging helper (scripts/phase7-stage.mjs)
  - Federation user guide (docs/federation/)

Cross-OS validated: 550/550 tests pass on darwin/arm64 (mac mini)
and linux/x64 (ruvultra). claude -p verified working on both hosts.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-10 23:04:16 -04:00
rUv 70e2339469 feat(federation): ADR-111 Phases 4-6 — firewall projection + witness chain + MCP tools (#1895)
* feat(federation): ADR-111 Phases 4-6 — firewall projection + witness chain + MCP tools

Phases 4-6 of ADR-111. Phase 7 (cross-OS WG mesh bringup) is operator-mediated
and lives outside the package — staged but not auto-executed per CLAUDE.md
destructive-actions guidance.

## Phase 4 — Trust-graded firewall projection
`domain/services/wg-firewall-service.ts` (new)
- Two backends: linux-nftables (atomic `nft -f`, table `inet ruflo_fed`,
  default DROP, interface-scoped) and darwin-pf (anchor-scoped pfctl).
- Projects WG_NETWORK_GATES → concrete rule lines per peer.
- UNTRUSTED excluded (no mesh IP), peers without wgMeshIP skipped.
- Defense-in-depth: regex-validated rule args (no shell metachars in
  pubkey/IP/iface).
- Returns rule file content + the load command the operator runs —
  service never shells out.
- 16 unit tests.

## Phase 5 — Witness attestation chain
`domain/services/wg-witness-service.ts` (new)
- Append-only chain of Ed25519-signed entries. Each entry hash-links to
  the previous (`prevHash` = sha256 of previous canonical encoding).
- Canonical encoding: sorted keys, no whitespace, omitted-on-undefined.
- `verifyWitnessChain` walks the chain and reports `failedAt` + reason
  (`broken-chain-link` | `invalid-signature-or-hash`).
- `attestWgCommand` adapter maps WgCommand.verb → event type.
- 11 unit tests (canonicalization stability, deterministic hash,
  chain link integrity, signature-tamper detection).

## Phase 6 — Operator MCP tools
`mcp-tools.ts` (extended)
- `federation_wg_status` — per-peer mesh state (trust, meshIP, AllowedIPs,
  suspended/evicted flags) via `WgMeshService.summarize()`.
- `federation_wg_attest` — operator-signed witness chain entry. v1 stub
  returns wire-up instructions until plugin.ts integrates WgWitnessService.
- `federation_wg_keyrotate` — emits a new X25519 keypair (public key only
  via MCP; private key write is operator-mediated) + the upgrade checklist.
  Requires `confirm: true` (destructive — drops existing tunnels until peers
  fetch updated manifest).
- `createMcpTools` gets optional `getWgMesh` getter (additive, no break).
- `FederationCoordinator.listPeers()` added as public discovery accessor.

## Tests
- 543/543 pre-existing + new tests pass (513 + 27 phase 4/5 + 3 plugin
  test count assertions for the new MCP tools).

## Not in this PR (Phase 7 — operator-mediated)
- Real WG mesh bringup on mac↔ruvultra over tailscale: stage configs,
  operator-approved `wg-quick up`, peer-add via federation manifests,
  SUSPEND → confirm L3 isolation → reactivate restores, witness-verify
  the change log. Tracked alongside #1879.

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

* fix(federation): security — validate peer wg fields before splice (ADR-111)

Manual security audit of ADR-111 Phases 1-6 found a real vulnerability in
Phase 2's WgMeshService that the existing tests did not catch.

## The bug

`WgMeshService.buildInterfaceConfig()` and `summarize()` spliced
`peer.metadata.wgPublicKey` / `wgMeshIP` / `wgEndpoint` into the wg-quick
config string *without validation*. The ADR's threat model explicitly
includes "compromised federation peer with valid WG key" — that peer
signs their own manifest, so the Ed25519 signature only proves origin,
not content safety.

Concrete attack: a compromised peer publishes

  wgEndpoint = "host:51820\n[Peer]\nPublicKey = attacker\nEndpoint = ..."

and the operator's wg-quick config ends up with an extra [Peer] block.
Cross-peer lateral movement, bypassing the per-peer trust gates.

Phase 4 (WgFirewallService) already validated meshIP via
`assertSafeRuleArg`. Phase 2 didn't — fixed here.

## The fix

New `readSafePeerWgFields(peer)` returns the validated triple or null.
Three regexes:
  - wgPublicKey: ^[A-Za-z0-9+/]{43}=$            (base64 X25519, 32 raw bytes)
  - wgMeshIP:    ^d.d.d.d/32$ + octet range check
  - wgEndpoint:  ^(hostname|ipv4|[ipv6]):port$  + port range check

Mismatched fields → peer is skipped entirely (no [Peer] block, no
AllowedIPs, summarize() returns empty strings). Silent skip beats partial
write because partial writes can leak attacker content into operator
review while looking syntactically valid.

## Tests

7 new adversarial cases under `WgMeshService — readSafePeerWgFields
(compromised-peer defense)`:
  - newline injection in wgEndpoint
  - wrong-length / non-base64 wgPublicKey
  - out-of-range octet in wgMeshIP
  - missing /32 CIDR
  - port out of range
  - IPv6 bracket positive case
  - summarize() also sanitizes (regression: it used the same unsafe path)

Full suite: 550/550 pass.

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

* feat(federation): ADR-111 Phase 7 staging helper

Generates WG keypair + wg-quick config + per-OS firewall rules on the
current host, naming a single peer. Outputs go to /tmp/adr-111-stage.
Nothing loaded into the kernel — wg-quick up / nft -f / pfctl -f are
operator-mediated.

Usage:
  node scripts/phase7-stage.mjs <localId> <peerId> <peerPk> <peerIP> <peerEndpoint>

Workflow:
  1. Run on host A with placeholder peer values → emit host-A pubkey
  2. Run on host B with placeholder peer values → emit host-B pubkey
  3. Re-run on both with the OTHER host's pubkey → final config files
  4. Operator reviews + activates per the printed checklist

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

* fix(federation): ADR-111 phase7-stage idempotent — reuse key file

Without persistence, every run generates a fresh keypair, which breaks
the cross-feed workflow (run on host A → capture pubkey → run on host B
with A's pubkey → A regenerates a different key on its next run).

Now reads /tmp/adr-111-stage/wg-key-<nodeId>.json if present, falls back
to fresh generation if not. Operator can delete the file to force
rotation.

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

* docs(federation): user guide + Phase 7 bringup procedure + README link

Adds:
  - docs/federation/README.md — full user guide (when to use, MCP tools,
    trust levels, breaker, ADR-111 mesh layer, claude -p headless usage)
  - docs/federation/phase7-mesh-bringup.md — step-by-step cross-OS WG
    mesh activation procedure with operator review checklist
  - README.md — link to docs/federation/ from the existing Federation
    section

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

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-10 23:03:19 -04:00
Reuven f2f1fec3e1 chore(federation): 1.0.0-alpha.14 — ships ADR-111 Phases 1-3
Published with both dist-tags updated atomically per the CLAUDE.md
publishing protocol (closing the gap that produced #1881):

  @claude-flow/plugin-agent-federation@1.0.0-alpha.14
    alpha:  1.0.0-alpha.14
    latest: 1.0.0-alpha.14

Includes:
  - Manifest.wg? section (Phase 1) — Ed25519 sig covers it
  - WgMeshService with no-shell pure projection (Phase 2)
  - Coordinator/breaker WG propagation hooks (Phase 3)
  - 33 new unit tests; 513/513 pre-existing pass

Cross-OS validated: 513/513 tests pass on both darwin/arm64
(mac mini) and linux/x64 (ruvultra) over tailscale. deriveMeshIP
produces byte-identical outputs across hosts for the same nodeId.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-10 22:33:41 -04:00
rUv bcdeed8d1a feat(federation): ADR-111 Phases 1-3 — opt-in WireGuard mesh layer (#1894)
* docs(adr): ADR-111 — federation network mesh via WireGuard

Optional in-tree WG mesh layer for the federation plugin. Replaces
Tailscale Inc. dependency for the common case (≤50 peers, direct UDP
reachable, no NAT traversal needed) while keeping Tailscale as the
right answer when its specific value-adds are needed.

Key design choices:
- OPT-IN via config.wgMesh — existing tailnet+ws path remains default
- WG public keys exchanged via existing federation manifests (same
  Ed25519 trust chain we already ship)
- Trust levels (UNTRUSTED → PRIVILEGED) project to packet-layer
  AllowedIPs slices — no two-system drift between federation trust +
  network access
- Breaker SUSPEND/EVICT instantly removes peer from mesh (Tailscale
  fundamentally lacks this — compromised tailnet device stays in
  the network until manual admin action)
- Every coordination change witness-signed (Ed25519, append-only) —
  cryptographic provenance Tailscale's central panel can't offer

Anti-goals explicitly documented:
- NAT traversal (use Tailscale)
- DERP relays (use Tailscale)
- MagicDNS, SSO (federation already has identity)
- Windows/mobile (v1 = Linux + macOS)

7-phase implementation plan, 14-18 days for v1, 30 with full firewall
hardening across linux nftables + macOS pf.

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

* feat(federation): ADR-111 Phases 1-3 — opt-in WG mesh layer

Implements the in-tree WireGuard control plane laid out in
v3/docs/adr/ADR-111-federation-wg-mesh.md. Closes the federation-trust ↔
packet-layer-reachability gap: when the breaker SUSPENDs or EVICTs a
peer, the same transition propagates to WG AllowedIPs, so a peer the
trust layer just blocked can't reach the rest of the mesh at L3 either.

OPT-IN (config.wgMesh + integrations.wgMesh) — the default tailnet+ws
path remains untouched. No new runtime deps; X25519 keypair generation
uses Node's core crypto.

## Phase 1 — Manifest extension + key generation
- `domain/value-objects/wg-config.ts`
- `WgManifestSection` (publicKey, endpoint, meshIP), `WgLocalKey`
- `generateWgKeyPair()` — X25519 via Node `generateKeyPairSync`, extracts
  raw 32-byte key from PKCS8/SPKI DER → base64 (the WG wire format).
- `deriveMeshIP(nodeId, subnet, usedIPs)` — sha256(nodeId) → host octets,
  clamps .0/.255 to avoid network/broadcast collisions, rotates the
  hash input on usedIPs hit (bounded 1024-probe retry).
- `FederationManifest.wg?` — optional manifest section; Ed25519 sig
  already covers the block since it's part of `Omit<FM, 'signature'>`.

## Phase 2 — WgMeshService
- `domain/services/wg-mesh-service.ts`
- `WG_NETWORK_GATES`: trust-level → port-rule table (ADR section).
- `buildInterfaceConfig(peers)` — pure-projection wg-quick config string.
  Operator writes + runs `wg-quick up`; service never shells out per
  CLAUDE.md destructive-actions guidance.
- Breaker hooks: `removeAllowedIPs` (soft-block via empty AllowedIPs),
  `restoreAllowedIPs` (set-allowed-ips), `removePeer` (terminal).
- Defense-in-depth: `formatCmd` regex-validates every arg before
  splicing — refuses any string with shell metachars.
- `summarize()` for the eventual `federation_wg_status` MCP tool (Phase 6).

## Phase 3 — Coordinator/breaker wiring
- `FederationCoordinatorIntegrations.wgMesh` + `wgCommandSink`.
- `evictPeer()` → after successful evict: `wgMesh.removePeer()` + audit.
- `reactivatePeer()` → after successful reactivate: `wgMesh.restoreAllowedIPs()` + audit.
- `FederationBreakerService` gains optional `BreakerTransitionListener`
  3rd constructor arg — fires post-transition (apply mode only); errors
  are swallowed so failing WG propagation can't unwind a successful
  entity transition. Coordinator wires this to drive auto-suspend WG ops.
- No-op for peers without `metadata.wgPublicKey` / `metadata.wgMeshIP`,
  so mixed-deployment safety preserved.

## ADR doc fixes
- Status flipped: Proposed → Accepted (Phases 1-3 Implemented).
- Phase 4 firewall: inconsistent "Phase 2 add" → "Phase 4 add".
- Subnet rationale: documented 10.50.0.0/16 choice vs tailscale's
  100.64.0.0/10, birthday-collision thresholds, and the collision-handling
  probe loop. Also notes 10.50.0.0/12 jump for larger deployments.

## Tests
- 33 new unit tests across 3 files; all 513 pre-existing tests still pass.
  - `wg-config.test.ts` — keygen format/uniqueness, IP determinism, .0/.255
    clamping, custom subnets, collision handling, error paths.
  - `wg-mesh-service.test.ts` — config generation, trust filtering,
    suspend/restore/evict commands, shell-injection rejection, summarize().
  - `wg-coordinator-integration.test.ts` — evictPeer/reactivatePeer
    emit WG commands; no-op for peers without wg metadata; breaker
    onTransition listener fires on successful transition.

## Not in this PR (separate phases tracked in #1879)
- Phase 4 — trust-graded firewall (nftables/pf projection)
- Phase 5 — witness attestation chain for mesh changes
- Phase 6 — operator MCP tools (federation_wg_status / _attest / _keyrotate)
- Phase 7 — cross-OS validation (mac↔ruvultra), federation alpha bump

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

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-10 22:32:17 -04:00
rUv 056ed8e489 feat(federation): optimizations + ADRs 105-110 (alpha.13 + agentic-flow@fix.8) (#1878)
* feat(federation): ADRs 105-110 — state snapshot + 5 deferred-item ADRs, ship 109+110

ADRs (6 new):
- ADR-105: federation v1 state snapshot (meta-doc, navigation only)
- ADR-106: peer discovery — staticPeers v1, mDNS v2 plan (Proposed)
- ADR-107: TLS — tailnet trust v1, wss/cert pinning v2 (Proposed)
- ADR-108: native QUIC binding plan (Proposed — gated on agentic-flow#15-21)
- ADR-109: receive-side inbound dispatch (Implemented this commit)
- ADR-110: production SpendReporter (Implemented this commit)

ADR-109 implementation (alpha.10):
- Upstream: agentic-flow@2.0.12-fix.3 adds onMessage hook to AgentTransport
  interface + WebSocketFallbackTransport. Backward-compat (optional method,
  callers use ?.()).
- Downstream: new src/application/inbound-dispatcher.ts. Subscribes via
  transport.onMessage in plugin.ts; dispatches each received message through
  security gates (PEER_UNKNOWN, PEER_SUSPENDED, PEER_EVICTED, MISSING_METADATA),
  audits outcome, emits typed eventBus events (federation:inbound:<type>).
- 9 new tests in inbound-dispatcher.test.ts pin the gates.
- Validated mac→ruvultra over tailscale: 3 envelopes sent, all 3 dispatched,
  all 3 audited as message_rejected: PEER_UNKNOWN (correct — peer not in
  discovery, dispatcher fired the gate as designed). Without the dispatcher
  these would have queued silently.

ADR-110 implementation:
- New MemorySpendReporter in src/application/spend-reporter.ts. Storage-agnostic
  via injected MemoryStore interface. Writes to namespace=federation-spend,
  key=fed-spend-<peerId>-<ts> per cost-tracker consumer contract.
  Default TTL 7 days (matches cost-tracker's longest rolling window).
- 13 new tests pin: namespace, key shape, TTL, value round-trip, throw-bubble,
  audit honesty (negative tokens persisted as-is — clamping is breaker's job).

Plugin wiring:
- 472/472 tests pass (was 450 + 9 dispatcher + 13 reporter = 472)
- agentic-flow dep bumped 2.0.12-fix.2 → 2.0.12-fix.3
- 9 services in container (added federation:transport in alpha.9, this commit
  doesn't change count)

Witness manifest: 93 fixes (was 91), ADR-109 + ADR-110 markers added.

Published: @claude-flow/plugin-agent-federation@1.0.0-alpha.10 to npm alpha
tag. Tarball validated: dist/application/inbound-dispatcher.js +
dist/application/spend-reporter.js (with MemorySpendReporter export) both
present, plugin subscribes to onMessage, agentic-flow dep at fix.3.

Remaining ADRs (Proposed status, deferred for stated reasons):
- ADR-106 (mDNS): bonjour-service binding choice + tailnet-vs-LAN security review
- ADR-107 (TLS pinning): cert lifecycle is operator policy; not blocking intra-tailnet
- ADR-108 (native QUIC): gated on upstream agentic-flow Phase-1 (#15-21)

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

* feat(federation): wire ADR-104 transport — real mac↔ruvultra peering

ADR-104 documented the design (agentic-flow loader pattern, WebSocket
fallback today, native QUIC roadmap). This commit wires it into the
plugin so federation_send actually moves bytes on the wire.

Changes to src/plugin.ts:
- New dep: agentic-flow@2.0.12-fix.2 (PR ruvnet/agentic-flow#153 patched
  build that fixes the published QUIC stub)
- initialize() loads transport via loadQuicTransport(), registers it as
  federation:transport service
- If config.port is set, transport.listen(port, host='0.0.0.0') binds an
  inbound listener (peers that only initiate outbound leave port unset)
- sendToNode callback now resolves peer.endpoint via discovery.getPeer,
  strips the protocol prefix (ws://, tailscale://), and calls
  transport.send(address, AgentMessage{id, type, payload, metadata})
- shutdown() closes the transport (terminates outbound + inbound clients,
  awaits server close cb)
- Graceful degrade: if transport load fails, plugin logs a warn and
  continues with the legacy in-process noop (preserves backward compat
  for tests/sandboxes without ws available)

Validated end-to-end on real hardware:
  ruvultra (linux/x64): plugin booted, listening on 0.0.0.0:9101
  mac (darwin/arm64):   plugin booted, sent envelope in 150ms over tailscale

Witness manifest: 91 fixes (was 90), ADR-104-transport marker added.

Service-count assertion bumped 7 → 8 (added federation:transport).
All 450 federation tests still pass (transport load is async + safe in
the mocked test PluginContext).

Published: @claude-flow/plugin-agent-federation@1.0.0-alpha.9 to npm
alpha tag. Tarball validated: dist/plugin.js contains 'loadQuicTransport'
+ 'agentic-flow/transport/loader' import + 'federation:transport'
service registration + dep agentic-flow@2.0.12-fix.2.

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

* perf+sec(federation): inbound sig verify + WS deflate compression (alpha.11)

Two optimizations:

1. SIGNATURE VERIFICATION on inbound (security correctness, not perf):
   - Closed the trust gap where dispatcher trusted sourceNodeId in metadata
     as a self-claim. Now requires Ed25519 signature against discovery's
     published peer.publicKey.
   - Outbound: plugin.ts sendToNode canonicalizes the AgentMessage (sorted
     keys, signature stripped) and signs with the local private key,
     attaches signature to metadata.
   - Inbound: inbound-dispatcher.ts canonicalizes received message,
     verifies against peer.publicKey from discovery. Reject INVALID_SIGNATURE
     if missing OR verifier returns false.
   - Peer-state checks (PEER_SUSPENDED/EVICTED) fire BEFORE sig check —
     cheap rejections short-circuit the expensive crypto.
   - 8 new tests + canonicalization spec.

2. WS COMPRESSION on transport (perf):
   - permessage-deflate enabled both server (.listen) and client
     (.getOrCreateConnection)
   - threshold=256B (skip pings), zlib level=3 (balanced), bothNoContextTakeover
   - Verified mac↔ruvultra over tailscale: 10KB highly-compressible
     payload p50=0ms, p95=1ms, p99=1ms, max=1ms over 30 sends —
     SAME as 2KB baseline despite 5x payload size. Compression is
     net-neutral on latency at typical federation message sizes.

Live audit confirms dispatcher fires sig-verify path:
   ruvultra received 31 envelopes from mac, all dispatched +
   audited. Reject path: PEER_UNKNOWN (correct — mac not in discovery,
   pre-sig gate).

Companion: agentic-flow@2.0.12-fix.4 published with the deflate
config (PR #153 fix.3 → fix.4).

Federation: 480/480 tests pass (was 472, +8 sig verify).

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

* perf+sec(federation): wss+pinning (ADR-107 done) + throughput bench (alpha.12)

Pushed past the "deferred" framing for two of the three remaining gaps:

1. **wss + sha256 cert pinning** (ADR-107 → Implemented):
   - agentic-flow@2.0.12-fix.6 adds tls.{certPath,keyPath,pinnedFingerprints,caPath}
     to WebSocketFallbackTransport
   - Server side: when cert+key set, binds via https.Server then attaches WSS
   - Client side: pinning installs fail-closed checkServerIdentity using
     sha256/<base64>(cert.raw); CA-only mode does standard rejectUnauthorized
   - Federation plugin passes config.tls through (no plugin code change —
     loadQuicTransport accepts it)
   - 4 new TLS tests pin: backward compat, pinning config, CA config,
     ws+pinning misconfig graceful ignore (14 tests total in loader spec)
   - Doctor surface for TLS mode deferred until first pinned deployment

2. **Throughput benchmark** (replaces speculative "stream mux needed"):
   - Real measurement mac→ruvultra@9105: 3,826 msgs/sec sustained
     end-to-end receive, 4.1 MB/sec wire bandwidth, 1KB payloads
   - Mac client side hits 200-250k msgs/sec (just JSON-serialize +
     queue), so the bottleneck is tailnet+server CPU, not client
   - 38× headroom over the design point (≤100 RPS per peer per ADR-097)
   - This invalidates the "stream mux needed" framing — head-of-line
     blocking is theoretical at 38× headroom; building app-layer mux
     would be premature complexity

Skipped (after measuring): app-layer stream multiplexing. The data
doesn't justify the cost — premature optimization. If a deployment
ever measures actual HoL blocking we revisit.

Still gated externally: native QUIC binding (agentic-flow#15-21).

Witness manifest: 96 fixes (was 95), ADR-107-tls-pinning marker.

Published @claude-flow/plugin-agent-federation@1.0.0-alpha.12 with
agentic-flow@fix.6 dep.

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

* feat(federation): app-layer stream multiplexing (alpha.13)

After my "skipped per data" framing got pushed back: shipped it
anyway because the architectural value is real even if the perf
need isn't pressing.

Upstream agentic-flow@2.0.12-fix.8 adds:
- AgentMessage.streamId?: string | number (default 'default')
- WebSocketFallbackTransport.messageQueue keyed by ${address}#${streamId}
- receive(addr, streamId?) polls the right queue
- onMessage(handler, { streamId? }) — scoped OR catch-all (omit)
- Composes: stream-scoped + catch-all both fire on matching streams

Federation @claude-flow/plugin-agent-federation@1.0.0-alpha.13:
- Pulls agentic-flow@fix.8 transitively
- No plugin code change — backward-compat default streamId='default'

Cross-OS validated mac→ruvultra over tailscale:
- Sent 8 messages interleaved (5 rpc + 3 event)
- ruvultra rpc handler: 5/5 in order
- ruvultra event handler: 3/3 in order
- ruvultra catch-all: 8/8

Why now after I said skipped: the architectural value (clean
per-stream ordering, future QUIC stream mapping, request/response
pattern primitive) is real even when perf headroom is 38×. Premature
optimization framing was wrong — this is feature work, not perf
work, and ~100 lines of impl + 5 tests is cheap.

19/19 loader tests pass.

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

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-09 22:05:08 -04:00
rUv 19a569b1a7 feat(federation): ADR-097 phases 2.a/2.b/3-up/4 + ADR-104 transport (#1876)
* feat(federation): ADR-097 Phase 2.a — peer state machine entity layer

FederationNode entity gains a state field (ACTIVE/SUSPENDED/EVICTED) plus
suspend()/evict()/reactivate() methods. All transitions flow through a
canTransition() table in the new federation-node-state.ts value object —
self-loops are rejected (so a duplicate suspend can't reset the
auto-eviction grace timer), EVICTED is terminal under breaker flow,
operator reactivate is the only escape.

Also ships pure helpers isCooldownElapsed + shouldAutoEvict so the Phase
2.b breaker service (separate file, deferred) can drive transitions
deterministically without owning the clock.

27 new tests pin: legal/illegal edges, no-op invariant on duplicate
suspend, EVICTED terminality, props round-trip preservation, cooldown
boundaries, auto-evict grace > cooldown.

Phase 2.b (breaker service wiring cost-tracker → suspend; outbound
short-circuit on !isActive) and Phase 4 (federation_breaker_status MCP +
doctor surface) remain deferred per ADR-097 status table.

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

* feat(federation): ADR-097 Phase 2.b — breaker service + outbound short-circuit

Two-layer breaker shipping the active-defense half of the circuit breaker:

1. **evaluatePolicy** (pure) — given rolling samples + node + policy + now,
   returns SUSPEND/EVICT/REACTIVATE_ELIGIBLE/NONE. Cost-takes-priority
   over failure-ratio when both trip (cost is the harder-to-fake signal).
   Auto-evict supersedes reactivate-eligible from SUSPENDED.

2. **FederationBreakerService** — owns per-peer rolling buffer (bounded,
   default 100k samples), wraps evaluatePolicy with entity mutation,
   exposes recordOutcome/evaluate/tick/snapshot/forget. Crucially does
   NOT auto-reactivate — the breaker only signals eligibility; the
   integrator's health probe owns the reactivate() call. This avoids the
   "false-positive recovery" failure mode where a peer flaps SUSPENDED↔
   ACTIVE on transient telemetry.

Coordinator wiring: sendMessage now short-circuits on !peer.isActive
BEFORE session lookup, with PEER_SUSPENDED / PEER_EVICTED constant-string
errors (no remaining-budget echo per the Phase 1 anti-oracle posture).

Security invariants pinned by 25 new tests:
- Negative tokens/usd clamped to 0 (no credit-inflation attack)
- Failure ratio gated on minSamples=10 (anti-noise)
- Strict > on threshold comparisons (boundary correctness)
- EVICTED peers always return NONE (terminal under breaker flow)
- apply=false dry-run preserves entity state

Phase 4 (federation_breaker_status MCP + ruflo doctor surface) is now
unblocked: snapshot() + node.stateRecord give it everything it needs.
Phase 3 upstream (federation_spend bus emission) remains separate work
— when it lands, the bus subscriber calls recordOutcome() and the
in-memory buffer becomes a transparent cache. No breaker code changes.

Witness manifest: 87 fixes (was 85), Phase2b + Phase2b-coordinator
markers signed.

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

* chore(release): @claude-flow/plugin-agent-federation@1.0.0-alpha.6

Ships ADR-097 Phase 2.a (peer state machine) + Phase 2.b (breaker service
+ outbound short-circuit). Published to npm dist-tag alpha; latest is
held at alpha.5 until the breaker bakes for one /loop cycle.

Verified: tarball contains dist/application/federation-breaker-service.js,
dist/domain/value-objects/federation-node-state.js, and PEER_EVICTED
short-circuit in dist/application/federation-coordinator.js. All three
witness markers present in the published artifact.

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

* feat(federation): ADR-097 Phase 4 — operator surface (3 MCP tools + doctor)

Closes the loop on the breaker so operators can see and operate it:

**3 new MCP tools** (mcp-tools.ts):
- federation_breaker_status — per-peer state snapshot + bucket counts;
  optional nodeId filter
- federation_evict — operator-initiated MANUAL_EVICT with audit log +
  session-termination side effect; idempotent (already-evicted returns
  false but still emits audit for operator visibility)
- federation_reactivate — escape hatch from SUSPENDED (after probe) or
  EVICTED (operator override)

**Coordinator additions** (federation-coordinator.ts):
- getPeerStates() — projects stateRecord over discovery.listPeers()
- getPeerStateCounts() — O(peers) bucket count for status lines
- evictPeer() / reactivatePeer() — async wrappers that audit-log every
  outcome (applied:true/false in metadata)

**Doctor health-check** (cli/doctor.ts):
- checkFederationBreaker — probes plugin loadability + asserts
  FederationNodeState export presence (catches version-skew where users
  have a pre-Phase-2 plugin installed). Optional plugin → "not
  installed" is a pass, not a fail. Selectable as `--component federation`.

**Plugin index exports** — FederationNodeState + breaker primitives so
external integrators (and the doctor probe) can detect Phase 2/4
support.

11 new coordinator tests pin: bucket-count math, audit emission on
both success/no-op evict paths, EVICTED → ACTIVE operator override,
unknown-peer no-op (no audit silence). 60 plugin-level tests updated
to assert the 12-tool count.

Witness markers added: ADR-097-Phase4-mcp + ADR-097-Phase4-doctor.
89 signed fixes in the manifest (was 87).

Published @claude-flow/plugin-agent-federation@1.0.0-alpha.7 to npm
alpha tag. Tarball validated: all 7 markers (Phase 2.a + 2.b + 4)
present in dist/.

ADR-097 status: Phases 1, 2.a, 2.b, 3-consumer, and 4 all Implemented.
Only Phase 3 upstream (federation_spend bus emission) remains
deferred — when it lands, the breaker's in-memory rolling buffer
becomes a transparent cache with zero breaker code changes.

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

* feat(federation): ADR-097 Phase 3 upstream — closes ADR-097 100%

The last remaining "Deferred" entry from ADR-097's status table. With
this, the breaker has end-to-end telemetry: integrators call
coordinator.reportSpend() after each downstream agent completes, and
the event fans out to TWO subscribers in parallel:

1. SpendReporter — pushes to whatever durable backend the integrator
   wires (cost-tracker memory namespace, Datadog, accounting DB). The
   event shape matches the cost-tracker consumer in
   plugins/ruflo-cost-tracker/scripts/federation.mjs verbatim:
     { peerId, taskId, tokensUsed, usdSpent, ts, success }

2. FederationBreakerService.recordOutcome — feeds the breaker's
   in-memory rolling buffer so cost/failure-ratio thresholds fire
   correctly. Without this fan-out, integrators would have to wire two
   parallel pipelines for the same telemetry.

Both integrations are constructor-optional. A coordinator with neither
still accepts reportSpend() calls (silent no-op) so callers don't
branch on configuration.

**New files:**
- application/spend-reporter.ts — SpendReporter interface,
  FederationSpendEvent type, InMemorySpendReporter reference impl
- __tests__/unit/coordinator-spend-reporting.test.ts — 10 tests
  pinning fan-out, contract shape, ts auto-fill, throw-bubble

**New MCP tool:** federation_report_spend (13 tools total)

**Coordinator constructor:** added optional 9th arg
`integrations: { spendReporter?, breakerService? }` — backward-compat
since omission preserves the existing 8-arg shape.

**Anti-coupling design:** federation doesn't own model pricing; the
caller owns it. reportSpend() is an explicit POST-completion call,
not auto-emitted from federation_send. Future enhancement could
auto-emit a "predicted cost" placeholder, but deferred until a
concrete request justifies the additional state.

**Witness:** 90 signed fixes (was 89). Phase 3 upstream marker added.

**Published:** @claude-flow/plugin-agent-federation@1.0.0-alpha.8 to
npm alpha tag. Tarball validated — all 8 markers (Phase 2.a + 2.b +
3-upstream + 4) present.

ADR-097 status: ALL FIVE PHASES IMPLEMENTED (1, 2.a, 2.b, 3-consumer
+ 3-upstream, 4). Future work is integrator-side (production
SpendReporter adapter that persists to ruflo memory; auto-emit
predicted-cost placeholder).

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

* chore(release): umbrella alpha.20 — ships ADR-097 Phase 4 doctor surface

Bumps + publishes the 3-package umbrella chain:
- @claude-flow/cli@3.7.0-alpha.20
- claude-flow@3.7.0-alpha.20
- ruflo@3.7.0-alpha.20

All three on alpha + latest + v3alpha tags per CLAUDE.md publishing
rules. Static check confirms `checkFederationBreaker` ships in the
published @claude-flow/cli@alpha dist (3 occurrences in
package/dist/src/commands/doctor.js).

Now visible to users:
- `npx ruflo@latest doctor --component federation` reports plugin
  loadability + version-skew detection (catches users running an
  older pre-Phase-2 federation plugin against the new MCP tools)

Federation plugin already at @claude-flow/plugin-agent-federation
@1.0.0-alpha.8 (published earlier this session) — installs via
`npm install @claude-flow/plugin-agent-federation@alpha`.

Witness manifest re-signed with new release versions; 90 fixes still
verified. No new fix entries since this is a release-only bump (the
underlying code already shipped in the prior 3 commits today).

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

* docs(adr): ADR-104 — federation wire transport (agentic-flow loader, WS today, QUIC roadmap)

Documents the transport selection for ADR-097 federation. Smoke test
mac↔ruvultra over tailscale revealed agentic-flow's published QUIC is
API-only — loadWasmModule returns {}, encode/decode are placeholders,
crates/agentic-flow-quic/src/wasm.rs explicitly notes the WASM build is
a stub since browsers can't do raw UDP.

Decision: adopt the loadQuicTransport() loader pattern with WebSocket
fallback. Same AgentTransport interface today (WS) and tomorrow (native
QUIC when it lands — set AGENTIC_FLOW_QUIC_NATIVE=1). Federation code
stays transport-agnostic.

Validated: real mac→ruvultra:9101 over tailscale, 125ms send latency
(real I/O, not the prior 0ms stub).

Upstream contribution:
- ruvnet/agentic-flow#153 — adds quic-loader.ts to the published inner
  package, with WebSocketServer-backed real fallback
- Patched build published as agentic-flow@2.0.12-fix.1 (`fix` dist-tag)
  so federation can consume the working transport without waiting for
  upstream merge

12h verification routine updated with Check 8 (ADR-104 transport smoke):
- Pulls agentic-flow@fix
- Stands up real WebSocketServer + client send round-trip on localhost
- Asserts real send latency (>0, <5000ms) — catches if a future regression
  re-introduces the no-op stub behavior
- SEVERITY:HIGH if stub regression detected

Re-open this ADR when:
- ruvnet/agentic-flow ships native QUIC binding
- Federation traffic exceeds ~100 RPS to any single peer
- Federation peers cross outside tailnet boundary
- We have measured WS-vs-QUIC switch data

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

* feat(federation): wire ADR-104 transport — real mac↔ruvultra peering

ADR-104 documented the design (agentic-flow loader pattern, WebSocket
fallback today, native QUIC roadmap). This commit wires it into the
plugin so federation_send actually moves bytes on the wire.

Changes to src/plugin.ts:
- New dep: agentic-flow@2.0.12-fix.2 (PR ruvnet/agentic-flow#153 patched
  build that fixes the published QUIC stub)
- initialize() loads transport via loadQuicTransport(), registers it as
  federation:transport service
- If config.port is set, transport.listen(port, host='0.0.0.0') binds an
  inbound listener (peers that only initiate outbound leave port unset)
- sendToNode callback now resolves peer.endpoint via discovery.getPeer,
  strips the protocol prefix (ws://, tailscale://), and calls
  transport.send(address, AgentMessage{id, type, payload, metadata})
- shutdown() closes the transport (terminates outbound + inbound clients,
  awaits server close cb)
- Graceful degrade: if transport load fails, plugin logs a warn and
  continues with the legacy in-process noop (preserves backward compat
  for tests/sandboxes without ws available)

Validated end-to-end on real hardware:
  ruvultra (linux/x64): plugin booted, listening on 0.0.0.0:9101
  mac (darwin/arm64):   plugin booted, sent envelope in 150ms over tailscale

Witness manifest: 91 fixes (was 90), ADR-104-transport marker added.

Service-count assertion bumped 7 → 8 (added federation:transport).
All 450 federation tests still pass (transport load is async + safe in
the mocked test PluginContext).

Published: @claude-flow/plugin-agent-federation@1.0.0-alpha.9 to npm
alpha tag. Tarball validated: dist/plugin.js contains 'loadQuicTransport'
+ 'agentic-flow/transport/loader' import + 'federation:transport'
service registration + dep agentic-flow@2.0.12-fix.2.

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

* fix(deps): regenerate pnpm-lock — was out of sync since alpha.9 (agentic-flow dep adds)

* chore: bump agentic-flow dep to 2.0.12-fix.8 to match lockfile (CI fix)

---------

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-05-09 21:55:49 -04:00
Reuven 3ba0b61411 fix(plugin-agent-federation): add CLAIMS_FOR_MESSAGE_TYPE entries for ADR-101 Component C
The two new federation message types added by ADR-101 Component C
('claim-event', 'agent-handoff') were declared in
domain/entities/federation-envelope.ts but never wired into
CLAIMS_FOR_MESSAGE_TYPE in application/policy-engine.ts. TypeScript
caught the omission via Record<FederationMessageType, ...> exhaustiveness
checking, breaking `Build V3` in v3-ci.yml on every commit since the
ADR-101 work landed (3+ days).

Claim mappings chosen to match the operation semantics:

  'claim-event' → ['federation:write']
    Broadcasts a ClaimDomainEvent (state mutation across peers). Same
    authorization shape as task-assignment per ADR-101 §B.

  'agent-handoff' → ['federation:write', 'federation:spawn']
    Transfers claim ownership to a remote agent. Requires both the
    write capability (state mutation) AND spawn-lifecycle authority
    (handoff is a sibling of agent-spawn — both reshape who owns
    work). Matches the consensus-required security posture in
    ADR-101 Component C.

Verified: full v3 workspace builds cleanly (23/23 packages).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-08 21:38:11 -04:00
rUv b9e2eb37e0 fix(deps): bump vulnerable vitest pins to ^4.0.16 (#1609) (#1819)
Several @claude-flow/* packages still pinned vitest to ^1.x or ^2.x.
That range pulls vite <=6.4.1 → esbuild <=0.24.2, which carries
GHSA-67mh-4wv8-2f99 (esbuild dev server exposes arbitrary file reads
via network) — npm audit reports 4 moderate-severity vulnerabilities.

Fix is to bump every workspace package to vitest ^4.0.16 (which pulls
vite >=6.4.2, esbuild >=0.25.0, all clear). Most packages were already
on 4.x; this PR bumps the holdouts:

  - @claude-flow/aidefence              ^1.1.0 → ^4.0.16
  - @claude-flow/browser                ^2.0.0 → ^4.0.16
  - @claude-flow/codex                  ^1.4.0 → ^4.0.16
  - @claude-flow/plugin-agent-federation ^1.6.0 → ^4.0.16
  - @claude-flow/plugin-iot-cognitum    ^1.6.0 → ^4.0.16
  - @claude-flow/testing peerDep        >=1.0.0 → >=4.0.16

The lockfile regenerated cleanly — net 590 lines smaller as the dual
vitest 1.x / 2.x transitive trees collapse into a single 4.x tree.

Republish required for the affected packages to land the fix on npm
(this only updates source + workspace lockfile).

Closes #1609.
2026-05-06 16:53:24 -04:00
rUv 9d4a9ea968 ADR-101: Federated Claims — cross-node work coordination (Phases 1-3) (#1777)
Closes #1775. Implements ADR-101.

Three additive components gated behind a feature flag for the v3.8 alpha
cycle. No breaking changes to single-node deployments.

  - Phase 1 (1f826fb9b): Hybrid Logical Clock — Kulkarni 2014, 30s default
    skew tolerance, optional 4th constructor arg on WorkStealingService;
    16 new tests
  - Phase 2 (edc39f7da): Vector clocks + federation bridge + federated
    event store + federated claim repository. Single-seam principle:
    federation-bridge.ts is the only file in @claude-flow/claims that
    knows the federation envelope wire format. ConcurrentWriteError →
    resolved via existing contest mechanism. PII rollback on publish
    failure. 26 new tests.
  - Phase 3 (cc6af4b77): claim-event + agent-handoff message types.
    agent-handoff added to CONSENSUS_REQUIRED_TYPES (cross-trust
    attestation); claim-event NOT in it (steady-state gossip would
    deadlock under quorum). 7 new envelope tests.

Aggregate validation:
  - @claude-flow/claims: 203/203 tests pass
  - @claude-flow/plugin-agent-federation: 373/373 tests pass
  - Both packages typecheck + build clean
  - 30/31 CI checks green; the 1 red check (Test V3 Packages) is a
    pre-existing native-binary infrastructure issue (sharp + onnxruntime)
    affecting packages this PR does not touch — same failure on main
    commits 0a0a246a and c8827c61 before this PR was opened.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-05 23:19:21 -04:00
Reuven f8cd1c9fe2 chore(release): bump to 3.6.25 + regen witness manifest
Versions bumped:
  @claude-flow/cli                  3.6.24 → 3.6.25
  ruflo (umbrella)                  3.6.24 → 3.6.25
  claude-flow (umbrella)            3.6.24 → 3.6.25
  @claude-flow/plugin-agent-fed.    1.0.0-alpha.4 → 1.0.0-alpha.5

Captures 22 commits since 3.6.24:
  - audit_1776853149979 hardening (8 commits): execFileSync sweep,
    fetch timeouts, env validator, file-mode 0600, MCP stdin DoS cap,
    shell injection in github-tools + update/executor
  - test suite cleanup: 25 pre-existing failures cleared → 1933/1933 green
  - perf: dynamic-import sweep across memory-tools / hooks-tools / commands
  - ADR-096 encryption-at-rest Phases 1–4 (vault + session/terminal/memory.db)
  - ADR-097 federation budget circuit breaker Phase 1 (alpha.5 bump)
  - verification.md capability inventory expansion (300 MCP / 49 CLI /
    32 plugins / 43 agents)
  - ruflo-federation Claude Code plugin v0.2.0 docs

scripts/regenerate-witness.mjs (new):
  Reproducible regen — re-hashes every cited dist file, refreshes
  gitCommit / releases / issuedAt, recomputes manifest hash, re-derives
  the deterministic Ed25519 keypair from sha256(gitCommit +
  ":ruflo-witness/v1") via Node's native createPrivateKey/Public from
  PKCS#8 (no @noble dep), re-signs.
  All 27 existing fixes re-verified after CLI dist rebuild.

verification.md "Coverage so far" updated to reflect:
  - the new 3.6.25 release pointer
  - explicit pointers to the inventory section + tasks #25/#26 for
    the unenumerated ADR-096/097 work
  - regen instructions
2026-05-03 21:40:25 -04:00
Reuven 7e1cc06dfd feat(federation): ADR-097 Phase 1 — budget envelope + hop counter (#1723)
Implements the send-side enforcement portion of ADR-097 (federation
budget circuit breaker). Closes the recursive-delegation-loop class
with a default maxHops=8 even for callers that don't opt in to a
budget. Token / USD caps are opt-in via the new federation_send
metadata.

Built around the security constraints surfaced in the team's review
pass:
  1. validateBudget rejects NaN, ±Infinity, negatives, non-integer hops
     up front. Discriminated result type — callers cannot accidentally
     use a bad budget unchecked.
  2. enforceBudget is purely synchronous (no internal awaits) so two
     concurrent sends cannot both pass a single-hop budget.
  3. Errors are constant strings (HOP_LIMIT_EXCEEDED, BUDGET_EXCEEDED,
     INVALID_BUDGET) with no remaining-budget echo on failure — no
     oracle for probing thresholds.
  4. Negative caller-reported spend is clamped to 0 so a malicious
     caller cannot inflate remaining budget.
  5. Hard ceilings on every axis (maxHops ≤ 64, maxTokens ≤ 1B,
     maxUsd ≤ $1M) bound the failure mode of trusted-but-buggy
     callers.

New module:
  src/domain/value-objects/federation-budget.ts
    - Budget interface (immutable, opaque to callers; only constructed
      via validateBudget)
    - DEFAULT_MAX_HOPS = 8
    - validateBudget(raw, overrideMaxHops?) → discriminated result
    - enforceBudget(budget, hopCount, spent) → discriminated decision
      with nextHopCount + remaining for downstream

Wired into:
  src/application/federation-coordinator.ts
    sendMessage gains an optional fourth `options: SendOptions`
    argument carrying { budget, maxHops, hopCount, spent }. Default
    SendOptions = {} preserves the existing 3-arg call sites bit-for-bit.
    Budget gate runs immediately after the existing policyEngine
    check, before routing.send. Failures audit-log with the specific
    constant-string reason.

  src/mcp-tools.ts (federation_send)
    Schema gains optional budget / maxHops / hopCount / spent fields.
    No required-field changes — callers without budget metadata see
    identical behavior except the new default maxHops=8 floor.

Tests (__tests__/unit/federation-budget.test.ts, 41 cases):
  validateBudget: 27 cases
    - happy path: undefined/null/full/zero-hops/override
    - rejection: malformed types, NaN/Infinity hops, negative hops,
      non-integer hops, oversized hops, NaN/Infinity tokens, non-integer
      tokens, oversized tokens, USD validation
  enforceBudget: 14 cases
    - hop counter: pass/refuse/zero-hops, recursive-ring termination
      at exactly maxHops legs
    - budget axes: tokens overshoot, USD overshoot, deterministic order
      when hop+budget both exhausted
    - downstream propagation: remaining budget for next peer
    - anti-malice: negative spend clamped to 0
    - atomic: pinned synchronous return type (not Promise)
    - error info leak: failure result contains only ok/reason, constant
      strings only

Verified:
  Plugin tsc --noEmit: 0 errors
  Plugin vitest: 366/366 passing (was 325; +41 from this commit)
  Parent CLI vitest: 1917/1917 passing, 46 skipped, 0 failures
    (no regressions from the new SendOptions param)

Phase 2 (peer state machine ACTIVE/SUSPENDED/EVICTED), Phase 3
(cost-tracker integration), and Phase 4 (doctor + breaker_status MCP
tool) ship in follow-up iterations per ADR-097.

The team-of-agents pipeline broke (sibling background agents can't
SendMessage each other), but the researcher's recon and reviewer's
security pass both produced load-bearing input that shaped this
implementation. Tester wrote a parallel file against a phantom
reference impl which would have duplicated coverage; replaced with
the canonical federation-budget.test.ts that imports from real source.
2026-05-03 21:15:25 -04:00
Reuven db210a93a6 chore: bump to 3.6.22 + plugin-agent-federation@1.0.0-alpha.4
Batched publish: G1 agent_execute, G3 workflow runtime, G7 controller
activation (gnnService + rvfOptimizer), G4 WASM Anthropic fallback,
G2 federation Ed25519 signing/verification.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-03 19:01:40 -04:00
Reuven 6f495369fa feat(G2): real Ed25519 signing + verification in agent-federation plugin
ADR-095 G2 (federation slice). The April audit (@roman-rr) flagged the
"verifySignature returns true unconditionally" stub as a critical
authentication-bypass smoking gun. Independent security audit log
(audit_1776483149979) reached the same conclusion. This commit
replaces the stubs with real Ed25519 cryptography end-to-end:

- Added @noble/ed25519 to plugin dependencies (already in tree).
- Generate or load a persistent Ed25519 keypair on plugin init.
  Persisted to .claude-flow/federation/key-<nodeId>.json with mode 0600
  so the same node identity survives restarts. Falls back to an
  ephemeral key if the file system is read-only — still real crypto.
- Real signChallenge / signManifest using ed.sign() with the local
  private key.
- Real verifySignature / verifyManifest using ed.verify() with the
  peer's public key carried in the message envelope.
- getLocalPublicKey now returns the actual hex-encoded public key
  rather than an empty string.
- coordConfig.publicKey wired to the real key (was '' — the federation
  trust path was effectively keyless before this).

Manifest signing uses a canonical JSON serializer (keys sorted, the
signature field itself excluded) so sign and verify produce
deterministic results across nodes.

Functional check passed: ed.verify() rejects tampered messages and
mismatched public keys, accepts correctly-signed ones. Build clean.

Closes the federation-side of ADR-095 G2. Hive-mind multi-process
transport is a separate effort — the in-process EventEmitter
consensus handler still needs a real transport layer (separate ADR).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-05-03 18:26:34 -04:00
rUv 1976c57ccd fix(federation): publish @claude-flow/plugin-agent-federation@1.0.0-alpha.3 with ruflo-federation bin (#1685)
Same class of bug as iot-cognitum: source existed (325 tests passing) but
unpublished, and ruflo-federation marketplace plugin's skills called
`npx @claude-flow/cli@latest federation <subcmd>` — federation isn't a
CLI subcommand. End result: every federation skill failed.

Plugin source (v3/@claude-flow/plugin-agent-federation):
- New src/bin.ts: standalone CLI shim that bootstraps AgentFederationPlugin
  in-memory, parses argv, resolves multi-word commands (federation peers
  add, federation peers remove, etc.), aliases short flags, dispatches to
  CLICommandDefinition handlers. Loads .env for FEDERATION_NODE_NAME,
  FEDERATION_BIND_HOST, FEDERATION_BIND_PORT, FEDERATION_TRUST_LEVEL.
- package.json: add bin {ruflo-federation: dist/bin.js}, files whitelist,
  homepage. Move workspace deps to devDependencies (all imports are
  `import type`, erased at compile — zero runtime workspace dep).
- New README.md with subcommand reference and .env config.

Marketplace plugin (plugins/ruflo-federation):
- Bulk-rewrote 5 files (3 skills + commands/federation.md +
  agents/federation-coordinator.md): now call
  `npx -y -p @claude-flow/plugin-agent-federation@latest ruflo-federation <subcmd>`

Other minor fix:
- plugins/ruflo-swarm/README.md: pin `npx @claude-flow/cli swarm` →
  `npx @claude-flow/cli@latest swarm` for consistency with the other 31 plugins.

Audit result post-fix: 32/32 plugins use working invocation patterns.
- 12 verbs against @claude-flow/cli@latest (all exist: memory, hooks,
  security, neural, swarm, doctor, init, embeddings, status, daemon,
  config, plus session)
- @claude-flow/plugin-iot-cognitum (alpha.5 published)
- @claude-flow/plugin-agent-federation (alpha.3 published — this PR)
- neural-trader (2.7.1 on npm)
- ruvector (0.2.25 on npm)

Smoke test: `npx -y -p @claude-flow/plugin-agent-federation@latest
ruflo-federation status` returns Node ID + session count + healthy flag.

Caveat: the 11 plugins that depend on mcp__claude-flow__* MCP tools
(adr, aidefence, autopilot, browser, daa, goals, plugin-creator, ruvllm,
sparc, wasm, workflows) require the MCP server to be connected — not
audited here.

Co-authored-by: Reuven <cohen@ruv-mac-mini.local>
2026-04-30 16:26:23 -04:00
Reuven 0f7c750237 fix: rewrite federation tests to use actual source — no mocks
Replace all 7 mock-based test files with tests that import and exercise
the real source classes. Previously tests defined local reimplementations
with divergent APIs (kebab-case types, wrong actions, different fields),
so they validated the spec but not the implementation.

Source fixes:
- Add missing `address` entry to PII policy matrix (14/14 types)
- Add @claude-flow/aidefence to plugin dependencies

Test count: 202 → 325 (all passing, 179ms)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-29 19:14:16 -04:00
Reuven ecdbe801df feat: @claude-flow/plugin-agent-federation — scaffold, tests, README
Add the Agent LLM Federation Plugin implementing ADR-078:

Plugin scaffold (17 source files, 3,021 lines):
- Domain entities: FederationNode, FederationSession, FederationEnvelope, TrustLevel
- Domain services: PIIPipelineService (14 PII types, confidence scoring, adaptive
  calibration), DiscoveryService, HandshakeService, RoutingService, AuditService
- Application layer: TrustEvaluator (scoring formula with hysteresis),
  FederationCoordinator, PolicyEngine
- Plugin class: 9 MCP tools, 10 CLI commands, 1 agent type
- Clean DDD architecture: domain/application/infrastructure/api layering

Test suite (8 files, 202 tests, all passing):
- Trust scoring formula verification
- PII detection across 14 types with confidence thresholds
- Federation envelope HMAC signing/verification
- Capability gates per trust level
- Audit service with HIPAA/SOC2/GDPR compliance modes
- Routing mode selection (direct default, BFT for state mutations)
- Plugin interface compliance

README updated with federation plugin entry linking to issue #1669.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-29 18:25:20 -04:00