@remnic/core 9.3.698 → 9.3.700
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +14 -10
- package/dist/access-cli.js +28 -26
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +18 -14
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +17 -13
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +16 -12
- package/dist/access-schema.js +3 -3
- package/dist/{access-service-DftqtNUy.d.ts → access-service-Cte3ol0W.d.ts} +2 -2
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +13 -9
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/action-confidence.d.ts +1 -1
- package/dist/action-confidence.js +2 -2
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-D7YDNNF7.d.ts → catalog-DN1PzThs.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +8 -4
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-SJQ4HY3E.js → chunk-2NWHLAXX.js} +2 -2
- package/dist/{chunk-UDDSC6PO.js → chunk-33L6XHU2.js} +54 -6
- package/dist/chunk-33L6XHU2.js.map +1 -0
- package/dist/{chunk-XFG3PVZE.js → chunk-3E5WRQNQ.js} +8 -8
- package/dist/{chunk-EZWQZYLK.js → chunk-6JDGADXK.js} +2 -2
- package/dist/{chunk-IZ5E6ZTK.js → chunk-CCOXIDRM.js} +6 -6
- package/dist/{chunk-COEZR6F5.js → chunk-CHM274U6.js} +2 -2
- package/dist/{chunk-TIVZ4MCG.js → chunk-D75JXBV4.js} +3 -3
- package/dist/{chunk-M4DQWUKX.js → chunk-DR2JTSLZ.js} +2 -2
- package/dist/{chunk-XL5RSHZP.js → chunk-EOBJRBLC.js} +2 -2
- package/dist/{chunk-YY2HGKWR.js → chunk-G5PKTQ5J.js} +2 -2
- package/dist/{chunk-JYVD2XZ4.js → chunk-GA5A6MJH.js} +57 -24
- package/dist/chunk-GA5A6MJH.js.map +1 -0
- package/dist/{chunk-E6GOVHHJ.js → chunk-GYVVQYA3.js} +3 -3
- package/dist/{chunk-2KJG6ZZK.js → chunk-HDLC75NX.js} +2 -2
- package/dist/{chunk-AC5LO7IU.js → chunk-I4WBNM3M.js} +7 -1
- package/dist/chunk-I4WBNM3M.js.map +1 -0
- package/dist/{chunk-QVTSLRKT.js → chunk-IJEZMWKA.js} +3 -3
- package/dist/{chunk-ZXUOAFUG.js → chunk-IKNQAGBV.js} +1 -1
- package/dist/chunk-IKNQAGBV.js.map +1 -0
- package/dist/{chunk-N55RJT4N.js → chunk-JKW5XSWC.js} +2 -2
- package/dist/{chunk-CQ4PGFMC.js → chunk-JX3YZVII.js} +4 -4
- package/dist/{chunk-3LSSFCB4.js → chunk-K4DWSPMW.js} +11 -1
- package/dist/chunk-K4DWSPMW.js.map +1 -0
- package/dist/{chunk-HZ5DRA4Q.js → chunk-NHBEO3F3.js} +20 -20
- package/dist/{chunk-FSEQXHEZ.js → chunk-O54DY26V.js} +2 -2
- package/dist/{chunk-MRFVMSTR.js → chunk-OMNTQPZO.js} +20 -1
- package/dist/{chunk-MRFVMSTR.js.map → chunk-OMNTQPZO.js.map} +1 -1
- package/dist/chunk-PCZR32VL.js +446 -0
- package/dist/chunk-PCZR32VL.js.map +1 -0
- package/dist/{chunk-2K64VH66.js → chunk-PQG4T5V3.js} +22 -2
- package/dist/chunk-PQG4T5V3.js.map +1 -0
- package/dist/{chunk-JI6HWBYL.js → chunk-Q4O3ET6F.js} +2 -2
- package/dist/{chunk-ANZLT74L.js → chunk-RJ2THZ4H.js} +2 -2
- package/dist/{chunk-LPIWEKZ3.js → chunk-SDPDU2PM.js} +3 -3
- package/dist/{chunk-A62RAIBN.js → chunk-SEWF2O74.js} +2 -2
- package/dist/{chunk-2ULWWAQH.js → chunk-SMIVW7XC.js} +2 -2
- package/dist/{chunk-RTN2BLZM.js → chunk-T5QAZIBO.js} +2 -2
- package/dist/{chunk-RIC5U67B.js → chunk-X74FJSW7.js} +232 -4
- package/dist/chunk-X74FJSW7.js.map +1 -0
- package/dist/{chunk-SIDSEXUG.js → chunk-XY4WJTEX.js} +2 -2
- package/dist/{chunk-LIWU4QI6.js → chunk-YMTGXDN6.js} +5 -5
- package/dist/chunk-YPR7DOPD.js +286 -0
- package/dist/chunk-YPR7DOPD.js.map +1 -0
- package/dist/{chunk-MRX6S22R.js → chunk-YXIFA36P.js} +2 -2
- package/dist/{chunk-AH2JUU6X.js → chunk-Z2YFLXHU.js} +2 -2
- package/dist/{chunk-PJIHCOGV.js → chunk-ZDK2IW5F.js} +2 -2
- package/dist/{cli-DUMkkdLl.d.ts → cli--yVN9yEV.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +32 -28
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -3
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +7 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +6 -6
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{forget-PLR6J5DN.js → forget-BEXG5PQC.js} +32 -1
- package/dist/forget-BEXG5PQC.js.map +1 -0
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +21 -10
- package/dist/index.js +63 -41
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +7 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -3
- package/dist/maintenance/rebuild-memory-projection.js +8 -4
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-provenance.js +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +8 -4
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +20 -2
- package/dist/namespaces/storage.js +7 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +19 -2
- package/dist/operator-toolkit.js +17 -11
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +9 -5
- package/dist/{orchestrator-Dv8JQu3-.d.ts → orchestrator-CJI4xdqV.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +20 -18
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +4 -4
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +5 -5
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +4 -4
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +3 -3
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +6 -6
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-CgREi9E0.d.ts → semantic-consolidation-BtUfv-AL.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +8 -4
- package/dist/semantic-rule-promotion.js +7 -3
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +7 -3
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +306 -2
- package/dist/storage.js +6 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-supersession.js +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/{types-CeZYSC-E.d.ts → types-PuiPZ9iE.d.ts} +34 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/utils/serialize-mutations.js +5 -280
- package/dist/utils/serialize-mutations.js.map +1 -1
- package/dist/verified-recall.js +7 -3
- package/package.json +2 -2
- package/src/config.ts +10 -0
- package/src/lifecycle/tombstones.test.ts +355 -0
- package/src/lifecycle/tombstones.ts +807 -0
- package/src/maintenance/forget.ts +41 -0
- package/src/maintenance/pattern-reinforcement.ts +47 -0
- package/src/memory-provenance.ts +12 -0
- package/src/namespaces/storage.ts +33 -0
- package/src/operator-toolkit.ts +77 -0
- package/src/orchestrator.ts +16 -0
- package/src/review/index.ts +48 -3
- package/src/storage.ts +329 -2
- package/src/temporal-supersession.ts +38 -0
- package/src/types.ts +34 -0
- package/dist/chunk-2K64VH66.js.map +0 -1
- package/dist/chunk-3LSSFCB4.js.map +0 -1
- package/dist/chunk-AC5LO7IU.js.map +0 -1
- package/dist/chunk-JYVD2XZ4.js.map +0 -1
- package/dist/chunk-RIC5U67B.js.map +0 -1
- package/dist/chunk-UDDSC6PO.js.map +0 -1
- package/dist/chunk-ZXUOAFUG.js.map +0 -1
- package/dist/forget-PLR6J5DN.js.map +0 -1
- /package/dist/{chunk-SJQ4HY3E.js.map → chunk-2NWHLAXX.js.map} +0 -0
- /package/dist/{chunk-XFG3PVZE.js.map → chunk-3E5WRQNQ.js.map} +0 -0
- /package/dist/{chunk-EZWQZYLK.js.map → chunk-6JDGADXK.js.map} +0 -0
- /package/dist/{chunk-IZ5E6ZTK.js.map → chunk-CCOXIDRM.js.map} +0 -0
- /package/dist/{chunk-COEZR6F5.js.map → chunk-CHM274U6.js.map} +0 -0
- /package/dist/{chunk-TIVZ4MCG.js.map → chunk-D75JXBV4.js.map} +0 -0
- /package/dist/{chunk-M4DQWUKX.js.map → chunk-DR2JTSLZ.js.map} +0 -0
- /package/dist/{chunk-XL5RSHZP.js.map → chunk-EOBJRBLC.js.map} +0 -0
- /package/dist/{chunk-YY2HGKWR.js.map → chunk-G5PKTQ5J.js.map} +0 -0
- /package/dist/{chunk-E6GOVHHJ.js.map → chunk-GYVVQYA3.js.map} +0 -0
- /package/dist/{chunk-2KJG6ZZK.js.map → chunk-HDLC75NX.js.map} +0 -0
- /package/dist/{chunk-QVTSLRKT.js.map → chunk-IJEZMWKA.js.map} +0 -0
- /package/dist/{chunk-N55RJT4N.js.map → chunk-JKW5XSWC.js.map} +0 -0
- /package/dist/{chunk-CQ4PGFMC.js.map → chunk-JX3YZVII.js.map} +0 -0
- /package/dist/{chunk-HZ5DRA4Q.js.map → chunk-NHBEO3F3.js.map} +0 -0
- /package/dist/{chunk-FSEQXHEZ.js.map → chunk-O54DY26V.js.map} +0 -0
- /package/dist/{chunk-JI6HWBYL.js.map → chunk-Q4O3ET6F.js.map} +0 -0
- /package/dist/{chunk-ANZLT74L.js.map → chunk-RJ2THZ4H.js.map} +0 -0
- /package/dist/{chunk-LPIWEKZ3.js.map → chunk-SDPDU2PM.js.map} +0 -0
- /package/dist/{chunk-A62RAIBN.js.map → chunk-SEWF2O74.js.map} +0 -0
- /package/dist/{chunk-2ULWWAQH.js.map → chunk-SMIVW7XC.js.map} +0 -0
- /package/dist/{chunk-RTN2BLZM.js.map → chunk-T5QAZIBO.js.map} +0 -0
- /package/dist/{chunk-SIDSEXUG.js.map → chunk-XY4WJTEX.js.map} +0 -0
- /package/dist/{chunk-LIWU4QI6.js.map → chunk-YMTGXDN6.js.map} +0 -0
- /package/dist/{chunk-MRX6S22R.js.map → chunk-YXIFA36P.js.map} +0 -0
- /package/dist/{chunk-AH2JUU6X.js.map → chunk-Z2YFLXHU.js.map} +0 -0
- /package/dist/{chunk-PJIHCOGV.js.map → chunk-ZDK2IW5F.js.map} +0 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdtemp, readFile, writeFile, rm, mkdir, appendFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import {
|
|
8
|
+
TombstoneStore,
|
|
9
|
+
parseTombstoneLine,
|
|
10
|
+
type TombstoneFileIo,
|
|
11
|
+
} from "./tombstones.js";
|
|
12
|
+
|
|
13
|
+
// Mirror the dedup normalizer EXACTLY — these are the wired injections.
|
|
14
|
+
function normalizeContent(content: string): string {
|
|
15
|
+
return content
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9\s]/g, " ")
|
|
18
|
+
.replace(/\s+/g, " ")
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
21
|
+
function computeHash(content: string): string {
|
|
22
|
+
return createHash("sha256").update(normalizeContent(content)).digest("hex");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeIo(): TombstoneFileIo {
|
|
26
|
+
return {
|
|
27
|
+
read: (p) => readFile(p, "utf8"),
|
|
28
|
+
append: async (p, c) => {
|
|
29
|
+
await mkdir(path.dirname(p), { recursive: true });
|
|
30
|
+
await appendFile(p, c, "utf8");
|
|
31
|
+
},
|
|
32
|
+
write: async (p, c) => {
|
|
33
|
+
await mkdir(path.dirname(p), { recursive: true });
|
|
34
|
+
await writeFile(p, c, "utf8");
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function makeStore(
|
|
40
|
+
namespace: string,
|
|
41
|
+
opts: { enabled?: boolean; semanticMatch?: boolean } = {},
|
|
42
|
+
): Promise<{ store: TombstoneStore; dir: string }> {
|
|
43
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-"));
|
|
44
|
+
const filePath = path.join(dir, "state", "tombstones.jsonl");
|
|
45
|
+
const store = new TombstoneStore(
|
|
46
|
+
filePath,
|
|
47
|
+
namespace,
|
|
48
|
+
{
|
|
49
|
+
enabled: opts.enabled ?? true,
|
|
50
|
+
semanticMatch: opts.semanticMatch ?? false,
|
|
51
|
+
semanticThreshold: 0.9,
|
|
52
|
+
hashContent: computeHash,
|
|
53
|
+
normalizeText: normalizeContent,
|
|
54
|
+
},
|
|
55
|
+
makeIo(),
|
|
56
|
+
);
|
|
57
|
+
return { store, dir };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe("TombstoneStore — append/lookup round-trip", () => {
|
|
61
|
+
it("blocks an exact contentHash match", async () => {
|
|
62
|
+
const env = await makeStore("default");
|
|
63
|
+
const id = await env.store.appendTombstone({
|
|
64
|
+
reason: "correction",
|
|
65
|
+
createdBy: "user_correction",
|
|
66
|
+
sourceMemoryId: "fact-1",
|
|
67
|
+
rawContent: "The database is MySQL.",
|
|
68
|
+
});
|
|
69
|
+
assert.match(id, /^tomb-/);
|
|
70
|
+
await env.store.load();
|
|
71
|
+
const match = env.store.lookup({
|
|
72
|
+
namespace: "default",
|
|
73
|
+
contentHash: computeHash("The database is MySQL."),
|
|
74
|
+
normalizedText: normalizeContent("The database is MySQL."),
|
|
75
|
+
});
|
|
76
|
+
assert.notEqual(match, null);
|
|
77
|
+
assert.equal(match!.matchedTier, "exact");
|
|
78
|
+
assert.equal(match!.tombstoneId, id);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("blocks a normalized-text match ignoring punctuation/case", async () => {
|
|
82
|
+
const env = await makeStore("default");
|
|
83
|
+
await env.store.appendTombstone({
|
|
84
|
+
reason: "supersession",
|
|
85
|
+
createdBy: "supersession",
|
|
86
|
+
sourceMemoryId: "fact-2",
|
|
87
|
+
rawContent: "We use PostgreSQL for the main DB.",
|
|
88
|
+
});
|
|
89
|
+
await env.store.load();
|
|
90
|
+
const match = env.store.lookup({
|
|
91
|
+
namespace: "default",
|
|
92
|
+
normalizedText: normalizeContent("we use postgresql for the main db!!!"),
|
|
93
|
+
});
|
|
94
|
+
assert.notEqual(match, null);
|
|
95
|
+
assert.equal(match!.matchedTier, "normalized");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("blocks a keyed (entityRef + supersessionKey) match", async () => {
|
|
99
|
+
const env = await makeStore("default");
|
|
100
|
+
await env.store.appendTombstone({
|
|
101
|
+
reason: "supersession",
|
|
102
|
+
createdBy: "supersession",
|
|
103
|
+
sourceMemoryId: "fact-3",
|
|
104
|
+
rawContent: "Acme's HQ is in London.",
|
|
105
|
+
entityRef: "entity-acme",
|
|
106
|
+
supersessionKey: "entity-acme::hq_city",
|
|
107
|
+
});
|
|
108
|
+
await env.store.load();
|
|
109
|
+
const match = env.store.lookup({
|
|
110
|
+
namespace: "default",
|
|
111
|
+
entityRef: "entity-acme",
|
|
112
|
+
supersessionKey: "entity-acme::hq_city",
|
|
113
|
+
});
|
|
114
|
+
assert.notEqual(match, null);
|
|
115
|
+
assert.equal(match!.matchedTier, "keyed");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("returns null when nothing matches", async () => {
|
|
119
|
+
const env = await makeStore("default");
|
|
120
|
+
await env.store.appendTombstone({
|
|
121
|
+
reason: "correction",
|
|
122
|
+
createdBy: "user_correction",
|
|
123
|
+
sourceMemoryId: "fact-1",
|
|
124
|
+
rawContent: "The database is MySQL.",
|
|
125
|
+
});
|
|
126
|
+
await env.store.load();
|
|
127
|
+
const match = env.store.lookup({
|
|
128
|
+
namespace: "default",
|
|
129
|
+
contentHash: computeHash("something completely different"),
|
|
130
|
+
});
|
|
131
|
+
assert.equal(match, null);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("TombstoneStore — revocation supersedes tombstone", () => {
|
|
136
|
+
it("a revocation entry re-allows the fact (newest wins)", async () => {
|
|
137
|
+
const env = await makeStore("default");
|
|
138
|
+
const tombId = await env.store.appendTombstone({
|
|
139
|
+
reason: "correction",
|
|
140
|
+
createdBy: "user_correction",
|
|
141
|
+
sourceMemoryId: "fact-x",
|
|
142
|
+
rawContent: "The API runs on port 3000.",
|
|
143
|
+
});
|
|
144
|
+
await env.store.load();
|
|
145
|
+
assert.notEqual(
|
|
146
|
+
env.store.lookup({ namespace: "default", contentHash: computeHash("The API runs on port 3000.") }),
|
|
147
|
+
null,
|
|
148
|
+
);
|
|
149
|
+
await env.store.revoke(tombId, "user_correction");
|
|
150
|
+
assert.equal(
|
|
151
|
+
env.store.lookup({ namespace: "default", contentHash: computeHash("The API runs on port 3000.") }),
|
|
152
|
+
null,
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("revocation round-trips through disk reload", async () => {
|
|
157
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-rt-"));
|
|
158
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
159
|
+
const io = makeIo();
|
|
160
|
+
const opts = { enabled: true, semanticMatch: false, semanticThreshold: 0.9, hashContent: computeHash, normalizeText: normalizeContent };
|
|
161
|
+
const store = new TombstoneStore(filePath, "default", opts, io);
|
|
162
|
+
const tombId = await store.appendTombstone({
|
|
163
|
+
reason: "correction",
|
|
164
|
+
createdBy: "user_correction",
|
|
165
|
+
sourceMemoryId: "fact-y",
|
|
166
|
+
rawContent: "Server is down.",
|
|
167
|
+
});
|
|
168
|
+
await store.revoke(tombId, "user_correction");
|
|
169
|
+
// New store instance over the same file → must reproduce the revoked state.
|
|
170
|
+
const store2 = new TombstoneStore(filePath, "default", opts, io);
|
|
171
|
+
await store2.load();
|
|
172
|
+
assert.equal(
|
|
173
|
+
store2.lookup({ namespace: "default", contentHash: computeHash("Server is down.") }),
|
|
174
|
+
null,
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("TombstoneStore — rebuild equivalence", () => {
|
|
180
|
+
it("rebuild from a fixture corpus reproduces identical lookup decisions", async () => {
|
|
181
|
+
const env = await makeStore("default");
|
|
182
|
+
const retired = [
|
|
183
|
+
{
|
|
184
|
+
memoryId: "fact-a",
|
|
185
|
+
rawContent: "The cache uses Redis.",
|
|
186
|
+
reason: "correction" as const,
|
|
187
|
+
createdBy: "user_correction" as const,
|
|
188
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
memoryId: "fact-b",
|
|
192
|
+
rawContent: "Deployments happen on Fridays.",
|
|
193
|
+
entityRef: "entity-deploy",
|
|
194
|
+
supersessionKey: "entity-deploy::day",
|
|
195
|
+
reason: "supersession" as const,
|
|
196
|
+
createdBy: "supersession" as const,
|
|
197
|
+
createdAt: "2026-01-02T00:00:00.000Z",
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
await env.store.rebuild(retired);
|
|
201
|
+
const d1 = env.store.lookup({ namespace: "default", contentHash: computeHash("The cache uses Redis.") });
|
|
202
|
+
const d2 = env.store.lookup({ namespace: "default", entityRef: "entity-deploy", supersessionKey: "entity-deploy::day" });
|
|
203
|
+
assert.equal(d1?.matchedTier, "exact");
|
|
204
|
+
assert.equal(d2?.matchedTier, "keyed");
|
|
205
|
+
|
|
206
|
+
// Delete the JSONL, rebuild from the same corpus via a fresh store → identical decisions.
|
|
207
|
+
await rm(path.join(env.dir, "state", "tombstones.jsonl"), { force: true });
|
|
208
|
+
const env2 = await makeStore("default");
|
|
209
|
+
await env2.store.rebuild(retired);
|
|
210
|
+
const e1 = env2.store.lookup({ namespace: "default", contentHash: computeHash("The cache uses Redis.") });
|
|
211
|
+
const e2 = env2.store.lookup({ namespace: "default", entityRef: "entity-deploy", supersessionKey: "entity-deploy::day" });
|
|
212
|
+
assert.equal(e1?.matchedTier, d1?.matchedTier);
|
|
213
|
+
assert.equal(e2?.matchedTier, d2?.matchedTier);
|
|
214
|
+
assert.equal(e1?.reason, d1?.reason);
|
|
215
|
+
assert.equal(e2?.reason, d2?.reason);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("rebuild produces byte-stable output for a fixed corpus (modulo id)", async () => {
|
|
219
|
+
const env = await makeStore("default");
|
|
220
|
+
const retired = [
|
|
221
|
+
{ memoryId: "fact-a", rawContent: "X.", reason: "correction" as const, createdBy: "user_correction" as const, createdAt: "2026-01-01T00:00:00.000Z" },
|
|
222
|
+
{ memoryId: "fact-b", rawContent: "Y.", reason: "supersession" as const, createdBy: "supersession" as const, createdAt: "2026-01-02T00:00:00.000Z" },
|
|
223
|
+
];
|
|
224
|
+
await env.store.rebuild(retired);
|
|
225
|
+
const first = await readFile(path.join(env.dir, "state", "tombstones.jsonl"), "utf8");
|
|
226
|
+
await env.store.rebuild(retired);
|
|
227
|
+
const second = await readFile(path.join(env.dir, "state", "tombstones.jsonl"), "utf8");
|
|
228
|
+
const parseLines = (raw: string) =>
|
|
229
|
+
raw.split("\n").filter((l) => l.trim()).map((l) => {
|
|
230
|
+
const { id, ...rest } = JSON.parse(l);
|
|
231
|
+
return rest;
|
|
232
|
+
});
|
|
233
|
+
assert.deepEqual(parseLines(second), parseLines(first));
|
|
234
|
+
assert.equal(
|
|
235
|
+
second.split("\n").filter((l) => l.trim()).length,
|
|
236
|
+
first.split("\n").filter((l) => l.trim()).length,
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("TombstoneStore — corrupted line skipped with counter, not crash", () => {
|
|
242
|
+
it("skips malformed lines and counts them", async () => {
|
|
243
|
+
const dir = await mkdtemp(path.join(tmpdir(), "tomb-corrupt-"));
|
|
244
|
+
const filePath = path.join(dir, "tombstones.jsonl");
|
|
245
|
+
const good = {
|
|
246
|
+
id: "tomb-good-1",
|
|
247
|
+
kind: "tombstone",
|
|
248
|
+
reason: "correction",
|
|
249
|
+
sourceMemoryId: "fact-1",
|
|
250
|
+
contentHash: computeHash("good content"),
|
|
251
|
+
normalizedText: normalizeContent("good content"),
|
|
252
|
+
namespace: "default",
|
|
253
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
254
|
+
createdBy: "user_correction",
|
|
255
|
+
};
|
|
256
|
+
const raw = [
|
|
257
|
+
JSON.stringify(good),
|
|
258
|
+
"this is not json {{{",
|
|
259
|
+
"",
|
|
260
|
+
JSON.stringify({ ...good, id: "tomb-good-2", reason: "INVALID_REASON" }),
|
|
261
|
+
JSON.stringify({ ...good, id: "tomb-good-2", kind: "tombstone", reason: "supersession" }),
|
|
262
|
+
].join("\n");
|
|
263
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
264
|
+
await writeFile(filePath, raw, "utf8");
|
|
265
|
+
const store = new TombstoneStore(
|
|
266
|
+
filePath,
|
|
267
|
+
"default",
|
|
268
|
+
{ enabled: true, semanticMatch: false, semanticThreshold: 0.9, hashContent: computeHash, normalizeText: normalizeContent },
|
|
269
|
+
makeIo(),
|
|
270
|
+
);
|
|
271
|
+
await store.load();
|
|
272
|
+
const stats = store.stats();
|
|
273
|
+
assert.equal(stats.count, 2);
|
|
274
|
+
assert.equal(stats.corruptedLines, 2);
|
|
275
|
+
assert.notEqual(
|
|
276
|
+
store.lookup({ namespace: "default", contentHash: computeHash("good content") }),
|
|
277
|
+
null,
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe("TombstoneStore — concurrent appends serialize", () => {
|
|
283
|
+
it("parallel appends all land on disk without interleaving", async () => {
|
|
284
|
+
const env = await makeStore("default");
|
|
285
|
+
const contents = Array.from({ length: 20 }, (_, i) => `fact content ${i}`);
|
|
286
|
+
await Promise.all(
|
|
287
|
+
contents.map((c) =>
|
|
288
|
+
env.store.appendTombstone({
|
|
289
|
+
reason: "correction",
|
|
290
|
+
createdBy: "user_correction",
|
|
291
|
+
sourceMemoryId: `fact-${c}`,
|
|
292
|
+
rawContent: c,
|
|
293
|
+
}),
|
|
294
|
+
),
|
|
295
|
+
);
|
|
296
|
+
await env.store.load();
|
|
297
|
+
const stats = env.store.stats();
|
|
298
|
+
assert.equal(stats.count, 20);
|
|
299
|
+
const raw = await readFile(path.join(env.dir, "state", "tombstones.jsonl"), "utf8");
|
|
300
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
301
|
+
assert.equal(lines.length, 20);
|
|
302
|
+
for (const line of lines) {
|
|
303
|
+
assert.doesNotThrow(() => JSON.parse(line));
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("TombstoneStore — namespace isolation", () => {
|
|
309
|
+
it("a tombstone in namespace A never blocks namespace B", async () => {
|
|
310
|
+
const a = await makeStore("ns-a");
|
|
311
|
+
const b = await makeStore("ns-b");
|
|
312
|
+
await a.store.appendTombstone({
|
|
313
|
+
reason: "correction",
|
|
314
|
+
createdBy: "user_correction",
|
|
315
|
+
sourceMemoryId: "fact-ns-a",
|
|
316
|
+
rawContent: "Shared text content.",
|
|
317
|
+
});
|
|
318
|
+
await a.store.load();
|
|
319
|
+
await b.store.load();
|
|
320
|
+
assert.notEqual(
|
|
321
|
+
a.store.lookup({ namespace: "ns-a", contentHash: computeHash("Shared text content.") }),
|
|
322
|
+
null,
|
|
323
|
+
);
|
|
324
|
+
assert.equal(
|
|
325
|
+
b.store.lookup({ namespace: "ns-b", contentHash: computeHash("Shared text content.") }),
|
|
326
|
+
null,
|
|
327
|
+
);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe("TombstoneStore — enabled gate (rule 30)", () => {
|
|
332
|
+
it("returns null when disabled (rollback safety)", async () => {
|
|
333
|
+
const env = await makeStore("default", { enabled: false });
|
|
334
|
+
await env.store.appendTombstone({
|
|
335
|
+
reason: "correction",
|
|
336
|
+
createdBy: "user_correction",
|
|
337
|
+
sourceMemoryId: "fact-1",
|
|
338
|
+
rawContent: "Disabled gate content.",
|
|
339
|
+
});
|
|
340
|
+
await env.store.load();
|
|
341
|
+
assert.equal(
|
|
342
|
+
env.store.lookup({ namespace: "default", contentHash: computeHash("Disabled gate content.") }),
|
|
343
|
+
null,
|
|
344
|
+
);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe("parseTombstoneLine", () => {
|
|
349
|
+
it("rejects malformed input", () => {
|
|
350
|
+
assert.equal(parseTombstoneLine(""), null);
|
|
351
|
+
assert.equal(parseTombstoneLine("not json"), null);
|
|
352
|
+
assert.equal(parseTombstoneLine(JSON.stringify({ id: "x" })), null);
|
|
353
|
+
assert.equal(parseTombstoneLine(JSON.stringify({ kind: "tombstone" })), null);
|
|
354
|
+
});
|
|
355
|
+
});
|