@remnic/core 10.0.83 → 10.0.84

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.
@@ -0,0 +1,90 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawn } from "node:child_process";
3
+ import { mkdtemp, rm } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import test from "node:test";
8
+
9
+ import { PROJECTION_REBUILD_RUNNER_FLAG } from "./rebuild-memory-projection-runner.js";
10
+
11
+ const RUNNER = fileURLToPath(new URL("./rebuild-memory-projection-runner.ts", import.meta.url));
12
+
13
+ type RunnerMessage =
14
+ | { ok: true; currentRows: number; timelineRows: number; entityMentionRows: number }
15
+ | { ok: false; error: string };
16
+
17
+ async function runRunner(args: string[]): Promise<{
18
+ code: number | null;
19
+ messages: RunnerMessage[];
20
+ stderr: string;
21
+ }> {
22
+ const child = spawn(
23
+ process.execPath,
24
+ ["--import", "tsx", RUNNER, PROJECTION_REBUILD_RUNNER_FLAG, ...args],
25
+ {
26
+ stdio: ["ignore", "ignore", "pipe", "ipc"],
27
+ env: { ...process.env },
28
+ },
29
+ );
30
+ const messages: RunnerMessage[] = [];
31
+ let stderr = "";
32
+ child.stderr?.setEncoding("utf8");
33
+ child.stderr?.on("data", (chunk: string) => {
34
+ stderr += chunk;
35
+ });
36
+ child.on("message", (message) => {
37
+ // SAFETY: the runner posts only RunnerMessage values over IPC; the assertions below check each field.
38
+ messages.push(message as RunnerMessage);
39
+ });
40
+ const { promise: exitPromise, resolve, reject } = Promise.withResolvers<number | null>();
41
+ child.once("error", reject);
42
+ child.once("exit", (exitCode) => resolve(exitCode));
43
+ const code = await exitPromise;
44
+ return { code, messages, stderr };
45
+ }
46
+
47
+ test("PROJECTION_REBUILD_RUNNER_FLAG is the documented argv marker", () => {
48
+ assert.equal(PROJECTION_REBUILD_RUNNER_FLAG, "--remnic-projection-rebuild-runner");
49
+ });
50
+
51
+ test("runner rejects missing argv with an IPC failure message", async () => {
52
+ const result = await runRunner([]);
53
+ assert.equal(result.code, 1);
54
+ assert.equal(result.messages.length, 1);
55
+ assert.equal(result.messages[0]!.ok, false);
56
+ if (result.messages[0]!.ok === false) {
57
+ assert.match(result.messages[0]!.error, /requires memoryDir, defaultNamespace, and now/);
58
+ }
59
+ });
60
+
61
+ test("runner rejects a non-timestamp now value", async () => {
62
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-runner-bad-now-"));
63
+ try {
64
+ const result = await runRunner([root, "default", "not-a-date"]);
65
+ assert.equal(result.code, 1);
66
+ assert.equal(result.messages[0]!.ok, false);
67
+ if (result.messages[0]!.ok === false) {
68
+ assert.match(result.messages[0]!.error, /not a timestamp/);
69
+ }
70
+ } finally {
71
+ await rm(root, { recursive: true, force: true });
72
+ }
73
+ });
74
+
75
+ test("runner rebuilds an empty memory dir and reports zero rows", async () => {
76
+ const root = await mkdtemp(path.join(os.tmpdir(), "remnic-proj-runner-ok-"));
77
+ try {
78
+ const result = await runRunner([root, "default", "2026-06-01T00:00:00.000Z"]);
79
+ assert.equal(result.code, 0, result.stderr || JSON.stringify(result.messages));
80
+ assert.equal(result.messages.length, 1);
81
+ assert.deepEqual(result.messages[0], {
82
+ ok: true,
83
+ currentRows: 0,
84
+ timelineRows: 0,
85
+ entityMentionRows: 0,
86
+ });
87
+ } finally {
88
+ await rm(root, { recursive: true, force: true });
89
+ }
90
+ });
@@ -0,0 +1,52 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import { fullCorpusHeapPressure } from "./full-corpus-heap-guard.js";
5
+
6
+ test("fullCorpusHeapPressure postpones only when usage is strictly above the limit", () => {
7
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 61, heap_size_limit: 100 }, 0.6), {
8
+ postpone: true,
9
+ ratio: 0.61,
10
+ });
11
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 60, heap_size_limit: 100 }, 0.6), {
12
+ postpone: false,
13
+ ratio: 0.6,
14
+ });
15
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 59, heap_size_limit: 100 }, 0.6), {
16
+ postpone: false,
17
+ ratio: 0.59,
18
+ });
19
+ });
20
+
21
+ test("fullCorpusHeapPressure never fires at limit ratio 1", () => {
22
+ const pressure = fullCorpusHeapPressure({ used_heap_size: 100, heap_size_limit: 100 }, 1);
23
+ assert.equal(pressure.postpone, false);
24
+ assert.equal(pressure.ratio, 1);
25
+ });
26
+
27
+ test("fullCorpusHeapPressure ignores unusable statistics or limits", () => {
28
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 90, heap_size_limit: 0 }, 0.6), {
29
+ postpone: false,
30
+ ratio: 0,
31
+ });
32
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 90, heap_size_limit: -1 }, 0.6), {
33
+ postpone: false,
34
+ ratio: 0,
35
+ });
36
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: Number.NaN, heap_size_limit: 100 }, 0.6), {
37
+ postpone: false,
38
+ ratio: 0,
39
+ });
40
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: -1, heap_size_limit: 100 }, 0.6), {
41
+ postpone: false,
42
+ ratio: 0,
43
+ });
44
+ assert.deepEqual(fullCorpusHeapPressure({ used_heap_size: 90, heap_size_limit: 100 }, Number.NaN), {
45
+ postpone: false,
46
+ ratio: 0,
47
+ });
48
+ assert.deepEqual(
49
+ fullCorpusHeapPressure({ used_heap_size: Number.POSITIVE_INFINITY, heap_size_limit: 100 }, 0.6),
50
+ { postpone: false, ratio: 0 },
51
+ );
52
+ });
@@ -0,0 +1,79 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT,
6
+ parseProjectionRebuildHeapUsageLimit,
7
+ parseProjectionRebuildScheduleConfig,
8
+ } from "./projection-rebuild-config.js";
9
+
10
+ const parseIntegerAtLeast: Parameters<typeof parseProjectionRebuildScheduleConfig>[1] = (
11
+ value,
12
+ fallback,
13
+ min,
14
+ keyName,
15
+ ) => {
16
+ if (value === undefined || value === null) return fallback;
17
+ const n = Number(value);
18
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < min) {
19
+ throw new Error(`${keyName} must be an integer >= ${min}; got ${JSON.stringify(value)}`);
20
+ }
21
+ return n;
22
+ };
23
+
24
+ test("parseProjectionRebuildHeapUsageLimit defaults and accepts (0, 1]", () => {
25
+ assert.equal(parseProjectionRebuildHeapUsageLimit(undefined), DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT);
26
+ assert.equal(parseProjectionRebuildHeapUsageLimit(null), DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT);
27
+ assert.equal(DEFAULT_PROJECTION_REBUILD_HEAP_USAGE_LIMIT, 0.6);
28
+ assert.equal(parseProjectionRebuildHeapUsageLimit(0.5), 0.5);
29
+ assert.equal(parseProjectionRebuildHeapUsageLimit("0.75"), 0.75);
30
+ assert.equal(parseProjectionRebuildHeapUsageLimit(1), 1);
31
+ });
32
+
33
+ test("parseProjectionRebuildHeapUsageLimit rejects 0, negatives, and non-numbers", () => {
34
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(0), /projectionRebuildHeapUsageLimit/);
35
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(-0.1), /projectionRebuildHeapUsageLimit/);
36
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(1.01), /projectionRebuildHeapUsageLimit/);
37
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit(Number.NaN), /projectionRebuildHeapUsageLimit/);
38
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit("abc"), /projectionRebuildHeapUsageLimit/);
39
+ assert.throws(() => parseProjectionRebuildHeapUsageLimit({}), /projectionRebuildHeapUsageLimit/);
40
+ });
41
+
42
+ test("parseProjectionRebuildScheduleConfig defaults and coerces booleans", () => {
43
+ const defaults = parseProjectionRebuildScheduleConfig({}, parseIntegerAtLeast);
44
+ assert.equal(defaults.projectionRebuildEnabled, true);
45
+ assert.equal(defaults.projectionRebuildIntervalMs, 6 * 60 * 60 * 1000);
46
+ assert.equal(defaults.projectionRebuildHeapUsageLimit, 0.6);
47
+
48
+ const disabled = parseProjectionRebuildScheduleConfig(
49
+ { projectionRebuildEnabled: "false", projectionRebuildIntervalMs: 120_000, projectionRebuildHeapUsageLimit: 0.4 },
50
+ parseIntegerAtLeast,
51
+ );
52
+ assert.equal(disabled.projectionRebuildEnabled, false);
53
+ assert.equal(disabled.projectionRebuildIntervalMs, 120_000);
54
+ assert.equal(disabled.projectionRebuildHeapUsageLimit, 0.4);
55
+ });
56
+
57
+ test("parseProjectionRebuildScheduleConfig floors the interval via the integer parser", () => {
58
+ assert.throws(
59
+ () =>
60
+ parseProjectionRebuildScheduleConfig(
61
+ { projectionRebuildIntervalMs: 59_999 },
62
+ parseIntegerAtLeast,
63
+ ),
64
+ /projectionRebuildIntervalMs/,
65
+ );
66
+ assert.throws(
67
+ () =>
68
+ parseProjectionRebuildScheduleConfig(
69
+ { projectionRebuildIntervalMs: 90_000.5 },
70
+ parseIntegerAtLeast,
71
+ ),
72
+ /projectionRebuildIntervalMs/,
73
+ );
74
+ const ok = parseProjectionRebuildScheduleConfig(
75
+ { projectionRebuildIntervalMs: 60_000 },
76
+ parseIntegerAtLeast,
77
+ );
78
+ assert.equal(ok.projectionRebuildIntervalMs, 60_000);
79
+ });
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+
7
+ import { StorageManager } from "../storage.js";
8
+
9
+ /**
10
+ * COS-388c Finding C2: ordinary entity reads must not follow a symlink at
11
+ * `entities/<id>.md` to content outside the memory dir. List skips the
12
+ * same entry. A normal entity file still reads.
13
+ */
14
+ test("readEntity refuses entities/evil.md symlink outside the memory dir (C2)", async () => {
15
+ const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-entity-c2-"));
16
+ const outside = await mkdtemp(path.join(os.tmpdir(), "remnic-entity-c2-out-"));
17
+ try {
18
+ const storage = new StorageManager(dir);
19
+ await storage.ensureDirectories({ waitForEntityMigration: false });
20
+ const id = await storage.writeEntity("Alice", "person", ["Normal entity inside the store."]);
21
+
22
+ await writeFile(path.join(outside, "secret.md"), "OUTSIDE_SECRET_CONTENT\n");
23
+ await symlink(path.join(outside, "secret.md"), path.join(dir, "entities", "evil.md"));
24
+
25
+ const evil = await storage.readEntity("evil");
26
+ assert.equal(
27
+ evil.includes("OUTSIDE_SECRET_CONTENT"),
28
+ false,
29
+ "entity read must not return the outside symlink target",
30
+ );
31
+ assert.equal(evil, "", "symlinked entity id is treated as absent, not as escaped content");
32
+
33
+ const alice = await storage.readEntity(id);
34
+ assert.match(alice, /Normal entity inside the store/);
35
+
36
+ const names = await storage.listEntityNames();
37
+ assert.equal(names.includes("evil"), false, "list must skip the symlinked entity entry");
38
+ assert.ok(names.includes(id), "normal entity remains listable");
39
+ } finally {
40
+ StorageManager.clearAllStaticCaches();
41
+ await rm(dir, { recursive: true, force: true });
42
+ await rm(outside, { recursive: true, force: true });
43
+ }
44
+ });
@@ -0,0 +1,69 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ isMalformedEntityRecord,
6
+ isPopulatedUnnamedEntityRecord,
7
+ isShapedUnnamedEntityRecord as isEntityLikeUnnamedRecord,
8
+ isUnnamedEntityRecord,
9
+ } from "./malformed-entity-record.js";
10
+
11
+ const NAMED = "# Alice\n\n**Type:** person\n\nNotes about Alice.\n";
12
+ const NAMED_NO_TYPE = "# Alice\n\nNotes about Alice.\n";
13
+ const TYPE_ONLY = "**Type:** person\n\nNo heading name.\n";
14
+ const TAB_HEADING = "#\tAlice\n\n**Type:** person\n";
15
+ const BLANK_HEADING = "# \n\n**Type:** person\n";
16
+ const PROSE_ONLY = "just some content without structure\n";
17
+ const EMPTY_BODY = "---\nid: x\n---\n\n \n";
18
+ const FRONTMATTER_NAMED = "---\nid: alice\n---\n\n# Alice\n\n**Type:** person\n";
19
+
20
+ test("isUnnamedEntityRecord requires a hash-space heading with a name", () => {
21
+ assert.equal(isUnnamedEntityRecord(NAMED), false);
22
+ assert.equal(isUnnamedEntityRecord(NAMED_NO_TYPE), false);
23
+ assert.equal(isUnnamedEntityRecord(FRONTMATTER_NAMED), false);
24
+ assert.equal(isUnnamedEntityRecord(TYPE_ONLY), true);
25
+ assert.equal(isUnnamedEntityRecord(TAB_HEADING), true, "tab after # is not a heading");
26
+ assert.equal(isUnnamedEntityRecord(BLANK_HEADING), true, "blank # heading is unnamed");
27
+ assert.equal(isUnnamedEntityRecord(PROSE_ONLY), true);
28
+ assert.equal(isUnnamedEntityRecord(""), true);
29
+ });
30
+
31
+ test("isPopulatedUnnamedEntityRecord needs body text without a name", () => {
32
+ assert.equal(isPopulatedUnnamedEntityRecord(NAMED), false);
33
+ assert.equal(isPopulatedUnnamedEntityRecord(TYPE_ONLY), true);
34
+ assert.equal(isPopulatedUnnamedEntityRecord(TAB_HEADING), true);
35
+ assert.equal(isPopulatedUnnamedEntityRecord(PROSE_ONLY), true);
36
+ assert.equal(isPopulatedUnnamedEntityRecord(EMPTY_BODY), false, "whitespace-only body stays readable");
37
+ assert.equal(isPopulatedUnnamedEntityRecord(""), false);
38
+ });
39
+
40
+ test("isShapedUnnamedEntityRecord keeps entity-like unnamed files only", () => {
41
+ assert.equal(isEntityLikeUnnamedRecord(NAMED), false);
42
+ assert.equal(isEntityLikeUnnamedRecord(TYPE_ONLY), true);
43
+ assert.equal(isEntityLikeUnnamedRecord(TAB_HEADING), true, "any # line shapes the record");
44
+ assert.equal(isEntityLikeUnnamedRecord(BLANK_HEADING), true);
45
+ assert.equal(isEntityLikeUnnamedRecord(PROSE_ONLY), false, "unstructured placeholders stay for filename recall");
46
+ assert.equal(isEntityLikeUnnamedRecord(EMPTY_BODY), false);
47
+ });
48
+
49
+ test("isMalformedEntityRecord requires both name and explicit type for migration", () => {
50
+ assert.equal(isMalformedEntityRecord(NAMED), false);
51
+ assert.equal(isMalformedEntityRecord(FRONTMATTER_NAMED), false);
52
+ assert.equal(isMalformedEntityRecord(NAMED_NO_TYPE), true, "missing type is malformed for migration");
53
+ assert.equal(isMalformedEntityRecord(TYPE_ONLY), true);
54
+ assert.equal(isMalformedEntityRecord(TAB_HEADING), true);
55
+ assert.equal(isMalformedEntityRecord(PROSE_ONLY), true);
56
+ assert.equal(isMalformedEntityRecord("# Bob\n\n**Type:** \n"), true, "blank type value is missing");
57
+ assert.equal(isMalformedEntityRecord("# Bob\n\n**Type:** org\r\n"), false, "CRLF type lines count");
58
+ });
59
+
60
+ test("entity body ignores closed frontmatter and keeps unclosed frontmatter as body", () => {
61
+ const unclosed = "---\nid: stuck\n# Not a real heading yet\n";
62
+ // Unclosed frontmatter is not stripped, so the # line is not at body start of a closed block —
63
+ // but it still appears in the full content scan as a named heading.
64
+ assert.equal(isUnnamedEntityRecord(unclosed), false);
65
+ assert.equal(isMalformedEntityRecord(unclosed), true, "no type line");
66
+
67
+ const namedAfterFrontmatter = "---\na: 1\n---\r\n# Carol\r\n\r\n**Type:** place\r\n";
68
+ assert.equal(isMalformedEntityRecord(namedAfterFrontmatter), false);
69
+ });
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/entity-mention-index-cache.ts"],"sourcesContent":["import path from \"node:path\";\nimport { mkdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { raceAbort } from \"./abort-error.js\";\nimport { entityMentionEpoch } from \"./entity-mention-epoch.js\";\nimport { log } from \"./logger.js\";\n\n/**\n * In-memory entity mention index.\n *\n * Scope (namespaces, dirs, secure-store key, alias map, native revision) is\n * the cache key. Mention epoch and entity-file mutation are identity inside\n * the slot, not part of the key:\n *\n * - metadata-only corpus bumps do not move the epoch, so the next recall hits\n * - entity-file changes rebuild from entity files before the recall returns,\n * reusing memory snippets (no fact scan). A new entity is visible on that\n * recall; its memory snippets arrive with the next full reconcile. A new\n * canonical id schedules that reconcile. Callers share one scan. One\n * caller's abort stops that caller waiting and leaves the scan running\n * for the others\n * - memory create/edit/delete moves the epoch. The recall serves the last\n * index immediately and one background reconcile runs, plus at most one\n * follow-up if that scan overlaps a later write. A recall during either\n * scan does not grant another follow-up. Further rescans wait for the\n * next recall. After writes quiesce and `settleEntityMentionIndex`\n * finishes, the index matches a full rebuild\n * - secure-store key, alias map, and native revision stay in the key, so a\n * change is a miss and awaits a full rebuild. A locked store never reads\n * another key's plaintext. Evicting that older scope also stops its scan\n * from publishing the persisted index\n * - a persisted index is readable per storage root. A process-wide cache\n * clear blocks every root until that root's own post-clear build publishes.\n * One root publishing does not reopen another root's file\n */\n\nconst MAX_SLOTS = 32;\nconst SETTLE_ROUNDS = 8;\n\nexport type EntityMentionIdentity = {\n mentionEpoch: string;\n entityMutation: string;\n};\n\ntype ScopedStorage = {\n dir: string;\n hotCacheKeyId?(): string;\n entityAliases?: Readonly<Record<string, string>> | null;\n getEntityMutationVersion?(): number;\n};\n\ntype Slot = {\n scopeKey: string;\n index: unknown;\n identity: EntityMentionIdentity;\n token: number;\n rebuild: Promise<void> | null;\n entityJob: Promise<void> | null;\n again: boolean;\n followUps: number;\n currentIdentity: () => EntityMentionIdentity;\n buildFull: (abortSignal?: AbortSignal) => Promise<unknown>;\n};\n\nconst slots = new Map<string, Slot>();\nconst rebuildsByScope = new Map<string, number>();\nconst GENERATION_STRIDE = 1_000_000_000;\nlet clearEpoch = 0;\nconst localGenerationByDir = new Map<string, number>();\nconst publishedGenerationByDir = new Map<string, number>();\nconst persistTailByDir = new Map<string, Promise<void>>();\n\nfunction resolveMentionDir(dir: string): string {\n return path.resolve(dir);\n}\n\nexport function entityMentionCacheGeneration(dir: string): number {\n const resolved = resolveMentionDir(dir);\n return clearEpoch * GENERATION_STRIDE + (localGenerationByDir.get(resolved) ?? 0);\n}\n\nexport function entityMentionPersistedIndexReadable(dir: string): boolean {\n const resolved = resolveMentionDir(dir);\n const local = localGenerationByDir.get(resolved) ?? 0;\n if (clearEpoch === 0 && local === 0) return true;\n return publishedGenerationByDir.get(resolved) === entityMentionCacheGeneration(resolved);\n}\n\nfunction dirsInScope(scopeKey: string): string[] {\n const nsEnd = scopeKey.indexOf(\"\\u001e\");\n const storageEnd = nsEnd === -1 ? -1 : scopeKey.indexOf(\"\\u001e\", nsEnd + 1);\n if (nsEnd === -1 || storageEnd === -1) return [];\n return scopeKey\n .slice(nsEnd + 1, storageEnd)\n .split(\"\\u001f\")\n .map((part) => part.split(\"\\u001d\")[0] ?? \"\")\n .filter((dir) => dir.length > 0)\n .map((dir) => resolveMentionDir(dir));\n}\n\nfunction persistedMentionIndexFiles(scopeKey: string): string[] {\n return dirsInScope(scopeKey).map((dir) => path.join(dir, \"state\", \"entity-mention-index.json\"));\n}\n\nfunction bumpScopeDirs(scopeKey: string): void {\n for (const dir of dirsInScope(scopeKey)) {\n localGenerationByDir.set(dir, (localGenerationByDir.get(dir) ?? 0) + 1);\n }\n}\n\nfunction captureDirGenerations(scopeKey: string): Map<string, number> {\n const captured = new Map<string, number>();\n for (const dir of dirsInScope(scopeKey)) captured.set(dir, entityMentionCacheGeneration(dir));\n return captured;\n}\n\nfunction generationsCurrent(captured: Map<string, number>): boolean {\n for (const [dir, generation] of captured) {\n if (entityMentionCacheGeneration(dir) !== generation) return false;\n }\n return true;\n}\n\nfunction publishDirGenerations(captured: Map<string, number>): void {\n for (const [dir, generation] of captured) {\n if (entityMentionCacheGeneration(dir) === generation) publishedGenerationByDir.set(dir, generation);\n }\n}\n\nexport async function writePersistedEntityMentionIndex(\n dir: string,\n generation: number,\n content: string,\n): Promise<void> {\n const resolved = resolveMentionDir(dir);\n const previous = persistTailByDir.get(resolved) ?? Promise.resolve();\n const run = previous.catch(() => undefined).then(async () => {\n if (entityMentionCacheGeneration(resolved) !== generation) return;\n const file = path.join(resolved, \"state\", \"entity-mention-index.json\");\n await mkdir(path.dirname(file), { recursive: true });\n const current = await readFile(file, \"utf-8\").catch(() => \"\");\n if (current === content) return;\n if (entityMentionCacheGeneration(resolved) !== generation) return;\n await writeFile(file, content, \"utf-8\");\n });\n persistTailByDir.set(resolved, run.then(() => undefined, () => undefined));\n await run;\n}\n\nfunction discardPersistedMentionIndex(scopeKey: string): void {\n for (const file of persistedMentionIndexFiles(scopeKey)) {\n void rm(file, { force: true }).catch(() => undefined);\n }\n}\n\nfunction throwIfMentionAborted(signal?: AbortSignal): void {\n if (!signal?.aborted) return;\n const reason = signal.reason;\n throw reason instanceof Error ? reason : new Error(\"entity mention rebuild aborted\");\n}\n\nfunction compareStrings(left: string, right: string): number {\n if (left < right) return -1;\n if (left > right) return 1;\n return 0;\n}\n\nfunction deduped(values: readonly string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const value of values) {\n const trimmed = value.trim();\n if (trimmed.length === 0 || seen.has(trimmed)) continue;\n seen.add(trimmed);\n out.push(trimmed);\n }\n return out;\n}\n\nfunction sameIdentity(left: EntityMentionIdentity, right: EntityMentionIdentity): boolean {\n return left.mentionEpoch === right.mentionEpoch && left.entityMutation === right.entityMutation;\n}\n\nfunction noteRebuild(scopeKey: string): void {\n rebuildsByScope.set(scopeKey, (rebuildsByScope.get(scopeKey) ?? 0) + 1);\n}\n\nfunction scopeFamily(scopeKey: string): string {\n const nsEnd = scopeKey.indexOf(\"\\u001e\");\n const storageEnd = nsEnd === -1 ? -1 : scopeKey.indexOf(\"\\u001e\", nsEnd + 1);\n const namespaceKey = nsEnd === -1 ? scopeKey : scopeKey.slice(0, nsEnd);\n const storageKey = nsEnd === -1 || storageEnd === -1 ? \"\" : scopeKey.slice(nsEnd + 1, storageEnd);\n const dirs = storageKey\n .split(\"\\u001f\")\n .map((part) => part.split(\"\\u001d\")[0] ?? \"\")\n .join(\"\\u001f\");\n return `${namespaceKey}\\u001e${dirs}`;\n}\n\nfunction forgetScope(scopeKey: string, bumpPersisted = false): void {\n const slot = slots.get(scopeKey);\n if (slot) {\n slot.token += 1;\n slot.again = false;\n }\n if (bumpPersisted) bumpScopeDirs(scopeKey);\n slots.delete(scopeKey);\n rebuildsByScope.delete(scopeKey);\n}\n\nfunction evictOldest(scopeKey: string): void {\n if (slots.size < MAX_SLOTS || slots.has(scopeKey)) return;\n const oldest = slots.keys().next().value;\n if (typeof oldest === \"string\") forgetScope(oldest);\n}\n\nfunction evictSameFamily(scopeKey: string): void {\n const family = scopeFamily(scopeKey);\n for (const key of [...slots.keys()]) {\n if (key !== scopeKey && scopeFamily(key) === family) forgetScope(key, true);\n }\n}\n\nfunction wantsSnippetReconcile(index: unknown): boolean {\n return (\n !!index &&\n typeof index === \"object\" &&\n (index as { pendingSnippetReconcile?: boolean }).pendingSnippetReconcile === true\n );\n}\n\nfunction armRebuild(slot: Slot): void {\n // A recall that arrives while a scan is already running must not grant\n // another follow-up. The chain stops after the one already armed.\n if (!slot.rebuild) slot.followUps = 1;\n startRebuild(slot);\n}\n\nfunction startRebuild(slot: Slot): void {\n // A recall that arrives during a scan is not a write. The follow-up is\n // armed only when the scan's end identity differs from the identity it\n // started with. Setting `again` here made every concurrent recall pay a\n // second full corpus scan after a stable rebuild.\n if (slot.rebuild) return;\n const token = slot.token;\n const captured = captureDirGenerations(slot.scopeKey);\n noteRebuild(slot.scopeKey);\n const started = slot.currentIdentity();\n slot.again = false;\n const handle: { pending: Promise<void> | null } = { pending: null };\n handle.pending = slot\n .buildFull(undefined)\n .then((index) => {\n if (slot.token !== token || !generationsCurrent(captured)) return;\n publishDirGenerations(captured);\n const ended = slot.currentIdentity();\n slot.index = index;\n if (sameIdentity(started, ended)) {\n slot.identity = ended;\n return;\n }\n // Scan overlapped later writes. Keep the start identity so the next\n // recall stays stale and exactly one follow-up reconcile runs.\n slot.identity = started;\n slot.again = true;\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`entity mention index rebuild failed: ${message}`);\n })\n .finally(() => {\n if (slot.rebuild === handle.pending) slot.rebuild = null;\n // One automatic follow-up per caller-started rebuild. A store that keeps\n // changing during every scan waits for the next recall (or settle) instead\n // of chaining full scans with nobody waiting.\n if (!slot.again || slot.followUps <= 0) return;\n slot.again = false;\n slot.followUps -= 1;\n startRebuild(slot);\n });\n slot.rebuild = handle.pending;\n}\n\nexport function entityMentionScopeKey(\n namespaces: readonly string[] | undefined,\n storages: readonly ScopedStorage[],\n nativeRevision: string,\n): string {\n const namespaceKey = deduped(namespaces ?? []).join(\"\\u001f\");\n const storageKey = storages\n .map((storage) => {\n const aliases = Object.entries(storage.entityAliases ?? {}).sort(([left], [right]) =>\n compareStrings(left, right),\n );\n return `${path.resolve(storage.dir)}\\u001d${storage.hotCacheKeyId?.() ?? \"\"}\\u001d${JSON.stringify(aliases)}`;\n })\n .join(\"\\u001f\");\n return `${namespaceKey}\\u001e${storageKey}\\u001e${nativeRevision}`;\n}\n\nexport function entityMentionIdentity(storages: readonly ScopedStorage[]): EntityMentionIdentity {\n return {\n mentionEpoch: storages.map((storage) => String(entityMentionEpoch.current(storage.dir))).join(\"\\u001f\"),\n entityMutation: storages.map((storage) => String(storage.getEntityMutationVersion?.() ?? 0)).join(\"\\u001f\"),\n };\n}\n\nexport function entityMentionFullRebuildsStarted(scopeKey: string): number {\n return rebuildsByScope.get(scopeKey) ?? 0;\n}\n\nexport function entityMentionIndexScopeKeys(): string[] {\n return [...slots.keys()];\n}\n\nexport function dropEntityMentionIndexCache(scopeKey?: string): void {\n if (scopeKey === undefined) {\n clearEpoch += 1;\n for (const key of [...slots.keys()]) {\n discardPersistedMentionIndex(key);\n forgetScope(key);\n }\n return;\n }\n discardPersistedMentionIndex(scopeKey);\n forgetScope(scopeKey, true);\n}\n\nexport async function settleEntityMentionIndex(scopeKey?: string): Promise<void> {\n const matches = (slot: Slot) => scopeKey === undefined || slot.scopeKey === scopeKey;\n for (let round = 0; round < SETTLE_ROUNDS; round += 1) {\n for (const slot of slots.values()) {\n if (!matches(slot)) continue;\n if (!slot.rebuild && slot.again) {\n slot.again = false;\n startRebuild(slot);\n }\n }\n const pending = [...slots.values()].filter((slot) => matches(slot) && slot.rebuild).map((slot) => slot.rebuild!);\n if (pending.length === 0) return;\n await Promise.all(pending);\n }\n}\n\nexport async function resolveEntityMentionIndex<T>(options: {\n scopeKey: string;\n currentIdentity: () => EntityMentionIdentity;\n buildFull: (abortSignal?: AbortSignal) => Promise<T>;\n rebuildEntities: (previous: T, abortSignal?: AbortSignal) => Promise<T>;\n abortSignal?: AbortSignal;\n}): Promise<T> {\n const live = options.currentIdentity();\n const existing = slots.get(options.scopeKey);\n if (!existing) {\n evictSameFamily(options.scopeKey);\n evictOldest(options.scopeKey);\n noteRebuild(options.scopeKey);\n const captured = captureDirGenerations(options.scopeKey);\n const started = options.currentIdentity();\n const index = await options.buildFull(options.abortSignal);\n // A clear or family eviction during this first scan must not publish it.\n if (!generationsCurrent(captured)) return index;\n publishDirGenerations(captured);\n const ended = options.currentIdentity();\n evictSameFamily(options.scopeKey);\n evictOldest(options.scopeKey);\n const slot: Slot = {\n scopeKey: options.scopeKey,\n index,\n identity: sameIdentity(started, ended) ? ended : started,\n token: 0,\n rebuild: null,\n entityJob: null,\n again: false,\n followUps: 0,\n currentIdentity: options.currentIdentity,\n buildFull: options.buildFull,\n };\n slots.set(options.scopeKey, slot);\n if (!sameIdentity(started, ended)) armRebuild(slot);\n return index;\n }\n existing.currentIdentity = options.currentIdentity;\n existing.buildFull = options.buildFull;\n while (existing.identity.entityMutation !== options.currentIdentity().entityMutation) {\n throwIfMentionAborted(options.abortSignal);\n const pending = existing.entityJob;\n if (pending) {\n await raceAbort(pending, options.abortSignal, \"entity mention rebuild aborted\");\n continue;\n }\n const liveNow = options.currentIdentity();\n const epochBefore = existing.identity.mentionEpoch;\n const mutationBefore = liveNow.entityMutation;\n let release = (): void => {};\n const gate = new Promise<void>((resolve) => {\n release = resolve;\n });\n existing.entityJob = gate;\n existing.token += 1;\n existing.again = false;\n const token = existing.token;\n const captured = captureDirGenerations(existing.scopeKey);\n // Shared scan. This caller's abort stops its wait and leaves the scan\n // running. Publish when the scan settles, even if this caller has left.\n // A family eviction bumps the token and the root generation; this scan\n // must not install its result or start a follow-up that would republish.\n const scan = options.rebuildEntities(existing.index as T, undefined);\n void scan\n .then(\n (rebuilt) => {\n if (existing.token !== token || !generationsCurrent(captured)) return;\n const now = options.currentIdentity();\n existing.index = rebuilt;\n const epochStable = epochBefore === liveNow.mentionEpoch && epochBefore === now.mentionEpoch;\n const mutationStable = mutationBefore === now.entityMutation;\n existing.identity = {\n entityMutation: mutationStable ? now.entityMutation : mutationBefore,\n mentionEpoch: epochStable ? now.mentionEpoch : epochBefore,\n };\n if (existing.identity.mentionEpoch !== now.mentionEpoch || wantsSnippetReconcile(rebuilt)) {\n armRebuild(existing);\n }\n },\n () => {},\n )\n .finally(() => {\n if (existing.entityJob === gate) existing.entityJob = null;\n release();\n });\n const rebuilt = await raceAbort(scan, options.abortSignal, \"entity mention rebuild aborted\");\n throwIfMentionAborted(options.abortSignal);\n return rebuilt;\n }\n if (existing.identity.mentionEpoch !== live.mentionEpoch) {\n armRebuild(existing);\n return existing.index as T;\n }\n return existing.index as T;\n}\n\nentityMentionEpoch.registerReset(() => {\n dropEntityMentionIndexCache();\n});\n"],"mappings":"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","names":["rebuilt"]}