From afe2cdd9fcdfd83b36552db454c93da7d0e06546 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Oct 2026 06:32:58 +0000 Subject: [PATCH] dream(memory): wire ControllerRegistry.set() so MutationGuard/AttestationLog activation stops being dead code memory-bridge.ts's post-init controller-activation wiring (reasoningBank, learningSystem, skills, semanticRouter, gnnService, rvfOptimizer, mutationGuard, attestationLog, guardedVectorBackend) has always gated registration on `typeof reg.set === 'function'`, falling back to an unread `reg._controllers` property otherwise. ControllerRegistry never implemented a public set() method, so every external activation silently took the dead fallback: registry.get('mutationGuard')/get('attestationLog') always returned null, permanently disabling write-mutation validation (fail-open "no guard installed = allow") and audit logging (silent no-op) on the real store/delete/purge paths. Same bug shape as the already-fixed #3327 Finding A (reasoningBank.store never existed, so its own typeof-guard was dead too). Adds ControllerRegistry.set(name, instance) mirroring the shape initController() already produces internally. Zero changes needed at any of the 9 memory-bridge.ts call sites. 5 new discriminating tests reproduce memory-bridge.ts's exact activation pattern; stash-isolated baseline fails all 5 (TypeError: registry.set is not a function), candidate passes all 5. Full @claude-flow/memory suite: 650/651 both ways (1 pre-existing environmental failure, byte-identical, unrelated chmod/root-bypass test). tsc --noEmit: 0 errors. Evaluated, pending independent adversarial critique. --- .../memory/src/controller-registry.test.ts | 82 +++++++++++++++++++ .../memory/src/controller-registry.ts | 26 ++++++ 2 files changed, 108 insertions(+) diff --git a/v3/@claude-flow/memory/src/controller-registry.test.ts b/v3/@claude-flow/memory/src/controller-registry.test.ts index 7fe4b737c1..6e6aae79ef 100644 --- a/v3/@claude-flow/memory/src/controller-registry.test.ts +++ b/v3/@claude-flow/memory/src/controller-registry.test.ts @@ -465,6 +465,88 @@ describe('ControllerRegistry', () => { }); }); + // ----- External controller registration (set) ----- + // + // Dream Cycle 2026-10-28: `memory-bridge.ts`'s post-init wiring + // constructs several AgentDB-bundled controllers itself (mutationGuard, + // attestationLog, semanticRouter, skills, reasoningBank, learningSystem, + // gnnService, rvfOptimizer, guardedVectorBackend) and has always gated + // registration on `typeof reg.set === 'function'`, falling back to an + // unread `reg._controllers` property otherwise. `ControllerRegistry` had + // no public `set()` until this fix, so every one of those activations + // silently wrote to the dead fallback — `get('mutationGuard')` and + // `get('attestationLog')` always returned null, permanently disabling + // write-mutation validation (fail-open) and audit logging (silent no-op). + describe('external controller registration (set)', () => { + it('makes an externally-constructed instance immediately visible via get()/isEnabled()', async () => { + await registry.initialize({ backend: mockBackend }); + + const fakeGuard = { validate: () => ({ allowed: true }) }; + expect(registry.get('mutationGuard')).toBeNull(); + expect(registry.isEnabled('mutationGuard')).toBe(false); + + registry.set('mutationGuard', fakeGuard); + + expect(registry.get('mutationGuard')).toBe(fakeGuard); + expect(registry.isEnabled('mutationGuard')).toBe(true); + }); + + it('reproduces the exact memory-bridge.ts activation pattern end-to-end', async () => { + // Mirrors memory-bridge.ts's real activation guard verbatim: + // if (typeof reg.set === 'function') reg.set(name, inst); + // else reg._controllers = { ...(reg._controllers || {}), [name]: inst }; + await registry.initialize({ backend: mockBackend }); + + const reg = registry as unknown as Record; + const fakeAttestation = { record: vi.fn() }; + + if (typeof reg.set === 'function') { + (reg.set as (n: ControllerName, i: unknown) => void)('attestationLog', fakeAttestation); + } else { + (reg as any)._controllers = { ...((reg as any)._controllers || {}), attestationLog: fakeAttestation }; + } + + // Before the fix this fell through to the dead `_controllers` branch + // and get() stayed null forever — the exact production symptom. + expect(registry.get('attestationLog')).toBe(fakeAttestation); + }); + + it('lists an externally-set controller via listControllers() and getActiveCount()', async () => { + await registry.initialize({ backend: mockBackend }); + expect(registry.isEnabled('guardedVectorBackend')).toBe(false); + const activeBefore = registry.getActiveCount(); + + registry.set('guardedVectorBackend', { lookup: () => [] }); + + expect(registry.getActiveCount()).toBe(activeBefore + 1); + const entry = registry.listControllers().find((c) => c.name === 'guardedVectorBackend'); + expect(entry).toBeDefined(); + expect(entry?.enabled).toBe(true); + }); + + it('shuts down an externally-set controller cleanly on registry.shutdown()', async () => { + await registry.initialize({ backend: mockBackend }); + const destroy = vi.fn(); + registry.set('mutationGuard', { destroy }); + + await registry.shutdown(); + + expect(destroy).toHaveBeenCalledOnce(); + expect(registry.getActiveCount()).toBe(0); + }); + + it('overwrites a prior entry cleanly on re-registration (non-discriminating control)', async () => { + await registry.initialize({ backend: mockBackend }); + const first = { id: 'first' }; + const second = { id: 'second' }; + + registry.set('semanticRouter', first); + registry.set('semanticRouter', second); + + expect(registry.get('semanticRouter')).toBe(second); + }); + }); + // ----- Health Check ----- describe('health check', () => { diff --git a/v3/@claude-flow/memory/src/controller-registry.ts b/v3/@claude-flow/memory/src/controller-registry.ts index 9abd60c7af..ce9efe03b4 100644 --- a/v3/@claude-flow/memory/src/controller-registry.ts +++ b/v3/@claude-flow/memory/src/controller-registry.ts @@ -431,6 +431,32 @@ export class ControllerRegistry extends EventEmitter { return false; } + /** + * Register an already-constructed controller instance directly, outside + * the level-based `initialize()` path. + * + * Dream Cycle 2026-10-28: this method did not exist until now, even though + * `memory-bridge.ts`'s post-init wiring (reasoningBank/learningSystem/ + * skills/semanticRouter/gnnService/rvfOptimizer/mutationGuard/ + * attestationLog/guardedVectorBackend) has called `typeof reg.set === + * 'function'` and fallen through to a dead `reg._controllers` property — + * never read anywhere — on every call since that wiring was added. As a + * result `get('mutationGuard')`/`get('attestationLog')` always returned + * null, so `guardValidate()` always took its "no guard installed = allow" + * branch and `logAttestation()` was a permanent no-op. Same shape as the + * already-fixed #3327 Finding A (`reasoningBank.store` never existed, so + * its own `typeof ... === 'function'` guard was dead too). + */ + set(name: ControllerName, instance: T): void { + this.controllers.set(name, { + name, + instance, + level: -1, // externally registered — not part of level-based init + initTimeMs: 0, + enabled: true, + }); + } + /** * Aggregate health check across all controllers. */