fix(mnemopi): made recall fact ids resolvable via memory reads

recall (includeFacts) surfaces facts.fact_id as a result id, but
store.get only searched working_memory + episodic_memory, so every
surfaced fact id was a dead end for 'read memory://<id>' and
memory_edit ('not found in any scoped bank').

- store.get now falls back to the facts table (visibility mirrors
  factRecall: same-session or scope='global'), returning a read-only
  row with memory_store 'fact' and the full triple as content.
- coding-agent labels the store honestly ('fact') in memory:// reads
  and reports not_editable (instead of not_found) for memory_edit ops
  on fact ids; the facts table stays immutable.

Fixes #4725
This commit is contained in:
can1357
2026-07-09 18:25:00 +02:00
parent 6e209d3ecc
commit ca68daa81c
6 changed files with 177 additions and 7 deletions
@@ -238,6 +238,51 @@ describe("MemoryProtocolHandler — mnemopi bridge (issue #4443)", () => {
});
});
it("resolves memory://<fact-id> to a read-only fact row (issue #4725)", async () => {
await withMnemopiSession(async ({ state }) => {
const beam = state.memory.beam;
beam.db
.prepare(
"INSERT INTO facts (fact_id, session_id, subject, predicate, object, timestamp, confidence) VALUES (?, ?, ?, ?, ?, ?, ?)",
)
.run("0473bbdb8da6df92", beam.sessionId, "Glab", "works-without", "mise prefix", "2026-07-01T00:00:00.000Z", 0.9);
const router = InternalUrlRouter.instance();
const resource = await router.resolve("memory://0473bbdb8da6df92");
expect(resource.content).toContain("id: 0473bbdb8da6df92");
expect(resource.content).toContain("store: fact");
expect(resource.content).toContain("Glab works-without mise prefix");
});
});
it("reports not_editable (not not_found) for memory_edit ops on a fact id (issue #4725)", async () => {
await withMnemopiSession(async ({ state }) => {
const beam = state.memory.beam;
beam.db
.prepare(
"INSERT INTO facts (fact_id, session_id, subject, predicate, object, timestamp, confidence) VALUES (?, ?, ?, ?, ?, ?, ?)",
)
.run("fact-readonly", beam.sessionId, "service", "uses", "postgres", "2026-07-01T00:00:00.000Z", 0.9);
expect(state.editScopedMemory("update", "fact-readonly", { content: "x" })).toMatchObject({
status: "not_editable",
store: "fact",
});
expect(state.editScopedMemory("forget", "fact-readonly")).toMatchObject({
status: "not_editable",
store: "fact",
});
expect(state.editScopedMemory("invalidate", "fact-readonly")).toMatchObject({
status: "not_editable",
store: "fact",
});
// The fact row itself is untouched by the rejected edits.
expect(beam.db.prepare("SELECT fact_id FROM facts WHERE fact_id = ?").get("fact-readonly")).not.toBeNull();
});
});
it("routes memory://root to the file-backed summary even when mnemopi is active", async () => {
await withMnemopiSession(async () => {
const router = InternalUrlRouter.instance();