Resolves the "four contradictory stat sources" item ADR-074 deferred to a
future round. Turns out the four sources aren't duplicates — they
authoritatively measure four different layers (globalStats =
trajectory-pipeline counters, sonaCoordinator = in-process SONA, memory-bridge
= AgentDB entries, neural-patterns = neural store rows). So the fix is to
aggregate the *view*, not the store.
- getUnifiedLearningStats() — returns all 4 sub-views with each sub-view
naming its source path, plus a consistency block that *flags* cross-store
drift (e.g. globalStats reports N patterns but neural_patterns is empty)
instead of silently disagreeing.
- hooks_intelligence_unified-stats — new MCP tool exposing it.
- getMemoryBridgeStats() + getNeuralStoreStats() — exported helpers so any
caller wanting a coherent learning-state snapshot uses the same primitives.
Verification:
- 7 cross-store consistency tests (__tests__/unified-stats-2245.test.ts)
- Benchmark §F added — observed: globalStats=10/11 tracks SONA, bridge=10 rows,
neural-patterns=10, sona.available=true, 1 consistency note (the pretrain-
vs-neural-store gap the reporter caught in #2245)
- 123/123 across unified-stats + self-learning + mcp-tools-deep
bump 3.10.14 → 3.10.15.
Co-Authored-By: RuFlo <ruv@ruv.net>
The self-learning subsystem reported success but persisted nothing queryable.
Three surface-level bugs, all in the ruflo CLI's wiring of the existing
ruvector primitives (no upstream changes needed):
1. hooks_task-completed {trainPatterns:true} was a stub — hardcoded
patternsLearned:0, ignored the trainPatterns flag entirely, noted in the
response itself that work was "delegated to the intelligence pipeline
(#1916 follow-up)." Now synthesizes a one-step trajectory from
{taskId, success, quality, content} and calls recordTrajectory() on the
real SONA + EWC++ path. Returns honest deltas and a learningPath field
('trajectory-pipeline' | 'recorded-only') so callers know what happened.
2. signalsProcessed was a dead counter — initialized in 3 places, read once,
incremented zero times. Added recordSignalProcessed() to intelligence.ts
(throttled persist) and wired it into bridgeStoreEntry. Also fixed
loadPersistedStats() to restore patternsLearned + signalsProcessed across
restarts (was only restoring trajectoriesRecorded — a separate "resets
learning history on every restart" bug surfaced by the audit).
3. hooks_pretrain stored a 1-row summary bundle in the `pretrain` namespace
but never wrote per-pattern rows to the neural store, so neural_patterns
list returned [] right after pretrain "succeeded" with N patterns
extracted. Now also calls storeNeuralPatterns() per pattern; surfaces
patternsBundled + patternsIndexed + sources.stores in the response so
callers can see exactly which stores were written.
Plus per the goal-condition "soften where possible to indicate multiple
paths":
- Every learning-adjacent handler returns explicit fields naming the path
taken and the store(s) written.
- task-completed description lists the three paths (one-step train,
multi-step trajectory, store-only).
- "[OK] Outcome recorded" theater replaced with notes that say what didn't
happen as well as what did.
Plus OWASP ASI06 (Memory/Context Poisoning) hardening — basic input
sanitization on task-completed content before it feeds SONA: strip ASCII
control chars, cap to 4 KB. Surfaced by #2241 (Dream Cycle 2026-05-30).
Tests: __tests__/self-learning-2245.test.ts — 9 tests across EASY (primitives)
/ MEDIUM (MCP surfaces) / COMPLEX (multi-step + persistence) categories.
Proof harness: scripts/benchmark-self-learning.mjs — 5 sections (A–E),
writes a run JSON. Latest run: signalsProcessed +10, trained=10/10 via
trajectory pipeline at ~18 ms/call, pretrain stored=10/listed=10,
multi-step persisted=5/sonaUpdate=5. Overall: PASSED. See
docs/learning/self-learning-2245-proof.md for copy-paste reproduction.
Closes#2245.
Co-Authored-By: RuFlo <ruv@ruv.net>