Commit Graph

241 Commits

Author SHA1 Message Date
ruv d61bf42022 perf(agentbbs): dedup PeerHello spam, dedup watch replays, O(1) seq append
Three optimizations to the ruflo-side AgentBBS federation integration
(ADR-164), all in v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts.

1. PeerHello dedup (register). `federation_bbs_register` appended a fresh
   PeerHello envelope on EVERY call even though the registry entry and nodeId
   were already idempotent — so a node retrying register N times emitted N
   near-identical hellos (observed live: one node published 8). Now a hello is
   emitted only for a genuinely new registration, or as a heartbeat refresh once
   FEDERATION_BBS_HELLO_WINDOW_SECS (default 60) has elapsed. Returns
   `helloEmitted` so the caller can see whether one was written (a silent
   suppression would otherwise be unverifiable).

2. Watch dedup. `federation_bbs_watch` now collapses duplicate envelopeIds
   before applying the limit, so a relay replay or a double-append cannot
   surface the same envelope twice to a watcher. Order preserved.

3. O(1) seq append. `nextSeq` re-parsed the whole room log on every
   publish/register (O(n) as the log grows). It now scans only the last 64 KiB
   to find the last seq, with a full-read fallback for the rare huge-line case.
   Monotonic seq (ADR-164 §3.2.2) is preserved and tested past the tail window.

Decision helpers (`shouldEmitHello`, `dedupEnvelopes`, `nextSeq`,
`lastPeerHelloMs`) are exported and unit-tested deterministically — the tool
handlers early-return `{degraded:true}` when the optional `agentbbs` package is
absent, so the pure logic is tested directly rather than through the wrapper.

Tests: 7 new cases; full file 15 passed / 4 skipped (the 4 are agentbbs-present
happy-path, skipped when the optional dep is absent). tsc --noEmit clean on the
changed file. Structural + degraded contract unchanged.

