Files
ruvnet__ruflo/v3/@claude-flow/cli/scripts/evolve-proof-round.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

97 lines
6.0 KiB
JavaScript

#!/usr/bin/env node
/**
* single-round proof-of-mechanism (ADR-176) — NOT flywheel/compounding/production proof.
*
* Runs ONE deterministic synthetic evolve round and:
* 1. emits the seven required artifacts (aborts if any is missing),
* 2. persists the receipt bundle as generation 0 (fixture for F-P1/F-P2),
* 3. registers the passing candidate in a SHADOW registry (state=shadow, served=false),
* 4. proves NO auto-serve path (asserts nothing was written to the active/served policy),
* 5. independently re-verifies the bundle (rehash + re-run accept()) WITHOUT service logs.
*
* Usage: node scripts/evolve-proof-round.mjs [--dir <projectRoot>]
*/
import { mkdirSync, writeFileSync, existsSync, appendFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
const CLI_ROOT = resolve(SCRIPT_DIR, '..');
const d = (p) => join(CLI_ROOT, 'dist/src', p);
const { runSyntheticProofRound, verifyReceiptBundle, reconstructLineage } = await import(`file://${d('services/evolve-proof.js')}`);
const argDir = process.argv.indexOf('--dir');
const projectRoot = argDir > -1 ? resolve(process.argv[argDir + 1]) : CLI_ROOT;
// Fixed timestamp → reproducible fixture (Generation 0).
const NOW = 1_700_000_000_000;
const bundle = runSyntheticProofRound({ now: NOW, generation: 0, parent: null });
// (1) GATE: emit only if all seven artifacts are present.
const required = {
'input holdout hash': bundle.inputHoldoutHash,
'baseline manifest hash': bundle.baselineManifestHash,
'candidate manifest hash': bundle.candidateManifestHash,
'meetsPromotionRule version': bundle.meetsPromotionRule?.version,
'decision receipt': bundle.decisionReceipt,
'SHADOW registration id': bundle.shadow?.registrationId,
'cost receipt': bundle.costReceipt,
};
const missing = Object.entries(required).filter(([, v]) => v === undefined || v === null).map(([k]) => k);
if (missing.length) { console.error(`ABORT — missing required artifacts: ${missing.join(', ')}`); process.exit(1); }
// (2) PERSIST the receipt bundle as generation 0.
const proofDir = join(projectRoot, '.claude', 'evolve-proof');
mkdirSync(proofDir, { recursive: true });
const bundlePath = join(proofDir, 'generation-0.json');
writeFileSync(bundlePath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
// (3) SHADOW registration — persisted to a shadow registry, explicitly not served.
const shadowDir = join(projectRoot, '.claude-flow', 'shadow');
mkdirSync(shadowDir, { recursive: true });
appendFileSync(join(shadowDir, 'registrations.jsonl'), JSON.stringify({ ...bundle.shadow, generation: 0, bundle: 'generation-0.json' }) + '\n', 'utf-8');
// (4) NO AUTO-SERVE: the active/served policy must be untouched by this round.
const activePolicy = join(projectRoot, '.claude-flow', 'harness-active-policy.json');
const autoServed = existsSync(activePolicy);
// (5) INDEPENDENT VERIFY (no service logs) + lineage reconstruction.
const verify = verifyReceiptBundle(bundle);
const lineage = reconstructLineage([bundle]);
console.log('single-round proof-of-mechanism (NOT flywheel/compounding/production proof)');
console.log('='.repeat(78));
console.log(`label : ${bundle.label}`);
console.log(`disclaimers : ${bundle.disclaimers.join(' | ')}`);
console.log('--- seven required artifacts ---');
console.log(`input holdout hash : ${bundle.inputHoldoutHash}`);
console.log(`baseline manifest hash : ${bundle.baselineManifestHash}`);
console.log(`candidate manifest hash : ${bundle.candidateManifestHash}`);
console.log(`meetsPromotionRule version : ${bundle.meetsPromotionRule.version} (result=${bundle.meetsPromotionRule.result})`);
console.log(`decision receipt : promoted=${bundle.decisionReceipt.promoted} reason="${bundle.decisionReceipt.reason}"`);
console.log(`SHADOW registration id : ${bundle.shadow.registrationId} (state=${bundle.shadow.state}, served=${bundle.shadow.served})`);
console.log(`cost receipt : $${bundle.costReceipt.usd} llmCalls=${bundle.costReceipt.llmCalls} tier=${bundle.costReceipt.tier}`);
console.log('--- causality (why, not just what) ---');
console.log(`mutation class : ${bundle.mutationClass}`);
console.log(`mutation summary : ${bundle.mutationSummary}`);
console.log(`deltas : benchmark=${bundle.deltas.benchmark.toFixed(4)} security=${bundle.deltas.security} cost=${bundle.deltas.cost}`);
console.log('--- proofs ---');
console.log(`gate wiring (accept/v1) : decision decided by the real versioned accept() ✓`);
console.log(`receipt persistence : ${bundlePath}`);
console.log(`SHADOW registration : ${join(shadowDir, 'registrations.jsonl')}`);
console.log(`no auto-serve path : active policy present? ${autoServed} → ${autoServed ? '✗ AUTO-SERVED' : '✓ NOT served'}`);
console.log('--- independent replay (no service logs) ---');
console.log(`hash checks : holdout=${verify.hashChecks.inputHoldout} baseline=${verify.hashChecks.baselineManifest} candidate=${verify.hashChecks.candidateManifest}`);
console.log(`recomputed decision matches: ${verify.decisionMatches}`);
console.log(`why : ${verify.explanation}`);
console.log(`bundle valid (independent) : ${verify.valid}`);
console.log('--- lineage (acceptance test) — immutable root of the evolution graph ---');
console.log(`generations=${lineage.generations} promotions=${lineage.promotions} rejections=${lineage.rejections} branches=[${lineage.branches.join(',')}] lineageIntact=${lineage.lineageIntact} allReplayable=${lineage.allReplayable}`);
console.log(`rootHash=${lineage.rootHash}`);
const ok = verify.valid && !autoServed && lineage.lineageIntact;
console.log('='.repeat(78));
console.log(ok ? 'RESULT: proof-of-mechanism PASSED (gate wired, receipt persisted, shadow registered, no auto-serve, independently replayable)'
: 'RESULT: proof-of-mechanism FAILED');
process.exit(ok ? 0 : 1);