Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
281df57
fix(installer): self-heal stale Codex marketplace
stuinfla Aug 2, 2026
42f1e42
fix(release): block publication with open issues
stuinfla Aug 2, 2026
489bc29
test(host): prove issue lifecycle boundaries
stuinfla Aug 2, 2026
28baa9c
fix(hooks): make dispatch timing contract truthful
stuinfla Aug 2, 2026
3668b1b
fix(console): preserve provenance and runtime integrity
stuinfla Aug 2, 2026
00110e5
fix(session): keep maintainer issue alerts private
stuinfla Aug 2, 2026
67b283e
feat(swarm): recycle completed agent slots
stuinfla Aug 2, 2026
5a638f6
fix(runtime): bind console identity and owner alerts
stuinfla Aug 2, 2026
5250414
test(console): cover packed provider state
stuinfla Aug 2, 2026
49dded1
test(console): prove installed provenance and inventory
stuinfla Aug 2, 2026
78e897b
fix(release): make publication remotely recoverable
stuinfla Aug 2, 2026
c51f5ab
test(version): read canonical console version
stuinfla Aug 2, 2026
5eba7ec
docs(adr): reconcile final 4.0.7 candidate
stuinfla Aug 3, 2026
fb36c32
fix(packaging): close installed module dependencies
stuinfla Aug 3, 2026
dfa4330
fix(release): reconcile candidate contracts
stuinfla Aug 3, 2026
51150bf
refactor(release): collapse gates into evidence DAG
stuinfla Aug 3, 2026
e0f4a1a
fix(hooks): preserve compact L4 contract
stuinfla Aug 3, 2026
16372b2
test(ci): remove cross-platform release races
stuinfla Aug 3, 2026
b358f45
test(installer): use complete runtime fixture
stuinfla Aug 3, 2026
f6ef30e
test(mesh): preserve installer dependency closure
stuinfla Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,17 @@ jobs:
run: node scripts/release-authority.mjs
- name: Version lockstep at source
run: npm run version:check
- name: Exact-artifact release QE
- name: Build the immutable npm candidate exactly once
shell: bash
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/release-evidence"
npm pack --json --pack-destination "$RUNNER_TEMP/release-evidence" > "$RUNNER_TEMP/release-evidence/npm-pack.json"
artifact="$RUNNER_TEMP/release-evidence/$(node -p "JSON.parse(require('fs').readFileSync(process.env.RUNNER_TEMP + '/release-evidence/npm-pack.json'))[0].filename")"
test -s "$artifact"
echo "RUVNET_SEALED_PACKAGE=$artifact" >> "$GITHUB_ENV"
- name: Exact-artifact release QE
run: |
npm exec -- vitest run \
--config tests/qe/release/vitest.config.mjs \
--reporter=json \
Expand All @@ -379,10 +387,8 @@ jobs:
- name: Seal the exact stabilization candidate
run: |
npm audit --audit-level=high --json > "$RUNNER_TEMP/release-evidence/npm-audit.json"
npm pack --json --pack-destination "$RUNNER_TEMP/release-evidence" > "$RUNNER_TEMP/release-evidence/npm-pack.json"
artifact="$RUNNER_TEMP/release-evidence/$(node -p "JSON.parse(require('fs').readFileSync(process.env.RUNNER_TEMP + '/release-evidence/npm-pack.json'))[0].filename")"
node scripts/stabilization-receipt.mjs \
--artifact "$artifact" \
--artifact "$RUVNET_SEALED_PACKAGE" \
--qe "$RUNNER_TEMP/release-evidence/release-qe.json" \
--audit "$RUNNER_TEMP/release-evidence/npm-audit.json" \
--out "$RUNNER_TEMP/release-evidence/candidate-receipt.json"
Expand Down
25 changes: 17 additions & 8 deletions .github/workflows/protected-release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: protected-release

# The candidate CI produces one immutable package + receipt. This workflow proves that exact
# candidate is current main, carries it across the reviewer-protected Production boundary, and is
# candidate is current main, carries it across the branch-protected Production boundary, and is
# the only workflow allowed to invoke the publisher.
on:
workflow_dispatch:
Expand All @@ -11,7 +11,7 @@ on:
required: true
type: string
version:
description: Exact release generation; this workflow is locked to 4.0.6
description: Exact release generation; this workflow is locked to 4.0.7
required: true
type: string
release_qe_run_id:
Expand All @@ -22,13 +22,14 @@ on:
permissions:
actions: read
contents: read
issues: read

concurrency:
group: protected-release-${{ inputs.version }}
group: ruvnet-brain-release
cancel-in-progress: false

