80 lines
3.8 KiB
TypeScript
80 lines
3.8 KiB
TypeScript
/**
|
|
* R5 / R2 closure — the memory-loop sign gate must be closed *end to end*.
|
|
*
|
|
* R2 (commit 5592727) wired `isSelfIncapacityAssertion()` into the autoSave
|
|
* `save()` chokepoint so DEFECT-2 poison is coerced to `temporary` importance.
|
|
* Its stated contract (orchestrator.ts:751-757, memory-sign-gate.ts:14-18) is
|
|
* that `temporary` frames are *recall-excluded* — "the recall path already
|
|
* excludes `temporary` (see getRecentFrames)".
|
|
*
|
|
* That is only true for the narrow `fetchRecentFrames` SQL path (catch-up /
|
|
* workspace-recent). The dominant prompt recall path is `recallMemory()`'s
|
|
* normal branch → `HybridSearch.search()`, which fetches frames with only
|
|
* optional *temporal* filtering and applies `importance` as a scoring weight,
|
|
* never an exclusion. So a sign-gated poison frame is still semantically
|
|
* retrievable and re-enters the prompt — the loop the unit test never proved
|
|
* closed because it only tested the classifier, not the round trip.
|
|
*
|
|
* This is the live, behavioral proof of the closed loop (TDD: RED on the
|
|
* pre-fix code, GREEN after recallMemory excludes non-authoritative
|
|
* importance).
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { MindDB } from '@waggle/core';
|
|
import { Orchestrator } from '../src/orchestrator.js';
|
|
import { MockEmbedder } from '../../hive-mind-core/tests/mind/helpers/mock-embedder.js';
|
|
|
|
describe('R2 sign-gate is closed end-to-end (temporary excluded from semantic recall)', () => {
|
|
let db: MindDB;
|
|
let orchestrator: Orchestrator;
|
|
|
|
beforeEach(() => {
|
|
db = new MindDB(':memory:');
|
|
orchestrator = new Orchestrator({ db, embedder: new MockEmbedder() });
|
|
});
|
|
|
|
afterEach(() => {
|
|
db.close();
|
|
});
|
|
|
|
it('does not surface a sign-gated (temporary) DEFECT-2 frame via recallMemory, but still recalls the authoritative decision', async () => {
|
|
// A self-incapacity assertion — the R2 save-side gate coerces this exact
|
|
// class to `temporary` (orchestrator.ts:757). Seed it at the importance the
|
|
// gate produces.
|
|
await orchestrator.executeTool('save_memory', {
|
|
content:
|
|
"run npm install -g @modelcontextprotocol/server-filesystem yourself and restart the session before I can use it",
|
|
importance: 'temporary',
|
|
});
|
|
// The authoritative truth on the same topic — the control. Recall MUST
|
|
// still surface this; the exclusion has to be specific to `temporary`.
|
|
await orchestrator.executeTool('save_memory', {
|
|
content:
|
|
'Decision: the MCP filesystem connector is installed and wired for this workspace',
|
|
importance: 'important',
|
|
});
|
|
|
|
// Precondition: seeds landed at the intended importances (fences off the
|
|
// save_memory dramatic-claim downgrade / dedup confounds — if either fired
|
|
// this fails here with a clear signal rather than passing vacuously).
|
|
const rows = db
|
|
.getDatabase()
|
|
.prepare('SELECT content, importance FROM memory_frames ORDER BY id')
|
|
.all() as Array<{ content: string; importance: string }>;
|
|
const poison = rows.find(r => r.content.includes('npm install -g @modelcontextprotocol'));
|
|
const decision = rows.find(r => r.content.includes('connector is installed and wired'));
|
|
expect(poison?.importance, 'poison frame must be stored as temporary').toBe('temporary');
|
|
expect(decision?.importance, 'decision frame must be stored as important').toBe('important');
|
|
|
|
const result = await orchestrator.recallMemory(
|
|
'how do I install the mcp filesystem connector',
|
|
);
|
|
|
|
// The closed-loop invariant: a sign-gated frame must NOT re-enter the
|
|
// prompt as authoritative recall…
|
|
expect(result.text).not.toContain('run npm install -g @modelcontextprotocol');
|
|
// …while legitimate, authoritative memory on the same query still surfaces.
|
|
expect(result.text).toContain('connector is installed and wired');
|
|
});
|
|
});
|