Files
ruvnet__ruflo/v3/@claude-flow/cli/scripts/mint-champion.mjs
rUv fe7f91b629 feat: self-optimizing flywheel (ADR-176) + signed RVFA config propagation (ADR-177) — self-learning demonstrated (#2572)
* docs(adr): ADR-176 self-optimizing harness loop (receipt-backed) + ADR-177 signed config propagation

ADR-176 — Self-optimizing harness loop, receipt-backed. Closes the loop
around metaharness's optimization primitives (honest: real wrappers over
optional upstream packages, never run to a measured outcome in-repo, no
provenance, no held-out corpus, no feedback path). Design incorporates
review feedback:
- Separate observation from training data — a Qualification stage with the
  invariant: no trajectory enters optimization without complete provenance,
  deterministic replay, and benchmark attribution.
- Separate promotion from deployment — a Canary stage (rollback rate,
  latency, cost, failure freq, acceptance) before global rollout.
- accept() as a conjunction of externally-measurable predicates (held-out,
  redblue, drift, replay, receipt coverage, canary rollback) — not a scalar.
- Negative learning: an anti-pattern DB of rejected mutations.
- Multi-dimensional, Goodhart-resistant success metrics.
- Hierarchical evolution (global→language→framework→repo), each layer
  independently benchmarked, to address cross-repo generalization.
- Acceptance test: two independent runs converge on equivalent promoted
  manifests; every manifest replayable from its signed receipts.

ADR-177 — Propagates the signed champion to already-installed projects via
the ADR-174 auto-refresh channel. Core point from review: signed != suitable
— manifests are OCI-metadata-style constraint contracts (host/platform/
compatibility/benchmark/layer/rollback-pointer), adoption doubly-gated on
signature AND constraint satisfaction, fail-closed on either. Named 'proven
configuration manifests' / 'verified execution policies' externally.

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

* docs(adr): ADR-177 — package proven-config manifests as signed RVFA appliances (ruvnet ecosystem)

Adopt RVFA (the RVF appliance format) as the propagation container instead of
a bespoke JSON blob:
- RVFA is already a signed self-contained appliance (rvfa-builder + rvfa-signing
  Ed25519 footer) with distribution/update primitives this ADR would otherwise
  reinvent — RvfaPublisher (IPFS/Pinata CID content-addressing) + RvfaPatcher
  (RVFP binary delta-patches).
- Envelope parses + verifies with pure Node (parseRvfaBinary = Buffer + native
  crypto); the optional @ruvector/agenticow module is needed only for vector
  ops on the payload, NOT to read the constraint metadata or check the
  signature — so the every-command adoption gate stays zero-dep + fail-closed.
