@remnic/core 9.35.2 → 9.35.3
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-admin-ops-surface.d.ts +5 -5
- package/dist/access-admin-ops-surface.js +19 -18
- package/dist/access-authorization-probe.d.ts +5 -5
- package/dist/access-authorization-probe.js +20 -19
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +19 -18
- package/dist/access-cli.js +39 -38
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +5 -5
- package/dist/access-extraction-force-flush.js +19 -18
- package/dist/access-http-lcm-compaction.d.ts +5 -5
- package/dist/access-http-lifecycle-flush.d.ts +5 -5
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +24 -23
- package/dist/access-identity-continuity-surface.d.ts +4 -4
- package/dist/access-identity-continuity-surface.js +19 -18
- package/dist/access-lcm-surface.d.ts +5 -5
- package/dist/access-lcm-surface.js +19 -18
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +22 -21
- package/dist/access-namespace-preflight.js +19 -18
- package/dist/access-observe-write-surface.d.ts +5 -5
- package/dist/access-observe-write-surface.js +19 -18
- package/dist/access-operations-batch.js +20 -19
- package/dist/access-operations.d.ts +5 -5
- package/dist/access-operations.js +21 -20
- package/dist/access-recall-concurrency.d.ts +5 -5
- package/dist/access-recall-concurrency.js +19 -18
- package/dist/access-recall-response.d.ts +5 -5
- package/dist/access-recall-response.js +19 -18
- package/dist/access-recall-surface.d.ts +5 -5
- package/dist/access-recall-surface.js +19 -18
- package/dist/access-schema.d.ts +4 -4
- package/dist/{access-service-D9zIBlpp.d.ts → access-service-C2WYT1pO.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +5 -5
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +19 -18
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-wearables-meetings-surface.d.ts +2 -2
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +10 -9
- package/dist/buffer.d.ts +1 -1
- package/dist/{capsule-merge-2H2IUTBT.js → capsule-merge-IDWJPCWK.js} +77 -8
- package/dist/capsule-merge-IDWJPCWK.js.map +1 -0
- package/dist/causal-consolidation.js +11 -10
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-65ZPVYTA.js → chunk-3CE77VAP.js} +55 -5
- package/dist/chunk-3CE77VAP.js.map +1 -0
- package/dist/chunk-4J7G5SY4.js +338 -0
- package/dist/chunk-4J7G5SY4.js.map +1 -0
- package/dist/{chunk-EO7X2LDK.js → chunk-4TAD3B56.js} +2 -2
- package/dist/{chunk-QHYB4MJV.js → chunk-5ZGHUWTT.js} +2 -2
- package/dist/{chunk-TCIWHK3N.js → chunk-6D5D5A4M.js} +5 -5
- package/dist/{chunk-BIXWNHIL.js → chunk-6TF4M2LB.js} +5 -5
- package/dist/{chunk-FHYGLITW.js → chunk-AKELMJO2.js} +217 -45
- package/dist/{chunk-FHYGLITW.js.map → chunk-AKELMJO2.js.map} +1 -1
- package/dist/{chunk-FBUE6PD3.js → chunk-BEQOZOYZ.js} +21 -4
- package/dist/chunk-BEQOZOYZ.js.map +1 -0
- package/dist/{chunk-SA3WJJF2.js → chunk-BGLDKAYY.js} +2 -2
- package/dist/{chunk-OPRP6Q4Z.js → chunk-DKKZ6SO2.js} +3 -3
- package/dist/{chunk-6TAUHL33.js → chunk-DLVPUX7U.js} +10 -2
- package/dist/chunk-DLVPUX7U.js.map +1 -0
- package/dist/{chunk-QNWXCJ76.js → chunk-ENTCVTQR.js} +18 -1
- package/dist/{chunk-QNWXCJ76.js.map → chunk-ENTCVTQR.js.map} +1 -1
- package/dist/{chunk-AEPYA55F.js → chunk-FE7MUX7M.js} +2 -2
- package/dist/{chunk-GHR7WHZX.js → chunk-GORHSN5C.js} +2 -2
- package/dist/{chunk-7TEZUUGM.js → chunk-HUS2XN3T.js} +2 -2
- package/dist/{chunk-XWDZQIYU.js → chunk-IJAQCBGV.js} +2 -2
- package/dist/{chunk-TJLLYV2X.js → chunk-IMEXE4XS.js} +7 -7
- package/dist/{chunk-IZXTZ7BF.js → chunk-JYLN6KHH.js} +6 -6
- package/dist/{chunk-HEVWWPTL.js → chunk-LH3KXIBJ.js} +17 -17
- package/dist/{chunk-EH5U2YCX.js → chunk-M3DBB2ND.js} +2 -2
- package/dist/{chunk-XABS455Y.js → chunk-NBAEGFXT.js} +5 -5
- package/dist/{chunk-T4HH5Q3U.js → chunk-OJRLTZQM.js} +3 -3
- package/dist/{chunk-7ZHGPX56.js → chunk-PRP3M5Y3.js} +2 -2
- package/dist/{chunk-Y2ZD2RVG.js → chunk-PYTUBWAS.js} +2 -2
- package/dist/{chunk-4TGZCYHH.js → chunk-Q3CVY7LV.js} +1954 -1642
- package/dist/chunk-Q3CVY7LV.js.map +1 -0
- package/dist/{chunk-PH3ZWL3W.js → chunk-Q53RLEW7.js} +2 -2
- package/dist/{chunk-Y6OWJ3V5.js → chunk-RY2XZTFN.js} +2 -2
- package/dist/{chunk-2SHIWGY7.js → chunk-RY7CHVGE.js} +2 -2
- package/dist/{chunk-7VUAG236.js → chunk-T3FVAHM7.js} +4 -4
- package/dist/{chunk-2LTDWSR6.js → chunk-TSH56VPU.js} +2 -2
- package/dist/{chunk-J6SLNQRO.js → chunk-UBWOCQ3I.js} +6 -2
- package/dist/chunk-UBWOCQ3I.js.map +1 -0
- package/dist/{chunk-5FHE4JI7.js → chunk-VEUVON5I.js} +2 -2
- package/dist/{chunk-KEWUNL7P.js → chunk-WET4M3R6.js} +2 -2
- package/dist/{chunk-SLPLPGJQ.js → chunk-WL2S2UBB.js} +5 -5
- package/dist/{chunk-AHZYNPLX.js → chunk-YVMVVE4B.js} +2 -2
- package/dist/{chunk-5HTAJCTO.js → chunk-ZB3YODID.js} +15 -15
- package/dist/{cli-mFX-QQvx.d.ts → cli-jkmzdT8P.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +34 -33
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +10 -9
- package/dist/connectors/codex-materialize-runner.js +10 -9
- package/dist/connectors/index.js +10 -9
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-provenance-check.js +8 -2
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/consolidation-undo.js +10 -1
- package/dist/consolidation-undo.js.map +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/corpus-watermark.js +12 -11
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +10 -9
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/explicit-capture.js +6 -3
- package/dist/extraction.js +10 -9
- package/dist/{forget-4UY2EOKH.js → forget-DNAJ66ZD.js} +2 -2
- package/dist/index.d.ts +10 -10
- package/dist/index.js +39 -38
- package/dist/maintenance/memory-governance.js +10 -9
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -9
- package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-projection.js +11 -10
- package/dist/{maintenance-70Wq118b.d.ts → maintenance-Ha9Dg93M.d.ts} +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +11 -10
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +10 -9
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/operator-doctor-corpus.js +13 -12
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +17 -16
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +15 -14
- package/dist/{orchestrator-BH8LT-NX.d.ts → orchestrator-DV_u9bfx.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +39 -38
- package/dist/page-versioning.js +7 -1
- package/dist/semantic-consolidation.js +11 -10
- package/dist/semantic-rule-promotion.js +10 -9
- package/dist/semantic-rule-verifier.js +10 -9
- package/dist/{service-CrxXHz5T.d.ts → service-JxXXY-Eh.d.ts} +1 -1
- package/dist/source-agent-qualifier.js +10 -9
- package/dist/{storage-CyWa1Jm6.d.ts → storage-BbH4HDUS.d.ts} +30 -26
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +9 -8
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/transfer/capsule-import.js +8 -2
- package/dist/verified-recall.js +10 -9
- package/package.json +2 -2
- package/src/binary-lifecycle/pipeline.ts +52 -1
- package/src/consolidation-undo.ts +4 -0
- package/src/curation/index.ts +41 -5
- package/src/lifecycle/tombstones.ts +40 -0
- package/src/maintenance/memory-governance.ts +4 -0
- package/src/page-versioning.ts +16 -1
- package/src/review/index.ts +97 -13
- package/src/spaces/index.ts +127 -6
- package/src/storage/citation-hash-source.ts +97 -0
- package/src/storage/entity-canonical-id-lock.ts +33 -0
- package/src/storage/entity-canonical-id-migration-adapter.ts +8 -4
- package/src/storage/entity-canonical-id-migration.ts +129 -77
- package/src/storage/entity-canonical-id-references.ts +557 -0
- package/src/storage/entity-ref-repair.ts +171 -0
- package/src/storage/entity-store.ts +163 -2
- package/src/storage/tombstone-blocked-capture-sync.ts +18 -0
- package/src/storage.ts +363 -396
- package/src/transfer/capsule-import.ts +91 -3
- package/src/transfer/capsule-merge.ts +128 -10
- package/dist/capsule-merge-2H2IUTBT.js.map +0 -1
- package/dist/chunk-4TGZCYHH.js.map +0 -1
- package/dist/chunk-65ZPVYTA.js.map +0 -1
- package/dist/chunk-6TAUHL33.js.map +0 -1
- package/dist/chunk-FBUE6PD3.js.map +0 -1
- package/dist/chunk-J6SLNQRO.js.map +0 -1
- /package/dist/{chunk-EO7X2LDK.js.map → chunk-4TAD3B56.js.map} +0 -0
- /package/dist/{chunk-QHYB4MJV.js.map → chunk-5ZGHUWTT.js.map} +0 -0
- /package/dist/{chunk-TCIWHK3N.js.map → chunk-6D5D5A4M.js.map} +0 -0
- /package/dist/{chunk-BIXWNHIL.js.map → chunk-6TF4M2LB.js.map} +0 -0
- /package/dist/{chunk-SA3WJJF2.js.map → chunk-BGLDKAYY.js.map} +0 -0
- /package/dist/{chunk-OPRP6Q4Z.js.map → chunk-DKKZ6SO2.js.map} +0 -0
- /package/dist/{chunk-AEPYA55F.js.map → chunk-FE7MUX7M.js.map} +0 -0
- /package/dist/{chunk-GHR7WHZX.js.map → chunk-GORHSN5C.js.map} +0 -0
- /package/dist/{chunk-7TEZUUGM.js.map → chunk-HUS2XN3T.js.map} +0 -0
- /package/dist/{chunk-XWDZQIYU.js.map → chunk-IJAQCBGV.js.map} +0 -0
- /package/dist/{chunk-TJLLYV2X.js.map → chunk-IMEXE4XS.js.map} +0 -0
- /package/dist/{chunk-IZXTZ7BF.js.map → chunk-JYLN6KHH.js.map} +0 -0
- /package/dist/{chunk-HEVWWPTL.js.map → chunk-LH3KXIBJ.js.map} +0 -0
- /package/dist/{chunk-EH5U2YCX.js.map → chunk-M3DBB2ND.js.map} +0 -0
- /package/dist/{chunk-XABS455Y.js.map → chunk-NBAEGFXT.js.map} +0 -0
- /package/dist/{chunk-T4HH5Q3U.js.map → chunk-OJRLTZQM.js.map} +0 -0
- /package/dist/{chunk-7ZHGPX56.js.map → chunk-PRP3M5Y3.js.map} +0 -0
- /package/dist/{chunk-Y2ZD2RVG.js.map → chunk-PYTUBWAS.js.map} +0 -0
- /package/dist/{chunk-PH3ZWL3W.js.map → chunk-Q53RLEW7.js.map} +0 -0
- /package/dist/{chunk-Y6OWJ3V5.js.map → chunk-RY2XZTFN.js.map} +0 -0
- /package/dist/{chunk-2SHIWGY7.js.map → chunk-RY7CHVGE.js.map} +0 -0
- /package/dist/{chunk-7VUAG236.js.map → chunk-T3FVAHM7.js.map} +0 -0
- /package/dist/{chunk-2LTDWSR6.js.map → chunk-TSH56VPU.js.map} +0 -0
- /package/dist/{chunk-5FHE4JI7.js.map → chunk-VEUVON5I.js.map} +0 -0
- /package/dist/{chunk-KEWUNL7P.js.map → chunk-WET4M3R6.js.map} +0 -0
- /package/dist/{chunk-SLPLPGJQ.js.map → chunk-WL2S2UBB.js.map} +0 -0
- /package/dist/{chunk-AHZYNPLX.js.map → chunk-YVMVVE4B.js.map} +0 -0
- /package/dist/{chunk-5HTAJCTO.js.map → chunk-ZB3YODID.js.map} +0 -0
- /package/dist/{forget-4UY2EOKH.js.map → forget-DNAJ66ZD.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transfer/capsule-import.ts"],"sourcesContent":["import { lstat, mkdir, readFile, realpath, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { gunzipSync } from \"node:zlib\";\nimport { bumpMemoryCorpusVersionForDir } from \"../memory-corpus-version.js\";\nimport {\n createVersion,\n type VersioningConfig,\n type VersioningLogger,\n} from \"../page-versioning.js\";\nimport {\n assertIsDirectoryNotSymlink,\n assertRealpathInsideRoot,\n fromPosixRelPath,\n isPathInsideRoot,\n sha256String,\n} from \"./fs-utils.js\";\nimport {\n parseExportBundle,\n type CapsuleBlock,\n type ExportManifestV2,\n type ExportMemoryRecordV1,\n} from \"./types.js\";\nimport { isEncryptedCapsuleFile, decryptCapsuleFileInMemory } from \"./capsule-crypto.js\";\n\n/**\n * Conflict-resolution mode for {@link importCapsule}. Inverse of the\n * `mode` selector in PR 4/6's CLI surface.\n *\n * - `\"skip\"` (default) — when a target file already exists at the same\n * relative path, leave it untouched. The corresponding record is reported\n * via {@link ImportCapsuleResult.skipped}.\n * - `\"overwrite\"` — when a target file exists, snapshot the prior content\n * via {@link createVersion} (gotcha #54: write-before-delete; gotcha #25:\n * don't destroy old state until new state is confirmed), then write the\n * incoming content. The snapshot's `note` includes the source capsule id.\n * - `\"fork\"` — never overwrite. Every record is rebased under\n * `forks/<capsule-id>/<original-path>` and any YAML-frontmatter `id:`\n * field is rewritten to a new fork-scoped value. The original tree is\n * not touched.\n *\n * The mode is selected once per call and applied uniformly to every record\n * in the bundle. Mixed-mode imports are not supported by design — that\n * would let a single corrupted manifest silently land partial state.\n */\nexport type ImportCapsuleMode = \"skip\" | \"overwrite\" | \"fork\";\n\n/**\n * Options accepted by {@link importCapsule}.\n *\n * `archivePath` — absolute or cwd-relative path to a `.capsule.json.gz`\n * archive produced by `exportCapsule`. The archive must contain a V2\n * bundle (`schemaVersion: 2`); V1 archives are rejected.\n *\n * `root` — absolute or cwd-relative path to the memory directory that\n * will receive the records. Must be an existing directory.\n *\n * `mode` — see {@link ImportCapsuleMode}. Defaults to `\"skip\"`.\n *\n * `versioning` — optional page-versioning config used by `mode: \"overwrite\"`\n * to snapshot prior content before replacing it. Snapshots are skipped when\n * not provided or when `enabled === false`. Tests pass an enabled config\n * to assert snapshot creation; production callers thread the\n * orchestrator's resolved versioning config.\n *\n * `log` — optional logger forwarded to {@link createVersion}.\n *\n * `now` — optional clock override (ms epoch) used to derive deterministic\n * fork ids in tests. Production callers omit this.\n */\nexport interface ImportCapsuleOptions {\n archivePath: string;\n root: string;\n mode?: ImportCapsuleMode;\n versioning?: VersioningConfig;\n log?: VersioningLogger;\n now?: number;\n /**\n * Memory directory whose secure-store keyring is used when the archive is\n * encrypted. Required when `archivePath` ends with `.enc` or when the file\n * starts with the REMNIC-ENC magic header. If `memoryDir` is omitted and the\n * archive turns out to be encrypted, `importCapsule` throws a clear error\n * rather than silently failing.\n *\n * When provided and the archive is NOT encrypted, the value is ignored.\n */\n memoryDir?: string;\n /**\n * Original secure-store passphrase for encrypted format-v2 archives. When\n * supplied, the importer derives the archive key from the embedded KDF\n * header, enabling restore without the source machine's unlocked keyring.\n */\n passphrase?: string;\n}\n\nexport interface ImportCapsuleSkippedRecord {\n /** Original record path (capsule-relative, posix). */\n path: string;\n /**\n * Why this record was not written.\n *\n * `\"exists\"` — the target path already existed and the mode was `\"skip\"`,\n * OR the computed fork path already existed in fork mode.\n *\n * `\"checksum_mismatch\"` is intentionally absent: a checksum failure aborts\n * the import entirely (fail-closed) rather than skipping the offending\n * record, so no `ImportCapsuleSkippedRecord` is ever produced for it.\n */\n reason: \"exists\";\n}\n\nexport interface ImportCapsuleImportedRecord {\n /** Capsule-relative posix path the record carried. */\n sourcePath: string;\n /** Memory-dir-relative posix path the file was written to. */\n targetPath: string;\n /** Whether a prior version snapshot was taken (overwrite-mode only). */\n snapshotted: boolean;\n /** Whether the frontmatter `id:` field was rewritten (fork-mode only). */\n rewroteId: boolean;\n}\n\nexport interface ImportCapsuleResult {\n /** Records that landed on disk. */\n imported: ImportCapsuleImportedRecord[];\n /** Records that were not written. */\n skipped: ImportCapsuleSkippedRecord[];\n /** The manifest decoded from the archive. */\n manifest: ExportManifestV2;\n}\n\n/**\n * Pure async function that imports a capsule archive into a memory\n * directory. Inverse of {@link import(\"./capsule-export.js\").exportCapsule}.\n *\n * Sequence:\n * 1. Read + gunzip + JSON.parse the archive.\n * 2. Validate the bundle through `parseExportBundle` (V1 rejected).\n * 3. Verify each record's content sha256 against the manifest entry.\n * Any mismatch aborts the import BEFORE any file is written —\n * partial-write recovery would require a full rollback we cannot\n * offer cheaply, so we fail closed (gotcha #25).\n * 4. Apply the selected {@link ImportCapsuleMode} to every record.\n *\n * Determinism guarantees:\n * - Imported records are returned sorted by `sourcePath`.\n * - Skipped records are returned sorted by `path`.\n * - Fork-mode rebases under a stable `forks/<capsule-id>/` prefix so\n * repeated fork imports of the same capsule shape land in predictable\n * locations (subsequent fork imports still skip-on-exist because the\n * target tree is computed from the source path).\n */\nexport async function importCapsule(\n opts: ImportCapsuleOptions,\n): Promise<ImportCapsuleResult> {\n const archiveAbs = path.resolve(opts.archivePath);\n const rootAbs = path.resolve(opts.root);\n await assertIsDirectoryNotSymlink(rootAbs, \"importCapsule\", \"root\");\n\n const mode: ImportCapsuleMode = opts.mode ?? \"skip\";\n // Reject unknown mode values up-front (rule 51). TypeScript callers get a\n // compile-time check via the union type; JS/CLI callers get a runtime error\n // here before any write begins — not a silent destructive fallback.\n if (mode !== \"skip\" && mode !== \"overwrite\" && mode !== \"fork\") {\n throw new Error(\n `importCapsule: unknown mode ${JSON.stringify(mode)}; expected \"skip\", \"overwrite\", or \"fork\"`,\n );\n }\n\n // Auto-detect encrypted archives. If the file ends with `.enc` or starts\n // with the REMNIC-ENC magic header, decrypt it first before gunzip+parse.\n // Rule 48: default is \"not encrypted\"; we only attempt decryption when the\n // header is definitively present, never on ambiguous content.\n const encrypted = await isEncryptedCapsuleFile(archiveAbs);\n let raw: Buffer;\n if (encrypted) {\n if (!opts.memoryDir && !opts.passphrase) {\n throw new Error(\n `importCapsule: archive is encrypted but 'memoryDir' was not provided. ` +\n `Pass the memory directory so the secure-store key can be retrieved, ` +\n `provide the original passphrase for a format-v2 archive restore, ` +\n `or run \\`remnic secure-store unlock\\` before importing.`,\n );\n }\n raw = await decryptCapsuleFileInMemory(archiveAbs, opts.memoryDir, {\n passphrase: opts.passphrase,\n });\n } else {\n raw = await readFile(archiveAbs);\n }\n const json = gunzipSync(raw).toString(\"utf-8\");\n let parsedJson: unknown;\n try {\n parsedJson = JSON.parse(json);\n } catch (cause) {\n throw new Error(\n `importCapsule: archive is not valid JSON after gunzip: ${archiveAbs}`,\n { cause: cause as Error },\n );\n }\n\n const parsed = parseExportBundle(parsedJson);\n if (parsed.capsuleVersion !== 2) {\n // PR 1/6 ships a V1 reader; capsule import is V2-only by design. A V1\n // bundle does not carry a capsule block, so fork-mode (which depends on\n // `capsule.id` for the rebase prefix) cannot work. Reject up-front per\n // gotcha #51 instead of silently routing to a different code path.\n throw new Error(\n \"importCapsule: archive is V1; only V2 capsule archives are supported\",\n );\n }\n const bundle = parsed.bundle as { manifest: ExportManifestV2; records: ExportMemoryRecordV1[] };\n const manifest = bundle.manifest;\n const capsule = manifest.capsule;\n\n // Build a path → manifest-entry index for O(1) checksum lookup. The\n // manifest is the source of truth; records-without-manifest-entries and\n // manifest-entries-without-records are both treated as corruption.\n const manifestIndex = new Map<string, ExportManifestV2[\"files\"][number]>();\n for (const f of manifest.files) {\n manifestIndex.set(f.path, f);\n }\n if (manifestIndex.size !== manifest.files.length) {\n throw new Error(\n \"importCapsule: manifest contains duplicate file paths\",\n );\n }\n const recordPaths = new Set<string>();\n for (const rec of bundle.records) {\n if (recordPaths.has(rec.path)) {\n throw new Error(\n `importCapsule: bundle contains duplicate record path: ${rec.path}`,\n );\n }\n recordPaths.add(rec.path);\n }\n\n // Phase 1: verify every record matches its manifest entry AND validate all\n // target paths before any filesystem mutation. We do both in a single pass\n // so a corrupted or malicious archive cannot leave the memory dir partially\n // written (fail-closed per gotcha #25).\n //\n // The real root is resolved once via realpath so the subsequent per-record\n // inside-root checks are symlink-aware: a record path like `facts/a.md`\n // cannot write outside the intended sandbox via a symlinked subdirectory\n // (Codex P1 feedback). If realpath fails (root does not exist) the earlier\n // assertIsDirectoryNotSymlink call already threw, so this should always succeed.\n const rootReal = await realpath(rootAbs).catch(() => rootAbs);\n\n // Tracks normalized, case-folded target paths seen so far in phase 1. Maps\n // targetAbs.toLowerCase() → first source path so the collision error can name\n // both offending entries. Two manifest entries whose computed target paths\n // normalize to the same absolute path (e.g. `subdir/file.md` and\n // `subdir/./file.md`, or differing case on case-insensitive filesystems such\n // as macOS and Windows) would both try to write the same inode — the second\n // would silently overwrite the first. We reject the import up-front before\n // any write (Codex P2 thread on PR #741, line 283).\n const seenTargetPaths = new Map<string, string>();\n\n for (const rec of bundle.records) {\n // Checksum validation.\n const entry = manifestIndex.get(rec.path);\n if (!entry) {\n throw new Error(\n `importCapsule: archive checksum mismatch (record without manifest entry: ${rec.path})`,\n );\n }\n const { sha256, bytes } = sha256String(rec.content);\n if (sha256 !== entry.sha256 || bytes !== entry.bytes) {\n throw new Error(\n `importCapsule: archive checksum mismatch for ${rec.path}: ` +\n `expected sha256=${entry.sha256} bytes=${entry.bytes}, ` +\n `got sha256=${sha256} bytes=${bytes}`,\n );\n }\n\n // Source path validation: reject any record whose posix source path\n // contains a `..` segment, an absolute prefix, or any component that would\n // let fork-mode bypass its isolation invariant. This check runs before\n // `computeTargetPath` so that fork mode's `forks/<id>/` rebase cannot be\n // bypassed by a path like `../../profile.md` (which `path.join` would\n // silently collapse, landing the file inside root but outside the fork\n // prefix — Cursor medium thread #741). Rejecting `..` up-front is simpler\n // and more robust than normalising after the fact.\n //\n // Extended guard (Codex P1 #741 round 5): also reject paths containing\n // backslash separators (`\\`), which are Windows path separators. On Windows\n // a path like `..\\\\escape.md` or `subdir\\\\..\\\\..\\\\escape.md` would resolve\n // to a parent traversal but would bypass a purely posix-split check.\n // Additionally, after posix-normalizing the path we re-check for a leading\n // `..` or `/` — this catches edge-cases such as `subdir/../..` where the\n // individual split segments are not `..` but the normalized result is.\n if (rec.path.includes(\"\\\\\")) {\n throw new Error(\n `importCapsule: record path contains backslash separators (Windows-style paths are not allowed): ${rec.path}`,\n );\n }\n const posixNormalized = path.posix.normalize(rec.path);\n if (\n rec.path.startsWith(\"/\") ||\n rec.path.split(\"/\").some((seg) => seg === \"..\") ||\n posixNormalized.startsWith(\"..\") ||\n posixNormalized.startsWith(\"/\")\n ) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Path-traversal validation (moved here from phase 2 per Cursor feedback:\n // the traversal check must run before ANY write so a malicious archive\n // with an escaping path that sorts last cannot land partial writes).\n //\n // We build targetAbs relative to the real-resolved root (rootReal) so\n // that the inside-root check is symlink-aware: if `rootReal !== rootAbs`\n // (the import root is itself behind a symlink), we still compare against\n // the canonical path (Codex P1).\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n if (!isPathInsideRoot(rootReal, targetAbs)) {\n throw new Error(\n `importCapsule: record path escapes target root: ${rec.path}`,\n );\n }\n\n // Symlink-aware containment check (Cursor thread #741 line 247/264).\n // The lexical check above handles `..`-traversal but cannot detect\n // symlinked subdirectories that point outside the root. We resolve the\n // nearest existing ancestor of the target via realpath to catch symlinks\n // anywhere in the path components. If any resolved prefix escapes rootReal,\n // the import is rejected before any write. This applies to ALL modes,\n // including fork mode whose rebase prefix may itself traverse a symlink.\n await assertRealpathInsideRoot(rootReal, targetAbs, rec.path, \"importCapsule\");\n\n // Target-file symlink check (Codex P1 #741 round 4, line 260).\n // `assertRealpathInsideRoot` resolves the nearest existing *ancestor* to\n // catch symlinked parent directories, but if the TARGET FILE itself already\n // exists and is a symlink, that check passes (the parent is real) while the\n // write would silently follow the symlink to a path outside root. We\n // therefore lstat the target directly: if it exists and is a symlink we\n // reject the record up-front. For new files the parent-canonicalization\n // performed above is sufficient; no realpath of a non-existent file is\n // needed.\n const targetLstat = await lstat(targetAbs).catch(() => null);\n if (targetLstat !== null && targetLstat.isSymbolicLink()) {\n throw new Error(\n `importCapsule: record target is a symlink and cannot be written to safely: ${rec.path}`,\n );\n }\n\n // Duplicate normalized target path detection (Codex P2 #741, line 283).\n // `path.join` already normalises `.` segments (e.g. `subdir/./file.md` →\n // `subdir/file.md`). On case-insensitive filesystems (macOS default,\n // Windows), two paths that differ only in case would resolve to the same\n // inode. We fold the dedup key to lowercase so that `subdir/File.md` and\n // `subdir/file.md` are detected as duplicates before any write occurs.\n // This is intentionally unconditional: the cost of an extra `.toLowerCase()`\n // on case-sensitive filesystems is negligible, and a defensive lowercase\n // is far simpler than probing filesystem case-sensitivity at runtime.\n const dedupKey = targetAbs.toLowerCase();\n const firstSourcePath = seenTargetPaths.get(dedupKey);\n if (firstSourcePath !== undefined) {\n throw new Error(\n `importCapsule: manifest contains two entries that resolve to the same target path: ` +\n `\"${firstSourcePath}\" and \"${rec.path}\" both map to \"${targetRel}\"`,\n );\n }\n seenTargetPaths.set(dedupKey, rec.path);\n }\n // Detect manifest-only entries (missing record). Treat as corruption.\n for (const f of manifest.files) {\n if (!recordPaths.has(f.path)) {\n throw new Error(\n `importCapsule: archive checksum mismatch (manifest entry without record: ${f.path})`,\n );\n }\n }\n\n // Phase 2: apply the mode. All records were validated in phase 1; we now\n // write to disk. Per-record errors propagate; each individual write is\n // atomic (mkdir + writeFile pair).\n const imported: ImportCapsuleImportedRecord[] = [];\n const skipped: ImportCapsuleSkippedRecord[] = [];\n\n // Sort by source path so the imported/skipped lists are deterministic\n // regardless of bundle order. (`exportCapsule` already sorts; we re-sort\n // defensively in case a hand-edited archive ships records in another order.)\n const sortedRecords = [...bundle.records].sort((a, b) =>\n a.path.localeCompare(b.path),\n );\n\n for (const rec of sortedRecords) {\n const targetRel = computeTargetPath(rec.path, mode, capsule.id);\n // Use rootReal (realpath-resolved) to stay consistent with phase 1's\n // traversal validation and avoid writing through stale symlinks.\n const targetAbs = path.join(rootReal, fromPosixRelPath(targetRel));\n\n const exists = await fileExistsAt(targetAbs);\n\n // Skip logic applies to both `skip` mode (explicit) and `fork` mode\n // (Cursor medium: fork mode must also be skip-on-exist for the computed\n // fork path, because production imports generate non-deterministic IDs —\n // so re-importing the same capsule should not overwrite user edits in the\n // fork tree or silently change file identities by generating new IDs).\n if ((mode === \"skip\" || mode === \"fork\") && exists) {\n skipped.push({ path: rec.path, reason: \"exists\" });\n continue;\n }\n\n let snapshotted = false;\n if (mode === \"overwrite\" && exists) {\n // Gotcha #54: snapshot BEFORE overwriting. If snapshot creation fails\n // we abort this record's import rather than silently destroying\n // history. The page-versioning module is responsible for atomic\n // sidecar writes; we just call it.\n if (opts.versioning && opts.versioning.enabled) {\n const prior = await readFile(targetAbs, \"utf-8\").catch(() => \"\");\n await createVersion(\n targetAbs,\n prior,\n \"manual\",\n opts.versioning,\n opts.log,\n `capsule-import: ${capsule.id}`,\n rootReal,\n );\n snapshotted = true;\n }\n }\n\n let contentToWrite = rec.content;\n let rewroteId = false;\n if (mode === \"fork\") {\n const forked = rewriteFrontmatterIdForFork(rec.content, capsule.id, opts.now);\n contentToWrite = forked.content;\n rewroteId = forked.rewrote;\n }\n\n await mkdir(path.dirname(targetAbs), { recursive: true });\n await writeFile(targetAbs, contentToWrite, \"utf-8\");\n // Bump the corpus sentinel per write (Cursor Medium, #1902): a concurrent\n // readAllMemories during the import must rescan and see records already on\n // disk, never the pre-import corpus, mid-batch.\n bumpMemoryCorpusVersionForDir(rootReal);\n imported.push({\n sourcePath: rec.path,\n targetPath: targetRel,\n snapshotted,\n rewroteId,\n });\n }\n\n // Sort skipped for stable output (see comment above).\n skipped.sort((a, b) => a.path.localeCompare(b.path));\n\n // Per-record corpus bumps happen inside the loop above (Cursor Medium,\n // #1902); no after-batch bump needed here.\n\n return { imported, skipped, manifest };\n}\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the destination relative path for a record under the chosen\n * mode.\n *\n * - `skip` / `overwrite` — identity: the record's posix path is used as-is.\n * - `fork` — rebased under `forks/<capsule-id>/<original-path>` so the\n * original tree is never modified.\n */\nfunction computeTargetPath(\n sourcePosix: string,\n mode: ImportCapsuleMode,\n capsuleId: string,\n): string {\n if (mode === \"fork\") return `forks/${capsuleId}/${sourcePosix}`;\n return sourcePosix;\n}\n\nasync function fileExistsAt(absPath: string): Promise<boolean> {\n const st = await stat(absPath).catch(() => null);\n return st !== null && st.isFile();\n}\n\n// ---------------------------------------------------------------------------\n// Fork-mode id rewriting\n// ---------------------------------------------------------------------------\n\n/**\n * Rewrite a YAML-frontmatter `id:` field to a fork-scoped value.\n *\n * Strategy: we deliberately do NOT parse the entire YAML — memory files\n * use a deterministic top-of-file frontmatter delimited by `---` lines,\n * and the orchestrator's `parseFrontmatter` reads `id` as a top-level\n * key with a single-line scalar value. We mirror that contract:\n *\n * - Detect a leading `---\\n` … `\\n---` block at the top of the file.\n * - Within that block, locate the first `id:` line that is not nested\n * under another key (cheap heuristic: line starts at column 0).\n * - Replace its value with a new fork-id derived from `capsuleId` and\n * a short random suffix (or {@link opts.now} when provided for tests).\n *\n * Files without a frontmatter block, or without an `id:` key inside one,\n * are returned unchanged with `rewrote: false`. This keeps non-memory\n * artifacts (READMEs, transcripts) byte-identical to the source.\n */\nfunction rewriteFrontmatterIdForFork(\n content: string,\n capsuleId: string,\n now: number | undefined,\n): { content: string; rewrote: boolean } {\n // Detect the dominant line ending in the file so we can preserve it when\n // rebuilding the frontmatter delimiters. Splitting/joining on `\\n` silently\n // drops `\\r` from CRLF files, changing the byte content (Cursor thread #741\n // line 467). We detect the ending of the very first line (the opening `---`)\n // and use that as the canonical separator for the reconstructed delimiters.\n // All three cases are covered: CRLF (\\r\\n), LF (\\n), and bare CR (\\r).\n const crlfMatch = /^---(\\r\\n|\\r|\\n)/.exec(content);\n // Fall back to LF if the file starts with `---` but has no newline (EOF).\n const eol = crlfMatch ? crlfMatch[1] : \"\\n\";\n\n // Frontmatter block detection: `---` on its own line at the very start,\n // followed by content, followed by `---` on its own line. The closing\n // delimiter must be at column 0. We capture the trailing terminator\n // (`\\r?\\n` or end-of-string) as a separate group so we can re-emit it\n // verbatim, preserving CRLF/LF/EOF shape from the original file.\n const fmMatch = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(\\r?\\n|$)/.exec(content);\n if (!fmMatch) return { content, rewrote: false };\n const fmBody = fmMatch[1];\n const fmTrailer = fmMatch[2];\n const fmStart = fmMatch.index;\n const fmEnd = fmStart + fmMatch[0].length;\n\n // Find a top-level `id:` line. Top-level means the line begins at\n // column 0 inside the frontmatter body (no leading whitespace) and the\n // key is exactly `id`. We deliberately accept `id:` and `id : ` shapes\n // but reject `nested_id:` to avoid touching unrelated keys.\n const idLineRe = /^id:[ \\t]*([^\\r\\n]*)$/m;\n const idMatch = idLineRe.exec(fmBody);\n if (!idMatch) return { content, rewrote: false };\n\n const newId = mintForkId(capsuleId, idMatch[1]?.trim() ?? \"\", now);\n const replacedBody = fmBody.replace(idLineRe, `id: ${newId}`);\n // Reconstruct the file: original prefix (always empty here, fmStart === 0\n // by the `^` anchor) + frontmatter delimiters around the rewritten body.\n // Use the detected `eol` for the opening/closing `---` lines so that CRLF\n // files remain CRLF and LF files remain LF, preserving byte-content fidelity\n // (Cursor thread #741 line 467). The captured `fmTrailer` (the newline\n // immediately after the closing `---`, or end-of-string) is re-emitted\n // verbatim so the byte immediately after the closing fence is unchanged.\n const prefix = content.slice(0, fmStart);\n const tail = content.slice(fmEnd);\n const rebuilt = `${prefix}---${eol}${replacedBody}${eol}---${fmTrailer}${tail}`;\n return { content: rebuilt, rewrote: true };\n}\n\n/**\n * Derive a deterministic-when-`now`-is-set fork id from the capsule id and\n * the original record id. Production callers omit `now` and get a UUID\n * suffix; tests pass `now` for byte-stable assertions.\n */\nfunction mintForkId(capsuleId: string, originalId: string, now: number | undefined): string {\n const base = originalId.length > 0 ? originalId : \"fork\";\n if (now === undefined) {\n const suffix = randomUUID().slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n }\n // Deterministic suffix: short hash of (capsuleId, originalId, now). Using\n // sha256 over a sorted-key serialization (gotcha #38) so the suffix does\n // not depend on Object.entries order.\n const payload = JSON.stringify({ c: capsuleId, i: originalId, n: now });\n const suffix = createHash(\"sha256\").update(payload, \"utf-8\").digest(\"hex\").slice(0, 8);\n return `${base}-fork-${capsuleId}-${suffix}`;\n}\n\n// Note: `CapsuleBlock` is re-exported here purely so callers that want to\n// inspect the manifest block don't have to deep-import from `./types.js`.\nexport type { CapsuleBlock };\n"],"mappings":"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","names":["suffix"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/page-versioning.ts"],"sourcesContent":["/**\n * Page-level versioning with history and revert (issue #371).\n *\n * Provides snapshot-based versioning for memory files using a sidecar\n * directory layout. Each memory page gets a `.versions/<pageName>/`\n * subdirectory containing numbered snapshots and a `manifest.json` that\n * records the version history.\n *\n * Storage layout:\n * memoryDir/\n * facts/preferences.md <- current file\n * .versions/\n * facts__preferences/\n * manifest.json <- VersionHistory\n * 1.md <- version 1 snapshot\n * 2.md <- version 2 snapshot\n */\n\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport {\n access,\n mkdir,\n readFile,\n writeFile,\n unlink,\n} from \"node:fs/promises\";\nimport { ALL_CATEGORY_DIRS, RECALL_FALLBACK_DIRS } from \"./utils/category-dir.js\";\nimport { bumpMemoryCorpusVersionForDir } from \"./memory-corpus-version.js\";\n\n// ---------------------------------------------------------------------------\n// Public interfaces\n// ---------------------------------------------------------------------------\n\nexport interface PageVersion {\n versionId: string;\n timestamp: string;\n contentHash: string;\n sizeBytes: number;\n trigger: VersionTrigger;\n note?: string;\n}\n\nexport type VersionTrigger = \"write\" | \"consolidation\" | \"revert\" | \"manual\";\n\nexport interface VersionHistory {\n pagePath: string;\n versions: PageVersion[];\n currentVersion: string;\n}\n\nexport interface VersioningConfig {\n enabled: boolean;\n maxVersionsPerPage: number;\n sidecarDir: string;\n}\n\n// ---------------------------------------------------------------------------\n// Logger interface (minimal, avoids coupling to the host logger)\n// ---------------------------------------------------------------------------\n\nexport interface VersioningLogger {\n debug(msg: string): void;\n warn(msg: string): void;\n}\n\nconst NOOP_LOGGER: VersioningLogger = {\n debug: () => {},\n warn: () => {},\n};\n\n// ---------------------------------------------------------------------------\n// Per-page write lock (promise-chain pattern, see gotcha #40)\n// ---------------------------------------------------------------------------\n\nconst writeLocks = new Map<string, Promise<void>>();\n\nfunction withPageLock<T>(pageKey: string, fn: () => Promise<T>): Promise<T> {\n const prev = writeLocks.get(pageKey) ?? Promise.resolve();\n const next = prev.then(fn, fn); // run fn after previous completes, even if previous failed\n writeLocks.set(pageKey, next.then(() => {}, () => {})); // recover chain per gotcha #40\n return next;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction contentHash(content: string): string {\n return createHash(\"sha256\").update(content, \"utf-8\").digest(\"hex\");\n}\n\n/**\n * Derive a filesystem-safe sidecar key from a page path relative to memoryDir.\n *\n * `facts/2026-01-15/pref-001.md` -> `facts__2026-01-15__pref-001`\n *\n * Exported so the `remnic doctor` consolidation-provenance check (issue\n * #561 PR 4) resolves snapshot locations using the canonical algorithm\n * without re-implementing it — preventing silent drift if the key\n * format ever changes.\n */\nexport function sidecarKey(pagePath: string): string {\n const withoutExt = pagePath.replace(/\\.md$/i, \"\");\n return withoutExt.replace(/[\\\\/]/g, \"__\");\n}\n\nfunction sidecarDir(memoryDir: string, sidecar: string, pagePath: string): string {\n return path.join(memoryDir, sidecar, sidecarKey(pagePath));\n}\n\nfunction manifestPath(memoryDir: string, sidecar: string, pagePath: string): string {\n return path.join(sidecarDir(memoryDir, sidecar, pagePath), \"manifest.json\");\n}\n\nasync function fileExists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readManifest(\n memoryDir: string,\n sidecar: string,\n pagePath: string,\n): Promise<VersionHistory> {\n const mp = manifestPath(memoryDir, sidecar, pagePath);\n if (!(await fileExists(mp))) {\n return { pagePath, versions: [], currentVersion: \"0\" };\n }\n try {\n const raw = await readFile(mp, \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null) {\n throw new Error(\"manifest root must be an object\");\n }\n const obj = parsed as Record<string, unknown>;\n if (!Array.isArray(obj.versions)) {\n throw new Error(\"manifest versions must be an array\");\n }\n const versions = obj.versions.map((entry, index) => {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) {\n throw new Error(`manifest version ${index} must be an object`);\n }\n const version = entry as Record<string, unknown>;\n if (\n typeof version.versionId !== \"string\" ||\n !/^\\d+$/.test(version.versionId) ||\n typeof version.timestamp !== \"string\" ||\n typeof version.contentHash !== \"string\" ||\n typeof version.sizeBytes !== \"number\" ||\n !Number.isFinite(version.sizeBytes) ||\n ![\"write\", \"consolidation\", \"revert\", \"manual\"].includes(String(version.trigger))\n ) {\n throw new Error(`manifest version ${index} has invalid shape`);\n }\n return version as unknown as PageVersion;\n });\n if (typeof obj.currentVersion !== \"string\" || !/^\\d+$/.test(obj.currentVersion)) {\n throw new Error(\"manifest currentVersion must be a numeric string\");\n }\n const currentVersion = obj.currentVersion;\n return { pagePath: typeof obj.pagePath === \"string\" ? obj.pagePath : pagePath, versions, currentVersion };\n } catch (error) {\n throw new Error(`page-versioning: invalid manifest ${mp}: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\nasync function writeManifest(\n memoryDir: string,\n sidecar: string,\n pagePath: string,\n history: VersionHistory,\n): Promise<void> {\n const dir = sidecarDir(memoryDir, sidecar, pagePath);\n await mkdir(dir, { recursive: true });\n const mp = manifestPath(memoryDir, sidecar, pagePath);\n await writeFile(mp, JSON.stringify(history, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a new version snapshot for a page.\n *\n * Call this BEFORE overwriting the current file so the previous content is\n * preserved. If the file does not exist yet (first write), the provided\n * `content` is snapshotted as version 1.\n *\n * Pruning: when the number of versions exceeds `config.maxVersionsPerPage`,\n * the oldest snapshots (and their files) are removed.\n */\nexport async function createVersion(\n pagePath: string,\n content: string,\n trigger: VersionTrigger,\n config: VersioningConfig,\n log: VersioningLogger = NOOP_LOGGER,\n note?: string,\n memoryDir?: string,\n): Promise<PageVersion> {\n const { sidecarDir: sidecar, maxVersionsPerPage } = config;\n const resolvedMemoryDir = memoryDir ?? resolveMemoryDir(pagePath);\n const mPath = manifestPath(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir));\n\n return withPageLock(mPath, async () => {\n const history = await readManifest(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir));\n const nextId = String(history.versions.length > 0\n ? Math.max(...history.versions.map((v) => Number(v.versionId))) + 1\n : 1);\n\n const hash = contentHash(content);\n const version: PageVersion = {\n versionId: nextId,\n timestamp: new Date().toISOString(),\n contentHash: hash,\n sizeBytes: Buffer.byteLength(content, \"utf-8\"),\n trigger,\n ...(note !== undefined ? { note } : {}),\n };\n\n // Write snapshot file\n const dir = sidecarDir(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir));\n await mkdir(dir, { recursive: true });\n const ext = path.extname(pagePath) || \".md\";\n const snapshotPath = path.join(dir, `${nextId}${ext}`);\n await writeFile(snapshotPath, content, \"utf-8\");\n\n history.versions.push(version);\n history.currentVersion = nextId;\n\n // Prune old versions if exceeding max\n if (maxVersionsPerPage > 0 && history.versions.length > maxVersionsPerPage) {\n const toRemove = history.versions.splice(0, history.versions.length - maxVersionsPerPage);\n for (const old of toRemove) {\n const oldPath = path.join(dir, `${old.versionId}${ext}`);\n try {\n await unlink(oldPath);\n } catch {\n log.debug(`page-versioning: could not remove old snapshot ${oldPath}`);\n }\n }\n }\n\n await writeManifest(resolvedMemoryDir, sidecar, relPath(pagePath, resolvedMemoryDir), history);\n log.debug(`page-versioning: created version ${nextId} for ${pagePath} (trigger=${trigger})`);\n\n return version;\n });\n}\n\n/**\n * List all versions for a page.\n */\nexport async function listVersions(\n pagePath: string,\n config: VersioningConfig,\n memoryDir?: string,\n): Promise<VersionHistory> {\n const resolvedMemoryDir = memoryDir ?? resolveMemoryDir(pagePath);\n const rel = relPath(pagePath, resolvedMemoryDir);\n const history = await readManifest(resolvedMemoryDir, config.sidecarDir, rel);\n // Sort ascending by versionId (numeric)\n history.versions.sort((a, b) => Number(a.versionId) - Number(b.versionId));\n return history;\n}\n\n/**\n * Read the content of a specific version.\n */\nexport async function getVersion(\n pagePath: string,\n versionId: string,\n config: VersioningConfig,\n memoryDir?: string,\n): Promise<string> {\n const resolvedMemoryDir = memoryDir ?? resolveMemoryDir(pagePath);\n const rel = relPath(pagePath, resolvedMemoryDir);\n const ext = path.extname(pagePath) || \".md\";\n const dir = sidecarDir(resolvedMemoryDir, config.sidecarDir, rel);\n const snapshotPath = path.join(dir, `${versionId}${ext}`);\n\n if (!(await fileExists(snapshotPath))) {\n throw new Error(`Version ${versionId} not found for ${pagePath}`);\n }\n\n return readFile(snapshotPath, \"utf-8\");\n}\n\n/**\n * Revert a page to a previous version.\n *\n * 1. Reads the target version's content.\n * 2. Snapshots the CURRENT content as a new version (trigger: \"revert\").\n * 3. Writes the reverted content to the page file.\n *\n * Returns the newly created version entry for the revert snapshot.\n */\nexport async function revertToVersion(\n pagePath: string,\n versionId: string,\n config: VersioningConfig,\n log: VersioningLogger = NOOP_LOGGER,\n memoryDir?: string,\n): Promise<PageVersion> {\n const resolvedMemoryDir = memoryDir ?? resolveMemoryDir(pagePath);\n\n // Read target version content\n const targetContent = await getVersion(pagePath, versionId, config, resolvedMemoryDir);\n\n // Snapshot current content before overwriting\n let currentContent = \"\";\n try {\n currentContent = await readFile(pagePath, \"utf-8\");\n } catch {\n // File may not exist; that's okay\n }\n\n const version = await createVersion(\n pagePath,\n currentContent,\n \"revert\",\n config,\n log,\n `reverted to version ${versionId}`,\n resolvedMemoryDir,\n );\n\n // Write the reverted content to the actual page\n await writeFile(pagePath, targetContent, \"utf-8\");\n // A revert overwrites a memory file out-of-band (not via a StorageManager\n // mutation), so bump the corpus sentinel when the reverted page lives under a\n // recall category dir — forcing a warm hot-memories cache to rescan on its\n // next read (issue #1902, Codex P1). Non-recall pages (entities/artifacts/\n // profiles) are not in the corpus scan, so they need no bump.\n // The hot-memories corpus covers the RECALL corpus, which excludes the\n // non-memory queue dirs (questions/). Use RECALL_FALLBACK_DIRS — a revert of a\n // questions/ page must NOT bump the recall corpus sentinel and force a rescan.\n const revertedTop = relPath(pagePath, resolvedMemoryDir).split(path.sep)[0];\n if ((RECALL_FALLBACK_DIRS as readonly string[]).includes(revertedTop)) {\n bumpMemoryCorpusVersionForDir(resolvedMemoryDir);\n }\n log.debug(`page-versioning: reverted ${pagePath} to version ${versionId}`);\n\n return version;\n}\n\n/**\n * Simple line-based diff between two versions.\n *\n * Returns a unified-style diff string showing added (+) and removed (-) lines.\n */\nexport async function diffVersions(\n pagePath: string,\n v1: string,\n v2: string,\n config: VersioningConfig,\n memoryDir?: string,\n): Promise<string> {\n const resolvedMemoryDir = memoryDir ?? resolveMemoryDir(pagePath);\n const content1 = await getVersion(pagePath, v1, config, resolvedMemoryDir);\n const content2 = await getVersion(pagePath, v2, config, resolvedMemoryDir);\n\n const lines1 = content1.split(\"\\n\");\n const lines2 = content2.split(\"\\n\");\n\n const result: string[] = [];\n result.push(`--- version ${v1}`);\n result.push(`+++ version ${v2}`);\n\n // Simple LCS-based diff\n const lcs = computeLCS(lines1, lines2);\n let i = 0;\n let j = 0;\n let k = 0;\n\n while (k < lcs.length) {\n // Emit removed lines before the next common line\n while (i < lines1.length && lines1[i] !== lcs[k]) {\n result.push(`-${lines1[i]}`);\n i++;\n }\n // Emit added lines before the next common line\n while (j < lines2.length && lines2[j] !== lcs[k]) {\n result.push(`+${lines2[j]}`);\n j++;\n }\n // Common line\n result.push(` ${lcs[k]}`);\n i++;\n j++;\n k++;\n }\n // Remaining removed lines\n while (i < lines1.length) {\n result.push(`-${lines1[i]}`);\n i++;\n }\n // Remaining added lines\n while (j < lines2.length) {\n result.push(`+${lines2[j]}`);\n j++;\n }\n\n return result.join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// LCS helper for diffVersions\n// ---------------------------------------------------------------------------\n\nfunction computeLCS(a: string[], b: string[]): string[] {\n const m = a.length;\n const n = b.length;\n // Build DP table\n const dp: number[][] = Array.from({ length: m + 1 }, () => new Array<number>(n + 1).fill(0));\n for (let i = 1; i <= m; i++) {\n for (let j = 1; j <= n; j++) {\n if (a[i - 1] === b[j - 1]) {\n dp[i][j] = dp[i - 1][j - 1] + 1;\n } else {\n dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);\n }\n }\n }\n // Backtrack to build LCS\n const result: string[] = [];\n let i = m;\n let j = n;\n while (i > 0 && j > 0) {\n if (a[i - 1] === b[j - 1]) {\n result.unshift(a[i - 1]);\n i--;\n j--;\n } else if (dp[i - 1][j] > dp[i][j - 1]) {\n i--;\n } else {\n j--;\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Path helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Legacy fallback: given an absolute page path, heuristically resolve the\n * memory directory by walking up past known subdirectory names.\n *\n * Callers should always pass an explicit `memoryDir` instead of relying on\n * this heuristic. It is retained only for backward compatibility when the\n * optional `memoryDir` parameter is omitted.\n */\nfunction resolveMemoryDir(pagePath: string): string {\n // Derive the recall category dirs from ALL_CATEGORY_DIRS (single source of\n // truth) so newly-routed categories (decisions/, preferences/, ...) are\n // recognized when walking up to the memory root (#1546); the non-category\n // subdirs are listed explicitly.\n const knownSubdirs = new Set<string>([\n ...ALL_CATEGORY_DIRS,\n \"entities\",\n \"state\",\n \"artifacts\",\n \"profiles\",\n ]);\n\n let dir = path.dirname(pagePath);\n // Walk up past date directories (YYYY-MM-DD) and known subdirs\n for (let depth = 0; depth < 5; depth++) {\n const base = path.basename(dir);\n if (knownSubdirs.has(base) || /^\\d{4}-\\d{2}-\\d{2}$/.test(base)) {\n dir = path.dirname(dir);\n } else {\n break;\n }\n }\n return dir;\n}\n\n/**\n * Compute relative path of a page within its memory directory.\n */\nfunction relPath(pagePath: string, memoryDir: string): string {\n return path.relative(memoryDir, pagePath);\n}\n"],"mappings":"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","names":["i","j"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/storage/content-hash-index.ts","../src/storage/tombstone-blocked-capture-mutation.ts","../src/storage/tombstone-blocked-capture-sync.ts","../src/storage/tombstone-blocked-capture-index.ts"],"sourcesContent":["// Content-hash dedup index for facts, extracted from storage.ts (issue #1909 /\n// PR #2016 write-hot-paths). Self-contained: cross-process locked reconcile /\n// rebuild saves plus the deferred durable-retry machinery. storage.ts imports\n// these back and re-exports the class + companion types that were previously\n// part of its public API.\nimport { mkdir, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { log } from \"../logger.js\";\nimport { isErrnoCode } from \"../utils/errno.js\";\nimport { withHeldFileLock } from \"../utils/serialize-mutations.js\";\nimport { normalizeContent, computeContentHash } from \"../content-hash.js\";\nimport {\n SecureStoreLockedError,\n readMaybeEncryptedFile,\n writeMaybeEncryptedFile,\n} from \"../secure-store/secure-fs.js\";\n\n/**\n * Stale threshold (ms) for the per-file advisory lock guarding a fact-hash\n * index merge-save (issue #1909 review round 7). The critical section is a\n * single read+atomic-rewrite of one index file (≤ a few MB), so a holder older\n * than this is treated as crashed. Generous to tolerate slow/encrypted disks\n * without breaking a legitimately in-progress publish.\n */\nconst CONTENT_HASH_INDEX_LOCK_STALE_MS = 30_000;\n\n/**\n * Durable-retry policy for a deferred content-hash reconcile save whose\n * cross-process lock acquire timed out (issue #1909 / PR #2016). When the lock\n * is contended past `withHeldFileLock`'s bounded wait, the added hashes are held\n * only in this process's memory; relying on \"some later batch save\" is not\n * durable (the run may be the last before idle/shutdown), and a long-lived peer\n * that already built its authoritative in-memory index will not see the write.\n * So we schedule a bounded, exponentially-backed-off background retry that keeps\n * re-attempting the locked publish until it lands. Bounded because the durable\n * fact `.md` is already on disk and a process restart rebuilds the index from\n * the corpus regardless — exhausting retries degrades to that rebuild safety\n * net, never to data loss or a fatal append failure.\n */\nconst CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS = 5;\n/** Base backoff (ms) before the first deferred reconcile-save retry; doubles each attempt. */\nconst CONTENT_HASH_INDEX_RETRY_BASE_MS = 500;\n/** Ceiling (ms) for the exponential reconcile-save retry backoff. */\nexport const CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS = 5_000;\n\n/**\n * Tuning for {@link ContentHashIndex} cross-process lock waits and the\n * deferred reconcile-save durable retry (issue #1909 / PR #2016). All optional;\n * production uses the module defaults. Tests inject small values so the\n * lock-timeout + retry path is exercised deterministically without real\n * multi-second waits.\n */\nexport interface ContentHashIndexLockOptions {\n /** Bounded wait to acquire the per-file advisory lock; passed to `withHeldFileLock` (default 5000ms). */\n readonly maxWaitMs?: number;\n /** Poll interval while waiting for a busy lock (default 50ms). */\n readonly pollMs?: number;\n /** Base backoff before the first deferred-save retry (default 500ms). */\n readonly retryBaseMs?: number;\n /** Max deferred-save retries before falling back to the corpus-rebuild safety net (default 5). */\n readonly retryMaxAttempts?: number;\n}\n\n/**\n * Thrown when the fact-hash index cannot be made authoritative — the\n * cross-process rebuild lock could not be acquired within the bounded retry\n * budget (PR #2016). Callers that require an authoritative dedup answer surface\n * this instead of silently trusting a stale loaded snapshot.\n */\nexport class FactHashIndexNotAuthoritativeError extends Error {\n constructor(stateDir: string) {\n super(\n `fact-hash index is not authoritative: the cross-process rebuild lock for ${stateDir} could not be acquired within the bounded retry budget`,\n );\n this.name = \"FactHashIndexNotAuthoritativeError\";\n }\n}\n\ntype DiskFingerprint = {\n mtimeMs: number;\n size: number;\n dev: number;\n ino: number;\n ctimeMs: number;\n birthtimeMs: number;\n};\n/**\n * Content-hash dedup index for facts.\n * Normalizes content (lowercase, strip punctuation, collapse whitespace),\n * computes SHA-256, and stores hashes in a line-delimited file.\n * Prevents writing semantically identical facts.\n */\nexport class ContentHashIndex {\n private hashes: Set<string> = new Set();\n private dirty = false;\n /**\n * Hashes explicitly removed since the last successful save (issue #1909 review\n * round 8 thread 3). Tracked separately so `saveMergingWithDisk` can be\n * removal-AWARE: under the file lock it reads the latest on-disk set, DROPS\n * these, then unions our additions — so a removal batch cannot resurrect a\n * hash (union alone would) and cannot clobber a concurrent extraction's\n * appended hash (blind overwrite would). Consumed + cleared by every save.\n */\n private removed: Set<string> = new Set();\n /**\n * Hashes added by THIS instance since the last successful save (issue #1909\n * review round 9 finding 3). Tracked separately from the loaded snapshot so a\n * reconciling save publishes `(on-disk \\ removed) ∪ added` — it re-applies only\n * OUR additions, never the stale loaded entries. Without this, seeding the\n * merge from `this.hashes` (loaded ∪ added) would resurrect a hash a peer\n * removed on disk while this instance held a stale snapshot.\n */\n private added: Set<string> = new Set();\n private readonly filePath: string;\n private readonly secureStoreKeyProvider: () => Buffer | null;\n private readonly secureStoreWriteKeyProvider: () => Buffer | null;\n private readonly memoryDir: string;\n private readonly lockOptions: ContentHashIndexLockOptions;\n /**\n * Deferred reconcile-save durable-retry state (issue #1909 / PR #2016). At\n * most ONE retry is ever armed (`reconcileRetryTimer` is the reentrancy guard)\n * so successive timed-out saves cannot stack duplicate/parallel retries.\n * `reconcileRetryBarrier` resolves when the chain settles (published or gave\n * up) — awaited by tests and any caller wanting the eventual result.\n */\n private reconcileRetryTimer: NodeJS.Timeout | null = null;\n private reconcileRetryAttempts = 0;\n private reconcileRetryBarrier: Promise<void> | null = null;\n private reconcileRetryResolve: (() => void) | null = null;\n /**\n * On-disk fingerprint captured at the last point THIS instance's in-memory\n * set matched disk — after `load()`, `save()`, or a reconcile publish. A peer\n * process that advances the durable index changes its file generation even\n * when byte size and mtime collide, so the freshness check remains safe\n * without an O(file-size) read on the hot path.\n */\n private lastSyncedFingerprint: DiskFingerprint | null = null;\n\n constructor(\n stateDir: string,\n secureStoreKeyProvider: () => Buffer | null = () => null,\n secureStoreWriteKeyProvider: () => Buffer | null = secureStoreKeyProvider,\n memoryDir: string = path.dirname(stateDir),\n lockOptions: ContentHashIndexLockOptions = {}\n ) {\n this.filePath = path.join(stateDir, \"fact-hashes.txt\");\n this.secureStoreKeyProvider = secureStoreKeyProvider;\n this.secureStoreWriteKeyProvider = secureStoreWriteKeyProvider;\n this.memoryDir = memoryDir;\n this.lockOptions = lockOptions;\n }\n\n /** Load existing hashes from disk. Safe to call multiple times. */\n async load(): Promise<void> {\n try {\n const raw = await readMaybeEncryptedFile(this.filePath, this.secureStoreKeyProvider(), this.memoryDir);\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.length > 0) {\n this.hashes.add(trimmed);\n }\n }\n log.debug(`content-hash index: loaded ${this.hashes.size} hashes`);\n } catch (err) {\n if (err instanceof SecureStoreLockedError) throw err;\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n log.debug(\"content-hash index: no existing index — starting fresh\");\n }\n // The loaded set now reflects the on-disk file — baseline the fingerprint so\n // a later peer write is detectable (PR #2016 review).\n await this.captureSyncedFingerprint();\n }\n\n /** Cheap `stat` of the durable index file; null when it does not exist yet. */\n private async statIndexFile(): Promise<DiskFingerprint | null> {\n try {\n const s = await stat(this.filePath);\n return {\n mtimeMs: s.mtimeMs,\n size: s.size,\n dev: s.dev,\n ino: s.ino,\n ctimeMs: s.ctimeMs,\n birthtimeMs: s.birthtimeMs,\n };\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return null;\n throw err;\n }\n }\n\n /** Record the current on-disk fingerprint as this instance's synced baseline. */\n private async captureSyncedFingerprint(): Promise<void> {\n try {\n this.lastSyncedFingerprint = await this.statIndexFile();\n } catch (err) {\n // A stat failure other than ENOENT leaves no baseline; the next freshness\n // check reports non-current and the caller confirms against the corpus.\n this.lastSyncedFingerprint = null;\n log.debug(`content-hash index: could not fingerprint ${this.filePath}: ${err}`);\n }\n }\n\n /**\n * True when the durable index file is unchanged since this instance last\n * synced it (load / save / reconcile). False when a peer advanced it, when\n * the file appeared or vanished, or when freshness cannot be established\n * (stat error) — the caller then treats the in-memory snapshot as\n * non-authoritative and confirms a dedup miss against the durable corpus.\n * The stat includes file identity and generation metadata so same-size,\n * same-mtime atomic replacements are detected without reading the file body.\n */\n async isDiskFingerprintCurrent(): Promise<boolean> {\n let current: DiskFingerprint | null;\n try {\n current = await this.statIndexFile();\n } catch {\n return false;\n }\n const last = this.lastSyncedFingerprint;\n if (last === null || current === null) {\n return last === null && current === null;\n }\n return (\n current.mtimeMs === last.mtimeMs &&\n current.size === last.size &&\n current.dev === last.dev &&\n current.ino === last.ino &&\n current.ctimeMs === last.ctimeMs &&\n current.birthtimeMs === last.birthtimeMs\n );\n }\n\n /** Check if content already exists in the index. */\n has(content: string): boolean {\n return this.hashes.has(ContentHashIndex.computeHash(content));\n }\n\n /** Add content hash to the index. */\n add(content: string): void {\n const hash = ContentHashIndex.computeHash(content);\n // A re-add supersedes a pending removal of the same hash (round 8 thread 3).\n this.removed.delete(hash);\n // Record OUR durable delta even when the local snapshot already holds the\n // hash (PR #2016 thread SD-nG): local membership can be STALE — a peer\n // removed this hash on disk while this instance kept it in memory. Skipping\n // `added`/`dirty` on that path let the reconciling save compute\n // (on-disk \\ removed) ∪ added and silently drop the reintroduced hash, so\n // the peer never saw it again. Set semantics keep this idempotent (no\n // duplicate entries) and the `removed.delete` above preserves remove/add\n // ordering.\n this.hashes.add(hash);\n this.added.add(hash);\n this.dirty = true;\n }\n\n get size(): number {\n return this.hashes.size;\n }\n\n /** Clear all loaded hashes so the next save rewrites the index from scratch. */\n clear(): void {\n if (this.hashes.size > 0) {\n this.hashes.clear();\n }\n // A full clear is always paired with a plain overwrite save() (rebuild);\n // pending per-hash add/remove deltas are subsumed by writing the rebuilt set.\n // Cancel any armed deferred-reconcile retry (PR #2016) so it cannot fire in\n // the clear→rebuild→save window and union the pre-clear on-disk rows back.\n this.added.clear();\n this.removed.clear();\n this.cancelReconcileRetry();\n this.dirty = true;\n }\n\n /** Persist index to disk if changed (plain whole-file overwrite). */\n async save(): Promise<void> {\n if (!this.dirty) return;\n await mkdir(path.dirname(this.filePath), { recursive: true });\n // Snapshot the deltas + overwrite body THIS save publishes BEFORE the write\n // yields the event loop. `writeMaybeEncryptedFile` materializes `serialized`\n // eagerly, then the disk await lets a concurrent same-process add()/remove()\n // land a late delta (PR #2016 thread PRRT_kwDORJXyws6SEBri). Clearing the\n // LIVE added/removed sets afterward — as the previous unconditional\n // clear()+`dirty = false` did — would drop that late hash from the index AND\n // leave no retry, so peers never see it until a corpus rebuild. Consume only\n // what we published; late deltas stay pending. Mirrors saveMergingWithDisk.\n const publishedAdded = new Set<string>(this.added);\n const publishedRemoved = new Set<string>(this.removed);\n const serialized = [...this.hashes].join(\"\\n\") + \"\\n\";\n await writeMaybeEncryptedFile(\n this.filePath,\n serialized,\n this.secureStoreWriteKeyProvider(),\n {},\n this.memoryDir\n );\n // Consume ONLY the deltas this overwrite published; anything that arrived\n // during the awaits above remains pending for the next save.\n for (const h of publishedAdded) this.added.delete(h);\n for (const h of publishedRemoved) this.removed.delete(h);\n // Honest dirty state: true iff late deltas remain unpersisted.\n this.dirty = this.added.size > 0 || this.removed.size > 0;\n // This overwrite is now the on-disk state — re-baseline the freshness\n // fingerprint so our own write is not later mistaken for a peer's (PR #2016).\n await this.captureSyncedFingerprint();\n if (this.dirty) {\n // Late deltas landed mid-save: arm a bounded durable retry so they reach\n // disk even without a further batch save — never a silent in-memory-only\n // hash. The locked reconcile republishes only OUR delta onto the overwrite.\n this.scheduleReconcileRetry();\n } else {\n // Clean overwrite supersedes any pending deferred reconcile work.\n this.cancelReconcileRetry();\n }\n log.debug(`content-hash index: saved ${this.hashes.size} hashes`);\n }\n\n /**\n * Persist the index by RECONCILING with the latest on-disk state under a\n * cross-process lock (issue #1909). Used by the extraction-persist batch save\n * for BOTH append and removal batches, so appends and removals to the same\n * index serialize against each other:\n * final = (on-disk \\ this.removed) ∪ this.added\n * Note it re-applies only OUR local additions (`added`), NOT the loaded\n * snapshot (review round 9 finding 3): seeding from `this.hashes` would\n * resurrect a hash a peer removed on disk while we held a stale snapshot. This\n * preserves a concurrent writer's appended hashes (a blind overwrite would\n * drop them), never resurrects a removed hash, and never loses our additions.\n *\n * Hardening:\n * - Dirty short-circuit (round 7 finding 3): when this instance neither added\n * nor removed anything this run, skip the O(file) read+rewrite entirely.\n * - Cross-process lock (round 7 finding 2): serialize the read→reconcile→write\n * window on a per-file advisory lock. If the lock is NOT acquired (timeout),\n * do NOT publish (round 9 finding 2): keep `dirty` set AND schedule a bounded\n * durable background retry (PR #2016) that keeps re-attempting the locked\n * publish until the deferred additions land — never an unlocked write that\n * could clobber a peer, and never a silent \"batch save complete\" that leaves\n * the addition in this process's memory only.\n */\n async saveMergingWithDisk(): Promise<void> {\n if (!this.dirty) return;\n // The advisory lock file lives beside the index — ensure the dir exists\n // before acquiring it (the index dir may not exist on a first write).\n await mkdir(path.dirname(this.filePath), { recursive: true });\n const publish = async (): Promise<void> => {\n // Snapshot the deltas this publish is responsible for. The read+rewrite\n // below yields the event loop, so a concurrent add()/remove() can land\n // new deltas while we await disk (PR #2016 thread SD7Tj). Consuming the\n // LIVE sets afterward would drop those late deltas AND clear `dirty`,\n // stranding them in no save at all. We consume only what we published;\n // anything that arrived meanwhile stays pending for the next save.\n const publishedAdded = new Set<string>(this.added);\n const publishedRemoved = new Set<string>(this.removed);\n const merged = new Set<string>(publishedAdded);\n try {\n const raw = await readMaybeEncryptedFile(\n this.filePath,\n this.secureStoreKeyProvider(),\n this.memoryDir\n );\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n // Keep prior/concurrent on-disk hashes EXCEPT the ones we removed.\n if (trimmed.length > 0 && !publishedRemoved.has(trimmed)) merged.add(trimmed);\n }\n } catch (err) {\n if (err instanceof SecureStoreLockedError) throw err;\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n // ENOENT → no on-disk index yet; write our additions as-is.\n }\n await mkdir(path.dirname(this.filePath), { recursive: true });\n await writeMaybeEncryptedFile(\n this.filePath,\n [...merged].join(\"\\n\") + \"\\n\",\n this.secureStoreWriteKeyProvider(),\n {},\n this.memoryDir\n );\n // Consume ONLY the deltas we just published; deltas that arrived during\n // the awaits above remain pending.\n for (const h of publishedAdded) this.added.delete(h);\n for (const h of publishedRemoved) this.removed.delete(h);\n // Fold any still-pending deltas onto the reconciled set so the in-memory\n // view stays consistent with what the next save will publish.\n for (const h of this.added) merged.add(h);\n for (const h of this.removed) merged.delete(h);\n this.hashes = merged;\n // Honest dirty state: true iff late deltas remain to be persisted. A\n // subsequent batch save or the shutdown flush drains them.\n this.dirty = this.added.size > 0 || this.removed.size > 0;\n // The reconciled set is now the on-disk state — re-baseline the freshness\n // fingerprint (captured under the held lock) so our own publish is not\n // later mistaken for a peer's advance (PR #2016 review).\n await this.captureSyncedFingerprint();\n };\n await withHeldFileLock(\n `${this.filePath}.lock`,\n {\n staleMs: CONTENT_HASH_INDEX_LOCK_STALE_MS,\n ...(this.lockOptions.maxWaitMs !== undefined ? { maxWaitMs: this.lockOptions.maxWaitMs } : {}),\n ...(this.lockOptions.pollMs !== undefined ? { pollMs: this.lockOptions.pollMs } : {}),\n },\n async (acquired) => {\n if (!acquired) {\n // Never publish unlocked (round 9 finding 2): keep dirty AND schedule a\n // durable background retry (PR #2016). Relying on an unguaranteed later\n // batch save silently left the addition in this process's memory only;\n // the retry keeps re-attempting the locked publish until the addition\n // lands on disk.\n log.warn(\n `content-hash index: lock not acquired for ${this.filePath}; deferring reconcile save (dirty retained, scheduling durable retry)`,\n );\n this.scheduleReconcileRetry();\n return;\n }\n await publish();\n if (this.dirty) {\n // A concurrent add()/remove() landed during publish's disk awaits, so\n // deltas remain unpersisted (PR #2016 thread SD7Tj). Arm a bounded\n // durable retry so they reach disk even if no further batch save comes\n // — never leave a settled barrier over pending work.\n this.scheduleReconcileRetry();\n } else {\n // Published cleanly: any pending deferred retry is now satisfied.\n this.cancelReconcileRetry();\n }\n },\n );\n log.debug(`content-hash index: reconcile-saved ${this.hashes.size} hashes`);\n }\n\n /**\n * Rebuild-and-publish the index from an authoritative source (the durable\n * corpus) under the SAME per-file cross-process lock the reconciling\n * append/removal saves (`saveMergingWithDisk`) use (issue #1909 / PR #2016).\n * The `populate` callback runs WHILE the lock is held and MUST repopulate this\n * index from the corpus (`clear()` then `addByHash(...)` for every corpus\n * hash); the rebuilt set is then published with a plain overwrite that is now\n * serialized against every locked writer.\n *\n * This closes the finding where the previous unlocked clear→scan→save() could\n * overwrite a peer's newer lock-merged or deferred additions: a locked writer\n * that committed BEFORE the rebuild acquired the lock has its memory `.md` on\n * disk and is re-scanned; one that commits AFTER the rebuild releases\n * reconciles its additions on top of the freshly-published set. No union with\n * the raw on-disk file is performed, so the rebuild keeps its\n * garbage-collection property (orphaned hashes for deleted memories drop).\n *\n * Lock-acquisition failure is explicit and non-destructive: `populate` is NOT\n * run and nothing is written (never an unlocked overwrite). Returns true when\n * the rebuild was published under the lock, false when the lock timed out —\n * the caller keeps the index non-authoritative and retries on next use. Uses\n * the same non-reentrant file lock as the reconcile path, so it can never\n * deadlock with the deferred reconcile-retry: a concurrent locked writer (or a\n * fired retry) simply yields `acquired=false` after the bounded wait.\n */\n async rebuildUnderLock(populate: () => Promise<void>): Promise<boolean> {\n // The advisory lock file lives beside the index — ensure the dir exists\n // before acquiring it (the index dir may not exist on a first rebuild).\n await mkdir(path.dirname(this.filePath), { recursive: true });\n let published = false;\n await withHeldFileLock(\n `${this.filePath}.lock`,\n {\n staleMs: CONTENT_HASH_INDEX_LOCK_STALE_MS,\n ...(this.lockOptions.maxWaitMs !== undefined ? { maxWaitMs: this.lockOptions.maxWaitMs } : {}),\n ...(this.lockOptions.pollMs !== undefined ? { pollMs: this.lockOptions.pollMs } : {}),\n },\n async (acquired) => {\n if (!acquired) {\n // Never publish unlocked: a concurrent locked writer holds the lock,\n // so overwriting now could clobber its addition. Leave the index as-is\n // and let the caller retry the authoritative rebuild on next use.\n log.warn(\n `content-hash index: lock not acquired for ${this.filePath}; deferring authoritative rebuild (index left non-authoritative, retried on next use)`,\n );\n return;\n }\n await populate();\n // Publish under the held lock. save() takes no lock (no re-entrancy) and\n // is a full overwrite that supersedes any pending add/remove deltas.\n await this.save();\n published = true;\n },\n );\n return published;\n }\n\n /**\n * True while a background durable reconcile-save retry is armed (PR #2016).\n * Test/introspection hook.\n */\n get hasPendingReconcileRetry(): boolean {\n return this.reconcileRetryTimer !== null;\n }\n\n /** True while this index has in-memory changes not yet published to disk. */\n get hasPendingChanges(): boolean {\n return this.dirty;\n }\n\n /**\n * Resolve once any armed background reconcile-save retry chain has settled —\n * either it published the deferred additions, or it exhausted its bounded\n * attempts and fell back to the corpus-rebuild safety net. Resolves\n * immediately when nothing is armed. Lets a caller (or a test) await the\n * eventual durability outcome of a lock-timed-out save.\n */\n async whenReconcileRetrySettled(): Promise<void> {\n await (this.reconcileRetryBarrier ?? Promise.resolve());\n }\n\n /**\n * Drive any deferred lock-timeout reconcile save to completion INLINE at a\n * lifecycle boundary (PR #2016 finding 3). The background retry timer is\n * `unref`'d so it never keeps a long-lived daemon alive — but a short-lived\n * writer (a one-shot CLI) can exit before it fires, leaving a durable fact\n * `.md` whose hash never reached `fact-hashes.txt` for peers that already\n * built their in-memory index. `orchestrator.destroy()` calls this so the\n * addition publishes before the process exits; long-lived hosts only reach it\n * at their own shutdown, so their in-flight retries keep running in the\n * background until then. Bounded by the same attempt ceiling as the background\n * retry; a permanently contended lock falls back to the corpus-rebuild-on-\n * restart safety net (the fact `.md` is already durable). No deadlock: each\n * attempt is the same non-reentrant, bounded-wait file lock.\n */\n async flushReconcileRetry(): Promise<void> {\n if (!this.dirty) return;\n const maxAttempts = this.lockOptions.retryMaxAttempts ?? CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS;\n const baseMs = this.lockOptions.retryBaseMs ?? CONTENT_HASH_INDEX_RETRY_BASE_MS;\n for (let attempt = 0; this.dirty && attempt < maxAttempts; attempt += 1) {\n // Cancel the unref'd background timer — we drive the locked publish inline.\n if (this.reconcileRetryTimer) {\n clearTimeout(this.reconcileRetryTimer);\n this.reconcileRetryTimer = null;\n }\n await this.saveMergingWithDisk();\n if (!this.dirty) break;\n if (attempt < maxAttempts - 1) {\n const wait = Math.min(baseMs * 2 ** attempt, CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);\n await new Promise<void>((resolve) => setTimeout(resolve, wait));\n }\n }\n // Clear any timer the final saveMergingWithDisk armed and settle the barrier\n // so awaiters unblock — the bounded inline drain has done its best.\n if (this.reconcileRetryTimer) {\n clearTimeout(this.reconcileRetryTimer);\n this.reconcileRetryTimer = null;\n }\n this.reconcileRetryAttempts = 0;\n this.settleReconcileRetry();\n }\n\n /**\n * Schedule ONE bounded, exponentially-backed-off background retry of the\n * deferred reconcile save (PR #2016). Reentrant-safe: an already-armed timer\n * short-circuits, so successive timed-out saves never stack duplicate retries.\n * When attempts are exhausted the chain settles without publishing — the\n * durable fact `.md` is on disk and a process restart rebuilds the index from\n * the corpus, so this degrades to that safety net, never a fatal failure.\n */\n private scheduleReconcileRetry(): void {\n if (this.reconcileRetryTimer) return; // one retry in flight — no duplicate/reentrant retries\n const maxAttempts = this.lockOptions.retryMaxAttempts ?? CONTENT_HASH_INDEX_RETRY_MAX_ATTEMPTS;\n if (this.reconcileRetryAttempts >= maxAttempts) {\n log.warn(\n `content-hash index: exhausted ${maxAttempts} deferred reconcile-save retries for ${this.filePath}; ` +\n `${this.added.size} addition(s) remain dirty in-memory (a process restart rebuilds the index from the durable corpus)`,\n );\n this.reconcileRetryAttempts = 0;\n this.settleReconcileRetry();\n return;\n }\n if (!this.reconcileRetryBarrier) {\n this.reconcileRetryBarrier = new Promise<void>((resolve) => {\n this.reconcileRetryResolve = resolve;\n });\n }\n const attempt = this.reconcileRetryAttempts + 1;\n const baseMs = this.lockOptions.retryBaseMs ?? CONTENT_HASH_INDEX_RETRY_BASE_MS;\n const delay = Math.min(baseMs * 2 ** (attempt - 1), CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);\n this.reconcileRetryTimer = setTimeout(() => {\n this.reconcileRetryTimer = null;\n this.reconcileRetryAttempts = attempt;\n void this.saveMergingWithDisk()\n .catch((err) => {\n // A publish-time I/O/encryption error (not a lock timeout) is best-effort:\n // log and let the corpus-rebuild safety net cover it rather than loop.\n log.warn(`content-hash index: deferred reconcile-save retry failed for ${this.filePath}: ${err}`);\n })\n .finally(() => {\n // If the attempt neither published (dirty cleared → cancelReconcileRetry)\n // nor re-armed a further retry, the chain has ended — settle the barrier.\n if (this.dirty && !this.reconcileRetryTimer) this.settleReconcileRetry();\n });\n }, delay);\n this.reconcileRetryTimer.unref?.();\n }\n\n /** Cancel any armed reconcile-save retry and settle the barrier (a publish landed). */\n private cancelReconcileRetry(): void {\n if (this.reconcileRetryTimer) {\n clearTimeout(this.reconcileRetryTimer);\n this.reconcileRetryTimer = null;\n }\n this.reconcileRetryAttempts = 0;\n this.settleReconcileRetry();\n }\n\n /** Resolve and clear the retry barrier if one is outstanding. */\n private settleReconcileRetry(): void {\n const resolve = this.reconcileRetryResolve;\n this.reconcileRetryBarrier = null;\n this.reconcileRetryResolve = null;\n resolve?.();\n }\n\n /** Remove a hash from the index (used when archiving/deleting). */\n remove(content: string): void {\n const hash = ContentHashIndex.computeHash(content);\n // Record the removal (round 8 thread 3) even when the hash is not in our\n // in-memory set — a reconciling save must still drop it from the latest\n // on-disk state. A removal supersedes a pending add. Always mark dirty.\n this.added.delete(hash);\n this.removed.add(hash);\n this.hashes.delete(hash);\n this.dirty = true;\n }\n\n /**\n * Remove a pre-computed SHA-256 hash directly from the index without\n * re-hashing. Use this when the caller already holds the stored hash\n * (e.g. `memory.frontmatter.contentHash`) to avoid the double-hash bug\n * where `remove(hash)` would compute `hash(hash)` and never match the\n * entry.\n */\n removeByHash(hash: string): void {\n this.added.delete(hash);\n this.removed.add(hash);\n this.hashes.delete(hash);\n this.dirty = true;\n }\n\n /**\n * Add a pre-computed SHA-256 hash directly to the index without re-hashing.\n * Use this when the caller already holds the stored hash\n * (e.g. `memory.frontmatter.contentHash`) so that the index records the raw\n * content hash rather than re-hashing the citation-annotated body.\n *\n * @internal Only called from `StorageManager.ensureFactHashIndexAuthoritative`.\n * Not part of the public API — prefer `add(content)` for external callers.\n */\n addByHash(hash: string): void {\n // A re-add supersedes a pending removal of the same hash.\n this.removed.delete(hash);\n // Record OUR durable delta even when the local snapshot already holds the\n // hash (PR #2016 thread PRRT_kwDORJXyws6SEHve). Outside a rebuild\n // (StorageManager.addActiveFactContentHash on reactivation) local\n // membership can be STALE: a peer removed this hash on disk while this\n // instance kept it in memory. Skipping added/dirty on that path let the\n // reconciling save compute (on-disk \\ removed) ∪ added and silently drop\n // the reintroduced hash, so the peer never saw it again. Set semantics keep\n // this idempotent (no duplicate entries) and the removed.delete above\n // preserves remove/add ordering. Mirrors add().\n this.hashes.add(hash);\n this.added.add(hash);\n this.dirty = true;\n }\n\n /** Normalize content (delegates to content-hash.ts for a single source of truth). */\n static normalizeContent(content: string): string {\n return normalizeContent(content);\n }\n\n /** Compute SHA-256 hash (delegates to content-hash.ts for a single source of truth). */\n static computeHash(content: string): string {\n return computeContentHash(content);\n }\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { createHash } from \"node:crypto\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { withHeldFileLock } from \"../utils/serialize-mutations.js\";\nimport type { MemoryFile, MemoryFrontmatter } from \"../types.js\";\nconst TOMBSTONE_CAPTURE_WRITE_LOCK_STALE_MS = 60_000;\nconst CAPTURE_WRITE_LOCK_MAX_ATTEMPTS = 3;\nconst CAPTURE_WRITE_LOCK_RETRY_BASE_MS = 25;\nconst CAPTURE_WRITE_LOCK_BUSY_MESSAGE = \"tombstone-blocked capture write lock remained busy\";\n\nexport function isTombstoneBlockedCaptureWriteLockBusy(error: unknown): boolean {\n return error instanceof Error && error.message === CAPTURE_WRITE_LOCK_BUSY_MESSAGE;\n}\n\nexport function normalizeExplicitCaptureContent(value: string): string {\n return value.toLowerCase().replace(/\\s+/g, \" \").trim();\n}\n\nexport function buildExplicitCaptureDedupKey(\n content: string,\n category: string,\n sourceConnector: string | undefined\n): string {\n const encode = (label: string, value: string): string => `${label} ${value.length} ${value}`;\n return [\n encode(\"category\", category),\n encode(\"connector\", sourceConnector?.trim() ?? \"\"),\n encode(\"content\", normalizeExplicitCaptureContent(content)),\n ].join(\" \");\n}\n\nexport function captureIdentityHash(memory: MemoryFile): string {\n return createHash(\"sha256\")\n .update(buildExplicitCaptureDedupKey(memory.content, memory.frontmatter.category, memory.frontmatter.sourceConnector))\n .digest(\"hex\");\n}\n\nexport type TombstoneBlockedCaptureWriteLockOptions = {\n readonly stateDir: string;\n readonly withHeldFileLock?: typeof withHeldFileLock;\n};\n\nexport class TombstoneBlockedCaptureWriteLock {\n private readonly context = new AsyncLocalStorage<ReadonlySet<string>>();\n\n constructor(private readonly options: TombstoneBlockedCaptureWriteLockOptions) {}\n\n private captureWriteLockPathForHash(identityHash: string): string {\n const filename =\n identityHash.length === 0\n ? \"explicit-capture-write.lock\"\n : `explicit-capture-write-${identityHash}.lock`;\n return path.join(this.options.stateDir, \"tombstone-blocked-capture\", filename);\n }\n\n private async withSingleCaptureWriteLock<T>(lockPath: string, task: () => Promise<T>): Promise<T> {\n await mkdir(path.dirname(lockPath), { recursive: true });\n const retry = Symbol(\"retry\");\n const permanentFailure = Symbol(\"permanent-failure\");\n const runWithHeldFileLock = this.options.withHeldFileLock ?? withHeldFileLock;\n for (let attempt = 0; attempt < CAPTURE_WRITE_LOCK_MAX_ATTEMPTS; attempt += 1) {\n const result = await runWithHeldFileLock<T | typeof retry | typeof permanentFailure>(\n lockPath,\n {\n staleMs: TOMBSTONE_CAPTURE_WRITE_LOCK_STALE_MS,\n maxWaitMs: 1_000,\n pollMs: 25,\n },\n async (acquired, controller) => {\n if (acquired) return await task();\n return controller.failure === \"error\" ? permanentFailure : retry;\n }\n );\n if (result === permanentFailure) {\n throw new Error(\"tombstone-blocked capture write lock acquisition failed\");\n }\n if (result !== retry) return result;\n if (attempt === CAPTURE_WRITE_LOCK_MAX_ATTEMPTS - 1) break;\n const delayState = Promise.withResolvers<void>();\n setTimeout(delayState.resolve, CAPTURE_WRITE_LOCK_RETRY_BASE_MS * 2 ** attempt);\n await delayState.promise;\n }\n throw new Error(CAPTURE_WRITE_LOCK_BUSY_MESSAGE);\n }\n private async withPhysicalCaptureWriteLocks<T>(\n task: () => Promise<T>,\n identityHashes: readonly string[]\n ): Promise<T> {\n const lockPaths = [...new Set(identityHashes.map((hash) => this.captureWriteLockPathForHash(hash)))].sort();\n const acquire = async (index: number): Promise<T> => {\n if (index === lockPaths.length) return await task();\n return await this.withSingleCaptureWriteLock(lockPaths[index], () => acquire(index + 1));\n };\n return await acquire(0);\n }\n\n\n async withCaptureWriteLock<T>(\n task: () => Promise<T>,\n identity?: string | readonly string[]\n ): Promise<T> {\n const identities =\n Array.isArray(identity) && identity.length > 0 ? identity : [typeof identity === \"string\" ? identity : \"\"];\n return await this.withCaptureWriteLocks(task, identities, []);\n }\n\n async withCaptureWriteLockHashes<T>(\n task: () => Promise<T>,\n identityHashes: readonly string[]\n ): Promise<T> {\n return await this.withCaptureWriteLocks(task, [], identityHashes);\n }\n\n async withCaptureWriteLockAndHashes<T>(\n task: () => Promise<T>,\n identity: string | readonly string[] | undefined,\n identityHashes: readonly string[]\n ): Promise<T> {\n const identities =\n Array.isArray(identity) && identity.length > 0 ? identity : [typeof identity === \"string\" ? identity : \"\"];\n return await this.withCaptureWriteLocks(task, identities, identityHashes);\n }\n\n private async withCaptureWriteLocks<T>(\n task: () => Promise<T>,\n identities: readonly string[],\n identityHashes: readonly string[]\n ): Promise<T> {\n const current = this.context.getStore();\n const hashToken = (key: string): string =>\n `\\u0000${key.length === 0 ? \"\" : createHash(\"sha256\").update(key).digest(\"hex\")}`;\n const identityHash = (key: string): string =>\n key.length === 0 ? \"\" : createHash(\"sha256\").update(key).digest(\"hex\");\n const identityKeys = [...new Set(identities)];\n const hashes = [...new Set(identityHashes)];\n const missingIdentityHashes = identityKeys\n .filter((key) => !current || (!current.has(key) && !current.has(hashToken(key))))\n .map(identityHash);\n const missingHashes = hashes.filter((hash) => !current || !current.has(`\\u0000${hash}`));\n const physicalMissing = [...new Set([...missingIdentityHashes, ...missingHashes])];\n if (current && physicalMissing.length > 0) {\n const heldLockPaths = [...current]\n .filter((token) => token.startsWith(\"\\u0000\"))\n .map((token) => this.captureWriteLockPathForHash(token.slice(1)));\n const requestedLockPaths = physicalMissing.map((hash) => this.captureWriteLockPathForHash(hash));\n if (heldLockPaths.some((held) => requestedLockPaths.some((requested) => requested < held))) {\n throw new Error(CAPTURE_WRITE_LOCK_BUSY_MESSAGE);\n }\n }\n const next = new Set(current);\n for (const key of identityKeys) {\n next.add(key);\n next.add(hashToken(key));\n }\n for (const hash of hashes) next.add(`\\u0000${hash}`);\n const run = () => this.context.run(next, task);\n if (physicalMissing.length === 0) return await run();\n return await this.withPhysicalCaptureWriteLocks(run, physicalMissing);\n }\n}\n\n\nexport type TombstoneBlockedMutationHost = {\n readCurrent: () => Promise<MemoryFile | null>;\n isBlocked: (memory: MemoryFile | null) => boolean;\n isQueuedReview: (memory: MemoryFile | null) => boolean;\n memoryIdentity: (memory: MemoryFile) => string;\n prepareWrite: () => Promise<string>;\n commitWrite: (marker: string) => Promise<void>;\n discardWrite: (marker: string) => Promise<void>;\n markUntrusted: () => void;\n writeStorageSecureFile: (pathname: string, fileContent: string) => Promise<void>;\n withCaptureWriteLock: (task: () => Promise<void>, identity: string | readonly string[]) => Promise<void>;\n logWarning: (message: string) => void;\n};\n\nexport type TombstoneBlockedMutation = {\n blocked: boolean;\n pathname: string;\n fileContent: string;\n identity: string | readonly string[];\n updateIndex: (rebuildMarker?: string, current?: MemoryFile | null) => Promise<void>;\n beforeIndexUpdate?: () => Promise<void>;\n coordinate?: boolean;\n};\n\nexport async function runTombstoneBlockedMutation(\n host: TombstoneBlockedMutationHost,\n mutation: TombstoneBlockedMutation,\n): Promise<void> {\n let lockIdentity: string | readonly string[] = mutation.identity;\n let mustLock = mutation.blocked || mutation.coordinate === true;\n for (;;) {\n let retryIdentity: string | undefined;\n const mutate = async (): Promise<void> => {\n const current = await host.readCurrent();\n const currentBlocked = host.isBlocked(current);\n const currentQueuedReview = host.isQueuedReview(current);\n if (current !== null && (currentBlocked || currentQueuedReview)) {\n const currentIdentity = host.memoryIdentity(current);\n const heldIdentities = Array.isArray(lockIdentity) ? lockIdentity : [lockIdentity];\n if (!heldIdentities.includes(currentIdentity)) {\n retryIdentity = currentIdentity;\n return;\n }\n }\n const rebuildMarker = mutation.blocked || currentBlocked ? await host.prepareWrite() : undefined;\n try {\n await host.writeStorageSecureFile(mutation.pathname, mutation.fileContent);\n } catch (err) {\n if (rebuildMarker) {\n try {\n await host.discardWrite(rebuildMarker);\n } catch (cleanupError) {\n host.markUntrusted();\n host.logWarning(`storage.tombstoneBlocked failed to clear write marker: ${cleanupError}`);\n }\n }\n throw err;\n }\n if (rebuildMarker) {\n try {\n await host.commitWrite(rebuildMarker);\n } catch (err) {\n host.markUntrusted();\n host.logWarning(`storage.tombstoneBlocked committed write marker failed: ${err}`);\n }\n }\n if (rebuildMarker) await mutation.beforeIndexUpdate?.();\n await mutation.updateIndex(rebuildMarker, currentBlocked && current !== null ? current : undefined);\n };\n if (mustLock) {\n await host.withCaptureWriteLock(mutate, lockIdentity);\n } else {\n await mutate();\n }\n if (retryIdentity === undefined) return;\n const identities = Array.isArray(lockIdentity)\n ? [...lockIdentity, retryIdentity]\n : [lockIdentity, retryIdentity];\n lockIdentity = [...new Set(identities)];\n mustLock = true;\n }\n}\n\nexport function tombstoneBlocked(frontmatter: MemoryFrontmatter): boolean {\n return frontmatter.status === \"pending_review\" && Boolean(frontmatter.blockedBy);\n}\n\nexport function isQueuedReviewFrontmatter(frontmatter: MemoryFrontmatter): boolean {\n return frontmatter.status === \"pending_review\" && (frontmatter.tags ?? []).includes(\"queued-review\");\n}\n\nexport function isQueuedReviewMemory(memory: MemoryFile | null): memory is MemoryFile {\n return memory !== null && isQueuedReviewFrontmatter(memory.frontmatter);\n}\n\nexport type TombstoneBlockedChunkMutation = {\n blocked: boolean;\n id: string;\n identity: string;\n findDuplicate: () => Promise<MemoryFile | null>;\n persistMemory: () => Promise<void>;\n afterWrite: () => Promise<void>;\n withCaptureWriteLock: (task: () => Promise<string>, identity: string) => Promise<string>;\n};\n\nexport async function runTombstoneBlockedChunkMutation(\n mutation: TombstoneBlockedChunkMutation,\n): Promise<string> {\n const persist = async (): Promise<string> => {\n if (mutation.blocked) {\n const duplicate = await mutation.findDuplicate();\n if (duplicate) return duplicate.frontmatter.id;\n }\n await mutation.persistMemory();\n await mutation.afterWrite();\n return mutation.id;\n };\n return mutation.blocked\n ? await mutation.withCaptureWriteLock(persist, mutation.identity)\n : await persist();\n}\n","import { createHash, randomUUID } from \"node:crypto\";\nimport { unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport { log } from \"../logger.js\";\nimport type { MemoryFile, MemoryFrontmatter } from \"../types.js\";\nimport { markProjectedMemoryPathInvalid } from \"../memory-projection-store.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport { isErrnoCode } from \"../utils/errno.js\";\nimport {\n readMaybeEncryptedFileFromChunks,\n writeMaybeEncryptedFileFromChunks,\n} from \"../secure-store/secure-fs.js\";\nimport {\n buildExplicitCaptureDedupKey,\n captureIdentityHash,\n tombstoneBlocked,\n isQueuedReviewFrontmatter,\n isQueuedReviewMemory,\n runTombstoneBlockedChunkMutation,\n runTombstoneBlockedMutation,\n type TombstoneBlockedMutationHost,\n} from \"./tombstone-blocked-capture-mutation.js\";\nimport { TombstoneBlockedCaptureIndex } from \"./tombstone-blocked-capture-index.js\";\nimport type { TombstoneBlockedCaptureIndexOptions } from \"./tombstone-blocked-capture-index.js\";\n\nconst OFFLINE_SYNC_BUFFER_LIMIT_BYTES = 1_048_576;\nconst OFFLINE_SYNC_STAGE_READ_BYTES = 64 * 1024;\nconst OFFLINE_SYNC_FRONTMATTER_DELIMITER = Buffer.from(\"\\n---\\n\", \"utf8\");\n\ntype StreamedOfflineSyncIdentity = {\n identityHash: string;\n};\n\ntype PreparedOfflineSyncChunks = {\n after: MemoryFile | null;\n chunks: AsyncIterable<Buffer>;\n stagePath?: string;\n streamedIdentity?: StreamedOfflineSyncIdentity;\n};\n\nfunction createExplicitContentNormalizer(onChunk: (chunk: string) => void): {\n push: (text: string) => void;\n finish: () => void;\n} {\n let started = false;\n let pendingSpace = false;\n let output = \"\";\n const flush = () => {\n if (output.length > 0) {\n onChunk(output);\n output = \"\";\n }\n };\n return {\n push(text) {\n for (const character of text.toLowerCase()) {\n if (/\\s/u.test(character)) {\n if (started) pendingSpace = true;\n continue;\n }\n if (pendingSpace) {\n output += \" \";\n pendingSpace = false;\n }\n output += character;\n started = true;\n if (output.length >= OFFLINE_SYNC_STAGE_READ_BYTES) flush();\n }\n },\n finish: flush,\n };\n}\n\nasync function forEachStagedTextChunk(\n stagePath: string,\n offset: number,\n onText: (text: string) => void,\n key: Buffer | null,\n memoryDir: string\n): Promise<void> {\n const decoder = new TextDecoder(\"utf-8\");\n let remaining = offset;\n for await (const chunk of readMaybeEncryptedFileFromChunks(stagePath, key, memoryDir)) {\n const textChunk =\n remaining >= chunk.length\n ? null\n : chunk.subarray(remaining);\n remaining = Math.max(0, remaining - chunk.length);\n if (textChunk === null || textChunk.length === 0) continue;\n const text = decoder.decode(textChunk, { stream: true });\n if (text.length > 0) onText(text);\n }\n const finalText = decoder.decode();\n if (finalText.length > 0) onText(finalText);\n}\n\nasync function findStagedBodyOffset(\n stagePath: string,\n key: Buffer | null,\n memoryDir: string\n): Promise<number | null> {\n let scannedBytes = 0;\n let tail = Buffer.alloc(0);\n for await (const chunk of readMaybeEncryptedFileFromChunks(stagePath, key, memoryDir)) {\n const window = tail.length > 0 ? Buffer.concat([tail, chunk]) : chunk;\n const windowStart = scannedBytes - tail.length;\n for (let searchFrom = 0; ; ) {\n const delimiterStart = window.indexOf(OFFLINE_SYNC_FRONTMATTER_DELIMITER, searchFrom);\n if (delimiterStart < 0) break;\n if (windowStart + delimiterStart >= 4) {\n return windowStart + delimiterStart + OFFLINE_SYNC_FRONTMATTER_DELIMITER.length;\n }\n searchFrom = delimiterStart + 1;\n }\n scannedBytes += chunk.length;\n const tailLength = Math.min(OFFLINE_SYNC_FRONTMATTER_DELIMITER.length - 1, window.length);\n tail = Buffer.from(window.subarray(window.length - tailLength));\n }\n return null;\n}\n\nasync function readStagedPrefix(\n stagePath: string,\n byteLimit: number,\n key: Buffer | null,\n memoryDir: string\n): Promise<Buffer> {\n const chunks: Buffer[] = [];\n let bytesRead = 0;\n for await (const chunk of readMaybeEncryptedFileFromChunks(stagePath, key, memoryDir)) {\n const length = Math.min(chunk.length, byteLimit - bytesRead);\n if (length > 0) chunks.push(chunk.subarray(0, length));\n bytesRead += length;\n if (bytesRead >= byteLimit) break;\n }\n return Buffer.concat(chunks, bytesRead);\n}\n\nexport type OfflineSyncMemoryParser = (filePath: string, content: Buffer) => MemoryFile | null;\n\nexport function parseTombstoneBlockedOfflineSyncMemory(\n filePath: string,\n content: Buffer,\n memoryDir: string,\n parseFrontmatter: (raw: string) => { frontmatter: MemoryFrontmatter; content: string } | null,\n normalizeFrontmatterForPath: (frontmatter: MemoryFrontmatter, pathRel: string, content?: string) => MemoryFrontmatter,\n toMemoryPathRel: (baseDir: string, filePath: string) => string\n): MemoryFile | null {\n const parsed = parseFrontmatter(content.toString(\"utf8\"));\n if (!parsed) return null;\n return {\n path: filePath,\n frontmatter: normalizeFrontmatterForPath(parsed.frontmatter, toMemoryPathRel(memoryDir, filePath), parsed.content),\n content: parsed.content,\n };\n}\n\nexport abstract class TombstoneBlockedCaptureIndexHost {\n private tombstoneBlockedCaptureIndex: TombstoneBlockedCaptureIndex | null = null;\n protected abstract tombstoneBlockedCaptureIndexOptions(): TombstoneBlockedCaptureIndexOptions;\n protected abstract writeStorageSecureFile(\n filePath: string,\n content: string | Buffer,\n forceEncrypt?: boolean\n ): Promise<void>;\n protected abstract assertManagedStoragePath(filePath: string, method: string): string;\n protected abstract notifyCatalogWriteForPath(filePath: string): void;\n protected abstract readMemoryByPath(filePath: string): Promise<MemoryFile | null>;\n protected abstract invalidateAllMemoriesCache(): void;\n protected abstract invalidateKnowledgeIndexCache(): void;\n protected abstract invalidateColdMemoriesCache(): void;\n protected abstract bumpArtifactWriteVersion(): number;\n protected abstract bumpMemoryStatusVersion(): void;\n protected abstract bumpMemoryCorpusVersion(): void;\n protected abstract markFactHashIndexNotAuthoritative(): void;\n\n private async writeStorageSecureFileChunks(filePath: string, chunks: AsyncIterable<Buffer>): Promise<void> {\n const options = this.tombstoneBlockedCaptureIndexOptions();\n await writeMaybeEncryptedFileFromChunks(\n filePath,\n chunks,\n options.secureStoreWriteKeyProvider(),\n {},\n options.memoryDir\n );\n this.notifyCatalogWriteForPath(filePath);\n }\n\n protected getTombstoneBlockedCaptureIndex(): TombstoneBlockedCaptureIndex {\n if (this.tombstoneBlockedCaptureIndex === null) {\n this.tombstoneBlockedCaptureIndex = new TombstoneBlockedCaptureIndex(this.tombstoneBlockedCaptureIndexOptions());\n }\n return this.tombstoneBlockedCaptureIndex;\n }\n async withTombstoneBlockedCaptureWriteLock<T>(\n task: () => Promise<T>,\n identity?: string | readonly string[]\n ): Promise<T> {\n return await this.getTombstoneBlockedCaptureIndex().withCaptureWriteLock(task, identity);\n }\n\n protected async withTombstoneBlockedCaptureWriteLockAndHashes<T>(\n task: () => Promise<T>,\n identity: string | readonly string[] | undefined,\n identityHashes: readonly string[]\n ): Promise<T> {\n return await this.getTombstoneBlockedCaptureIndex().withCaptureWriteLockAndHashes(task, identity, identityHashes);\n }\n\n private async writeTombstoneBlockedMutation(\n blocked: boolean,\n pathname: string,\n fileContent: string,\n identity: string | readonly string[],\n updateIndex: (rebuildMarker?: string, current?: MemoryFile | null) => Promise<void>,\n beforeIndexUpdate?: () => Promise<void>,\n coordinate = false\n ): Promise<void> {\n const host: TombstoneBlockedMutationHost = {\n readCurrent: () => this.readMemoryByPath(pathname),\n isBlocked: (memory) => this.isTombstoneBlockedMemory(memory),\n isQueuedReview: (memory) => isQueuedReviewMemory(memory),\n memoryIdentity: (memory) => this.offlineSyncMemoryIdentity(memory),\n prepareWrite: () => this.getTombstoneBlockedCaptureIndex().prepareWrite(),\n commitWrite: (marker) => this.getTombstoneBlockedCaptureIndex().commitWrite(marker),\n discardWrite: (marker) => this.getTombstoneBlockedCaptureIndex().discardWrite(marker),\n markUntrusted: () => this.getTombstoneBlockedCaptureIndex().markUntrusted(),\n writeStorageSecureFile: (target, content) => this.writeStorageSecureFile(target, content),\n withCaptureWriteLock: (task, lockIdentity) =>\n this.withTombstoneBlockedCaptureWriteLock(task, lockIdentity),\n logWarning: (message) => log.warn(message),\n };\n await runTombstoneBlockedMutation(host, {\n blocked,\n pathname,\n fileContent,\n identity,\n updateIndex,\n beforeIndexUpdate,\n coordinate,\n });\n }\n\n protected async writeTombstoneBlockedMemory(\n pathname: string,\n fileContent: string,\n frontmatter: MemoryFrontmatter,\n content: string,\n beforeIndexUpdate?: () => Promise<void>\n ): Promise<void> {\n await this.writeTombstoneBlockedMutation(\n tombstoneBlocked(frontmatter),\n pathname,\n fileContent,\n buildExplicitCaptureDedupKey(content, frontmatter.category, frontmatter.sourceConnector),\n (rebuildMarker) =>\n this.getTombstoneBlockedCaptureIndex().addWrittenMemory(pathname, frontmatter, content, rebuildMarker),\n beforeIndexUpdate\n );\n }\n\n protected async writeTombstoneBlockedChunk(\n pathname: string,\n fileContent: string,\n frontmatter: MemoryFrontmatter,\n content: string,\n findDuplicate: () => Promise<MemoryFile | null>,\n afterWrite: () => Promise<void>\n ): Promise<string> {\n return await runTombstoneBlockedChunkMutation({\n blocked: tombstoneBlocked(frontmatter),\n id: frontmatter.id,\n identity: buildExplicitCaptureDedupKey(content, frontmatter.category, frontmatter.sourceConnector),\n findDuplicate,\n persistMemory: () => this.writeTombstoneBlockedMemory(pathname, fileContent, frontmatter, content),\n afterWrite,\n withCaptureWriteLock: (task, identity) => this.withTombstoneBlockedCaptureWriteLock(task, identity),\n });\n }\n\n protected async writeTombstoneBlockedUpdate(\n before: MemoryFile,\n fileContent: string,\n frontmatter: MemoryFrontmatter,\n content: string,\n beforeIndexUpdate?: () => Promise<void>\n ): Promise<void> {\n await this.writeTombstoneBlockedMutation(\n tombstoneBlocked(before.frontmatter) || tombstoneBlocked(frontmatter),\n before.path,\n fileContent,\n [\n ...(tombstoneBlocked(before.frontmatter)\n ? [\n buildExplicitCaptureDedupKey(\n before.content,\n before.frontmatter.category,\n before.frontmatter.sourceConnector\n ),\n ]\n : []),\n ...(tombstoneBlocked(frontmatter)\n ? [buildExplicitCaptureDedupKey(content, frontmatter.category, frontmatter.sourceConnector)]\n : []),\n ...(isQueuedReviewMemory(before)\n ? [\n buildExplicitCaptureDedupKey(\n before.content,\n before.frontmatter.category,\n before.frontmatter.sourceConnector\n ),\n ]\n : []),\n ...(isQueuedReviewFrontmatter(frontmatter)\n ? [buildExplicitCaptureDedupKey(content, frontmatter.category, frontmatter.sourceConnector)]\n : []),\n ],\n (rebuildMarker, current) =>\n this.getTombstoneBlockedCaptureIndex().syncUpdatedMemory(\n current ?? before,\n frontmatter,\n content,\n rebuildMarker\n ),\n beforeIndexUpdate,\n isQueuedReviewMemory(before) || isQueuedReviewFrontmatter(frontmatter)\n );\n }\n\n protected async writeTombstoneBlockedFrontmatter(\n before: MemoryFile,\n fileContent: string,\n frontmatter: MemoryFrontmatter,\n beforeIndexUpdate?: () => Promise<void>\n ): Promise<void> {\n await this.writeTombstoneBlockedMutation(\n tombstoneBlocked(before.frontmatter) || tombstoneBlocked(frontmatter),\n before.path,\n fileContent,\n [\n ...(tombstoneBlocked(before.frontmatter)\n ? [\n buildExplicitCaptureDedupKey(\n before.content,\n before.frontmatter.category,\n before.frontmatter.sourceConnector\n ),\n ]\n : []),\n ...(tombstoneBlocked(frontmatter)\n ? [buildExplicitCaptureDedupKey(before.content, frontmatter.category, frontmatter.sourceConnector)]\n : []),\n ...(isQueuedReviewMemory(before)\n ? [\n buildExplicitCaptureDedupKey(\n before.content,\n before.frontmatter.category,\n before.frontmatter.sourceConnector\n ),\n ]\n : []),\n ...(isQueuedReviewFrontmatter(frontmatter)\n ? [buildExplicitCaptureDedupKey(before.content, frontmatter.category, frontmatter.sourceConnector)]\n : []),\n ],\n (rebuildMarker, current) =>\n this.getTombstoneBlockedCaptureIndex().syncUpdatedFrontmatter(\n current ?? before,\n frontmatter,\n rebuildMarker\n ),\n beforeIndexUpdate,\n isQueuedReviewMemory(before) || isQueuedReviewFrontmatter(frontmatter)\n );\n }\n\n private isTombstoneBlockedMemory(memory: MemoryFile | null): memory is MemoryFile {\n return memory !== null && memory.frontmatter.status === \"pending_review\" && Boolean(memory.frontmatter.blockedBy);\n }\n\n private offlineSyncMemoryIdentity(memory: MemoryFile): string {\n return buildExplicitCaptureDedupKey(\n memory.content,\n memory.frontmatter.category,\n memory.frontmatter.sourceConnector\n );\n }\n private async isDuplicateTombstoneBlockedOfflineSync(\n target: string,\n incoming: MemoryFile,\n streamedIdentity?: StreamedOfflineSyncIdentity\n ): Promise<boolean> {\n const before = await this.readMemoryByPath(target);\n const duplicate = streamedIdentity\n ? true\n : await this.getTombstoneBlockedCaptureIndex().has(\n incoming.content,\n incoming.frontmatter.category,\n incoming.frontmatter.sourceConnector\n );\n if (!duplicate) return false;\n const incomingIdentity = streamedIdentity?.identityHash ?? this.offlineSyncMemoryIdentity(incoming);\n const options = this.tombstoneBlockedCaptureIndexOptions();\n const existing = [...(await options.readAllMemories()), ...(await options.readAllColdMemories())];\n const exactMatch = existing.some(\n (memory) =>\n this.isTombstoneBlockedMemory(memory) &&\n (streamedIdentity ? captureIdentityHash(memory) : this.offlineSyncMemoryIdentity(memory)) === incomingIdentity\n );\n if (!exactMatch) return false;\n if (before === null) return true;\n if (!this.isTombstoneBlockedMemory(before)) return false;\n const beforeIdentity = streamedIdentity ? captureIdentityHash(before) : this.offlineSyncMemoryIdentity(before);\n if (beforeIdentity !== incomingIdentity) return false;\n return streamedIdentity\n ? isDeepStrictEqual(before.frontmatter, incoming.frontmatter)\n : before.content === incoming.content && isDeepStrictEqual(before.frontmatter, incoming.frontmatter);\n }\n\n private async invalidateAfterOfflineSyncMutation(filePath: string, ownedMarker?: string): Promise<void> {\n this.invalidateAllMemoriesCache();\n this.invalidateKnowledgeIndexCache();\n this.markFactHashIndexNotAuthoritative();\n if (filePath.includes(`${path.sep}cold${path.sep}`)) {\n this.invalidateColdMemoriesCache();\n }\n await this.rebuildTombstoneBlockedCaptureAfterInvalidationForPath(filePath, ownedMarker);\n if (filePath.includes(`${path.sep}artifacts${path.sep}`)) {\n this.bumpArtifactWriteVersion();\n }\n this.bumpMemoryStatusVersion();\n }\n\n protected async runTombstoneBlockedArchive(\n memory: MemoryFile,\n task: (current: MemoryFile, markDurable: () => void) => Promise<string | null>\n ): Promise<string | null> {\n if (!this.isTombstoneBlockedMemory(memory) && !isQueuedReviewMemory(memory)) {\n return await task(memory, () => {});\n }\n let archivedPath: string | null = null;\n const archived = await this.runTombstoneBlockedInvalidation(\n memory,\n async (current, rebuildMarker, markDurable) => {\n let archiveDurable = false;\n const markArchiveDurable = () => {\n archiveDurable = true;\n markDurable();\n };\n archivedPath = await task(current, markArchiveDurable);\n if (rebuildMarker !== undefined && (archivedPath !== null || archiveDurable)) {\n await this.rebuildTombstoneBlockedCaptureAfterInvalidation(rebuildMarker);\n } else if (rebuildMarker !== undefined) {\n const index = this.getTombstoneBlockedCaptureIndex();\n try { await index.discardWrite(rebuildMarker); } catch { index.markUntrusted(); }\n }\n return archivedPath !== null;\n }\n );\n return archived ? archivedPath : null;\n }\n\n protected async runTombstoneBlockedInvalidation(\n memory: MemoryFile,\n task: (\n current: MemoryFile,\n rebuildMarker: string | undefined,\n markDurable: () => void\n ) => Promise<boolean>,\n propagateErrors = false,\n coordinate = false\n ): Promise<boolean> {\n let lockIdentity =\n coordinate || this.isTombstoneBlockedMemory(memory) || isQueuedReviewMemory(memory)\n ? this.offlineSyncMemoryIdentity(memory)\n : undefined;\n for (;;) {\n const attempt = async () => {\n const options = this.tombstoneBlockedCaptureIndexOptions();\n const current =\n (await this.readMemoryByPath(memory.path)) ??\n (await options.readAllMemories()).find((candidate) => candidate.frontmatter.id === memory.frontmatter.id) ??\n (await options.readAllColdMemories()).find((candidate) => candidate.frontmatter.id === memory.frontmatter.id);\n if (!current) return { result: false };\n const blocked = this.isTombstoneBlockedMemory(current);\n const queuedReview = isQueuedReviewMemory(current);\n if (blocked || queuedReview) {\n const currentIdentity = this.offlineSyncMemoryIdentity(current);\n if (lockIdentity !== currentIdentity) return { retryIdentity: currentIdentity };\n }\n const blockedIndex = blocked ? this.getTombstoneBlockedCaptureIndex() : null;\n let rebuildMarker: string | undefined;\n let durable = false;\n try {\n if (blockedIndex) rebuildMarker = await blockedIndex.prepareWrite();\n const result = await task(current, rebuildMarker, () => {\n durable = true;\n });\n if (!result && rebuildMarker && !durable && blockedIndex) {\n try {\n await blockedIndex.discardWrite(rebuildMarker);\n } catch {\n blockedIndex.markUntrusted();\n }\n }\n return { result };\n } catch (error) {\n if (rebuildMarker && !durable && blockedIndex) {\n try {\n await blockedIndex.discardWrite(rebuildMarker);\n } catch {\n blockedIndex.markUntrusted();\n }\n }\n if (propagateErrors) throw error;\n return { result: false };\n }\n };\n const result =\n lockIdentity === undefined\n ? await attempt()\n : await this.withTombstoneBlockedCaptureWriteLock(attempt, lockIdentity);\n if (result.retryIdentity !== undefined) {\n lockIdentity = result.retryIdentity;\n continue;\n }\n return result.result;\n }\n }\n async deleteMemoryForMaintenance(\n memory: MemoryFile,\n shouldDelete: (current: MemoryFile) => boolean = () => true\n ): Promise<MemoryFile | null> {\n let deleted: MemoryFile | null = null;\n const removed = await this.runTombstoneBlockedInvalidation(\n memory,\n async (current, rebuildMarker, markDurable) => {\n if (!shouldDelete(current)) return false;\n await unlink(current.path);\n markDurable();\n deleted = current;\n const memoryDir = this.tombstoneBlockedCaptureIndexOptions().memoryDir;\n markProjectedMemoryPathInvalid(memoryDir, current.frontmatter.id);\n this.invalidateAllMemoriesCache();\n if (current.path.includes(`${path.sep}cold${path.sep}`)) {\n this.invalidateColdMemoriesCache();\n }\n await this.rebuildTombstoneBlockedCaptureAfterInvalidation(rebuildMarker);\n this.bumpMemoryCorpusVersion();\n this.bumpMemoryStatusVersion();\n return true;\n },\n true,\n true\n );\n return removed ? deleted : null;\n }\n\n protected async runTombstoneBlockedOfflineSyncMutation(\n target: string,\n after: MemoryFile | null,\n write: () => Promise<void>,\n coordinate = false,\n streamedIdentity?: StreamedOfflineSyncIdentity\n ): Promise<void> {\n for (;;) {\n const before = await this.readMemoryByPath(target);\n const targetIdentity = this.isTombstoneBlockedMemory(before)\n ? this.offlineSyncMemoryIdentity(before)\n : undefined;\n const blocked = coordinate || targetIdentity !== undefined || this.isTombstoneBlockedMemory(after);\n const identities = [\n ...(targetIdentity === undefined ? [] : [targetIdentity]),\n ...(!streamedIdentity && this.isTombstoneBlockedMemory(after)\n ? [this.offlineSyncMemoryIdentity(after)]\n : []),\n ...(coordinate ? [target] : []),\n ...(coordinate && after === null && streamedIdentity === undefined ? [\"\"] : []),\n ];\n let retryIdentity: string | undefined;\n const mutate = async (): Promise<void> => {\n const current = await this.readMemoryByPath(target);\n if (this.isTombstoneBlockedMemory(current)) {\n const currentIdentity = this.offlineSyncMemoryIdentity(current);\n if (currentIdentity !== targetIdentity) {\n retryIdentity = currentIdentity;\n return;\n }\n }\n if (\n this.isTombstoneBlockedMemory(after) &&\n (await this.isDuplicateTombstoneBlockedOfflineSync(target, after, streamedIdentity))\n ) {\n return;\n }\n const marker = blocked ? await this.getTombstoneBlockedCaptureIndex().prepareWrite() : undefined;\n let durable = false;\n try {\n await write();\n durable = true;\n if (marker) {\n try {\n await this.getTombstoneBlockedCaptureIndex().commitWrite(marker);\n } catch (err) {\n this.getTombstoneBlockedCaptureIndex().markUntrusted();\n log.warn(`storage.offlineSyncFile committed write marker failed: ${err}`);\n }\n }\n await this.invalidateAfterOfflineSyncMutation(target, marker);\n } catch (err) {\n if (marker && !durable) {\n try {\n await this.getTombstoneBlockedCaptureIndex().discardWrite(marker);\n } catch (cleanupError) {\n this.getTombstoneBlockedCaptureIndex().markUntrusted();\n log.warn(`storage.offlineSyncFile failed to clear write marker: ${cleanupError}`);\n }\n }\n throw err;\n }\n };\n if (!blocked) {\n await mutate();\n } else {\n if (streamedIdentity) {\n await this.withTombstoneBlockedCaptureWriteLockAndHashes(\n mutate,\n identities,\n [streamedIdentity.identityHash]\n );\n } else {\n await this.withTombstoneBlockedCaptureWriteLock(mutate, identities);\n }\n }\n if (retryIdentity !== undefined) continue;\n return;\n }\n }\n\n protected async writeTombstoneBlockedOfflineSyncFile(target: string, content: Buffer): Promise<void> {\n await this.runTombstoneBlockedOfflineSyncMutation(\n target,\n this.tombstoneBlockedCaptureIndexOptions().parseMemory?.(target, content) ?? null,\n () => this.writeStorageSecureFile(target, content)\n );\n }\n\n private async prepareTombstoneBlockedOfflineSyncChunks(\n target: string,\n chunks: AsyncIterable<Buffer>\n ): Promise<PreparedOfflineSyncChunks> {\n const parseMemory = this.tombstoneBlockedCaptureIndexOptions().parseMemory;\n if (!parseMemory) return { after: null, chunks };\n\n const iterator = chunks[Symbol.asyncIterator]();\n const buffered: Buffer[] = [];\n let bufferedBytes = 0;\n let firstOverflow: Buffer | undefined;\n for (;;) {\n const next = await iterator.next();\n if (next.done) break;\n if (bufferedBytes + next.value.length > OFFLINE_SYNC_BUFFER_LIMIT_BYTES) {\n firstOverflow = next.value;\n break;\n }\n bufferedBytes += next.value.length;\n buffered.push(next.value);\n }\n const overflow = firstOverflow;\n if (overflow === undefined) {\n let after: MemoryFile | null = null;\n try {\n after = parseMemory(target, Buffer.concat(buffered));\n } catch {\n after = null;\n }\n const replay = (async function* (): AsyncIterable<Buffer> {\n for (const chunk of buffered) yield chunk;\n })();\n return { after, chunks: replay };\n }\n\n const options = this.tombstoneBlockedCaptureIndexOptions();\n const stagePath = path.join(\n options.stateDir,\n \"tombstone-blocked-capture\",\n \"offline-sync-staging\",\n `${randomUUID()}.stage`\n );\n const stagedChunks = (async function* (): AsyncIterable<Buffer> {\n for (const chunk of buffered) yield chunk;\n yield overflow;\n for (;;) {\n const next = await iterator.next();\n if (next.done) break;\n yield next.value;\n }\n })();\n try {\n await writeMaybeEncryptedFileFromChunks(\n stagePath,\n stagedChunks,\n options.secureStoreWriteKeyProvider(),\n { atomic: false },\n options.memoryDir\n );\n return await this.describeStagedOfflineSyncChunks(target, stagePath, parseMemory);\n } catch (err) {\n await unlink(stagePath).catch(() => {});\n throw err;\n }\n }\n\n private async describeStagedOfflineSyncChunks(\n target: string,\n stagePath: string,\n parseMemory: OfflineSyncMemoryParser\n ): Promise<PreparedOfflineSyncChunks> {\n const options = this.tombstoneBlockedCaptureIndexOptions();\n const key = options.secureStoreKeyProvider();\n const bodyOffset = await findStagedBodyOffset(stagePath, key, options.memoryDir);\n if (bodyOffset === null) {\n return { after: null, chunks: this.readStagedOfflineSyncChunks(stagePath), stagePath };\n }\n const prefix = await readStagedPrefix(stagePath, bodyOffset, key, options.memoryDir);\n let after: MemoryFile | null = null;\n try {\n after = parseMemory(target, prefix.subarray(0, bodyOffset));\n } catch {\n after = null;\n }\n if (after === null) {\n return { after: null, chunks: this.readStagedOfflineSyncChunks(stagePath), stagePath };\n }\n const streamedIdentity = await this.computeStagedOfflineSyncIdentity(stagePath, bodyOffset, after);\n return {\n after,\n chunks: this.readStagedOfflineSyncChunks(stagePath),\n stagePath,\n streamedIdentity,\n };\n }\n\n private async computeStagedOfflineSyncIdentity(\n stagePath: string,\n bodyOffset: number,\n after: MemoryFile\n ): Promise<StreamedOfflineSyncIdentity> {\n let normalizedLength = 0;\n const countNormalizer = createExplicitContentNormalizer((chunk) => {\n normalizedLength += chunk.length;\n });\n const options = this.tombstoneBlockedCaptureIndexOptions();\n const key = options.secureStoreKeyProvider();\n await forEachStagedTextChunk(\n stagePath,\n bodyOffset,\n (text) => countNormalizer.push(text),\n key,\n options.memoryDir\n );\n countNormalizer.finish();\n\n const category = after.frontmatter.category;\n const connector = after.frontmatter.sourceConnector?.trim() ?? \"\";\n const encode = (label: string, value: string): string => `${label} ${value.length} ${value}`;\n const identityPrefix =\n [encode(\"category\", category), encode(\"connector\", connector), `content ${normalizedLength} `].join(\" \");\n const identityHash = createHash(\"sha256\");\n identityHash.update(identityPrefix);\n const identityNormalizer = createExplicitContentNormalizer((chunk) => identityHash.update(chunk));\n await forEachStagedTextChunk(\n stagePath,\n bodyOffset,\n (text) => identityNormalizer.push(text),\n key,\n options.memoryDir\n );\n identityNormalizer.finish();\n return {\n identityHash: identityHash.digest(\"hex\"),\n };\n }\n\n private async *readStagedOfflineSyncChunks(stagePath: string): AsyncIterable<Buffer> {\n const options = this.tombstoneBlockedCaptureIndexOptions();\n for await (const chunk of readMaybeEncryptedFileFromChunks(\n stagePath,\n options.secureStoreKeyProvider(),\n options.memoryDir\n )) {\n yield chunk;\n }\n }\n\n protected async writeTombstoneBlockedOfflineSyncFileChunks(\n target: string,\n chunks: AsyncIterable<Buffer>\n ): Promise<void> {\n const coordinate = this.getTombstoneBlockedCaptureIndex().shouldRebuildAfterInvalidationForPath(target);\n const prepared: PreparedOfflineSyncChunks = coordinate\n ? await this.prepareTombstoneBlockedOfflineSyncChunks(target, chunks)\n : { after: null, chunks };\n const streamedIdentity = prepared.streamedIdentity;\n const mutate = async (): Promise<void> => {\n try {\n await this.runTombstoneBlockedOfflineSyncMutation(\n target,\n prepared.after,\n () => this.writeStorageSecureFileChunks(target, prepared.chunks),\n coordinate,\n streamedIdentity\n );\n } finally {\n if (prepared.stagePath !== undefined) await unlink(prepared.stagePath).catch(() => {});\n }\n };\n await mutate();\n }\n\n async writeOfflineSyncFile(filePath: string, content: Buffer): Promise<void> {\n const target = this.assertManagedStoragePath(filePath, \"storage.writeOfflineSyncFile\");\n await this.writeTombstoneBlockedOfflineSyncFile(target, content);\n }\n\n async writeOfflineSyncFileChunks(filePath: string, chunks: AsyncIterable<Buffer>): Promise<void> {\n const target = this.assertManagedStoragePath(filePath, \"storage.writeOfflineSyncFileChunks\");\n await this.writeTombstoneBlockedOfflineSyncFileChunks(target, chunks);\n }\n\n async deleteOfflineSyncFile(filePath: string): Promise<void> {\n const target = this.assertManagedStoragePath(filePath, \"storage.deleteOfflineSyncFile\");\n await this.deleteTombstoneBlockedOfflineSyncFile(target);\n }\n\n protected async deleteTombstoneBlockedOfflineSyncFile(target: string): Promise<void> {\n await this.runTombstoneBlockedOfflineSyncMutation(target, null, async () => {\n await unlink(target).catch((error: unknown) => {\n if (isErrnoCode(error, \"ENOENT\")) return;\n throw error;\n });\n });\n }\n\n async checkTombstoneBlockedExplicitCapture(\n ...args: [string, string, string?]\n ): Promise<{ has: boolean; authoritative: boolean }> {\n return this.getTombstoneBlockedCaptureIndex().check(...args);\n }\n\n async hasTombstoneBlockedExplicitCapture(...args: [string, string, string?]): Promise<boolean> {\n return this.getTombstoneBlockedCaptureIndex().has(...args);\n }\n\n async isTombstoneBlockedExplicitCaptureIndexAuthoritative(): Promise<boolean> {\n return this.getTombstoneBlockedCaptureIndex().isAuthoritative();\n }\n\n protected async rebuildTombstoneBlockedCaptureAfterInvalidationForPath(\n filePath: string,\n ownedMarker?: string\n ): Promise<void> {\n const index = this.getTombstoneBlockedCaptureIndex();\n if (index.shouldRebuildAfterInvalidationForPath(filePath)) {\n await this.rebuildTombstoneBlockedCaptureAfterInvalidation(ownedMarker);\n }\n }\n protected async rebuildTombstoneBlockedCaptureAfterInvalidation(ownedMarker?: string): Promise<void> {\n await this.getTombstoneBlockedCaptureIndex().rebuildAfterInvalidation(ownedMarker);\n }\n}\n","import { execFile } from \"node:child_process\";\nimport { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, readdir, rename, stat, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\nimport { log } from \"../logger.js\";\nimport type { MemoryFile, MemoryFrontmatter } from \"../types.js\";\nimport { RECALL_FALLBACK_DIRS } from \"../utils/category-dir.js\";\nimport { isErrnoCode } from \"../utils/errno.js\";\nimport { withHeldFileLock } from \"../utils/serialize-mutations.js\";\nimport {\n CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS,\n ContentHashIndex,\n type ContentHashIndexLockOptions,\n} from \"./content-hash-index.js\";\nimport {\n buildExplicitCaptureDedupKey,\n TombstoneBlockedCaptureWriteLock,\n} from \"./tombstone-blocked-capture-mutation.js\";\nimport type { OfflineSyncMemoryParser } from \"./tombstone-blocked-capture-sync.js\";\nexport {\n buildExplicitCaptureDedupKey,\n isTombstoneBlockedCaptureWriteLockBusy,\n normalizeExplicitCaptureContent,\n} from \"./tombstone-blocked-capture-mutation.js\";\nexport {\n parseTombstoneBlockedOfflineSyncMemory,\n type OfflineSyncMemoryParser,\n TombstoneBlockedCaptureIndexHost,\n} from \"./tombstone-blocked-capture-sync.js\";\n\nconst REBUILD_MAX_ATTEMPTS = 3;\nconst execFileAsync = promisify(execFile);\nconst REBUILD_RETRY_BASE_MS = 50;\n\nconst ABANDONED_MARKER_MIN_AGE_MS = 60_000;\nconst MARKER_PROCESS_START_TOLERANCE_MS = 2_000;\nconst DOTNET_UNIX_EPOCH_TICKS = 621_355_968_000_000_000;\nconst MARKER_PROCESS_STARTED_AT_MS = Date.now() - process.uptime() * 1000;\n\n\ntype RebuildMarker = {\n path: string;\n committed: boolean;\n pid?: number;\n ownerId?: string;\n createdAt?: number;\n processStartedAtMs?: number;\n malformed?: boolean;\n};\n\n\nexport type TombstoneBlockedCaptureIndexOptions = {\n readonly stateDir: string;\n readonly memoryDir: string;\n readonly secureStoreKeyProvider: () => Buffer | null;\n readonly secureStoreWriteKeyProvider: () => Buffer | null;\n readonly lockOptions: () => ContentHashIndexLockOptions;\n readonly readAllMemories: () => Promise<MemoryFile[]>;\n readonly readAllColdMemories: () => Promise<MemoryFile[]>;\n readonly parseMemory?: OfflineSyncMemoryParser;\n readonly withHeldFileLock?: typeof withHeldFileLock;\n};\n\n/**\n * Durable targeted deduplication for tombstone-blocked explicit captures.\n *\n * The active fact-hash index intentionally excludes blocked rows. This small\n * companion index keeps the blocked identity visible without making every\n * authoritative active-index miss scan the full corpus.\n */\nexport class TombstoneBlockedCaptureIndex {\n private readonly options: TombstoneBlockedCaptureIndexOptions;\n private index: ContentHashIndex | null = null;\n private loadPromise: Promise<ContentHashIndex> | null = null;\n private refreshPromise: Promise<ContentHashIndex> | null = null;\n private authoritative = false;\n private readonly captureWriteLock: TombstoneBlockedCaptureWriteLock;\n\n constructor(options: TombstoneBlockedCaptureIndexOptions) {\n this.options = options;\n this.captureWriteLock = new TombstoneBlockedCaptureWriteLock(options);\n }\n\n private createIndex(): ContentHashIndex {\n return new ContentHashIndex(\n path.join(this.options.stateDir, \"tombstone-blocked-capture\"),\n this.options.secureStoreKeyProvider,\n this.options.secureStoreWriteKeyProvider,\n this.options.memoryDir,\n this.options.lockOptions()\n );\n }\n\n private indexPath(): string {\n return path.join(this.options.stateDir, \"tombstone-blocked-capture\", \"fact-hashes.txt\");\n }\n\n private rebuildMarkerPath(): string {\n return path.join(this.options.stateDir, \"tombstone-blocked-capture\", \"rebuild-required\");\n }\n\n private async readRebuildMarkers(): Promise<RebuildMarker[]> {\n try {\n const entries = (await readdir(this.rebuildMarkerPath())).filter((entry) => !entry.startsWith(\".\"));\n return (\n await Promise.all(\n entries.map(async (entry) => {\n const markerPath = path.join(this.rebuildMarkerPath(), entry);\n try {\n const raw = (await readFile(markerPath, \"utf8\")).trim();\n if (raw === \"committed\") return { path: markerPath, committed: true };\n let value: Record<string, unknown>;\n let malformed = false;\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n malformed = true;\n value = {};\n } else {\n value = parsed as Record<string, unknown>;\n }\n } catch {\n malformed = true;\n value = {};\n }\n const state = value.state;\n if (state !== \"pending\" && state !== \"committed\") malformed = true;\n const markerStat = await stat(markerPath).catch(() => null);\n // A torn payload can lose its JSON metadata, but the atomic\n // marker filename and filesystem mtime still identify its owner\n // and age. Preserve both so stale malformed markers can be\n // reaped without treating a fresh pre-commit gap as abandoned.\n const createdAt =\n typeof value.createdAt === \"number\" && Number.isFinite(value.createdAt)\n ? value.createdAt\n : markerStat?.mtimeMs;\n if (typeof value.pid !== \"number\" || !Number.isInteger(value.pid)) malformed = true;\n const ownerId =\n typeof value.ownerId === \"string\" && value.ownerId.length > 0\n ? value.ownerId\n : path.basename(markerPath);\n if (typeof value.ownerId !== \"string\" || value.ownerId.length === 0) malformed = true;\n const processStartedAtMs =\n typeof value.processStartedAtMs === \"number\" &&\n Number.isFinite(value.processStartedAtMs) &&\n value.processStartedAtMs > 0\n ? value.processStartedAtMs\n : undefined;\n return {\n path: markerPath,\n committed: state === \"committed\",\n ...(typeof value.pid === \"number\" && Number.isInteger(value.pid) ? { pid: value.pid } : {}),\n ...(ownerId.length > 0 ? { ownerId } : {}),\n ...(typeof createdAt === \"number\" && Number.isFinite(createdAt) ? { createdAt } : {}),\n ...(processStartedAtMs === undefined ? {} : { processStartedAtMs }),\n ...(malformed ? { malformed: true } : {}),\n };\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return null;\n throw err;\n }\n })\n )\n ).filter((marker): marker is RebuildMarker => marker !== null);\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) return [];\n throw err;\n }\n }\n private async isAbandonedMarker(marker: RebuildMarker): Promise<boolean> {\n if (marker.committed || marker.createdAt === undefined) return false;\n if (Date.now() - marker.createdAt < ABANDONED_MARKER_MIN_AGE_MS) return false;\n if (marker.malformed) return true;\n return marker.pid !== undefined && !(await this.isProcessAlive(marker.pid, marker.processStartedAtMs));\n }\n\n private async isProcessAlive(pid: number, processStartedAtMs?: number): Promise<boolean> {\n try {\n process.kill(pid, 0);\n } catch (err) {\n return !isErrnoCode(err, \"ESRCH\");\n }\n if (processStartedAtMs === undefined) return true;\n const runningStartedAtMs = await this.readProcessStartedAtMs(pid);\n if (runningStartedAtMs === null) return true;\n return runningStartedAtMs <= processStartedAtMs + MARKER_PROCESS_START_TOLERANCE_MS;\n }\n private async readProcessStartedAtMs(pid: number): Promise<number | null> {\n try {\n if (process.platform === \"win32\") {\n const { stdout } = await execFileAsync(\n \"powershell.exe\",\n [\n \"-NoLogo\",\n \"-NoProfile\",\n \"-NonInteractive\",\n \"-Command\",\n `(Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks`,\n ],\n { encoding: \"utf8\", timeout: 1_000 }\n );\n const ticks = Number(stdout.trim());\n return Number.isFinite(ticks) ? (ticks - DOTNET_UNIX_EPOCH_TICKS) / 10_000 : null;\n }\n const { stdout } = await execFileAsync(\"ps\", [\"-p\", String(pid), \"-o\", \"lstart=\"], {\n encoding: \"utf8\",\n timeout: 1_000,\n });\n const output = stdout.trim();\n if (!output) return null;\n const startedAtMs = Date.parse(output);\n return Number.isFinite(startedAtMs) ? startedAtMs : null;\n } catch {\n return null;\n }\n }\n\n private async getClearableRebuildMarkerPaths(markers: readonly RebuildMarker[]): Promise<string[]> {\n const paths = await Promise.all(\n markers.map(async (marker) => (marker.committed || (await this.isAbandonedMarker(marker)) ? marker.path : null))\n );\n return paths.filter((markerPath): markerPath is string => markerPath !== null);\n }\n\n private async getRebuildMarkers(): Promise<RebuildMarker[]> {\n return await this.readRebuildMarkers();\n }\n\n // A pending marker protects the pre-commit gap; only committed markers are safe for another writer to clear.\n private async writeMarkerAtomically(markerPath: string, payload: string): Promise<void> {\n const temporaryPath = `${markerPath}.tmp-${process.pid}-${randomUUID()}`;\n try {\n await writeFile(temporaryPath, payload, \"utf8\");\n await rename(temporaryPath, markerPath);\n } finally {\n await unlink(temporaryPath).catch((err: unknown) => {\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n });\n }\n }\n\n private async markRebuildRequired(committed = false): Promise<string> {\n const markerDir = this.rebuildMarkerPath();\n await mkdir(markerDir, { recursive: true });\n const ownerId = randomUUID();\n const markerPath = path.join(markerDir, ownerId);\n await this.writeMarkerAtomically(\n markerPath,\n `${JSON.stringify({\n state: committed ? \"committed\" : \"pending\",\n pid: process.pid,\n ownerId,\n createdAt: Date.now(),\n processStartedAtMs: MARKER_PROCESS_STARTED_AT_MS,\n })}\\n`\n );\n return markerPath;\n }\n\n private async markRebuildCommitted(markerPath: string): Promise<void> {\n await this.writeMarkerAtomically(\n markerPath,\n `${JSON.stringify({\n state: \"committed\",\n pid: process.pid,\n ownerId: path.basename(markerPath),\n createdAt: Date.now(),\n processStartedAtMs: MARKER_PROCESS_STARTED_AT_MS,\n })}\\n`\n );\n }\n\n private async clearRebuildRequired(markerPaths: readonly string[]): Promise<void> {\n await Promise.all(\n markerPaths.map(async (markerPath) => {\n try {\n await unlink(markerPath);\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n }\n })\n );\n }\n\n private async hasRebuildRequired(excludedMarkers: readonly string[] = []): Promise<boolean> {\n const excluded = new Set(excludedMarkers);\n return (await this.getRebuildMarkers()).some((marker) => !excluded.has(marker.path));\n }\n\n private async setAuthoritative(rebuilt: boolean): Promise<void> {\n this.authoritative = rebuilt && !(await this.getRebuildMarkers()).some((marker) => !marker.committed);\n }\n private isBlocked(memory: MemoryFile): boolean {\n return memory.frontmatter.status === \"pending_review\" && Boolean(memory.frontmatter.blockedBy);\n }\n\n private async rebuild(index: ContentHashIndex): Promise<boolean> {\n return await index.rebuildUnderLock(async () => {\n index.clear();\n const existing = [...(await this.options.readAllMemories()), ...(await this.options.readAllColdMemories())];\n for (const memory of existing) {\n if (!this.isBlocked(memory)) continue;\n index.add(\n buildExplicitCaptureDedupKey(memory.content, memory.frontmatter.category, memory.frontmatter.sourceConnector)\n );\n }\n });\n }\n\n private async hasPersistedIndexOrMarkers(): Promise<boolean> {\n try {\n await stat(this.indexPath());\n return true;\n } catch (err) {\n if (!isErrnoCode(err, \"ENOENT\")) throw err;\n return (await this.readRebuildMarkers()).length > 0;\n }\n }\n\n private async getIndex(): Promise<ContentHashIndex> {\n if (this.index) return this.index;\n if (!this.loadPromise) {\n const index = this.createIndex();\n this.loadPromise = (async () => {\n await index.load();\n const rebuildMarkers = await this.getRebuildMarkers();\n const clearableRebuildMarkers = await this.getClearableRebuildMarkerPaths(rebuildMarkers);\n let persisted = true;\n try {\n await stat(this.indexPath());\n if (rebuildMarkers.length > 0) persisted = false;\n } catch (err) {\n if (isErrnoCode(err, \"ENOENT\")) persisted = false;\n else throw err;\n }\n if (!persisted) {\n const lockOptions = this.options.lockOptions();\n const maxAttempts = lockOptions.retryMaxAttempts ?? REBUILD_MAX_ATTEMPTS;\n const baseMs = lockOptions.retryBaseMs ?? REBUILD_RETRY_BASE_MS;\n let rebuilt = false;\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n rebuilt = await this.rebuild(index);\n if (rebuilt || attempt === maxAttempts - 1) break;\n const wait = Math.min(baseMs * 2 ** attempt, CONTENT_HASH_INDEX_RETRY_MAX_DELAY_MS);\n const delayState = Promise.withResolvers<void>();\n setTimeout(delayState.resolve, wait);\n await delayState.promise;\n }\n if (!rebuilt) {\n throw new Error(\"tombstone-blocked capture index rebuild lock unavailable\");\n }\n await this.clearRebuildRequired(clearableRebuildMarkers);\n }\n await this.setAuthoritative(true);\n this.index = index;\n return index;\n })().catch((err) => {\n this.loadPromise = null;\n throw err;\n });\n }\n return this.loadPromise;\n }\n\n async withCaptureWriteLock<T>(task: () => Promise<T>, identity?: string | readonly string[]): Promise<T> {\n return await this.captureWriteLock.withCaptureWriteLock(task, identity);\n }\n\n async withCaptureWriteLockHashes<T>(task: () => Promise<T>, identityHashes: readonly string[]): Promise<T> {\n return await this.captureWriteLock.withCaptureWriteLockHashes(task, identityHashes);\n }\n async withCaptureWriteLockAndHashes<T>(\n task: () => Promise<T>,\n identity: string | readonly string[] | undefined,\n identityHashes: readonly string[]\n ): Promise<T> {\n return await this.captureWriteLock.withCaptureWriteLockAndHashes(task, identity, identityHashes);\n }\n\n private async reload(): Promise<ContentHashIndex> {\n if (!this.refreshPromise) {\n this.refreshPromise = (async () => {\n this.index = null;\n this.loadPromise = null;\n return await this.getIndex();\n })().finally(() => {\n this.refreshPromise = null;\n });\n }\n return this.refreshPromise;\n }\n\n /** Return blocked-row membership and authority from one index snapshot. */\n async check(\n content: string,\n category: string,\n sourceConnector?: string\n ): Promise<{ has: boolean; authoritative: boolean }> {\n const key = buildExplicitCaptureDedupKey(content, category, sourceConnector);\n let index = await this.getIndex();\n if ((await this.hasRebuildRequired()) || !(await index.isDiskFingerprintCurrent())) {\n index = await this.reload();\n }\n return { has: index.has(key), authoritative: this.authoritative };\n }\n\n /** Return whether a blocked row matches this explicit-capture identity. */\n async has(content: string, category: string, sourceConnector?: string): Promise<boolean> {\n return (await this.check(content, category, sourceConnector)).has;\n }\n\n /** Return whether the in-memory index is safe to answer an authoritative miss. */\n async isAuthoritative(): Promise<boolean> {\n const index = await this.getIndex();\n if ((await this.hasRebuildRequired()) || !(await index.isDiskFingerprintCurrent())) {\n await this.reload();\n }\n return this.authoritative;\n }\n\n /** Reserve durable rebuild intent before a blocked memory file is committed. */\n async prepareWrite(): Promise<string> {\n return this.markRebuildRequired();\n }\n\n /** Mark a prepared write committed once its memory file is durable. */\n async commitWrite(rebuildMarker: string): Promise<void> {\n await this.markRebuildCommitted(rebuildMarker);\n }\n\n /** Drop a pending marker when the protected memory write never commits. */\n async discardWrite(rebuildMarker: string): Promise<void> {\n await this.clearRebuildRequired([rebuildMarker]);\n }\n\n /** Add a newly persisted blocked row to the durable targeted index. */\n async add(memory: MemoryFile, rebuildMarker?: string): Promise<void> {\n if (!this.isBlocked(memory)) return;\n let index = await this.getIndex();\n if (\n (await this.hasRebuildRequired(rebuildMarker ? [rebuildMarker] : [])) ||\n !(await index.isDiskFingerprintCurrent())\n ) {\n index = await this.reload();\n }\n index.add(\n buildExplicitCaptureDedupKey(memory.content, memory.frontmatter.category, memory.frontmatter.sourceConnector)\n );\n const marker = rebuildMarker ?? (await this.markRebuildRequired(true));\n if (rebuildMarker) await this.markRebuildCommitted(rebuildMarker);\n await index.saveMergingWithDisk();\n await index.flushReconcileRetry();\n if (index.hasPendingChanges) {\n this.markUntrusted();\n return;\n }\n await this.clearRebuildRequired([marker]);\n await this.setAuthoritative(true);\n }\n\n /** Rebuild the index after a blocked row changes or is removed. */\n async rebuildIfLoaded(ownedMarker?: string): Promise<void> {\n const index = this.index ?? ((await this.hasPersistedIndexOrMarkers()) ? this.getIndex() : null);\n if (!index) return;\n const rebuildMarkers = await this.getRebuildMarkers();\n const rebuildMarker = await this.markRebuildRequired(true);\n const clearableRebuildMarkers = await this.getClearableRebuildMarkerPaths(rebuildMarkers);\n const rebuilt = await this.rebuild(await index);\n if (rebuilt) {\n await this.clearRebuildRequired([\n ...clearableRebuildMarkers,\n ...(ownedMarker === undefined || clearableRebuildMarkers.includes(ownedMarker) ? [] : [ownedMarker]),\n rebuildMarker,\n ]);\n }\n await this.setAuthoritative(rebuilt);\n }\n\n /** Rebuild when either side of a write is blocked and its identity changed. */\n async sync(before: MemoryFile, after: MemoryFile, rebuildMarker?: string): Promise<void> {\n const beforeBlocked = this.isBlocked(before);\n const afterBlocked = this.isBlocked(after);\n if (!beforeBlocked && !afterBlocked) return;\n const beforeKey = buildExplicitCaptureDedupKey(\n before.content,\n before.frontmatter.category,\n before.frontmatter.sourceConnector\n );\n const afterKey = buildExplicitCaptureDedupKey(\n after.content,\n after.frontmatter.category,\n after.frontmatter.sourceConnector\n );\n if (beforeBlocked && afterBlocked && beforeKey === afterKey) {\n if (rebuildMarker) {\n await this.markRebuildCommitted(rebuildMarker);\n await this.clearRebuildRequired([rebuildMarker]);\n }\n return;\n }\n const existingMarkers = await this.getRebuildMarkers();\n const marker = rebuildMarker ?? (await this.markRebuildRequired(true));\n const clearableExistingMarkers = await this.getClearableRebuildMarkerPaths(existingMarkers);\n if (rebuildMarker) await this.markRebuildCommitted(rebuildMarker);\n const rebuilt = await this.rebuild(await this.getIndex());\n if (rebuilt) {\n await this.clearRebuildRequired([\n ...clearableExistingMarkers,\n marker,\n ]);\n }\n await this.setAuthoritative(rebuilt);\n }\n markUntrusted(): void {\n this.authoritative = false;\n }\n\n private async failOpen(operation: string, action: () => Promise<void>): Promise<void> {\n try {\n await action();\n } catch (err) {\n this.markUntrusted();\n log.warn(`storage.${operation} completed but failed to update tombstone-blocked capture index: ${err}`);\n }\n }\n\n async addWrittenMemory(\n pathname: string,\n frontmatter: MemoryFrontmatter,\n content: string,\n rebuildMarker?: string\n ): Promise<void> {\n await this.failOpen(\"writeMemory\", () => this.add({ path: pathname, frontmatter, content }, rebuildMarker));\n }\n\n async rebuildAfterInvalidation(ownedMarker?: string): Promise<void> {\n await this.failOpen(\"invalidateMemory\", () => this.rebuildIfLoaded(ownedMarker));\n }\n\n async syncUpdatedMemory(\n before: MemoryFile,\n frontmatter: MemoryFrontmatter,\n content: string,\n rebuildMarker?: string\n ): Promise<void> {\n await this.failOpen(\"updateMemory\", () => this.sync(before, { ...before, frontmatter, content }, rebuildMarker));\n }\n\n async syncUpdatedFrontmatter(\n before: MemoryFile,\n frontmatter: MemoryFrontmatter,\n rebuildMarker?: string\n ): Promise<void> {\n await this.failOpen(\"writeMemoryFrontmatter\", () => this.sync(before, { ...before, frontmatter }, rebuildMarker));\n }\n shouldRebuildAfterInvalidationForPath(filePath: string): boolean {\n const relative = path.relative(path.resolve(this.options.memoryDir), path.resolve(filePath));\n const category = relative.split(path.sep)[0];\n return category === \"cold\" || RECALL_FALLBACK_DIRS.includes(category);\n }\n}\n"],"mappings":"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","names":["mkdir","path","path","mkdir","createHash","randomUUID","unlink","path","mkdir","stat","path","path","stat","stdout","mkdir","path","result","unlink","randomUUID","createHash"]}
|