env:
EXPECTED_VERSION: 4.0.6
EXPECTED_VERSION: 4.0.7
EXPECTED_SHA: ${{ inputs.candidate_sha }}

jobs:
Expand Down Expand Up @@ -63,6 +64,15 @@ jobs:
test "$(git rev-parse origin/main)" = "$EXPECTED_SHA" || { echo 'candidate is not current origin/main' >&2; exit 1; }
test -z "$(git status --porcelain)" || { echo 'candidate checkout is dirty' >&2; exit 1; }

- name: Require zero maintainer-governed release blockers
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
blockers="$(gh issue list --repo "$GITHUB_REPOSITORY" --state open --label release-blocker --limit 100 --json number --jq 'length')"
test "$blockers" -eq 0 || { echo "$blockers governed release blocker(s) remain open" >&2; exit 1; }

- name: Prove exact-SHA CI and release-qe completed successfully
env:
GH_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -173,7 +183,7 @@ jobs:
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/release-seed"
gh release download v4.0.3 --repo "$GITHUB_REPOSITORY" --pattern ruvnet-brain.zip --dir "$RUNNER_TEMP/release-seed"
gh release download --repo "$GITHUB_REPOSITORY" --pattern ruvnet-brain.zip --dir "$RUNNER_TEMP/release-seed"
unzip -q "$RUNNER_TEMP/release-seed/ruvnet-brain.zip" -d "$RUNNER_TEMP/release-seed/extracted"
# Finder metadata (`__MACOSX/._*.big.rvf`) preserves a filename but is not an RVF. Select
# one canonical directory deterministically and fail closed if the archive is ambiguous.
Expand All @@ -189,7 +199,7 @@ jobs:
node scripts/rvf-index-audit.mjs --dir "${asset_dirs[0]}"
printf '%s\n' "${asset_dirs[0]}" > "$RUNNER_TEMP/release-assets-path"

- name: Revalidate after approval and invoke the one canonical publisher
- name: Publish the CI-sealed candidate through the one canonical transaction
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ github.token }}
Expand All @@ -206,8 +216,6 @@ jobs:
set -euo pipefail
RUVNET_RELEASE_ASSETS="$(cat "$RUVNET_RELEASE_ASSETS_FILE")"
export RUVNET_RELEASE_ASSETS
node scripts/release-authority.mjs
node scripts/release-proof.mjs --candidate "$RUVNET_CANDIDATE_RECEIPT"
node scripts/release.mjs --publish

- name: Require the machine-generated publication seal
Expand All @@ -220,6 +228,7 @@ jobs:
--publication release-evidence/publication-receipt.json

- name: Preserve both append-only receipts
if: always()
uses: actions/upload-artifact@v4
with:
name: publication-evidence-${{ needs.release-qe-proof.outputs.candidate_sha }}
Expand Down
79 changes: 62 additions & 17 deletions .github/workflows/stranger-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,42 @@ name: stranger-matrix
# named — "wait until the stdout-budget finding has a real fix" — is now discharged; what remains
# is the admin action and the rest of items 2-9.
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch: {}
workflow_run:
workflows: [ci]
types: [completed]
workflow_dispatch:
inputs:
release_qe_run_id:
description: Successful ci run containing the sealed candidate
required: true
type: string

permissions:
actions: read
contents: read

env:
CANDIDATE_RUN_ID: ${{ github.event.workflow_run.id || inputs.release_qe_run_id }}
CANDIDATE_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}