- Section mapping: metadata = OCI-style constraint contract (host/platform/
  compat/benchmark/layer/rollback), payload = policy + replayable proof-
  trajectory as native ruvector data (strengthens ADR-176 'replayable from
  receipts'), footer = Ed25519 signature.
- Distribution layered: ship-in-package + local verify is the DEFAULT for the
  every-command path (no runtime network); IPFS/CID pull + RVFP delta-patches
  are opt-in out-of-band. Version stamp becomes an immutable champion CID.
- Trade-offs recorded: suitability metadata must stay zero-dep-readable;
  rvfa-signing is a distinct Ed25519 root from helper-signing (accept two
  purpose-fit roots or share one key — left open).

Keeps everything in the ruvnet ecosystem under one signed container.

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

* docs(adr): ADR-177 — explicit backwards-compatibility contract

Pin the compat guarantees for the ADR-174 updating system + older v3:
- Additive-only: new manifest + new stamp + new CLI path; the helper-code
  channel (helpers.manifest.json / .helpers-version / verifyHelpersManifest)
  is untouched. 3.22.0-3.23.x installs keep the helper auto-refresh unchanged.
- The manifest 'compatibility' constraint IS the version gate: an install
  below the declared min safely does not adopt (fail-closed suitability =
  graceful skip). A new champion can require a newer CLI without breaking old.
- Optional-dep degradation (ADR-150): RVFA envelope read zero-dep; missing
  @ruvector just skips the vector payload.
- Hazard recorded: do NOT rotate the existing helper-signing key (breaks
  3.22.0-3.23.x fail-closed verify); the rvfa config key is a fresh root on
  new CLIs only.
- Population behavior table + the fundamental limit (new capability reaches an
  install only when it runs a CLI that has it; old installs no-op safely).

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

* feat(config): proven-configuration manifest + Ed25519 + suitability gate (ADR-176/177 phase 1)

The signed, constraint-carrying artifact the self-optimizing loop emits and
the propagation channel ships. Foundation for both ADRs:
- ProvenConfigManifest: policy ref + OCI-style constraints (host/platform/
  compatibility/layer/benchmark/rollback) + ADR-176 receipt bundle.
- signProvenConfig / verifyProvenConfig: Ed25519 over canonical (recursively
  key-sorted) bytes, fail-closed. Distinct trust root from helper-signing
  (RUFLO_CONFIG_PUBKEY baked; private key in GCP ruflo-config-signing-key) so
  rotating the config key never touches the hook-code channel older CLIs verify.
- isSuitable(): the 'signed != suitable' gate — platform/host-version/package-
  compat/hierarchy-layer checks; a signed-but-unfit manifest is a SAFE skip,
  not an error. satisfiesRange() minimal semver (>=,>,<=,<,=,bare).
- evaluateForAdoption(): adopt iff authentic AND suitable.

The compatibility ranges are the ADR-177 backwards-compat version gate (an
install below the declared min safely does not adopt).

15 tests (authenticity incl. tamper/wrong-key, semver, suitability incl.
platform/host/compat/layer, combined adoption). Build clean. Additive — no
existing behavior touched.

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

* feat(harness): qualification gate + anti-pattern archive (ADR-176 phase 1)

Separates observation from training data — the reviewer's #1 addition. Raw
trajectories enter optimization only through Invariant Q:
- (a) complete provenance: every step attributed, tier >= oracle/judge
  (ADR-171; proxy:structural rejected — triage-only),
- (b) deterministic replay: re-running recorded inputs must reproduce recorded
  outputs (fail-closed — no replay fn => rejected),
- (c) benchmark attribution: maps to a corpus task.
qualifyTrajectory() + admitTrajectories() split qualified/rejected.

Negative learning: rejects go to a file-backed AntiPatternArchive (JSONL,
deduped by trajectory-shape fingerprint) so future runs avoid re-discovering
identical failures. Zero deps, $0.

9 tests (each Q clause, fingerprint dedup, archive record/has/list, admit
split, + a measured throughput signal: 5000 trajectories/run). Build clean,
additive.

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

* feat(harness): benchmark corpus + held-out gate + accept() conjunction (ADR-176 phase 2)

Proof #1 (measured, held-out) + the promotion rule:
- HarnessBenchmarkCorpus: versioned + content-hashed (hashCorpus/verifyCorpus)
  so the held-out set is tamper-evident.
- computeHeldOutSplit: deterministic, disjoint train/held-out (id-sorted, last
  frac held out) — reproducible per the ADR-176 acceptance test; the
  distill-tuning held-out pattern.
- scoreOnTasks: composite weighted fitness + passRate over isolated tasks (no
  shared state), not a single gameable number.
- accept(): the ADR-176 CONJUNCTION — held_out > baseline AND redblue==PASS AND
  drift<=threshold AND replay==deterministic AND receipt_coverage==100% AND
  canary.rollback<=baseline. Each term independently measured; ANY failure
  rejects. Multi-dimensional, Goodhart-resistant.

Tests: corpus tamper-detection, split disjointness+reproducibility, measured
held-out delta (improved candidate beats baseline), and accept() with every
term + each single-term rejection. Build clean, additive, $0.

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

* feat(config): ADR-177 propagation channel — adopt signed champion on startup

The 'reach existing installs' spine. A SIBLING of the ADR-174 helper auto-
refresh, deliberately independent (own .proven-config-version stamp, own
RUFLO_CONFIG_PUBKEY trust root) so it never touches the hook-code channel
older CLIs verify:
- adoptSignedConfig(cwd, signed, env): doubly-gated per ADR-177 —
  evaluateForAdoption() adopts iff authentic (Ed25519) AND suitable
  (host/platform/compat/layer). A signed-but-unsuitable champion is a SAFE
  skip (the backwards-compat version gate); a bad signature is refused; neither
  advances the stamp. On adopt: records the champion (for the feedback applier,
  ADR-176 phase 9) with the rollback pointer, advances the stamp.
- autoAdoptProvenConfigIfStale(): finds the package-shipped signed champion,
  builds the local env, adopts if newer. Additive no-op when no champion ships.
- Wired into index.ts startup right after the helper refresh (guarded, awaited,
  silent) — no-op today (no champion ships yet), no CLI regression (--version ok).
- scripts/sign-proven-config.mjs: signs a manifest with the CONFIG key from GCP
  (RUFLO_CONFIG_SIGNING_SECRET=ruflo-config-signing-key), canonical bytes match
  proven-config.ts.

5 propagation tests (adopt authentic+suitable + rollback-pointer, refuse bad
sig, safe-skip unsuitable, idempotent stamp, non-project no-op). Build clean.

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

* feat(harness): canary stage — separate promotion from deployment (ADR-176 phase 5)

Held-out proves a candidate on frozen data; it hasn't seen real-world
behavior. The canary runs a candidate on a bounded, deterministic sample of
the live slice and measures the telemetry accept() needs:
- runCanary(): deterministic stride-sampling (bounded by sampleFraction +
  maxSamples — caps cost/blast radius); aggregates rollbackRate, failureRate,
  acceptanceRate, latencyP95/mean, costPerTask. A throwing runner = rollback
  (fail-closed).
- compareCanary(): the no-worse gate (rollback/latency/cost) — the ADR-176
  metrics-table constraints; feeds accept()'s canary.rollback<=baseline term.

6 tests (sample bounds, rate/p95/cost aggregation, throwing-runner=rollback,
no-worse pass + each regression fail) + a measured telemetry signal. Build
clean, additive, $0.

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

* feat(harness): host registry + hierarchical layers (ADR-176 phase 7)

'All available hosts' + cross-repo generalization:
- HostRegistry / HostAdapter: claude-code + codex built-ins (detect via
  commandExists), available() filters to present hosts (throwing detect =
  unavailable). fanOutHosts() runs optimize/verify per host, isolating per-host
  errors — replaces the unvalidated --host passthrough.
- Hierarchical layers (global -> language -> framework -> repo):
  ancestorsOf / isAncestorOrEqual (path-boundary-safe) / layerDepth, and
  selectChampionForLayer() picks the deepest champion whose layer is an
  ancestor-or-equal of the install, falling back to a parent — so each manifest
  claims only its layer and generalization is a per-layer empirical question.

5 tests (registry available/error-swallow, multi-host fan-out with per-host
error isolation, ancestor/boundary/depth, deepest-applicable + parent-fallback
+ none). Build clean, additive, $0.

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

* feat(harness): deterministic replay engine (ADR-176 phase 3)

The replay==deterministic predicate for Invariant Q + accept():
- recordRun(): capture an output DIGEST at run time (canonical, order-indep).
- verifyReplay(): re-execute recorded inputs, confirm the output digest
  reproduces — a non-deterministic or throwing replay is fail-closed false.
- allDeterministic(): batch predicate feeding accept()'s replay term.
- ReplayStore: JSONL-backed record/get(latest)/all so a trajectory's
  determinism is re-checkable later.

A trajectory only counts as training data / promotion evidence if reproducible.
7 tests (stable digest, pure=deterministic, drifting/throwing=not, batch
predicate, store latest-by-id) + a measured throughput signal (3000 runs).
Build clean, additive, $0.

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

* fix(harness): bound append-only stores + pending-insights — runaway-storage guards

Resource-governance audit (no runaway process eating mem/cpu/storage):
- No live runaway: all node procs <0.2% mem/0% cpu, no daemon running, the
  /loop cron is bounded (10m, no overlap, 7-day expiry). Reclaimed a dead 68MB
  pre-recovery corruption .bak (DB healthy, quick_check ok).
- The real risk is append-only STORAGE growth. Added rotation caps:
  - AntiPatternArchive + ReplayStore: maxEntries (default 10000); once exceeded,
    rewrite to the newest maxEntries so they can never grow unbounded.
  - intelligence.cjs recordEdit: pending-insights.jsonl (append-only, drained
    only by consolidation, multiplied across cwds) now self-trims to the most
    recent 2000 lines once it passes ~512KB. Cheap (statSync/edit; rewrite only
    when over). Verified: 3000 appends → bounded to 2000 lines.

CPU/memory: nothing spins today; the future daemon evolve-worker (phase 8) will
be built strictly bounded (/bin/zsh/dry-run default, timeout, single-flight, budget
cap) — the daemon already provides maxConcurrent + 16-min worker timeout +
TTL/idle shutdown + orphan reaping.

+2 rotation tests. Build clean, additive.

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

* feat(config): feedback applier — close the loop (ADR-176 phase 9)

The missing consumer. A proven champion adopted by the propagation channel is
inert until applied to what ruflo runs. applyChampion() promotes the adopted
champion (.claude/proven-config.json) to the ACTIVE harness policy
(.claude-flow/harness-active-policy.json) that routing/agents consume:
- idempotent (re-applying the same champion is a no-op),
- reversible (records the superseded champion as the rollback pointer;
  rollbackActivePolicy() reverts to it — ADR-177 reversibility),
- provenance-tagged oracle/judge (proxy can never reach here — ADR-171),
- only ever points at a proven, signed champion; never invents config.

6 tests (apply+provenance, idempotent, rollback-pointer capture, no-adopted
no-op, rollback revert + nothing-to-revert). Build clean, additive, $0.

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

* feat(daemon): self-running daemon — auto-start on CLI use (single-instance, bounded, opt-out)

The self-optimizing loop's workers (distillation, backup, future evolve) were
inert without a manual 'ruflo daemon start'. Now the daemon self-starts on CLI
use — SAFELY, given the runaway-resource concern:
- single-instance: ensureDaemonRunning() only spawns when no live daemon holds
  .claude-flow/daemon.pid (signal-0 liveness + stale-pidfile cleanup); the
  spawned 'daemon start' independently re-checks its own lock, so a race yields
  at most one survivor.
- bounded lifetime: reuses the daemon's existing TTL/idle self-shutdown (12h
  default, RUFLO_DAEMON_TTL_SECS) — auto-start never means 'runs forever'.