Scope: this is the ruflo-side integration only. Claim leases, signed result
receipts, and node reputation are agentbbs/relay-authority concerns (external
pkg + x.ruv.io gateway) — a separate track.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-09-09 22:11:58 -04:00
rUv d55b1bfeac fix(memory): stop reporting a gated native bridge as the active backend (#3228) + fast-uri CVE (#3245)
* fix(memory): stop reporting a gated native bridge as the active backend (#3228)

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

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

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

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

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

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

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

* chore(release): 3.39.0 -> 3.39.1

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

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

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

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

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

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

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

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

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


Claude-Session: https://claude.ai/code/session_01J2s8oQsAZjJtJ6inoYRFPj
2026-09-08 16:46:28 +00:00
rUv 6acc689150 dream(intelligence): #3220 gate EnhancedModelRouter's forwarded modelId on tier match (evaluated, ACCEPT-scoped) (#3221)
Merged after independent re-verification: 109/109 CI green, the earlier '168 failures' finding traced to an unbuilt-sibling-package local artifact (unrelated to changed files), and a subsequent CI flake confirmed unrelated via clean re-run. Overriding the PR's self-merge gate on explicit user (ruvnet) authorization.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_013XaRQntabVwGT9SvnW8cKK
2026-09-07 10:57:05 -04:00
ruv d413297dde fix(memory): stop seeding the bridge's ControllerRegistry with the sql.js dbPath
Fixes #3155. `initializeMemoryDatabase()` passed its just-resolved
sql.js-facing `memory.db` path straight through to
`activateControllerRegistry()` -> `bridge.getControllerRegistry(dbPath)`.
That seeded the process-wide ControllerRegistry singleton with `memory.db`
as AgentDB's own native better-sqlite3 database, instead of the dedicated
`agentdb-memory.db` sibling `getAgentDbPath()` exists specifically to
provide (#2786).

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

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

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01MND33UDrtWPRH851k9w8M3
2026-09-01 21:40:17 -04:00
Dragan Spiridonov 36740e4304 fix(proxy): verify effective daemon on install 2026-08-30 15:27:37 +02:00
rUv b291ac4049 dream(intelligence): #3048 discounted Thompson sampling for model-router bandit (evaluated, ACCEPT-scoped) (#3049)
* dream(intelligence): discounted Thompson sampling for model-router bandit

Adds an opt-in `priorDecay` config field (default 1 = disabled, fully
backward compatible) to ModelRouter's Beta-Bernoulli bandit. When enabled,
every bucket/model's alpha/beta decays geometrically once per
recordOutcome() call before that round's reward is added (arXiv
2305.10718 discounted Thompson sampling), so accumulated routing history
from a long-running persisted state file no longer permanently dominates
the posterior after a real-world model-quality shift.

Benchmark (n=30 paired trials, seeded PRNG, identical stream baseline vs
candidate): non-stationary regime-shift recovery 26.5 -> 21.9 rounds
(-17.6%, t=7.24); post-shift correct-routing rate +1.3pp (t=6.10);
stationary-workload invariant held (delta +0.02pp, not a regression).
Receipt: v3/@claude-flow/cli/benchmarks/results/prior-decay-receipt.json.

Mirrors the epsilon-decay pattern q-learning-router.ts already applies to
its own exploration rate -- an internal-consistency fix, not an imported
pattern. Evaluation and adversarial critique in progress; this commit is
the checkpointed, test-passing candidate + its baseline/candidate receipt.

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

* dream(intelligence): #3048 fix adversarial-critique findings, add gist + ledger

Independent adversarial critique found and this commit fixes: a paired-t
statistics bug (population vs sample stddev inflated every t-value by
n/(n-1)), an unguarded priorDecay input (NaN/negative could silently poison
persisted .swarm/model-router-state.json forever, bypassing sampleBeta's
own defensive fallback), and a single-bucket benchmark blind spot (now
covers low + med complexity buckets -- the med bucket shows no significant
effect, disclosed rather than hidden).

Also adds the research gist and backfills 3 missing ledger rows for
2026-08-14/15/16 (real PRs/issues existed for those nights but the ledger
table wasn't updated) plus tonight's own row.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-21 11:21:27 -04:00
rUv 0e50fe7f57 dream(security): #3043 advisory scanner for untrusted settings.json hooks/allow-rules (evaluated, ACCEPT) (#3044)
* dream(security): #3043 advisory scanner for untrusted settings.json hooks/allow-rules carried forward by ruflo init/--upgrade

mergeSettingsForUpgrade() and writeSettings() both read a target
project's pre-existing .claude/settings.json and spread its hooks /
permissions.allow entries into the merged output verbatim — the same
trust shape as CVE-2025-59536 (settings.json hook payload achieving
command execution with no review step), reached via ruflo init's own
merge logic rather than Claude Code's loader. Neither of last night's
flagged CVEs (2025-59536, 2025-6514) applies to Ruflo's own hook
dispatch or OAuth client directly, but this distinct gap in the same
family was genuinely unaddressed.

Adds a static, advisory-only (non-blocking) scanner that inspects
hook commands and Bash allow-rules for known-dangerous patterns
before the merge and surfaces findings as CLI warnings. Merge/write
behavior itself is completely unchanged. Hardened after an
independent adversarial-critic pass found 6 concrete bypasses
(intermediate-pipe-stage downloads, two-step download+exec,
eval-wrapped base64, interpreter-mediated dangerous commands,
absolute-path-prefixed commands, whitespace-evasive allow rules) —
all fixed and pinned as a held-out regression set. Also fixes an
ANSI/control-character injection risk in the warning output itself,
self-identified during review.

87/87 relevant tests green (1 pre-existing environmental failure
elsewhere in the package, unrelated — unbuilt sibling package).

Dream Cycle 2026-08-16, security surface. Full research, evaluation
receipts, and adversarial critique: issue #3043 and
docs/dream-cycle/dream-gist-2026-08-16.md.

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

* docs(dream-cycle): update ledger — backfill 2026-08-14/2026-08-15, add 2026-08-16

2026-08-14 (swarm, REJECT) and 2026-08-15 (performance, REJECT) both had
fully-run pipelines whose live ledger rows never reached main (every
dream-cycle PR to date is unmerged, so branch-local ledger commits never
merge forward) — verified via git ls-remote / search_pull_requests before
backfilling, not assumed.

2026-08-16 (security, ACCEPT): advisory scanner for untrusted settings.json
hooks/allow-rules carried forward by ruflo init/--upgrade. Issue #3043,
PR #3044.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-21 10:39:22 -04:00
Wu Shuwen 02eae9c117 fix(hooks): persist real session-end state (#3077) 2026-08-21 10:39:19 -04:00
Wu Shuwen f7aab68602 fix: resolve Claude Code launches on Windows (#3076)
* fix: launch Claude Code from Windows npm installs

* test: use Windows path semantics in resolver
2026-08-21 10:39:15 -04:00
Wu Shuwen 3a1b2257fb fix(cli): honor MCP tool selection when listing (#3060) 2026-08-21 10:39:12 -04:00
rUv 703e7741d7 fix(memory): #3051 memory_store tags lost via write-through cache (3.38.14) (#3079)
Root cause: bridgeStoreEntry's post-write cache set stored only a partial
entry shape — { id, key, namespace, content, embedding } — with no `tags`,
no timestamps, no metadata, no access_count. The DB row itself had the
tags JSON-serialized correctly. But any subsequent bridgeGetEntry hit the
cache first and returned `tags: cached.tags || []` = `[]`, so
`memory_store({ tags })` → `memory_retrieve()` looked like it silently
dropped tags.

Fix: replace the partial write-through with cache invalidation. The next
read then re-fetches the row via the DB reader (which correctly parses
the tags JSON column) and repopulates the cache from the full shape.
Cleaner than expanding the write-through payload — future entry-shape
fields don't need to remember to update the cache-write signature.

Regression test verifies: store {tags:[a,b,c]} → get returns {tags:[a,b,c]}.
Proven to catch the bug: pre-fix the test fails (tags: [] returned);
post-fix it passes.

The same defect would have caused reads to see access_count stuck at 0
and drifted createdAt/updatedAt timestamps from the moment the cache
was first populated until eviction; cache invalidation fixes those too.

Release
- Bumps @claude-flow/cli, claude-flow, ruflo: 3.38.13 → 3.38.14 (PATCH).


Claude-Session: https://claude.ai/code/session_0118jMsYhwHD5dx2vStENsEB
2026-08-21 10:30:08 -04:00
rUv 0f3c45101b fix: address #3045 #3064 #3065 + new ruflo-deepseek-harness plugin (3.38.13) (#3078)
Bug fixes
- #3045 statusline.cjs: getGitInfo() runs the 5-command git chain per render;
  on large repos with concurrent Claude Code sessions this queued subprocesses
  faster than they finished (reporter observed hundreds of orphan children +
  load-avg in the hundreds). Added a per-cwd tmp file cache with 5s TTL —
  dirty status still feels live, pileup is bounded.

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

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

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

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

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


Claude-Session: https://claude.ai/code/session_0118jMsYhwHD5dx2vStENsEB
2026-08-21 10:10:13 -04:00
rUv e43f8b9750 Merge pull request #3073 from ruvnet/fix/322c-receipt-roundtrip
fix(flywheel): compute receipt statistics from encoded scale-12 values — honest receipts no longer fail their own verifier (ruflo#3072)
2026-08-20 08:33:49 -04:00
ruv a38647e1f9 fix(flywheel): compute receipt statistics from encoded scale-12 values (ruflo#3072)
createFlywheelReceipt ran the bootstrap over full-precision score/delta
doubles while the payload stored them as scale-12 decimal strings, and
verifyFlywheelReceipt recomputes from those strings — the only values a
verifier ever has. Any input that does not terminate within twelve
decimals (e.g. a corpus mean of 0.7687083333333332, stored as
"0.768708333333") shifted relativeLift by one ULP on re-decode, so an
honest, correctly signed receipt failed its own verification with
"statistical decision does not recompute" — indistinguishable from
tampering.

Encode baselineScore, candidateScore, heldOutDeltas, and
frozenAnchorRegression first and feed the decoded encoded values into
computePromotionStatistics, making producer and verifier bit-identical
by construction. This implements the ADR-322C amendment in #3067: the
statistical decision is computed from the encoded values of record.

Regression test at the reproduction mean fails against the previous
producer and passes with the fix.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_012Jib2gQyJpqCoo2xYAbb4X
2026-08-20 08:24:06 -04:00
ruv 7d0bb9c53b fix(flywheel): reject unknown fields in receipt and commit verification
ADR-322C requires that unknown fields fail verification for a given schema
version. verifyFlywheelReceipt did not enforce it: a signature is valid
over whatever the producer canonicalized, so a producer-emitted field the
contract never defined verified cleanly. Enforcement lived only in
out-of-band JSON Schema validation, which is a step a consumer may or may
not run, while verifyFlywheelReceipt is what every consumer calls.

This matters now because rvm#35 and autogenous#10 are about to anchor
promotion receipts into a witness chain and verify them across repo
boundaries; a permissive verifier makes the anchor silently permissive too.

Closes the class, not just the instance. verifyFlywheelLedger had the same
weakness - a commit hashes consistently over whatever fields it carries, so
an undefined field chained cleanly - and now runs the same check.

Contract-open objects stay open, with a test pinning that: candidatePolicy
is owned by policySchemaVersion, gates is a caller-named term map, and
evidence.verification / evidence.canary carry evidence-specific payloads.

Errors read "unknown field: <path>" so a caller can tell an unrecognized
field from a tampered one, per the issue's triage requirement.

Both regression tests were confirmed to fail with the checks disabled and
pass with them enabled. Full flywheel suite: 37 passed (receipt 10,
transaction 11, proposer 6, sequential-evidence 10).

Strictness is versioned: a v1 verifier will reject a future receipt that
adds fields. That is correct fail-closed behavior for a security property,
and is now stated in ADR-322C's Update section (see #3069, PR #3067) so
consumers plan for it rather than discover it.

Refs #3068

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-08-19 22:07:22 -04:00
ruv 693385eab0 test(ci): keep safety guard root-compatible 2026-08-15 11:23:26 -04:00
ruv 13cbd697db fix(release): enforce CI and idle assignment safety 2026-08-15 11:14:06 -04:00
ruv b9a5077292 test(cli): model atomic fs operations in MCP suite 2026-08-14 17:35:36 -04:00
ruv 3f285bfb26 fix(memory): disable unsafe native bridge on Windows (#3024) 2026-08-14 17:16:05 -04:00
dajiaohuang 5c676fe242 fix(doctor): report missing native sqlite bindings 2026-08-14 17:10:34 -04:00
dajiaohuang 6b8f0d0b5a fix(mcp): share state across HTTP transports 2026-08-14 17:10:34 -04:00
dajiaohuang 2dbb790a0b fix(mcp): restore HTTP protocol and CLI tools 2026-08-14 17:10:34 -04:00
dajiaohuang fa11ee8c24 fix(cli): honor helper refresh opt-out before locking 2026-08-14 17:10:34 -04:00
dajiaohuang 0c73bab4d0 fix(cli): serialize helper refreshes across processes 2026-08-14 17:10:34 -04:00
rUv 1e24bb8878 fix(agent): propagate explicit provider/model config into agent execution (#2962) (#3007)
* fix(agent): propagate explicit provider/model config into agent execution (#2962)

`providers configure` and `agent spawn --provider/--model` persisted user
intent but the execution path (callAnthropicMessages, executeAgentTask,
determineAgentModel) only ever consulted env vars and a 5-alias model list,
silently discarding both. A local Ollama/OpenRouter setup with explicit
config would either fail closed or fall back to whatever provider the
env vars happened to select.

- determineAgentModel(): treat any non-alias config.model string as an
  explicit selection via the existing modelId fast-path, instead of
  falling through to task-based routing / agent-type defaults.
- agent_spawn: forward config.provider to the stored (and returned) agent
  record when it's an unambiguous explicit choice ('ollama'/'openrouter';
  'anthropic' is excluded since the CLI silently defaults to it when
  --provider isn't passed).
- callAnthropicMessages(): accept an optional provider param and consult
  the persisted `agents.providers` config for baseUrl/apiKey/model when
  env vars are absent. A self-hosted Ollama baseUrl no longer requires
  the undocumented OLLAMA_API_KEY='local' sentinel.
- executeAgentTask(): forward agent.provider into the first dispatch call.

Precedence: explicit per-agent flag > env vars > persisted config >
key-presence inference (unchanged, last resort).

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

* fix(ci): update #2042 smoke to match the widened OpenRouter branch shape

The #2042 regression smoke statically matched the literal token sequence
`useOpenRouter && openrouterKey`. #2962 widened that condition to
`if (useOpenRouter) { const apiKey = openrouterKey || persistedOpenRouter?.apiKey; ... }`
so a persisted `agents.providers` config can supply the key when no env
var is set — the smoke's ordering guarantee (OpenRouter branch reachable
before the Anthropic-key early-return) still holds, only the literal
pattern needed updating.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-08-12 18:28:25 -04:00
rUv 57c0fcba98 fix(memory): stop misreporting bridge brute-force search as HNSW-accelerated (#3006)
* fix(intelligence): stamp contentFingerprint in consolidate()'s graph write too

consolidate() (the session-end path) rebuilt graph-state.json unconditionally
per the #2920 fix in v3.38.6, but its write never included the
contentFingerprint field that init()'s cache-hit gate checks — only init()'s
own write set it. So every init() immediately following a consolidate saw
nodeCount match but contentFingerprint undefined !== a real hash, missed the
cache, and did a full rebuild even with zero content changes. Fail direction
was safe (extra rebuilds, never stale data) so it didn't block the v3.38.6
release, but it meant the 60s graph cache never actually fired across a
consolidate/restart boundary.

* fix(memory): stop misreporting bridge brute-force search as HNSW-accelerated (#2922)

getHNSWStatus() reported `available: true` whenever the AgentDB v3 bridge
was loaded, on the theory that "AgentDB v3 is HNSW-equivalent" — but the
bridge's actual default search path is a full-table SELECT + brute-force
cosine similarity loop over every embedding row. It never touches
@ruvector/core or an HNSW index, regardless of whether ruvector-core is
installed and resolvable. Same issue in bridgeGetHNSWStatus (unconditional
available: true) and in three misleadingly-named functions that do no HNSW
work at all: bridgeSearchHNSW, bridgeAddToHNSW, bridgeGetHNSWStatus.

- getHNSWStatus(): available=false + new algorithm field
  ('hnsw' | 'brute-force-cosine') when the bridge is the active path,
  instead of a blanket available=true that implied acceleration that
  was never in the request path.
- memory-bridge.ts: bridgeGetHNSWStatus -> bridgeGetVectorSearchStatus
  (adds algorithm: 'brute-force-cosine' to its return shape),
  bridgeSearchHNSW -> bridgeSearchBruteForceCosine,
  bridgeAddToHNSW -> bridgeAddEmbedding. No behavior change — call sites
  updated in memory-initializer.ts, no other source callers existed.
- memory-tools.ts: the `backend: 'sql.js + HNSW'` field on 7 MCP tool
  responses (memory_store, memory_retrieve, memory_search, etc.) was a
  hardcoded literal regardless of which path actually ran. Replaced with
  describeBackend(), which reports the real algorithm via the fixed
  getHNSWStatus() probe.

Actually wiring AgentDBBackend's HNSW into the bridge's default search
path (the issue's broader architectural question) is out of scope here —
this fixes the status/naming layer lying about what's already true, not
the search path itself.
2026-08-12 17:40:19 -04:00
rUv c0c3ab071a fix(intelligence): stamp contentFingerprint in consolidate()'s graph write too (#3003)
consolidate() (the session-end path) rebuilt graph-state.json unconditionally
per the #2920 fix in v3.38.6, but its write never included the
contentFingerprint field that init()'s cache-hit gate checks — only init()'s
own write set it. So every init() immediately following a consolidate saw
nodeCount match but contentFingerprint undefined !== a real hash, missed the
cache, and did a full rebuild even with zero content changes. Fail direction
was safe (extra rebuilds, never stale data) so it didn't block the v3.38.6
release, but it meant the 60s graph cache never actually fired across a
consolidate/restart boundary.
2026-08-12 17:23:59 -04:00
rUv 35a6ad3697 fix(daemon): config.json resourceThresholds overrides were lost to stale daemon-state.json (#2935) (#3000)
initializeWorkerStates()'s restoration guard only checked
`originalConfig` (the constructor arg) before restoring
resourceThresholds from a saved daemon-state.json — a value explicitly
set via .claude-flow/config.json (e.g. minFreeMemoryPercent: 0, a
workaround for Darwin's os.freemem() undercounting reclaimable memory)
was not covered by that guard, so a stale daemon-state.json from
before the override existed kept winning on every restart. Same bug
class as #2661 (aiWorkersEnabled), which already documents the
"deliberately not restored from stale state" pattern this now follows
for resourceThresholds too — per-field, so an unrelated field with no
explicit source can still legitimately restore from saved state.

Also fixes a secondary diagnostic bug reported in the same issue:
readDaemonConfigFromFile() logged "Daemon config loaded from ..." via
this.log(), but runs from the constructor before this.config (and
therefore this.config.logDir) is assigned — the log() call threw
inside its own try/catch and the line never reached daemon.log,
making "is the config file even being read?" impossible to answer
from the log alone. The chosen path (or a YAML-parser-unavailable
warning) is now returned and logged once this.config exists.

Does not address this issue's primary Darwin os.freemem() memory-gate
bug — that needs vm_stat/memory_pressure-based measurement, a larger,
platform-specific change left for a follow-up.
2026-08-12 16:43:21 -04:00
rUv 9039e2bb33 fix(intelligence): same-ID content edits were cached/dropped instead of refreshed (#2920) (#2999)
* fix(intelligence): same-ID content edits were cached/dropped instead of refreshed (#2920)

Two related staleness bugs in the intelligence helper's graph/store
caching, both keyed on entry count instead of content:

- init()'s graph-cache-hit gate compared only graphState.nodeCount to
  the deduped store length. Hand-editing a MEMORY.md section's body
  while its heading (and therefore generated ID) stays the same
  changes neither the count nor the ID set, so the stale cache kept
  being served and ranked-context.json was never refreshed.

- consolidate()'s STORE_PATH persist gate
  (`newEntries > 0 || store.length < preDedupCount`) only fired on
  insight creation or a dedup-driven shrink. Any other in-memory
  content change — e.g. assigning an id to a previously id-less entry
  while building the graph — left entry count and the dedup outcome
  unchanged, so the update was silently never written back to disk.

Fix: add a `storeFingerprint()` helper that folds every entry's own
content fingerprint into one aggregate value, used to (1) gate the
init() cache hit alongside nodeCount, and (2) make consolidate()
always persist the store — trivial cost given GRAPH_PATH and
RANKED_PATH are already rewritten unconditionally on every call.

Regenerated and re-signed helpers.manifest.json (ADR-174) for the
changed intelligence.cjs content via scripts/sign-helpers.mjs.

* fix(intelligence): re-sign helpers manifest with the correct auto-memory-hook.mjs hash

The previous commit's manifest captured a transient, incorrect hash for
auto-memory-hook.mjs — a file this change never touches — because a
concurrent Claude Code session briefly overwrote it with stale content
at the exact moment sign-helpers.mjs ran (the known helper-drift race
documented in CLAUDE.md). Re-signed with a before/after git-status
check bracketing the signing step to confirm no file changed underneath
it this time.
2026-08-12 16:38:25 -04:00
rUv 7715eb5ea9 fix(security): loosen Stripe/OpenAI secret regex boundaries (#2931) (#2996)
The 20-char floor after the sk-/sk_live_/sk_test_ prefix missed
shorter-but-real keys, and the quote-adjacency anchor required a
quote literally next to the prefix, missing the common
`Authorization: "Bearer sk_live_..."` shape where the quote sits
next to "Bearer", not the key. Lookaround boundaries match the
prefix as a standalone token wherever it appears, with a lower
(but still non-trivial) length floor.
2026-08-12 15:43:27 -04:00
rUv 2fd63cf3db fix(github-tools): pass --repo to gh in github_issue_track (#2963) (#2995)
owner/repo were validated but never passed to gh, so `gh` silently
resolved the target repository from the cwd's git remote instead of
the caller-specified one. A caller supplying owner/repo expecting the
issue to land there had no guarantee it did, even on the "real"
gh-backed success path.
2026-08-12 15:36:06 -04:00
rUv d41af6e4d5 fix(hooks): await the ADR-130 reinforced-by graph edge write (#2961) (#2991)
`hooks post-task` (CLI) reliably dropped the "reinforced-by" graph
edge on every successful call. The write was a fire-and-forget async
IIFE the handler never awaited — invisible from the long-running MCP
server, but the CLI is a one-shot process that calls process.exit(0)
immediately after the command action resolves (#1552), killing the
still-pending write before its first tick, every time.
2026-08-12 14:41:18 -04:00
rUv 3d3f6e08cc fix(init): read the parser's actual camelCase flag keys (#2952) (#2989)
`--all-agents`, `--skip-claude`, `--only-claude`, and `--cloud-mcp` were
silent no-ops. Every long-flag write path in the parser normalizes
kebab-case to camelCase before storing (`--all-agents` -> `flags.allAgents`),
but `initClaudeAction` read the literal kebab-case keys, which the parser
never produces. Same bug class already fixed for `--no-global` (#2098A)
five lines above — never applied to these siblings.
2026-08-12 14:40:50 -04:00
rUv 1e2178f246 fix(hooks): make hooks intelligence --train actually train (#2940) (#2988)
`--train` declared a `forceTraining` input on the `hooks_intelligence`
MCP tool but never read it — the handler always returned the same
read-only status dashboard, and the CLI action's --train branch was a
cosmetic 500ms sleep followed by an unconditional "Training cycle
completed". `lastAdaptation` (what `--status` reports as "Last
Training") never moved, so it could only ever age no matter how many
times --train ran.

Now `hooks_intelligence` calls the existing `distillLearning()` when
`forceTraining` is set and reports what actually happened: patterns
distilled, ran with nothing new to distill, or MCP unavailable — so a
real training cycle and a no-op are no longer the same output.
2026-08-12 13:56:55 -04:00
rUv 87f4f8ab67 fix(mcp): mcp start -t http exits after false-success table + doc drift from #2985 (#2987)
* fix(migrate,mcp): unresolvable remediation command + false-success HTTP server exit

Two independently-reported, freshly-filed defects:

- #2985: `ruflo migrate status`'s ADR-128 removed-agent remediation
  (shipped today via #2973/ADR-382 Part B) printed a command that cannot
  succeed twice over: `ruflo plugins install <plugin>` requires -n/--name
  (not a positional) AND targets the npm-package plugin system, while the
  gap detector's own registry check (installed_plugins.json) is for the
  Claude Code marketplace system entirely. Fixed to print the correct
  `/plugin install <plugin>@ruflo` slash-command flow, with a one-time
  "run /plugin marketplace add ruvnet/ruflo first" hint. Also corrects
  three adjacent doc-drift items the reporter found while verifying:
  README/USERGUIDE's `claude mcp add ruflo` examples (should be the
  canonical `claude-flow` key per #2206 — the exact stray-key duplicate
  #2612's healing logic exists to clean up), README's blanket "MCP server
  registered: No" claim for the plugin path (ruflo-core ships its own
  .mcp.json and does register one), and a stale comment in executor.ts
  conflating the CLI-track's mcp__claude-flow__* tool refs with the
  marketplace plugin binding (mcp__plugin_<plugin>_<server>__*).

- #2984: `ruflo mcp start -t http` printed a full "Status: Running"
  success table — PID, health/RPC URLs — then exited 0 within seconds,
  never actually leaving the port bound. Root cause: bin/cli.js's
  "normal CLI mode" branch (which any explicit non-stdio --transport
  always takes) unconditionally exits once the dispatched command's
  action() promise resolves (#1552's "one-shot command" assumption).
  mcp.ts's `start` action returned immediately after printing the table,
  so the freshly-bound http.Server was torn down within milliseconds of
  the printed success claim. Fixed by blocking in the foreground after a
  successful start (SIGINT/SIGTERM-driven graceful shutdown), matching
  daemon.ts's established `--foreground` pattern — `daemonize` isn't
  currently wired to fork/detach anywhere in MCPServerManager, so every
  successful start via this path is a foreground server regardless of
  the -d flag.

Tests: migrate-agent-detection.test.ts updated for the corrected command
string; new mcp-http-foreground-2984.test.ts spawns the built CLI exactly
like the bug report and asserts the port actually accepts connections
after the success table prints (not just that it printed), then confirms
SIGTERM still produces a clean exit. Both verified via git-stash A/B:
fail against the pre-fix code, pass against the fix.

Full suite otherwise unaffected — same 9 pre-existing sandbox-environment
failures (ruvector/wasm bindings, embedder model downloads) reproduced
identically before this change.

Closes #2985. Closes #2984.

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

* revert: drop migrate-agent-detection changes, superseded by PR #2986

An external contributor (pacphi) independently opened #2986 fixing the
same #2985 with the identical installCommand correction plus a real
`migrate fix --agents` restore command — strictly more complete than
this branch's minimal fix. Dropping the overlapping files here to avoid
a duplicate/conflicting PR; reviewed #2986 and recommended it for merge.

Remaining on this branch: the #2984 mcp start -t http fix and the
doc-drift items #2986 explicitly scoped out (README/USERGUIDE mcp-add
key, executor.ts comment).

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-08-12 11:39:24 -04:00
Chris Phillipson dbd88254e3 fix(migrate): correct removed-agent install suggestion and add 'migrate fix --agents' restore (#2985) (#2986)
The suggestion printed by 'migrate status' (ADR-382 Part B) could not
succeed: 'ruflo plugins install <plugin>' fails on syntax (--name is
required) and targets the npm-package PluginManager, while the owning
plugins are Claude Code marketplace plugins (not npm packages). The
detection module's own header documents that split.

- point installCommand at the marketplace flow the registry check
  actually verifies: /plugin install <plugin>@ruflo
- add 'migrate fix --agents' (with --dry-run): restores missing agents
  from canonical plugin content — marketplace clone, then repo checkout,
  then GitHub raw pinned to the CLI's version tag with main fallback —
  rewriting the plugin-only mcp__plugin_ruflo-core_ruflo__ namespace to
  the canonical claude-flow server key (#2206) and recording provenance
  after the frontmatter. Never overwrites existing files.
- status now hints at the fix command when gaps are found
2026-08-12 11:33:21 -04:00
rUv b8c37da8e9 fix(memory): unordered LIMIT truncation in search + honest sql.js persistence status (#2983)
Two independently-reported, verified defects in the memory bridge:

- #2982/#2976 (duplicate reports): bridgeSearchEntries() and
  bridgeSearchHNSW() pre-ranked their corpus with `LIMIT 1000`/`LIMIT
  10000` and no ORDER BY, so SQLite returned rows in arbitrary storage
  order (insertion order on a fresh table) before BM25/embedding scoring
  ever ran. On any corpus over the limit, newest entries were silently
  excluded from scoring regardless of relevance. bridgeListEntries()
  already ordered by updated_at DESC for the same reason; both search
  paths now match it. Verified via a stash A/B: identical repro returns
  zero results without the fix, recalls the target with it.

- #2968: bridgeStoreEntry() swallowed every wal_checkpoint(PASSIVE)
  failure as "non-fatal" and unconditionally reported store success. On
  the sql.js fallback driver (engaged when better-sqlite3's native
  binding never got built — e.g. a skipped optionalDependency
  postinstall), that PRAGMA throws "Invalid PRAGMA command" every time,
  and writes may never reach disk. The specific failure signature is now
  distinguished from ordinary busy/non-WAL pragma failures and surfaced
  as `persistWarning`, which `memory store` prints instead of an
  unconditional green "Data stored successfully".

Both fixes stay within the CLI's own bridge layer — no changes to the
external agentdb/better-sqlite3 packages.

Tests: 2 new regression files (3 tests), full CLI suite otherwise
unaffected (10 pre-existing failures reproduced identically against a
clean baseline with these changes stashed — all in ruvector/wasm/
embedder-model-download paths, unrelated to this change).

Closes #2982, #2976. Part of #2968 (root-cause guarded; upstream
agentdb sql.js persistence itself is unchanged).
2026-08-12 10:02:31 -04:00
rUv 2c8f863583 fix(migrate): implement ADR-128's promised removed-agent detection (ADR-382 Part B) (#2973)
ADR-128 Phase 2 deleted 9 forked agent files from the init template
(coder.md, researcher.md, reviewer.md, tester.md, memory-specialist.md,
security-auditor.md, sparc-orchestrator.md, goal-planner.md,
adr-architect.md), making each plugin canonical. Its Consequences
section promised "`ruflo migrate` should detect removed agents and
print install suggestions" — never implemented until now.

Adds detectRemovedAgentGaps() (new sibling module
migrate-agent-detection.ts, kept out of the already-783-line migrate.ts
per the file-size budget) which, for each of the 9 agents, checks
whether the basename is missing from the project's .claude/agents/
tree and whether the owning plugin is installed per Claude Code's
global ~/.claude/plugins/installed_plugins.json registry (the only
real on-disk signal for marketplace-plugin install state; ruflo's own
PluginManager manifest tracks a separate npm-package-based system).
Pure filesystem checks, no CLI version comparison, per ADR-382 Part B
scope.

`ruflo migrate status` now prints a second table naming any such gap,
its owning plugin, and the exact `ruflo plugins install <plugin>`
command to restore it (JSON output via ctx.flags.format === 'json'
gains a removedAgentGaps field, matching this file's existing
patterns).

See v3/docs/adr/ADR-382-init-scaffold-content-drift-remediation.md
(PR #2972) and https://github.com/ruvnet/ruflo/issues/2971.
2026-08-11 18:26:21 -04:00
Dragan Spiridonov 6d58837f4f fix(proxy): make the subscription plane reachable again after cloud routing (#2928)
* fix(proxy): disclose what enabling cloud routing actually decides, and let users pin a tier

Closes the terminal half of cognitum-one/meta-proxy#43 M5b, and adds the
`routing_mode` control ruflo never had.

meta-proxy ADR-321 rev-2 (shipped v0.6.0, #43 M2) applies tier selection to
ALL Cloud traffic. Before it, a deliberately configured Cloud plane forwarded
the client's own model name -- and since Claude Code names a frontier model by
default, every request was served at the top tier with no ceiling and no proxy
daily cap. Difficulty-routing all Cloud traffic is the fix for that defect, not
a regression, and it deliberately does not touch Passthrough or Local: those
are the user's own subscription and their own backend.

But ruflo never told anyone. `ruflo proxy config --cloud` said only that
prompts go to api.cognitum.one "and forwarded to the selected provider" -- so a
user could enable cloud routing still believing their model choice held. #43
M5a fixed the Developer Console selector; M5b ("equivalent disclosure to any
terminal flow that explicitly activates Cloud") is this command, and it was
still open.

The disclosure now answers the three things activating Cloud actually decides,
each of which has a different answer than the plane being left:

  - who processes the prompt (Cognitum, server-side)
  - who pays (the Cognitum account -- NOT the user's own Claude subscription,
    which is what Passthrough uses and is unaffected)
  - which model runs (the plane picks a tier per prompt rather than honoring
    the requested model)

`--routing-mode <auto|low|mid|high>` is rev-2's escape hatch. The console got a
control for it in meta-proxy#52; ruflo had none, so a ruflo user was stuck on
auto with no way to disagree -- and the scorer reads prompt SHAPE, not task
difficulty, so a short-but-hard prompt can under-escalate.

Setting a tier must never activate Cloud (ADR-321 Revision 3 keeps the plane
choice and this Cloud-only setting as separate controls), so `--routing-mode`
alone writes only `routing_mode`, asks for no consent it does not need, and
says the setting is inert until Cloud is on. `--routing-mode` with
`--local-only` is refused rather than silently resolved, and an unconfirmed
`--cloud` still writes nothing at all.

Wire values confirmed against meta-proxy's `RoutingMode` enum (`src/config.rs`,
same `#[serde(rename_all = "snake_case")]` treatment as `DataPlane`), and
`#[serde(default)]` there means an existing config that omits the field keeps
working and reads as auto. An unrecognized value already in the file reports as
auto rather than being echoed back, matching the proxy's own
degrade-to-safe-default behavior.

ADR-304 records both, and drops its inline copy of the disclosure text -- that
copy had already fallen behind the shipped string, and a reader could not tell
which was real. #43 M3 (a Cloud tier ceiling and daily cap) stays open upstream
as a product decision, so the disclosure makes no claim about caps either way.

Test: 9 proxy test files, 68 tests, all passing (was 56).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(proxy): make the subscription plane reachable again after cloud routing

`--local-only` was the only exit from the cloud plane, and it writes
`default_data_plane = "local"` -- the user's own Ollama/vLLM/SGLang backend.
`passthrough`, which is meta-proxy's own default AND the plane that uses the
user's own Claude subscription, was unreachable from this command by design
("passthrough is never written", ADR-304's 07-16 addendum).

So the undo we advertised was wrong. A user on passthrough who followed our own
disclosure -- "Disable anytime: ruflo proxy config --local-only" -- landed on a
THIRD state they never chose, pointed at a local backend that may not be
installed. Two consequences, neither visible:

  1. Their Claude subscription stops being used at all. That is the same harm
     cognitum-one/meta-proxy#51 closed on the login path ("silently switches a
     user away from Passthrough -- their own Claude subscription"), reached
     through a different door.

  2. Automatic quota failover silently stops applying. meta-proxy gates it on
     the plane (`src/routing.rs`: `automatic_eligible =
     cfg.default_data_plane == DataPlane::Passthrough`) because Passthrough is
     the only plane that sees Anthropic's own rate-limit headers (ADR-320). A
     user parked on `local` is opted out of ADR-321 entirely and is never told.

The no-flag report papered over the difference, printing one line -- "Cloud
routing is OFF -- requests never leave this machine (or use your own Claude
subscription on Passthrough)" -- for two planes that do not behave alike.

`--local-only` keeps its meaning exactly; the flag name and ADR-304 both
promise a local backend, and silently retargeting it would just be a second
surprise. Instead:

  - `--passthrough` is added, writing `default_data_plane = "passthrough"` and
    revoking cloud-routing consent the same way `--local-only` does.
  - The disclosure names both exits instead of presenting one as THE undo.
  - `--cloud` reads the plane it is leaving before overwriting it and prints
    the command to restore it. That read is the only moment ruflo knows where
    the user was, and it beats persisting a "previous plane" that could go
    stale against a hand-edited TOML.
  - `--local-only` now says the Claude subscription is not used on that plane,
    and how to choose it instead.
  - The no-flag report describes each plane distinctly and calls out the
    ADR-321 failover consequence while on `local`.
  - Plane flags are mutually exclusive -- more than one is refused rather than
    resolved by precedence, since any precedence would discard something the
    user explicitly asked for.

No default changes: a user who never runs this command is on passthrough,
exactly as before. ADR-304 records the decision and supersedes its own
"passthrough is never written" claim.

Test: 9 proxy test files, 80 tests, all passing (was 68).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(proxy): correct disclosure's who-pays claim (post-merge reapply)

Same fix as on #2927 — the disclosure asserted Passthrough "is the plane
in effect until you turn this on", which is false whenever the user is
already on --local-only. Reapplying after merging main (which already
had this fix on the pre-passthrough disclosure text) since this branch's
own commit still carried the original wording.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: ruv <ruv@ruv.net>
2026-08-11 13:03:23 -04:00
Dragan Spiridonov e1f7e4a232 fix(proxy): disclose what enabling cloud routing decides, and let users pin a tier (#2927)
* fix(proxy): disclose what enabling cloud routing actually decides, and let users pin a tier

Closes the terminal half of cognitum-one/meta-proxy#43 M5b, and adds the
`routing_mode` control ruflo never had.

meta-proxy ADR-321 rev-2 (shipped v0.6.0, #43 M2) applies tier selection to
ALL Cloud traffic. Before it, a deliberately configured Cloud plane forwarded
the client's own model name -- and since Claude Code names a frontier model by
default, every request was served at the top tier with no ceiling and no proxy
daily cap. Difficulty-routing all Cloud traffic is the fix for that defect, not
a regression, and it deliberately does not touch Passthrough or Local: those
are the user's own subscription and their own backend.

But ruflo never told anyone. `ruflo proxy config --cloud` said only that
prompts go to api.cognitum.one "and forwarded to the selected provider" -- so a
user could enable cloud routing still believing their model choice held. #43
M5a fixed the Developer Console selector; M5b ("equivalent disclosure to any
terminal flow that explicitly activates Cloud") is this command, and it was
still open.

The disclosure now answers the three things activating Cloud actually decides,
each of which has a different answer than the plane being left:

  - who processes the prompt (Cognitum, server-side)
  - who pays (the Cognitum account -- NOT the user's own Claude subscription,
    which is what Passthrough uses and is unaffected)
  - which model runs (the plane picks a tier per prompt rather than honoring
    the requested model)

`--routing-mode <auto|low|mid|high>` is rev-2's escape hatch. The console got a
control for it in meta-proxy#52; ruflo had none, so a ruflo user was stuck on
auto with no way to disagree -- and the scorer reads prompt SHAPE, not task
difficulty, so a short-but-hard prompt can under-escalate.

Setting a tier must never activate Cloud (ADR-321 Revision 3 keeps the plane
choice and this Cloud-only setting as separate controls), so `--routing-mode`
alone writes only `routing_mode`, asks for no consent it does not need, and
says the setting is inert until Cloud is on. `--routing-mode` with
`--local-only` is refused rather than silently resolved, and an unconfirmed
`--cloud` still writes nothing at all.

Wire values confirmed against meta-proxy's `RoutingMode` enum (`src/config.rs`,
same `#[serde(rename_all = "snake_case")]` treatment as `DataPlane`), and
`#[serde(default)]` there means an existing config that omits the field keeps
working and reads as auto. An unrecognized value already in the file reports as
auto rather than being echoed back, matching the proxy's own
degrade-to-safe-default behavior.

ADR-304 records both, and drops its inline copy of the disclosure text -- that
copy had already fallen behind the shipped string, and a reader could not tell
which was real. #43 M3 (a Cloud tier ceiling and daily cap) stays open upstream
as a product decision, so the disclosure makes no claim about caps either way.

Test: 9 proxy test files, 68 tests, all passing (was 56).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(proxy): correct disclosure's who-pays claim for --local-only users

The disclosure text asserted "[Passthrough] is the plane in effect until
you turn this on" — false whenever default_data_plane is already "local"
(i.e. exactly a --local-only user considering re-enabling cloud routing).
Reword to state Passthrough is the proxy's default and what the user's
Claude subscription is used by, without claiming it's necessarily the
plane currently in effect.

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

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: ruv <ruv@ruv.net>
2026-08-11 12:48:26 -04:00
Dragan Spiridonov d3e647a5dd fix(proxy): bump pinned meta-proxy release 0.4.0 -> 0.7.3 (#2925)
* fix(proxy): bump pinned meta-proxy release 0.4.0 -> 0.7.3

`DEFAULT_PROXY_RELEASE` had not moved in six meta-proxy releases, so
`ruflo proxy install` shipped a binary predating every fix since v0.4.0:

  - require explicit Cognitum Cloud routing consent (#60)  first in v0.7.0
  - handle --help before binding (#74/#76)                 first in v0.7.2
  - --help/--version win from any argv position (#85)       first in v0.7.3
  - reload live daemon config on login/logout (#28)         first in v0.4.1
  - pin the Linux glibc floor to 2.28 (#66)                 first in v0.7.1

The consent one matters most: the install disclosure promises cloud
routing is "never enabled by install alone", but the commit that
separated authentication from routing consent landed in v0.7.0 — after
the pinned build.

The --version regression is directly observable on 0.4.0: the binary
starts the daemon, writes the token file and binds 127.0.0.1:11435,
where meta-proxy's own source documents --version as side-effect-free
("no server, no I/O"). Any probe silently leaves a listener running.

Also stop hardcoding the version in proxy.ts's example text. That string
was written as a literal rather than interpolating the constant, so the
advertised version could — and did — drift from the installed one. Both
tests now read the pin from the module for the same reason; asserting a
literal means every bump needs hand-edited test expectations.

Verified on Windows x64: `ruflo proxy install --release 0.7.3 --yes`
installs with signature + checksum verification intact, and `--version`
then prints and exits with no process left running and no port bound.

Test: 9 proxy test files, 56 tests, all passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(proxy): surface pin drift to already-installed users

Bumping DEFAULT_PROXY_RELEASE only changed what a NEW install receives.
Someone who had already run `proxy install` on 0.4.0 got no signal that
anything moved: `proxy status` reported installation as a boolean, and
`proxy update` required an explicit --release, so reaching a newer binary
meant already knowing its version number. The fixes that motivated the bump
therefore reached only the users who did not yet have the problem.

ProxyStatus now carries `version`, read from the install manifest install.ts
already writes. `proxy status` prints it, the console guidance names the gap
and the command that closes it, and `proxy update` defaults to the pin the
same way `install` does. Updating stays explicit and user-initiated.

The version is read from the manifest, never by executing the binary:
releases before v0.7.2 start the server and bind a port on ANY invocation,
so a `--version` probe against an old build leaves a listener running --
precisely the state this check exists to detect. A missing or malformed
manifest reports "unknown", never "up to date".

Also corrects two stale claims this change makes load-bearing:

  - proxy/lifecycle.ts's header asserted as fact that the binary "takes no
    CLI flags -- no --version/--help, any invocation just starts the server"
    (confirmed 2026-07-16). v0.7.2 handles --help before binding and v0.7.3
    makes --help/--version win from any argv position. Left as-is, the next
    reader concludes no version handshake is possible.

  - ADR-307 still described the pin as v0.4.0 -- a fourth copy of the literal
    this PR is de-duplicating, and the one a reviewer reads to decide what
    the pin should be. It now names no version at all and points at the
    constant, plus records the drift-surfacing behavior.

Test: 9 proxy test files, 64 tests, all passing (was 56).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:22:55 -04:00
Dragan Spiridonov a39a1dccd0 fix(proxy): fall back to tar when Expand-Archive fails, and cover extraction (#2926)
`ruflo proxy install` aborted on Windows after the release had already been
downloaded and its Ed25519 signature + sha256 verified:

  [ERROR] Install failed
    Expand-Archive failed (exit 1): Expand-Archive : The 'Expand-Archive'
    command was found in the module 'Microsoft.PowerShell.Archive', but the
    module could not be loaded.

`Microsoft.PowerShell.Archive` is a script module reached through PowerShell's
autoloading, so it can fail for environment reasons unrelated to the archive.
The same command succeeded on the same machine minutes later, so this is
intermittent rather than a hard platform break — which makes it worse, not
better: a verified archive was discarded and the install left with nothing.

Windows is the only platform exposed to this. releaseArchiveExtension() sends
`*-windows-msvc` to .zip and every other target to .tar.gz, and the .tar.gz
branch already shells out to tar.

Expand-Archive stays the PRIMARY zip path — bsdtar's zip support is still not
something to lean on by default, per the existing comment. It now falls back to
`tar -xf` (bsdtar ships on Windows 10+) instead of giving up, and only a failure
of BOTH extractors is fatal, reporting each cause. Extraction is split into two
helpers so each path is independently exercisable.

Adds __tests__/proxy-extract-archive.test.ts (10 tests). Extraction was the only
platform-divergent step in the install pipeline and had no direct coverage:
nothing in __tests__ referenced extractArchive, ExtractionError, or
Expand-Archive. The executor is mocked, so routing is asserted on every platform
without needing a real powershell.exe or bsdtar.

`extractArchive` is exported for those tests.

Verified the tests pin the fallback rather than merely the new export: with the
fallback disabled but the export kept, exactly the 5 fallback-specific tests
fail and the other 5 still pass.

Test: 10 proxy test files, 66 tests, all passing. Changed files typecheck clean.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 12:22:31 -04:00
rUv 1d365d4a65 fix: silent memory-write loss + daemon lock cwd bypass (#2887, #2878, #2877) (#2966)
Three independent data-integrity/correctness bugs in the memory and daemon
subsystems:

#2887 — agentdb_hierarchical-store reported success:true for writes that
were never persisted. Root cause: agentdb dropped its HierarchicalMemory
export at 3.0.0-alpha.17, so every call was silently landing on an
in-process-only TieredMemoryStore fallback. The fallback now writes through
to a `tiered_memory` SQLite table when given a db handle, and the bridge
refuses to report success for a write that isn't durable — it names the
fallback controller and reason instead. agentdb_health now reports the real
persisted row count so a claimed write vs. an empty table is visible.

#2878 — concurrent sql.js writers lost updates while reporting success
(measured 10/12 lost in a 12-way concurrent repro), because storeEntry,
getEntry, deleteEntry, applyTemporalDecay, and ensureSchemaColumns did
unlocked whole-image read-modify-write against memory.db. All five now run
under the existing withMemoryDbLock primitive (previously only used by
purgeNamespace). Made the lock reentrant via AsyncLocalStorage, since every
mutator calls ensureSchemaColumns internally and a plain O_EXCL lock isn't
reentrant. Also fixed a path-normalization bug where different callers
spelled the same db path differently and so serialized against nothing.

#2877 — the daemon lock/PID file was keyed to raw process.cwd(), so
`daemon start` from a project subdirectory span its own lock/PID pair
instead of contending with one from the project root, bypassing the
#2407/#2484 dedup. Added resolveDaemonProjectRoot(), which walks up from
cwd to the nearest directory carrying a real Ruflo marker (not just any
package.json, so independently-initialized nested sub-projects in a
monorepo keep their own daemon), stopping at a .git boundary so the walk
can't escape into an unrelated ancestor project. Wired into every daemon
command that previously used process.cwd() directly.

Tests: 123 passing across affected suites (controller-registry.test.ts,
tiered-memory.test.ts, hierarchical-store-durability-2887.test.ts,
memory-concurrent-write-loss-2878.test.ts, daemon-autostart.test.ts).
Both packages typecheck clean. Pre-existing failures in unrelated suites
(ruvector wasm bindings, agenticow, memory-search-recall-2558) reproduce
identically with these changes reverted — confirmed not regressions.

Fixes #2887, #2878, #2877
2026-08-11 10:15:42 -02:30
rUv f35c545fbe feat(metaharness): pull in @metaharness/turn-credit + fix stale router/darwin pins (#2958)
* feat(metaharness): pull in @metaharness/turn-credit + fix stale router/darwin pins (post metaharness#176)

metaharness#176 shipped @metaharness/turn-credit (ADR-248, recursive
turn-level credit assignment) and bumped darwin to 0.9.0 (ADR-249 signal
seams) + router to 0.4.0 (calibration module). This brings ruflo's
dependency contract back in sync and makes the new package available:

- Add @metaharness/turn-credit ~0.1.0 as a new optionalDependency,
  following the same "must be installable, not peer-only" pattern
  darwin/flywheel/radio already use (ADR-150) — dependency-free, 64.9K
  unpacked, zero lifecycle scripts, same profile as the other three.
- Bump @metaharness/darwin ~0.8.3 -> ~0.9.0 and the paired MH_DARWIN_PIN
  constant in distill-oracle.ts (was already out of range: tilde only
  absorbs patches, and 0.9.0 is a minor bump).
- Bump @metaharness/router peer range ^0.3.2 -> ^0.4.0 (still deliberately
  peer-only + triple-gated behind CLAUDE_FLOW_ROUTER_NEURAL=1, per
  neural-router.ts — that design choice is unchanged, only the stale
  range is fixed) and update the matching manual-install hint in neural.ts.
- scripts/check-metaharness-pins.mjs + scripts/metaharness-clean-install-test.mjs:
  add turn-credit to the watched/contract-checked package list.
- .github/workflows/no-cli-optdep-bloat-2561.yml: CLI_MAX 10 -> 13. The
  prior bump (PR #2956) left zero slack (budget == count exactly), which a
  code review flagged as a latent trap — the very next unrelated optional
  dep would trip this guard. This bump leaves real headroom (11 declared
  today, budget 13) instead of repeating that mistake.
- Also fixes a live ReferenceError in distill-oracle.test.ts (MH_DARWIN_PIN
  used but never imported) — a gap from the prior release's test fix that
  somehow didn't surface in that PR's CI; caught here while touching the
  same file.

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

* fix: regenerate v3/pnpm-lock.yaml — was out of sync with package.json edits

The previous commit edited v3/@claude-flow/cli/package.json directly
(darwin/router/turn-credit pin changes) without regenerating the pnpm
workspace lockfile, so every CI job running `pnpm install --frozen-lockfile`
failed immediately with ERR_PNPM_OUTDATED_LOCKFILE — cascading into every
downstream smoke/test job that depends on that install step.

Regenerated with pnpm@8.15.9 (matching CI's pinned version) in an isolated
worktree to avoid the lockfile-version drift a newer local pnpm would
introduce.

Co-Authored-By: RuFlo <ruv@ruv.net>
2026-08-10 15:08:06 -02:30
rUv 4af1cfdded chore(release): bump to 3.35.0 — MetaHarness dependency-contract fix + ADR-381 sequential-evidence governance (#2957)
Minor bump: this release folds in PR #2956 (MetaHarness hardening — repair
the dependency contract, strict sequential promotion evidence, ADR-381) plus
a follow-up commit on that PR fixing 3 concurrency/epoch-boundary bugs found
during review that could silently violate the <=5% family-wise
false-promotion guarantee. All backward-compatible additions (new
optionalDependencies, new doctor checks, new CLI/MCP surface) — no breaking
changes, hence minor not major.

Also fixes a stale test assertion in distill-oracle.test.ts: the darwin
bench-run command now embeds the pinned @metaharness/darwin version
(MH_DARWIN_PIN) per the #2561 cold-npx regression guard, so the test's bare
'darwin bench run' substring check no longer matched the intentional pinned
output ('darwin@0.8.3 bench run').
2026-08-10 11:48:19 -02:30
rUv 0b3cfb77d6 MetaHarness hardening: repair the dependency contract + strict sequential promotion evidence (#2956)
* feat(metaharness): repair dependency contract + strict sequential promotion evidence

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

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

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

* chore: gitignore node-compile-cache build artifact

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

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

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

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

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

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

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

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

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

Completes the sequential-evidence governance PR #2956 opened:

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

pnpm-lock.yaml regenerated (scoped to this one dependency).
2026-07-31 10:11:41 -04:00
rUv 1e64c2fd3b ADR-378/379/380: npm Trusted Publishing, statusline segments, AGNTCY/Outshift runtime integration (#2879)
* docs(adr): ADR-378/379/380 — npm trusted publishing, statusline segments, AGNTCY runtime integration

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

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

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

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

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

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

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

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

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

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

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

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

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

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

CI's fleet-wide SKILL.md frontmatter audit correctly flagged this —
no implicit 'all tools' is allowed. Scoped to Read since this is a
scaffolding stub (not yet implemented) that will only ever need to
inspect local package.json/config state, never execute commands.
2026-07-30 23:05:44 -04:00
Dragan Spiridonov 76453115a8 fix(security): fail closed on unvalidated security scan --depth/--type/--target (#2866)
`security scan` validated none of its three input flags, so a typo was
indistinguishable from a clean result:

- An unknown --type matched none of the three phase guards, so no phase ran at
  all and the command still printed "No security issues found!" and exited 0.
  The guards are case-sensitive, so `--type CODE` hit this too.
- An unknown --depth fell through the chained ternaries to the shallowest
  traversal, so `--depth full` scanned *less* than the default `standard`
  (3 levels vs 5). On a tree whose only HIGH finding sat below that budget it
  also flipped the critical/high exit-code gate from 1 to 0 — the gate silently
  stopped gating.
- A non-existent or non-directory --target read nothing, and the swallowed
  dir-read catches turned that into zero findings, the clean banner, exit 0, and
  a PERSISTED clean report that getSecurityStatus reports as CLEAN. mkdirSync
  with recursive:true even created the tree to hold it. --target names what gets
  scanned and is the likelier typo of the three.

All three now fail closed before anything is printed, scanned or written.

`--depth full` is deprecated, NOT rejected. It was never a supported depth, but
the CLI itself emitted that exact string in five places — the statusline insight,
the announcement, the release-notes blurb, the CLAUDE.md that `init` generates,
and two shipped agent definitions — across 21 tracked files. Hard-rejecting it
would break the invocations we told users to run, and would strand the statusline
insight in a loop: it fires on scan status PENDING, tells the user to run
`--depth full`, which would exit 1 and never write a report, leaving status
PENDING forever. It is normalised to `deep` (the depth the word promised) with a
warning, and the emitters we control now say `--depth deep`.

Also in this change:

- Chained ternaries replaced with exhaustive Record<ScanDepth, number> maps
  behind real type predicates rather than `as` casts. The casts mattered: an
  invalid key yields undefined, and `undefined <= 0` is false while
  `undefined - 1` is NaN, so the recursion guard would never fire and the depth
  limiter would silently disable itself. The guard now positive-tests its budget
  (`!(depthLimit > 0)`) so a bad value stops traversal instead of unbounding it.
- `container` was advertised in --type's help since this command was written but
  no phase implements it, so it validated fine, scanned nothing and reported
  clean. Rejected explicitly, case-insensitively so `--type Container` gets the
  accurate "not implemented" message rather than being called a misspelling.
  Breaking for pipelines passing it; noted in CHANGELOG.
- The report path is built as scan-${scanType}-${depth}.json, so an unvalidated
  --type could shape it: `--type ../../../escaped` wrote
  <target>/.claude/escaped-deep.json. Validating the enums closes it.

Tests: 20 cases, black-box against the built CLI. Beyond the rejection paths they
pin the parts a previous revision of this suite left unconstrained — a level-6
fixture so `deep` is distinguishable from `standard` (a mutant cutting deep from
10 to 4 passed the earlier suite and now fails three tests), stdout/stderr kept
separate so a stdout-redirecting caller is provably not left with an empty
document and no diagnostic, and the --target and deprecation paths.
security-scan-persistence, security-audit, security-verification,
issue-2670-security-defend, fs-secure, funnel and funnel-messages-seed-pool all
pass. tsc error count is 17 with and without the patch, none in security.ts.

Note for anyone re-verifying: bin/cli.js loads dist/, tsconfig has
composite: true, and the build cache lives at dist/tsconfig.tsbuildinfo — an
incremental `npx tsc` will not re-emit after an out-of-band dist edit.

Reported privately via SECURITY.md by an external reporter, with a reproduction
and a proposed patch.

Co-Authored-By: Ruflo & AQE
2026-07-30 08:39:44 -04:00