jobs:
# ── shared prep, three POSIX-shaped jobs (ubuntu / macos share the exact same steps) ─────────────
ubuntu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.CANDIDATE_SHA }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: npm pack the candidate SHA
run: npm pack --silent --pack-destination "$RUNNER_TEMP"
- name: Download the one CI-sealed candidate
uses: actions/download-artifact@v4
with:
github-token: ${{ github.token }}
run-id: ${{ env.CANDIDATE_RUN_ID }}
name: release-evidence-${{ env.CANDIDATE_SHA }}
path: ${{ runner.temp }}
- name: npm install FROM the tarball into a fresh project (never the checkout)
run: |
mkdir -p "$RUNNER_TEMP/proj"
Expand Down Expand Up @@ -90,11 +109,18 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.CANDIDATE_SHA }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: npm pack the candidate SHA
run: npm pack --silent --pack-destination "$RUNNER_TEMP"
- name: Download the one CI-sealed candidate
uses: actions/download-artifact@v4
with:
github-token: ${{ github.token }}
run-id: ${{ env.CANDIDATE_RUN_ID }}
name: release-evidence-${{ env.CANDIDATE_SHA }}
path: ${{ runner.temp }}
- name: npm install FROM the tarball into a fresh project (never the checkout)
run: |
mkdir -p "$RUNNER_TEMP/proj"
Expand Down Expand Up @@ -134,11 +160,18 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.CANDIDATE_SHA }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: npm pack the candidate SHA
run: npm pack --silent --pack-destination "$RUNNER_TEMP"
- name: Download the one CI-sealed candidate
uses: actions/download-artifact@v4
with:
github-token: ${{ github.token }}
run-id: ${{ env.CANDIDATE_RUN_ID }}
name: release-evidence-${{ env.CANDIDATE_SHA }}
path: ${{ runner.temp }}
- name: npm install FROM the tarball into a fresh project (never the checkout)
run: |
mkdir -p "$RUNNER_TEMP/proj"
Expand Down Expand Up @@ -170,11 +203,18 @@ jobs:
shell: pwsh
steps:
- uses: actions/checkout@v4
with:
ref: ${{ env.CANDIDATE_SHA }}
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: npm pack the candidate SHA
run: npm pack --silent --pack-destination "$env:RUNNER_TEMP"
- name: Download the one CI-sealed candidate
uses: actions/download-artifact@v4
with:
github-token: ${{ github.token }}
run-id: ${{ env.CANDIDATE_RUN_ID }}
name: release-evidence-${{ env.CANDIDATE_SHA }}
path: ${{ runner.temp }}
- name: npm install FROM the tarball into a fresh project (never the checkout)
run: |
New-Item -ItemType Directory -Force -Path "$env:RUNNER_TEMP/proj" | Out-Null
Expand Down Expand Up @@ -233,12 +273,17 @@ jobs:
- name: apk add zip/unzip ONLY — explicitly no jq, no gh
run: apk add --no-cache zip unzip
- uses: actions/checkout@v4
with:
ref: ${{ env.CANDIDATE_SHA }}
# No actions/setup-node here: the alpine image's own node/npm are what a musl-based hostile
# box actually has; actions/setup-node downloads a glibc build that will not run on it.
- name: npm pack the candidate SHA
run: |
mkdir -p /tmp/ci-out
npm pack --silent --pack-destination /tmp/ci-out
- name: Download the one CI-sealed candidate
uses: actions/download-artifact@v4
with:
github-token: ${{ github.token }}
run-id: ${{ env.CANDIDATE_RUN_ID }}
name: release-evidence-${{ env.CANDIDATE_SHA }}
path: /tmp/ci-out
- name: npm install FROM the tarball into a fresh project (never the checkout)
run: |
mkdir -p /tmp/ci-out/proj
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# 🧠 RuvNet Brain

### 🧠 RuvNet Brain — [![RuvNet Brain version 4.0.6 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.0.6-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
### 🧠 RuvNet Brain — [![RuvNet Brain version 4.0.7 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.0.7-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)

**A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack — delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**

Expand Down Expand Up @@ -247,11 +247,11 @@ Our code now does **one honest job**: a price transform. A model your subscripti

Result: **11 jobs supervised, every one producing a fresh successful receipt.** One had been *totally blind* — writing zero bytes on a healthy day, so "ran fine" and "never ran" were indistinguishable. Cured without changing a line of its logic.

### 3. A subagent can no longer inherit your expensive model by accident
### 3. Inherited subagent models are now auditable