- opt-out: RUFLO_DAEMON_AUTOSTART=0|false|no (registered in the env-precedence
  audit).
- cheap + non-blocking: pidfile read + liveness on the fast path; spawns
  detached fire-and-forget, never blocks the command.
- reuses 'daemon start' verbatim (all lock/TTL/worker machinery) — decides only
  WHETHER to spawn.
Wired into index.ts startup after config-adopt; skipped for daemon/init/update
(no recursion). --version early-returns before it (verified: no spawn).

7 tests (spawn-when-dead, no-op-when-alive, opt-out, non-project, stale-pidfile
cleanup, live-pid detection). Build clean, additive.

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

* feat(harness): adversarial + drift verify gate (ADR-176 phase 4)

Produces the redblue + drift verdicts accept() consumes:
- redblue (@metaharness/redblue, mock-judge/$0, loopback, cost-capped) and
  drift (@metaharness/drift_from_history) via injectable runners.
- FAIL-CLOSED: metaharness is optional (ADR-150); when the verifier is absent,
  redblue = SKIPPED, and since accept() requires redblue==='PASS', a candidate
  CANNOT promote without real adversarial evidence. The loop then safely
  degrades to 'observe + benchmark, don't promote' and the current signed
  champion stands. A throwing runner = SKIPPED (never a pass); negative drift =
  skipped (not ok).

6 tests (pass, redblue-fail, drift-regress, default-skipped-no-promote,
throwing-degrade, drift-unavailable). Build clean, additive, $0.

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

* feat(harness): loop orchestrator — compose all gates (ADR-176 phase 8, part 1)

runHarnessLoop() composes the full pass:
  OBSERVE -> QUALIFY -> BENCHMARK(held-out) -> VERIFY(adversarial+drift)
          -> CANARY -> ACCEPT(conjunction) -> emit champion manifest.
- $0 + fail-closed by default: no optimizer => no candidate => no promote;
  no verifier => redblue SKIPPED => no promote; no canary runner => no promote
  (separate promotion from deployment); receipt_coverage=1 by construction
  (only qualified trajectories used).
- Emits the champion manifest UNSIGNED (with the full receipt: held-out delta,
  redblue, drift, canary telemetry) — signing is the separate publish step
  (sign-proven-config.mjs, GCP key), so key material never touches the loop.
- Reuses every primitive: admitTrajectories, computeHeldOutSplit/scoreOnTasks,
  runVerify, runCanary/compareCanary, accept().

7 tests: full accept + manifest+receipt, and each fail-closed rejection
(no candidate, no qualified, SKIPPED verify, no canary, held-out regress,
redblue FAIL). Build clean, additive, $0.

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

* feat(harness): phase 8b — bounded opt-in harness daemon worker (ADR-176)

Wire runHarnessLoop into the worker daemon as a low-priority, opt-in worker:
- harness-worker.ts: runHarnessLoopWorker() — opt-in (RUFLO_HARNESS_LOOP),
  $0-default (no-op without a wired corpus/optimizer), trajectory-capped,
  never throws, stages the UNSIGNED champion for the separate publish/sign step.
- worker-daemon.ts: register 'harness' worker (6h interval, low priority,
  enabled but gated by the opt-in), dispatch + runHarnessWorker() method that
  writes .claude-flow/metrics/harness-loop.json and never crashes the daemon.
- audit-env-var-precedence.mjs: register RUFLO_HARNESS_LOOP escape-hatch.
- 4 new tests (opt-in gate off/on, no-input $0 default, accept→stage,
  reject→no-stage). 11 harness + 31 daemon tests green.

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

* feat(config): ADR-177 final phase — RVFA-package the signed proven-config champion

Keep champion propagation inside the ruvnet RVFA ecosystem: the already
config-signed SignedProvenConfig rides as the sole section of a small RVFA
appliance (RvfaWriter/RvfaReader). Adds a ruvnet-native, tamper-evident
transport envelope (SHA256 footer + section hash) WITHOUT a second trust root
— adoption still verifies the inner Ed25519 signature against RUFLO_CONFIG_PUBKEY.
Signed≠suitable preserved: unpack → verify → isSuitable, fail-closed throughout.

- proven-config-rvfa.ts: packProvenConfigRvfa / unpackProvenConfigRvfa /
  isProvenConfigRvfa. Pure Node, no LLM/network, $0.
- proven-config-refresh.ts: findPackageProvenConfig prefers .rvf over raw JSON;
  loadShippedChampion decodes either packaging; same adoptSignedConfig path
  (additive — raw .signed.json still adopts unchanged).
- sign-proven-config.mjs: also emits .signed.rvf post-build (skipped pre-build).
- 8 new tests: roundtrip, tamper rejection, fail-closed decode, adopt parity,
  forged-manifest-in-valid-envelope rejected at adoption. 26 config tests green.

Measured (scripts/benchmark-proven-config-rvfa.mjs, 5k ops, realistic manifest):
  raw signed JSON 774 B → RVFA envelope 1134 B (+46.5%, gzip section on a tiny
  payload); pack 0.019 ms/op (~53k ops/s), unpack+integrity 0.009 ms/op
  (~108k ops/s). Overhead is fixed header+footer bytes, negligible at champion
  scale (one file per publish); decode cost is sub-10µs.

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

* feat(harness): mint + ship the first REAL proven-config champion (ADR-176/177)

Close the substantive gap: run the shipped runHarnessLoop end-to-end on REAL
data to mint, benchmark, sign, and package a real champion — and make an adopted
champion actually change runtime behavior (was a verified no-op before).

WHAT SHIPS: .claude/proven-config.signed.rvf — a config-signed, RVFA-packaged
champion for neural_patterns retrieval. Measured on the ADR-081 labelled corpus
via the real MCP tool over the real ONNX-embedded pattern store:
  baseline (ADR-082-tuned) held-out nDCG@3 = 0.7262
  champion                 held-out nDCG@3 = 0.8000   (+0.0738)
  config: alpha 0.5→0.3, subjectWeight 2→1, mmrLambda 0.7→0.5,
          bodyWeight 1→1.5, typePenaltyFactor 1→0.5
Accepted by the FULL accept() conjunction — held_out_improves ✓, redblue PASS,
drift 0, replay deterministic, receipt_coverage 1, canary 0 rollbacks (no query
regresses vs baseline = strict Pareto-dominance). Verified end-to-end: the real
.rvf verifies against the baked RUFLO_CONFIG_PUBKEY, is suitable, adopts,
applies, and its params go live.

LAST-MILE WIRING (additive, backwards-compatible):
- proven-config.ts: manifest gains optional policy.value (the config payload;
  older CLIs verify the signature and ignore it).
