fix: harden unified memory boundaries

This commit is contained in:
Affaan Mustafa
2026-07-26 04:35:46 -04:00
parent 60919b9472
commit c64875d9c6
22 changed files with 632 additions and 56 deletions
+16 -3
View File
@@ -365,7 +365,10 @@ function runTests() {
assert.ok(result.stdout.includes('Mode: manifest'));
assert.ok(result.stdout.includes('Profile: core'));
assert.ok(result.stdout.includes('Included components: (none)'));
assert.ok(result.stdout.includes('Selected modules: rules-core, agents-core, commands-core, hooks-runtime, platform-configs, workflow-quality'));
assert.ok(result.stdout.includes(
'Selected modules: rules-core, agents-core, commands-core, hooks-runtime, '
+ 'platform-configs, skill-unified-memory, workflow-quality'
));
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'ecc', 'install-state.json')));
} finally {
cleanup(homeDir);
@@ -404,7 +407,10 @@ function runTests() {
assert.strictEqual(result.code, 0, result.stderr);
assert.ok(result.stdout.includes('Mode: manifest'));
assert.ok(result.stdout.includes('Profile: minimal'));
assert.ok(result.stdout.includes('Selected modules: rules-core, agents-core, commands-core, platform-configs, workflow-quality'));
assert.ok(result.stdout.includes(
'Selected modules: rules-core, agents-core, commands-core, platform-configs, '
+ 'skill-unified-memory, workflow-quality'
));
assert.ok(!result.stdout.includes('hooks-runtime'));
assert.ok(!fs.existsSync(path.join(homeDir, '.claude', 'ecc', 'install-state.json')));
} finally {
@@ -491,7 +497,14 @@ function runTests() {
assert.strictEqual(state.request.legacyMode, false);
assert.deepStrictEqual(
state.resolution.selectedModules,
['rules-core', 'agents-core', 'commands-core', 'platform-configs', 'workflow-quality']
[
'rules-core',
'agents-core',
'commands-core',
'platform-configs',
'skill-unified-memory',
'workflow-quality'
]
);
assert.ok(state.resolution.skippedModules.includes('hooks-runtime'));
assert.ok(!state.resolution.skippedModules.includes('workflow-quality'));
+52 -1
View File
@@ -9,6 +9,11 @@ const { PassThrough } = require('stream');
const { pathToFileURL } = require('url');
const SERVER = path.join(__dirname, '..', '..', 'scripts', 'memory-mcp.mjs');
const {
MAX_RESULTS,
resolveVaultRoots,
saveMemory,
} = require('../../scripts/lib/memory-vault');
let passed = 0;
let failed = 0;
@@ -345,6 +350,51 @@ async function main() {
});
});
await test('filters harness-visible backlinks before applying the response cap', async () => {
await withClient(async (client, fixture) => {
const roots = resolveVaultRoots({
cwd: fixture.projectRoot,
env: fixture.env,
});
const saveWithId = (input, id) => saveMemory(input, {
roots,
now: () => '2026-07-26T20:00:00.000Z',
idFactory: () => id,
});
const targetId = 'mem_backlink_target';
saveWithId({
title: 'Backlink target',
body: 'Visible target body.',
targetHarnesses: ['claude'],
}, targetId);
for (let index = 0; index < MAX_RESULTS; index += 1) {
saveWithId({
title: `Hidden backlink ${index}`,
body: 'Only Hermes may see this backlink.',
targetHarnesses: ['hermes'],
links: [targetId],
}, `mem_backlink_hidden_${String(index).padStart(3, '0')}`);
}
saveWithId({
title: 'Visible backlink',
body: 'Claude must still receive this backlink.',
targetHarnesses: ['claude'],
links: [targetId],
}, 'mem_backlink_visible_zzz');
const read = parseTextResult(await client.callTool({
name: 'memory_read',
arguments: { id: targetId },
}));
assert.deepStrictEqual(
read.backlinks.map(memory => memory.id),
['mem_backlink_visible_zzz']
);
assert.strictEqual(read.backlinksTruncated, false);
});
});
await test('denies user scope unless the server explicitly grants it', async () => {
await withClient(async client => {
await assert.rejects(
@@ -429,7 +479,8 @@ async function main() {
env: fixture.env,
encoding: 'utf8',
});
assert.notStrictEqual(started.status, 0);
assert.strictEqual(started.error, undefined);
assert.strictEqual(started.status, 1);
assert.match(started.stderr, /ECC_MEMORY_HARNESS/);
assert.ok(!started.stderr.includes('\n at '));
} finally {
+77 -1
View File
@@ -8,7 +8,7 @@ const { spawnSync } = require('child_process');
const MEMORY_SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'memory.js');
const ECC_SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'ecc.js');
const { sanitizeTerminalText } = require(MEMORY_SCRIPT);
const { readBoundedStdin, sanitizeTerminalText } = require(MEMORY_SCRIPT);
let passed = 0;
let failed = 0;
@@ -103,6 +103,68 @@ test('routes stdin through ecc memory without dropping the body', () => {
}
});
test('retries transient stdin EAGAIN without busy-spinning and preserves byte bounds', () => {
const originalReadSync = fs.readSync;
let readCalls = 0;
let waitCalls = 0;
try {
fs.readSync = (_descriptor, buffer) => {
readCalls += 1;
if (readCalls <= 2) {
const error = new Error('temporarily unavailable');
error.code = 'EAGAIN';
throw error;
}
if (readCalls === 3) {
buffer.write('ready');
return 5;
}
return 0;
};
assert.strictEqual(readBoundedStdin(8, {
retryDelayMs: 1,
maxRetryWaitMs: 4,
wait: () => {
waitCalls += 1;
},
}), 'ready');
assert.strictEqual(readCalls, 4);
assert.strictEqual(waitCalls, 2);
} finally {
fs.readSync = originalReadSync;
}
});
test('bounds persistent stdin EAGAIN retries instead of waiting forever', () => {
const originalReadSync = fs.readSync;
let readCalls = 0;
let waitCalls = 0;
try {
fs.readSync = () => {
readCalls += 1;
const error = new Error('temporarily unavailable');
error.code = 'EAGAIN';
throw error;
};
assert.throws(
() => readBoundedStdin(8, {
retryDelayMs: 1,
maxRetryWaitMs: 3,
wait: () => {
waitCalls += 1;
},
}),
/standard input remained unavailable/i
);
assert.strictEqual(readCalls, 4);
assert.strictEqual(waitCalls, 3);
} finally {
fs.readSync = originalReadSync;
}
});
test('initializes selected scopes and reports their roots as JSON', () => {
const fixture = createFixture();
try {
@@ -310,6 +372,20 @@ test('rejects ambiguous body sources and does not expose a trust promotion flag'
], fixture, { input: ' \n\t' });
assert.notStrictEqual(empty.status, 0);
assert.ok(empty.stderr.includes('non-whitespace context'));
const invalidUtf8Body = path.join(fixture.root, 'invalid-utf8.md');
fs.writeFileSync(invalidUtf8Body, Buffer.from([0x61, 0xc3, 0x28, 0x62]));
const invalidUtf8 = run(MEMORY_SCRIPT, [
'save',
'--title', 'Invalid UTF-8',
'--body-file', invalidUtf8Body,
], fixture);
assert.notStrictEqual(invalidUtf8.status, 0);
assert.match(invalidUtf8.stderr, /valid UTF-8/i);
assert.strictEqual(
fs.existsSync(path.join(fixture.projectRoot, '.ecc', 'memory')),
false
);
} finally {
fs.rmSync(fixture.root, { recursive: true, force: true });
}