**A subagent inherits your session's model unless something says otherwise.** Ten agents on an Opus session are ten Opus agents; on a Fable session that's `$10/$50` per Mtok — up to **10× what the same mechanical work costs on Haiku**. That single default was the biggest cost leak in the harness, and an advisory rule did not fix it (the router's entire first life saved **$0.018**).

So 2.5.1 makes it a **wall, not advice**: a `PreToolUse` gate that **blocks any subagent dispatch that doesn't declare a `model`**, tells you which tier the task actually needs, and logs every allowed dispatch so routing is *auditable* rather than merely claimed. Forks still inherit — that's what a fork is.
The hook records both explicit-model and inherited-model dispatches so routing is *auditable* rather than merely claimed. It does not claim to block Agent/Task calls: Claude Code 2.1.220 consumes those `PreToolUse` results only after `tool_dispatch_end` ([issue #84](https://github.com/stuinfla/ruvnet-brain/issues/84), [upstream #83195](https://github.com/anthropics/claude-code/issues/83195)). Forks still inherit — that's what a fork is.

> **`npm run falsify`** — the adversary. Every question that had to be asked of this project ("is the nightly *actually* running?", "is that really rUv's code?", "why is my quota still burning?", "is CI *actually* green?") is now a check that fails on an **unproven claim**, not merely on broken code. Because tests you wrote yourself passing is circular evidence.

Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Security Policy

Updated: 2026-07-22
Updated: 2026-08-02
Created: 2026-07-06

RuvNet Brain runs on your machine, downloads a knowledge bundle, and (with your consent) can update
Expand Down Expand Up @@ -39,7 +39,7 @@ is every one of them, what it does, and whether it can block you:
| `SessionStart` | `session-start.sh` | Prints the "brain active" confirmation, checks a handful of local state files (nightly-failure marker, brain-health flags), and — rate-limited to once per ~15 min — does a single read-only `curl` to check whether a newer plugin version exists. First-run-only, it asks two one-time yes/no questions (auto-update? anonymous usage counts?) and records your answer to a local file. | No — always exits 0. |
| `UserPromptSubmit` | `ground-ruvnet.sh` | Reads your prompt text and, if it matches certain keyword patterns, injects grounding/context text before Claude answers (e.g. call `search_ruvnet` before asserting a stack capability). Also runs a rate-limited (~6 h) background version check against the public npm registry for a few packages. | No — always exits 0. |
| `PreToolUse` (`Write`\|`Edit`\|`Bash`) | `hijack-ruvnet.sh` | Scans the content of a proposed Write/Edit/Bash for third-party defaults it thinks the rUv stack already has a native replacement for, and injects a suggestion via `additionalContext`. | No — `permissionDecision: "defer"`, never denies. |
| `PreToolUse` (`Task`) | `route-dispatch.sh` | If — and only if — you've opted into model-cost routing (a confirmed `~/.claude/model-router/profile.json` exists), blocks a subagent dispatch that doesn't declare an explicit `model`, so it can't silently inherit the calling session's (possibly expensive) model. A non-interactive install may write detected values with an explicit `assumed:` basis; that profile is inert until you confirm it with the router setup. Recovery: re-issue the same call with an explicit `model:` (`haiku` mechanical / `sonnet` analytical / `opus` judgment). Escape hatch when inheritance is genuinely correct: `RUVNET_ALLOW_INHERITED_MODEL=1`. | **Yes, but opt-in only.** No profile, or an assumption-only profile, means this hook is a no-op for you. |
| `PreToolUse` (`Task`\|`Agent`) | `route-dispatch.sh` | For users who opted into model-cost routing, records whether a subagent declared a model or inherited the caller's model. Claude Code 2.1.220 checks Agent/Task hook results after dispatch, so this is bounded, silent audit rather than a false refusal ([#84](https://github.com/stuinfla/ruvnet-brain/issues/84)). No profile, or an assumption-only profile, means the hook is a no-op. | **No.** Always exits 0; the host currently offers no synchronous input-dependent enforcement boundary for these tools. |
| `PreToolUse` (`Bash`) | `verify-interface.sh` | For a confirmed router profile, legacy raw Bash that mentions a managed ecosystem CLI receives advisory context pointing to the structured `ruvnet_cli_help` → `ruvnet_cli_run` boundary. Issue #48 retired authorization decisions reconstructed from shell text; the structured MCP tools enforce the finite executable list, successful-help freshness, literal argv, and `shell:false`. | **No.** The legacy hook always exits 0 and is silent for absent or assumption-only profiles. |
| `PreToolUse` (`Bash`) | `design-wall.sh` | **Gated to this repo, not opt-in-gated.** First resolves the git root and checks the plugin manifest's own `name` field (`design-wall.sh:48-56`); **on any other project it exits 0 immediately and does nothing.** Inside a ruvnet-brain checkout it blocks `git commit` when staged files include anything under `explainer/`, `console/`, or a `README.md`, blocks `vercel ... --prod`, and blocks `open`-ing a hardcoded list of this project's own URLs — unless a fresh (≤45 min) passing design-grade stamp exists. Escape hatch: `RUVNET_SKIP_DESIGN_WALL=1`. | **Yes — but only inside this repo.** It cannot block work in your own projects. (An earlier version of this row said it could match paths in *any* project; the repo-identity gate makes that false. Corrected 2026-07-22.) |
| `PreToolUse` (`Write`\|`Edit`\|`MultiEdit`\|`NotebookEdit`) | `protect-brain-state.sh` | Blocks a write **only** to the brain's own on/off state: the sentinel `~/.config/ruvnet-brain/brain-off` and the settings mirror `~/.config/ruvnet-brain/settings.json` (plus their `.bak-*`/`.lock`/`.tmp-*` siblings). It exists so an agent cannot switch the brain back on — or off — behind your back; ADR-054 treats an agent-initiated flip as a consent violation. It reads only the target path, never file content, and touches nothing else. | **Yes — and this one is neither opt-in nor repo-scoped**, because a consent switch an agent can silently flip is not a switch. It matches those paths and nothing else, so it cannot block ordinary work. Added 2026-07-26 (v3.9.84). |
Expand Down
Loading
Loading