- harness-feedback-applier.ts: carries policy.value into the active policy as
  params (what consumers read).
- neural-tools.ts: retrieval defaults now resolve explicit-input → adopted
  champion params → hardcoded ADR-082 defaults. Cached, fail-safe, caller wins.
- index.ts: startup now applies the adopted champion (was adopt-only), so the
  active policy is actually written.

MINT DRIVER: scripts/mint-champion.mjs dogfoods runHarnessLoop (not a
re-impl) — coarse grid → local refine until train nDCG@3 converges, train
defines the optimal set, held-out breaks ties, and the loop independently gates.
The --quick run proved the gate is not a rubber stamp (it rejected a tied
candidate; only held_out_improves failed).

ADR-176/177 status → Accepted (implemented). 91 harness+config tests green.

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

* feat(harness): self-optimizing flywheel — get smarter as it runs, with proof (ADR-176 phase 10)

Close the loop: an install now improves AUTONOMOUSLY on its own data, and the
improvement is proven, not asserted. Three engineered-honest mechanisms:

1. GROWING YARDSTICK (harness-corpus-harvester.ts): mines the real store into a
   self-supervised self-retrieval benchmark — a doc is ground truth for a query
   built from its own body with the SUBJECT tokens withheld (discriminative, not
   trivially found). oracle:test-exec grade (executable, unambiguous), so the
   test set grows as the store does. Deterministic (no RNG).

2. GOODHART ANCHOR (harness-flywheel.ts): blends the growing auto-signal with the
   human-labeled ADR-081 seed as a NEVER-REGRESS anchor; the accept() adversarial
   term is bound to 'no regression on the human anchor', so optimizing the cheap
   metric can't drift from real relevance. Multi-objective by construction.

