mirror of
https://github.com/ruvnet/ruflo.git
synced 2026-11-09 05:35:21 +09:00
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<T>(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.
This commit is contained in:
2 files changed
+108
No files matched your search
@@ -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<string, unknown>;
|
||||
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', () => {
|
||||
|
||||
@@ -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<T>(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.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user