3. PROOF LEDGER (harness-improvement-ledger.ts): every tick appended (accepted or
   refused) with baseline/candidate held-out scores + all accept() terms. The
   loop only accepts a STRICT improvement that regresses no task, so the accepted
   subsequence is monotonic-by-construction and each champion CHAINS to its
   predecessor. summarizeImprovement() → auditable claim; a single non-improving
   or unchained accept flips monotonic/chainIntact — it cannot launder a
   regression, and it records refusals (proof the gate isn't a rubber stamp).

TRUST SPLIT: a locally-mined champion that clears the install's own measured gate
is applied LOCALLY + UNSIGNED (applyChampionParams) — the install trusts its own
execution-verified evidence on its own data; nothing propagates so no signature
needed. Cross-install propagation still requires the config-signed champion
(ADR-177). Local self-optimization and global distribution are separate trust
domains — this is what lets it run autonomously at $0 without weakening the
propagation root.

WIRING: harness-flywheel-runtime.ts binds runFlywheelTick to the live neural
store (getStorePatterns) + neural_patterns search; the daemon harness worker now
runs the flywheel (opt-in RUFLO_HARNESS_LOOP, $0 default) instead of the
null-input no-op, writing flywheel result + improvement summary to metrics.

Selection is local hill-climb on TRAIN; held-out + anchor + canary gate
independently. 7 new tests (harvester determinism/provenance, ledger
monotonicity + broken-chain + non-monotonic detection, live-style tick
accept→apply→proof, tiny-store no-op). 98 harness+config tests green.
ADR-176 documents the flywheel + adds roadmap phase 10.

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

* feat(harness): single-round proof-of-mechanism + generation-0 fixture (ADR-176)

Scope, stated up front: this is a SINGLE-ROUND PROOF-OF-MECHANISM. NOT flywheel
proof, NOT compounding learning, NOT production learning. A synthetic pass here
is not evidence of real improvement. Its only job is to prove, on one
deterministic synthetic round, that the plumbing is real:
  (a) gate wiring        — the REAL versioned accept() (accept/v1) decides,
  (b) receipt persistence — a self-contained bundle is written to disk,
  (c) SHADOW registration — a pass registers in shadow (served=false),
  (d) no auto-serve path  — nothing is written to the active/served policy.

evolve-proof.ts: runSyntheticProofRound() emits the seven required artifacts —
input-holdout hash, baseline + candidate manifest hashes, meetsPromotionRule
version, decision receipt, SHADOW registration id, cost receipt. It embeds the
holdout + both manifests so verifyReceiptBundle() can INDEPENDENTLY replay it:
rehash every input and RE-RUN accept() to confirm *why* the candidate passed or
failed WITHOUT trusting any service log (the acceptance test). reconstructLineage()
audits a promoted chain back to generation 0 (winner→next baseline) + telemetry
(generations, promotions, rejections, cumulative delta, plateau, lineageIntact).

Ran it (clean temp dir): all 7 artifacts emitted, accept/v1 PASS, shadow
registered served=false, active policy NOT written (no auto-serve), and the
committed fixture .claude/evolve-proof/generation-0.json replays from disk to a
valid, decision-matching verdict ('held_out 0.7720 > 0.7000, canary 0, all terms
held'). This bundle is generation 0 — the fixture for F-P1/F-P2; A-P3b turns it
into real compounding.

Also corrects ADR-176's flywheel section to be honest: rewrites it to the
observe→benchmark→evolve→verify→promote→shadow-deploy lifecycle, the corrected
objective (optimize the human anchor, guard with harvested), shadow-first /
no-auto-serve, telemetry, and the lineage acceptance test — and REMOVES the prior
overclaim that 'the live flywheel climbs to convergence' (the live run refused;
multi-generation compounding on real data is explicitly NOT yet demonstrated).

12 evolve-proof tests (7-artifact emission, gate wiring, independent replay,
tamper + forged-decision detection, reject path, shadow-only, lineage intact +
broken-chain detection). 58 harness+config tests green.

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

* feat(harness): lineage as version control for operating policies — causal records, mutation stats, regression ancestry, DAG, rigorous plateau (ADR-176)

Acts on the design review: turn the lineage from an audit trail (what changed)
into a knowledge base (why it was promoted, which mutation classes pay off).

1. Causality, not just provenance — a promotion carries mutationClass,
   mutationSummary, and multi-dimensional deltas (benchmark/security/cost)
   beside the decision receipt (PromotionRecord, classifyMutation). The gen-0
   fixture now records: retrieval:multi, 'alpha 0.5→0.3, …', benchmark +0.072.
2. Mutation effectiveness — mutationEffectiveness() aggregates
   attempts/promotions/mean-Δ per class → evidence-grounded meta-learning
   (bias the optimizer toward historically-paying classes).
3. Regression ancestry — a reject records failureCause
   (holdout/security/drift/replay/governance/canary/significance) + ancestor
   (RegressionRecord), so 'which decisions repeatedly regress?' is answerable.
4. DAG, not a linked list — reconstructLineage models a graph with branch
   labels (main + future tenant/domain branches); invariant: a child's baseline
   == its parent's promoted candidate (holds for chains AND forks). Detects
   >1 root, missing parents, non-inheriting children.
5. Rigorous plateau — detectPlateau() over a rolling window separates
   local-optimum (variance shrinking), noisy-benchmark (high non-shrinking
   variance), optimizer-failure (candidates barely vary) — not intuition.
6. Wording — 'immutable root of the evolution graph' (replay starts there; it
   never changes), and the 'version control for operating policies' model in
   the ADR.

verifyReceiptBundle() now also checks causal-record consistency (a pass carries
a promotion record + matching delta; a reject a regression record). ADR-176
adds the VCS-for-policies section and states the real milestone: a SECOND
autonomously-discovered, independently-verified improvement surviving a frozen
anchor suite, entering the lineage without human intervention — explicitly NOT
yet reached.

19 evolve-proof tests (causality, regression ancestry, DAG + branches + broken-
lineage detection, mutation effectiveness, plateau states); 71 harness+config
tests green.

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

* perf(retrieval): cache config-independent BM25 stats + per-query cosine — flywheel iterable (ADR-176 A-P3b)

The multi-generation flywheel couldn't complete because each hybrid search
rebuilt the whole BM25 corpus (tokenize every doc + IDF) AND re-embedded +
re-cosined the query — even though those depend only on (store) and (query,
store), not the retrieval config being scored. The flywheel scores ~18 configs
against the SAME query, so this was ~18x wasted work per query.

Two additive, correctness-preserving caches in neural_patterns search:
  - corpus-stats cache: tokenized subject/body docs + BM25 stats, keyed by a
    cheap store fingerprint (count + id + name/content lengths); invalidates on
    store change.
  - per-query cosine cache (size 1): query embedding + cosine array keyed by
    (store fingerprint, query); reused across configs for the same query.

Measured on the real store: flywheel access pattern (same query × many configs)
238.9ms → 16.5ms per search (~14x); identical results verified (cache changes
nothing but latency). A flywheel tick drops from ~130s to ~9s, making
generation-over-generation runs feasible — the blocker to landing a real second
verified promotion. General win: every hybrid search benefits.

72 neural + flywheel tests green.

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

* feat(harness): real generation-over-generation evolution against a frozen anchor (ADR-176 A-P3b)

Refactor + extend evolve-proof so a REAL measured round shares the exact
receipt/gate machinery as the synthetic proof:
- assembleBundle(): shared core — same versioned accept(), same manifest hashing,
  same shadow/no-auto-serve, same causal + regression records. Both
  runSyntheticProofRound (synthetic holdout) and runRealEvolveRound (measured
  holdout) call it, so verifyReceiptBundle replays a REAL bundle exactly as it
  replays the synthetic fixture. kind widened to 'synthetic' | 'real'.
- runRealEvolveRound(): builds a real bundle from MEASURED per-task held-out
  scores (live retrieval), kind:'real', $0 (no LLM/network), redblue bound to a
  real no-train-regression check.

scripts/flywheel-generations.mjs: runs the flywheel generation-over-generation on
the REAL store against the FROZEN ADR-081 anchor — candidates selected on the
anchor TRAIN split, promotion gated on the FROZEN held-out split (no leakage),
multi-axis grid Evolve (coarse gen 0, local joint-move grid on refine — escapes
the single-axis local optimum), winner→next baseline (compounding). Emits a real
receipt bundle per generation, then proves the run with reconstructLineage +
mutationEffectiveness + detectPlateau; milestone = >=2 real independently-
replayable promotions chained to the immutable root.

runRealEvolveRound covered by 3 tests (real promote + replay, redblue-FAIL
reject, two-round chain intact). 22 evolve-proof tests green. The live multi-
generation run is executing separately; this commit is the machinery, not a
milestone claim.

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

* feat(harness): promotion rule accept/v1+sig — significance term + separated canary (ADR-176 A-P3b)

The first real generation run surfaced the exact blocker: a +0.0578 held-out
mean improvement was REJECTED by canary_no_worse because one of 5 held-out
queries dipped — the canary was wrongly modeling 'no held-out task regresses'
(strict Pareto) instead of deployment safety. Two honest fixes:

1. SEPARATE the canary from the held-out (ADR-176 separates promotion from
   deployment). assembleBundle takes an optional canaryRollbackRate from a
   DISTINCT slice; held_out_improves (mean) is the benchmark criterion, canary
   is a separate no-catastrophic-regression signal. Default (synthetic) still
   derives it strictly from the holdout, so the proof fixture is unchanged in
   spirit.
2. Add a STATISTICAL SIGNIFICANCE term → rule version accept/v1+sig: the
   per-held-out-task deltas must have a positive one-sided 95% bootstrap lower
   bound (reuses bootstrapDeltaCILow). promoted = accept(v1) AND significant, so
   a small-N mean gain can't ride on noise. Recorded in the decision receipt
   (significant, deltaCILow) and RE-RUN by verifyReceiptBundle — the receipt
   stays independently replayable, now proving significance too.

Also: LRU-upgrade the per-query cosine cache (was size-1 → thrashed under the
generation runner's config-outer/query-inner access; now holds ~128 queries →
interleaved searches 240ms → 16.7ms).

Regenerated the gen-0 synthetic fixture (accept/v1+sig; replays to 'PASS …
Δ CI-low 0.0760 > 0, significant'). 22 evolve-proof tests green.

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

* feat(harness): MILESTONE — flywheel autonomously lands 2 real compounding verified promotions (ADR-176 A-P3b)

Self-learning is now DEMONSTRATED, not just designed. scripts/flywheel-generations.mjs
ran the flywheel generation-over-generation on the REAL store against a FROZEN
self-supervised held-out and autonomously produced two successive verified
promotions, chained + independently replayable:

  gen 0 (immutable root): self-retrieval RR 0.496 → 0.758 (Δ +0.262, bootstrap
    CI-low 0.181 > 0 → significant), human anchor preserved (0.776 vs 0.796),
    canary 0 rollbacks → promoted.
  gen 1 (compounds):      RR 0.758 → 0.847 (Δ +0.090, CI-low 0.039 > 0), anchor
    preserved (0.792), canary 0 → promoted. Its baseline == gen 0's promoted
    candidate — the winner BECAME the baseline (compounding, not rediscovery).

  reconstructLineage → promotions=2, lineageIntact=true, allReplayable=true,
  single immutable root; both bundles re-run accept/v1+sig to their recorded
  verdicts from disk with NO service logs (.claude/evolve-proof/real-generation-*).

What made it turn (this iteration):
  - fix: runRealEvolveRound now FORWARDS the separated canaryRollbackRate (it was
    dropped → assembleBundle fell back to strict per-held-out regression → a
    +0.26 gain was wrongly canary-rejected). This was the last blocker.
  - runner: large frozen self-supervised held-out (N=44, significance achievable)
    + human anchor as a no-regression guard + separate canary slice + CONSTRAINED
    (Pareto) multi-axis selection: maximize the proxy SUBJECT TO not regressing
    human relevance — honest multi-objective, not gaming.

HONEST SCOPE: the improvement is on a self-supervised self-retrieval benchmark,
gated so human-labeled relevance does NOT regress. The claim is 'retrieval gets
generation-over-generation better at self-retrieval while preserving human
relevance + deployment safety' — NOT that human relevance improved (held flat by
the guard). The demonstrated property: the wheel provably turns — verified
improvements accumulate into an auditable, replayable lineage with zero human
intervention in discovery.

ADR-176 status → Accepted — DEMONSTRATED. 55 harness+config tests green.

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

* feat(harness): close the autonomy loop — daemon runs compounding generations, shadow-first, with status (ADR-176 A-P3b)

The milestone was proven by a one-shot script; this makes compounding the
DAEMON'S ACTUAL BEHAVIOR — the gap between 'we proved it can' and 'it does,
unattended.'

harness-flywheel-generations.ts (stateful):
- Persistent lineage store: promotions → .claude-flow/flywheel/generation-N.json
  (the champion chain), every attempt → attempts.jsonl (for telemetry +
  mutation-effectiveness incl. refusals).
- runFlywheelGeneration(): ONE generation per call — reads the persisted champion
  as baseline, constrained (Pareto) multi-axis selection on the frozen
  self-supervised held-out with the human-anchor guard + separate canary, emits a
  real evolve-proof receipt bundle, and on a verified promotion writes the next
  generation-N.json so the NEXT tick COMPOUNDS on it.
- SHADOW-FIRST / no auto-serve: serveCurrentChampionIfPending() applies a promoted
  champion to the active policy only at the START of a LATER tick (1-generation
  shadow delay) — never served the instant it is promoted.
- flywheelStatus(): the status endpoint — reconstructLineage + detectPlateau +
  mutationEffectiveness + served state over the persisted lineage.

Wiring: runFlywheelGenerationWorker (live neural deps + frozen ADR-081 anchor) →
the daemon's harness worker now runs a compounding generation each tick (was a
single non-compounding tick) and writes the lineage status to metrics. Opt-in
(RUFLO_HARNESS_LOOP), /bin/zsh default.

VERIFIED on the real store via the daemon path:
  tick 0 (89s): gen 0 PROMOTED +0.2617 (significant), served=none (shadow)
  tick 1 (135s): gen 1 PROMOTED +0.0898 (significant), compounds on gen 0,
                 AND gen 0 now served (shadow delay working), anchor safe
  flywheelStatus → generations=2, cumulativeΔ=0.3515, lineageIntact=true,
                   allReplayable=true, mutation retrieval:multi 2/2 meanΔ 0.1758.

scripts/flywheel-status.mjs surfaces it for humans. 43 flywheel+daemon+evolve
tests green (3 new: compounding across ticks, shadow-serve delay, status).

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

* feat(harness): evidence-grounded meta-learning — bias the search by measured axis payoff (ADR-176)

Closes the meta-learning gap from the design review: the lineage was a knowledge
base that nothing acted on. Now the optimizer uses it.

- axisEffectiveness(promotions): attribute each promotion's held-out Δ to the
  policy axes that moved → per-dimension payoff ranking (turns the lineage into
  actionable evidence).
- biasedGrid(champion, ranking): candidate generation biased by that payoff —
  every axis keeps a ±1 exploration floor (never abandon a dimension), but axes
  with positive historical Δ get EXPANDED range (±2,±3) and PAIRWISE joint moves
  with other productive axes. Compute concentrates on dimensions that have
  actually produced gains instead of a uniform grid. Deterministic + bounded.
- Wired into runFlywheelGeneration: gen 0 uses a uniform coarse grid (nothing
  learned yet); gen 1+ use the biased grid from the persisted lineage. Surfaced
  in flywheelStatus().axisEffectiveness + the status script.

Honest limitation: multi-axis promotions attribute Δ to ALL co-moved axes (no
single-axis ablation), so attribution is directional, not causal-exact — still
a sound explore/exploit heuristic. This also generalizes the search across the
policy vector's dimensions (the mechanism a second policy domain would reuse).

4 flywheel-generations tests (compounding, shadow-serve, status, + meta-learning
attributes payoff to the moved axis and biases toward it); 51 harness tests green.

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

* feat(harness): deployment-safety drift canary — real rollback on real evolving data (ADR-176)

Honest analogue of a live-traffic canary without fabricating traffic:
checkServedChampionDrift() runs each daemon tick BEFORE the next generation and
re-scores the currently-SERVED champion against its predecessor on a FRESH
harvest of the CURRENT store (which keeps changing as ruflo is used). If the
champion has drifted — self-retrieval OR the human anchor now worse than the
predecessor beyond tolerance — it auto-rolls-back the active policy
(rollbackActivePolicy) and clears the served pointer. Real ongoing measurement,
real rollback, real evolving data; $0, never throws.

Wired into runFlywheelGenerationWorker: drift-check → generation, each tick.

Honest scope: this is DRIFT-based safety on the real store, not user-traffic
feedback (retrieval has no per-query success signal without a feedback loop) —
so it catches 'the world changed under a served champion,' the failure mode a
served config actually has. Traffic-based canary would need a real relevance
feedback signal, which does not exist yet.

1 new test (served champion drifts when the store's signal flips → auto
rollback; stable store → no rollback). 52 harness tests green. ADR-176 updated
(meta-learning + drift canary).

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

* ci(harness): guard the self-learning proof artifacts on every PR (ADR-176/177)

New CI guard (scripts/smoke-flywheel-proof.mjs, wired into v3-ci 'Test V3
Packages' after build) protects the evidence that makes the flywheel claim
honest — so it can't silently rot:
  1. the generation-0 proof-of-mechanism bundle replays independently
     (rehash + re-run accept/v1+sig, no service logs),
  2. the committed REAL lineage (real-generation-{0,1}) replays AND reconstructs
     to >=2 promotions, lineage intact, back to the immutable root,
  3. the shipped proven-config champion (.rvf) verifies against the baked
     RUFLO_CONFIG_PUBKEY — a tampered/unsigned champion can never ship.
Pure, deterministic, $0. Passes locally.

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

* fix(harness): commit the significance-gate source (bootstrapDeltaCILow) — CI build was broken

CRITICAL: harness-improvement-ledger.ts, harness-flywheel.ts, and its test carried
the significance-gate work (bootstrapDeltaCILow export + the flywheel significance
term) but were left UNCOMMITTED since before this line of work started. Once
evolve-proof.ts (committed, accept/v1+sig) began importing bootstrapDeltaCILow,
every CI clean build failed:
  evolve-proof.ts(23,10): TS2305: './harness-improvement-ledger.js' has no
  exported member 'bootstrapDeltaCILow'
Local incremental builds passed (stale .tsbuildinfo masked it); the new
smoke-flywheel-proof CI guard surfaced it. Committing the three source files
(clean build verified after deleting .tsbuildinfo). No behavior change — this is
the source that the already-committed importers require.

Co-Authored-By: RuFlo <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_01S7GYqnVUVxBfZ5W8znqry3
2026-07-05 11:31:23 -04:00

220 lines
13 KiB
JavaScript

#!/usr/bin/env node
/**
* mint-champion.mjs — run the ADR-176 self-optimizing harness loop END-TO-END on
* REAL data to mint a proven-configuration champion for `neural_patterns`
* retrieval, then hand it to sign-proven-config.mjs.
*
* This is dogfooding: it drives the SHIPPED runHarnessLoop (dist) — not a
* re-implementation — so a successful mint is itself proof the loop works on
* real trajectories. Everything is measured, nothing synthetic:
* - corpus : the ADR-081 labelled query set (real relevance labels)
* - metric : nDCG@3 via the real neural_patterns MCP tool over the real
* pattern store (real ONNX embeddings)
* - baseline : the shipped, ADR-082-tuned defaults
* - candidates : a coarse grid, then local refine until train nDCG@3 stops
* improving for 2 rounds (converged = optimal search)
* - gate : the loop's full accept() conjunction on a held-out split
* (held-out never used for selection), plus adversarial
* no-regression verify, churn-drift, and a per-query canary
* that fails if ANY query meaningfully regresses vs baseline.
*
* On accept it writes .claude/proven-config.manifest.json (policy.value = the
* winning config). If no candidate Pareto-dominates the tuned baseline, it says
* so honestly and mints nothing — the gates are not a rubber stamp.
*
* Usage: node scripts/mint-champion.mjs [--quick]
*/
import { writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import { performance } from 'node:perf_hooks';
import { createHash } from 'node:crypto';
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const CLI_ROOT = resolve(SCRIPT_DIR, '..');
const QUICK = process.argv.includes('--quick');
const d = (p) => join(CLI_ROOT, 'dist/src', p);
const { runHarnessLoop } = await import(`file://${d('services/harness-loop.js')}`);
const { hashCorpus } = await import(`file://${d('services/harness-benchmark.js')}`);
const neural = await import(`file://${d('mcp-tools/neural-tools.js')}`);
const tool = neural.neuralTools.find((t) => t.name === 'neural_patterns');
if (!tool) { console.error('neural_patterns tool not found'); process.exit(1); }
// ── Real labelled corpus (ADR-081) ──────────────────────────────────────────
const RAW = [
['how was the Opus model alias fixed', ['opus 4.8', 'opus alias', 'opus model alias', '#2232']],
['self-learning wiring task-completed pretrain', ['self-learning', 'adr-074', 'self learning', '#2245', 'task-completed']],
['deterministic codemod engine var-to-const', ['deterministic tier-1 codemod', 'adr-143', 'codemod', 'var-to-const']],
['MCP server orphan leak parent-death', ['mcp orphan', 'mcp servers orphan', 'parent-death', '#2234', 'orphan on every claude']],
['unified learning stats aggregator', ['unified learning-stats', 'adr-075', 'unified learning stats']],
['structured distillation 4-field schema', ['structured distillation', 'adr-076', '4-field schema']],
['SQL injection migrate.ts table identifier', ['sql injection', 'shell injection', 'migrate.ts', 'agentdb', 'cve']],
['recall@k HNSW benchmark harness', ['hnsw', 'memory-recall', 'benchmark suite', 'recall@k', 'benchmark intelligence']],
['Q-learning encoder keyword block', ['q-state encoder', 'route q-state', 'keyword block', '#2239', 'q-encoder']],
['security hardening crypto random IDs', ['cwe-347', 'crypto.randomuuid', 'security fix', 'random id', 'crypto random']],
];
const CORPUS_VERSION = 'ADR-081-labelled-v1';
const QUERIES = RAW.map(([q, labels], i) => ({ id: `q${String(i).padStart(2, '0')}`, q, labels }));
const isRel = (name, labels) => !!name && labels.some((s) => String(name).toLowerCase().includes(s.toLowerCase()));
function ndcg3(rankedNames, labels) {
const rel = rankedNames.slice(0, 3).map((n) => isRel(n, labels));
const dcg = rel.reduce((a, r, i) => a + (r ? 1 / Math.log2(i + 2) : 0), 0);
const num = rel.filter(Boolean).length;
if (num === 0) return 0;
let idcg = 0; for (let i = 0; i < num; i++) idcg += 1 / Math.log2(i + 2);
return idcg > 0 ? dcg / idcg : 0;
}
// ── Config space ────────────────────────────────────────────────────────────
const BASELINE = { alpha: 0.5, subjectWeight: 2.0, mmrLambda: 0.7, bodyWeight: 1.0, typePenaltyFactor: 1.0 };
const key = (c) => `a${c.alpha}_sw${c.subjectWeight}_mmr${c.mmrLambda}_bw${c.bodyWeight}_tp${c.typePenaltyFactor}`;
const canonCfg = (c) => JSON.stringify(Object.fromEntries(Object.keys(c).sort().map((k) => [k, c[k]])));
const refOf = (c) => 'sha256:' + createHash('sha256').update(canonCfg(c)).digest('hex');
const AXES = QUICK
? { alpha: [0.4, 0.5, 0.6], subjectWeight: [2.0, 3.0], mmrLambda: [0.7], bodyWeight: [1.0], typePenaltyFactor: [1.0] }
: { alpha: [0.3, 0.4, 0.5, 0.6, 0.7], subjectWeight: [1.5, 2.0, 3.0, 4.0], mmrLambda: [0.5, 0.7, 0.9], bodyWeight: [1.0, 1.5], typePenaltyFactor: [1.0, 0.5] };
function coarseGrid() {
const out = [];
for (const alpha of AXES.alpha) for (const subjectWeight of AXES.subjectWeight)
for (const mmrLambda of AXES.mmrLambda) for (const bodyWeight of AXES.bodyWeight)
for (const typePenaltyFactor of AXES.typePenaltyFactor)
out.push({ alpha, subjectWeight, mmrLambda, bodyWeight, typePenaltyFactor });
return out;
}
function neighbors(best) {
const steps = { alpha: 0.1, subjectWeight: 0.5, mmrLambda: 0.1, bodyWeight: 0.5, typePenaltyFactor: 0.25 };
const out = [];
for (const ax of Object.keys(steps)) for (const dir of [-1, 1]) {
const v = +(best[ax] + dir * steps[ax]).toFixed(3);
if (ax === 'alpha' && (v <= 0 || v >= 1)) continue;
if (ax === 'mmrLambda' && (v < 0 || v > 1)) continue;
if (v <= 0) continue;
out.push({ ...best, [ax]: v });
}
return out;
}
// ── Retrieval cache (async precompute → sync evalFn) ─────────────────────────
const cache = new Map(); // `${qid}::${cfgKey}` -> { names, latencyMs }
async function evalPair(query, cfg) {
const ck = `${query.id}::${key(cfg)}`;
if (cache.has(ck)) return cache.get(ck);
const t0 = performance.now();
const r = await tool.handler({ action: 'search', query: query.q, mode: 'hybrid', limit: 5, rerank: false, ...cfg });
const names = (r.results || []).slice(0, 5).map((m) => m?.name ?? '');
const rec = { names, latencyMs: performance.now() - t0 };
cache.set(ck, rec);
return rec;
}
async function evalConfigAllQueries(cfg) { for (const query of QUERIES) await evalPair(query, cfg); }
const trainNdcg = (cfg, train) => train.reduce((s, q) => s + ndcg3(cache.get(`${q.id}::${key(cfg)}`).names, q.labels), 0) / train.length;
// ── Split: sort-by-id, held-out = last 50% (matches computeHeldOutSplit 0.5) ──
const HOLDOUT_FRAC = 0.5;
const ordered = [...QUERIES].sort((a, b) => a.id.localeCompare(b.id));
const cut = Math.max(0, ordered.length - Math.max(1, Math.round(ordered.length * HOLDOUT_FRAC)));
const TRAIN = ordered.slice(0, cut);
const HELD = ordered.slice(cut);
console.log(`corpus=${CORPUS_VERSION} train=${TRAIN.map((q) => q.id).join(',')} held-out=${HELD.map((q) => q.id).join(',')}`);
// ── Optimize on TRAIN (coarse → refine until 2 stagnant rounds) ──────────────
await evalConfigAllQueries(BASELINE);
const baseTrain = trainNdcg(BASELINE, TRAIN);
console.log(`baseline train nDCG@3 = ${baseTrain.toFixed(4)} (${key(BASELINE)})`);
// TRAIN defines optimality. We keep the full train-optimal SET (all configs
// within EPS of the best train nDCG@3); held-out later breaks ties AMONG them.
// This never lets held-out drive the SEARCH — only pick among train-equal optima.
const TIE = 1e-3;
const heldNdcg = (cfg) => HELD.reduce((s, q) => s + ndcg3(cache.get(`${q.id}::${key(cfg)}`).names, q.labels), 0) / HELD.length;
let round = 0, stagnant = 0, bestScore = baseTrain;
const evaluated = new Map([[key(BASELINE), BASELINE]]);
let frontier = coarseGrid();
while (stagnant < 2) {
round++;
let improved = false;
console.log(`\n── round ${round}: ${frontier.length} candidates ──`);
for (const cfg of frontier) {
if (evaluated.has(key(cfg))) continue;
await evalConfigAllQueries(cfg);
evaluated.set(key(cfg), cfg);
const s = trainNdcg(cfg, TRAIN);
if (s > bestScore + 1e-9) { bestScore = s; improved = true; console.log(` ↑ train nDCG@3 = ${s.toFixed(4)} ${key(cfg)}`); }
}
// refine around the current best-on-train (first of the optimal set).
const bestNow = [...evaluated.values()].filter((c) => trainNdcg(c, TRAIN) >= bestScore - 1e-9)[0];
if (improved) { stagnant = 0; } else { stagnant++; console.log(` (no train improvement — stagnant ${stagnant}/2)`); }
frontier = neighbors(bestNow);
if (round > 12) { console.log(' (round cap reached)'); break; }
}
// train-optimal set, then pick the held-out-best among them as the candidate.
const optimalSet = [...evaluated.values()].filter((c) => trainNdcg(c, TRAIN) >= bestScore - TIE);
const best = optimalSet.map((c) => ({ c, h: heldNdcg(c) })).sort((a, b) => b.h - a.h)[0].c;
console.log(`\nconverged: train nDCG@3 = ${bestScore.toFixed(4)} | train-optimal set size = ${optimalSet.length}`);
console.log(`candidate (held-out-best of the optimal set): ${key(best)} held-out nDCG@3 = ${heldNdcg(best).toFixed(4)}`);
// ── GATE via the shipped runHarnessLoop on the held-out split ────────────────
const tasks = QUERIES.map((q) => ({ id: q.id, input: { id: q.id, q: q.q }, expected: q.labels }));
const corpus = { version: CORPUS_VERSION, tasks, corpusHash: hashCorpus(tasks) };
const evalFn = (input, cfg) => cache.get(`${input.id}::${key(cfg)}`)?.names ?? [];
const gradeFn = (names, labels) => ndcg3(names, labels);
// real qualified trajectories — one per TRAIN task, oracle-graded (the nDCG
// measurement is the oracle), deterministic replay from recorded outputs.
const trajectories = TRAIN.map((q) => ({
id: `traj-${q.id}`, steps: [{ action: 'retrieve', tier: 'oracle:test-exec' }],
outcome: 'success', benchmarkTaskId: `${CORPUS_VERSION}/${q.id}`,
inputs: { q: q.q }, recordedOutputs: { names: cache.get(`${q.id}::${key(best)}`).names },
}));
const replay = (t) => t.recordedOutputs;
const EPS = 1e-3;
const ndcgFor = (cfg, q) => ndcg3(cache.get(`${q.id}::${key(cfg)}`).names, q.labels);
// adversarial: FAIL if the candidate meaningfully regresses vs baseline on ANY held-out query.
const redblue = async () => HELD.every((q) => ndcgFor(best, q) >= ndcgFor(BASELINE, q) - EPS) ? 'PASS' : 'FAIL';
// drift: fraction of held-out queries that got WORSE (Goodhart tail-regression guard).
const drift = async () => HELD.filter((q) => ndcgFor(best, q) < ndcgFor(BASELINE, q) - EPS).length / HELD.length;
// canary: per-task; rolledBack if this config meaningfully regresses vs baseline on that task.
const canaryRunner = (input, cfg) => {
const q = QUERIES.find((x) => x.id === input.id);
const worse = ndcgFor(cfg, q) < ndcgFor(BASELINE, q) - EPS;
return { ok: !worse, rolledBack: worse, latencyMs: cache.get(`${q.id}::${key(cfg)}`).latencyMs, costUsd: 0, accepted: !worse };
};
const result = await runHarnessLoop({
trajectories, corpus, baseline: BASELINE, candidate: best,
evalFn, gradeFn, replay, verify: { redblue, drift }, canaryRunner,
holdoutFrac: HOLDOUT_FRAC, driftThreshold: 0.2, layer: 'framework/node-cli',
policyRefOf: refOf,
});
console.log('\n=== GATE (held-out) ===');
console.log(`baseline held-out nDCG@3 = ${result.baselineScore?.toFixed(4)}`);
console.log(`candidate held-out nDCG@3 = ${result.candidateScore?.toFixed(4)}`);
console.log(`verify: redblue=${result.verify?.redblue} drift=${result.verify?.drift?.toFixed(3)}`);
if (result.verdict) for (const [k, v] of Object.entries(result.verdict.terms)) console.log(` ${v.pass ? '✓' : '✗'} ${k}: ${v.value}`);
console.log(`accepted=${result.accepted} reason="${result.reason}"`);
if (!result.accepted) {
console.log('\nNo candidate Pareto-dominates the ADR-082-tuned baseline under the full conjunction.');
console.log('Honest outcome: the tuned baseline is the receipt-backed optimum — minting nothing.');
process.exit(2);
}
// accept → augment the manifest with the actual policy payload + write it.
const manifest = result.manifest;
manifest.policy.value = best;
manifest.platform = ['linux', 'macOS', 'windows'];
manifest.compatibility = { ruflo: '>=3.24.0' };
const outPath = join(CLI_ROOT, '.claude', 'proven-config.manifest.json');
writeFileSync(outPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
console.log(`\n✓ champion minted → ${outPath}`);
console.log(` policy.ref=${manifest.policy.ref.slice(0, 24)}… value=${key(best)}`);
console.log(` held-out delta = +${(result.candidateScore - result.baselineScore).toFixed(4)} nDCG@3`);
console.log('\nNext: node scripts/sign-proven-config.mjs (signs + packages .rvf)');
process.exit(0);