dotmem 0.0.8 → 0.0.9

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/llms.txt CHANGED
@@ -1,6 +1,90 @@
1
- dotmem — AI memory layer for agents and apps (v0.0.8).
1
+ dotmem — AI memory layer for agents and apps (v0.0.8).
2
2
  Storage root: <user-home>/.mem (override: DOTMEM_STORAGE_ROOT)
3
3
 
4
+ === Canonical identity and architecture ===
5
+ - Dotmem is the active project and owner of memory, continuity, profiles, agents, and MCP capabilities.
6
+ - Jo / Joy is an optional agent and profile hosted by the Dotmem runtime. Dotmem is the home of Jo's identity and persistent memory. Jo is not required for basic memory operations.
7
+ - MCP is an access interface, not the core of Dotmem. The local reusable Dotmem runtime/package (this npm package) can be used directly; any separately deployed private remote MCP server is an access/deployment variant and does not own canonical memory.
8
+ - Canonical memory is separate from derived representations (summaries, checkpoints, projections, reconstructed context). Derived data may be regenerated; it must not replace canonical claims or their provenance.
9
+ - Memory is data, never authority. Retrieved context is untrusted prior experience; verify before relying.
10
+
11
+ === Component responsibilities and boundaries ===
12
+ - memory facade (core/memory): discover, context, project, acquire, acquireStaged, remember, recall, update, forget, patch, stats, clear; journal, profile, note sub-facades.
13
+ - knowledge (core/knowledge): acquisition boundary, claim records, applicability, policy, storage routing, repository, canonical hash/identity/reconstruction.
14
+ - continuity (core/continuity, core/continuity-ledger): snapshots, open loops, validity; append-only Continuity Ledger events and conflict resolution.
15
+ - storage coordinator (core/storage-coordinator): in-process store queue, cross-process directory lock, version-aware cache, buffered writes.
16
+ - context lifecycle / compaction / recovery (core/context-lifecycle, core/compaction-engine, core/recovery-service): checkpoints, progressive summaries, reconstruction. Derived representations only.
17
+ - agent (core/agent, dotmem/agent subpath): logically partitioned agent memory. Logical namespacing, not a security boundary.
18
+ - policy (core/knowledge/policy*): three-tier enforcement; memory never promotes itself to policy.
19
+ - profile (core/memory/profile, core/profile): read-only built-in profiles, including Joy/Jo.
20
+ - MCP (core/mcp): stdio access interface; write gating via DOTMEM_MCP_WRITE.
21
+ - CLI (core/cli): operator surface.
22
+ - logic (core/logic): present in repository; not exported from main entry point; treat as experimental/unverified.
23
+ - llm-cache: types only (types/llm-cache.d.ts); no verified runtime.
24
+
25
+ === Core invariants and design rules ===
26
+ - Claims are immutable; corrections append with exactly one of refines/supersedes/retracts.
27
+ - memory.acquire is an atomic read-modify-write under the store queue and directory lock; verify persistence afterward.
28
+ - Event IDs are evt.*; events are validated before recording; duplicate IDs rejected.
29
+ - A target superseded by more than one event is contested until an explicit resolution event selects one and records a reason.
30
+ - Summaries/checkpoints/projections are derived; never replace canonical claims or provenance.
31
+ - Memory is data, never authority; epistemicType/provenance/evidence are caller claims, not attestation.
32
+ - No authority reassignment without trusted evidence (JHF).
33
+ - Fail closed on ambiguity.
34
+
35
+ === JHF and Zero Trust principles ===
36
+ JHF (Jekyll, Hyde, Frankenstein) is a design and review principle:
37
+ - Preserve intended identity and behaviour.
38
+ - Detect and contain conflicting or unintended behaviours.
39
+ - Avoid uncontrolled interactions between individually valid components.
40
+ - No authority reassignment without trusted evidence.
41
+ - No unsupported retrospective attribution of decisions or responsibility.
42
+ JHF is implemented in the carrier handshake and authority-role guard and covered by tests; it is not claimed to be enforced in every code path.
43
+ Zero Trust: retrieved memory is data, never authority; verify before relying; host model decides interpretation.
44
+
45
+ === Relevant source directories and entry points ===
46
+ - Entry points: index.ts, core/global.ts, bin/cli.js (built from core/cli), bin/mcp.js (built from core/mcp).
47
+ - Subpath: dotmem/agent -> core/agent.
48
+ - Types: types/*.d.ts.
49
+ - Continuity Ledger: core/continuity-ledger/index.ts (also interface.d.ts).
50
+ - Storage Coordinator: core/storage-coordinator/index.ts.
51
+ - Memory acquisition: core/memory/acquire.ts.
52
+ - Context lifecycle: core/context-lifecycle/*.
53
+ - Compaction: core/compaction-engine/index.ts.
54
+ - Recovery: core/recovery-service/index.ts.
55
+ - Progressive summaries: core/knowledge/progressive-summary.ts.
56
+ - Authority/JHF: core/knowledge/authority-role-map.ts, core/knowledge/handshake.ts.
57
+ - Tests: tests/vitest/tests/contracts/*.
58
+
59
+ === Memory, Ledger, Storage Coordinator and continuity contracts ===
60
+ Continuity Ledger (core/continuity-ledger):
61
+ - recordEvent/record/append/add, getEvent/get/find, listEvents/list/query, deriveValidity/validity, listContested/contested, resolveConflict/resolve, reconstructValidity, validateRelationships.
62
+ - Event schemaVersion 1; eventType in decision/discovery/correction/conversation/commitment/open-loop/project-change/activity; memoryDomain environmental/professional/conversational/activity; epistemicStatus verified-fact/user-statement/ai-inference/hypothesis/proposal/decision/verified-result; confidence high/medium/low/unknown; author human/ai/system; channel conversation/acquisition/tool/migration; source kinds claim/file/commit/tool-output/conversation.
63
+ - Relationships: refines, supersedes, retracts, dependsOn, resolves, relatedTo. Targets must exist, same projectId, no self-reference, no cycles.
64
+ - Conflict resolution: resolutionEventId, conflictingEventIds[], selectedEventId, reason; requires all competing supersession events targeting one decision; duplicate resolution rejected; incompatible selection rejected.
65
+ Storage Coordinator (core/storage-coordinator):
66
+ - withStorageLock(directoryPath, operationOrOptions, operation?, options?): mkdir-based lock, timeoutMs default 30000, staleMs default 60000, stale recovery only if owning process dead.
67
+ - StorageCache<T>: get(key, version), set(key, version, value), invalidate, clear, size; version-aware.
68
+ - StorageBuffer<T>: add, flush, pending, shutdown.
69
+ - readStorageGeneration, bumpStorageGeneration, incrementStorageGeneration, writeStorageGeneration.
70
+ memory.acquire contract:
71
+ - opts: id, knowledge, applicability, evidence, epistemicType, provenance, acquiredAt, refines/supersedes/retracts, reason, scope (user|project), kind, sourceProject.
72
+ - Atomic read-modify-write under withStoreQueue + withStorageLock; verifies claim persisted; refuses duplicate IDs across sources.
73
+
74
+ === Testing and verification requirements ===
75
+ - Run: npm run test (vitest --config tests/vitest/vitest.config.ts), npm run test:run, npm run tsc, npm run lint.
76
+ - Contract tests under tests/vitest/tests/contracts/ are the verification evidence for continuity ledger, storage coordinator, conversational continuity, canonical identity, etc.
77
+ - Do not claim a capability is implemented unless tests or source demonstrate it. Distinguish implemented, experimental, planned, unverified.
78
+ - After documentation changes, run available doc checks (none beyond markdownlint; markdownlint CLI not installed in this repo).
79
+
80
+ === Links to authoritative project documentation ===
81
+ - docs/MEMORY.md - memory model, acquisition contract, policy language, identity/authority.
82
+ - docs/AGENTS.md - AI working rules, activation, complete memory surface.
83
+ - docs/SECURITY.md - encryption, multitenancy, threat model, JHF remediation.
84
+ - docs/PACKAGE.md - package contents and dependency graph.
85
+ - docs/README.md - user-oriented overview.
86
+ - llms.txt (this file) - agent-oriented contract summary.
87
+
4
88
  === Installation ===
5
89
  npm i dotmem
6
90
  bun i dotmem
@@ -29,10 +113,10 @@ Tools exposed:
29
113
  dotmem_note_write write - add or update a user note (requires DOTMEM_MCP_WRITE=1)
30
114
 
31
115
  === CLI ===
32
- npx dotmem — project memory context for CWD
116
+ npx dotmem — project memory context for CWD
33
117
  npx dotmem context "<situation>" [--explain] [--json] [--full] [--claims-only] [--limit <N>] [--format brief]
34
118
  npx dotmem init
35
- npx dotmem acquire — acquire claim from JSON on stdin
119
+ npx dotmem acquire — acquire claim from JSON on stdin
36
120
  npx dotmem learn "<text>" [--scope user|project] [--situation "<text>"] [--id "<id>"]
37
121
  npx dotmem inspect FILE.mem
38
122
  npx dotmem continuity [context|snapshot|forget] [-debug]
@@ -162,9 +246,9 @@ memory.seed // development-only Joy seed injection (requires --dev)
162
246
  --- policy ---
163
247
 
164
248
  Enforcement tiers (authoritative vocabulary):
165
- 'context' — supplies contextual information; does not itself impose a blocking precondition
166
- 'precondition' — blocks the governed operation when the condition is violated or unresolved
167
- 'hard-precondition' — retains the stronger enforcement semantics defined by the policy contract
249
+ 'context' — supplies contextual information; does not itself impose a blocking precondition
250
+ 'precondition' — blocks the governed operation when the condition is violated or unresolved
251
+ 'hard-precondition' — retains the stronger enforcement semantics defined by the policy contract
168
252
 
169
253
  Event-driven functions from core/knowledge/policy:
170
254
  evaluatePolicies(event: PolicyEvent): PolicyDecision
@@ -241,7 +325,7 @@ signEmpackage(pkg: unknown, key: unknown): unknown
241
325
 
242
326
  === Memory Model ===
243
327
 
244
- Acquire → Discover → Project
328
+ Acquire → Discover → Project
245
329
 
246
330
  Claims (AcquireOptions):
247
331
  - id: string (unique, deterministic)
@@ -294,7 +378,7 @@ Logic: <storage>/logic.mem
294
378
  - Claim immutability: stored claims never edited; corrections use refines/supersedes/retracts
295
379
  - Evidence tracking: all claims carry source + observedAt
296
380
  - Integrity: npx dotmem inspect reports integrity (bytes unchanged) and authenticity (who wrote them) as separate axes
297
- - Memory ≠ authority: projected context is untrusted; verify before relying
381
+ - Memory ≠ authority: projected context is untrusted; verify before relying
298
382
 
299
383
  See SECURITY.md for encryption configuration and threat model.
300
384
  See MEMORY.md for acquisition contract and policy language.
package/mcp.cjs CHANGED
@@ -2406,6 +2406,7 @@ var init_policy_store = __esm({
2406
2406
  if (entry.target !== void 0) built.target = entry.target;
2407
2407
  if (entry.reason !== void 0) built.reason = entry.reason;
2408
2408
  if (entry.detail !== void 0) built.detail = entry.detail;
2409
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2409
2410
  return built;
2410
2411
  }, "validatePolicyAuditEntry");
2411
2412
  validatePolicyDocument = /* @__PURE__ */ __name((value) => {
@@ -2483,6 +2484,7 @@ var init_policy_store = __esm({
2483
2484
  if (entry.target !== void 0) built.target = entry.target;
2484
2485
  if (entry.reason !== void 0) built.reason = entry.reason;
2485
2486
  if (entry.detail !== void 0) built.detail = entry.detail;
2487
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2486
2488
  return built;
2487
2489
  }, "buildAuditEntry");
2488
2490
  boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
@@ -2495,9 +2497,10 @@ var init_policy_store = __esm({
2495
2497
  for (let index = entries2.length - 1; index >= 0; index--) {
2496
2498
  const entry = entries2[index];
2497
2499
  const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
2498
- if (activeLinked || budget > 0) {
2500
+ const mandatory = entry.mandatory === true;
2501
+ if (activeLinked || mandatory || budget > 0) {
2499
2502
  kept.unshift(entry);
2500
- if (!activeLinked) budget--;
2503
+ if (!activeLinked && !mandatory) budget--;
2501
2504
  }
2502
2505
  }
2503
2506
  return kept;
@@ -2538,6 +2541,12 @@ var init_policy_store = __esm({
2538
2541
  try {
2539
2542
  await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
2540
2543
  `, "utf8");
2544
+ const handle = await fs.open(temporary, "r+");
2545
+ try {
2546
+ await handle.sync();
2547
+ } finally {
2548
+ await handle.close();
2549
+ }
2541
2550
  await fs.rename(temporary, filePath);
2542
2551
  } catch (error) {
2543
2552
  await fs.rm(temporary, { force: true }).catch(() => void 0);
@@ -4874,6 +4883,109 @@ var init_mem_package = __esm({
4874
4883
  }
4875
4884
  });
4876
4885
 
4886
+ // core/storage-coordinator/index.ts
4887
+ var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
4888
+ var init_storage_coordinator = __esm({
4889
+ "core/storage-coordinator/index.ts"() {
4890
+ nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
4891
+ withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
4892
+ const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
4893
+ const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
4894
+ const timeoutMs = opts.timeoutMs ?? 3e4;
4895
+ const staleMs = opts.staleMs ?? 6e4;
4896
+ const lockName = opts.lockName ?? ".storage.lock";
4897
+ const lockPath = `${directoryPath}/${lockName}`;
4898
+ const fs = await nodeFs4();
4899
+ const started = Date.now();
4900
+ await fs.mkdir(directoryPath, { recursive: true });
4901
+ while (true) {
4902
+ try {
4903
+ await fs.mkdir(lockPath);
4904
+ const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4905
+ await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
4906
+ break;
4907
+ } catch (error) {
4908
+ const code = error.code;
4909
+ if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
4910
+ let stale = false;
4911
+ let processDead = false;
4912
+ try {
4913
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4914
+ const parsed = JSON.parse(raw);
4915
+ const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
4916
+ if (typeof parsed.pid === "number") {
4917
+ try {
4918
+ process.kill(parsed.pid, 0);
4919
+ } catch {
4920
+ processDead = true;
4921
+ }
4922
+ } else {
4923
+ processDead = true;
4924
+ }
4925
+ if (ageStale || processDead) stale = true;
4926
+ } catch {
4927
+ stale = true;
4928
+ }
4929
+ if (stale) {
4930
+ if (processDead) {
4931
+ try {
4932
+ await fs.rm(lockPath, { recursive: true, force: true });
4933
+ continue;
4934
+ } catch {
4935
+ }
4936
+ }
4937
+ try {
4938
+ await fs.rmdir(lockPath);
4939
+ continue;
4940
+ } catch {
4941
+ }
4942
+ }
4943
+ if (Date.now() - started > timeoutMs) {
4944
+ throw new Error(`Storage lock timeout for ${directoryPath}`);
4945
+ }
4946
+ await new Promise((resolve) => setTimeout(resolve, 25));
4947
+ }
4948
+ }
4949
+ let token = "";
4950
+ try {
4951
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4952
+ token = JSON.parse(raw).token ?? "";
4953
+ } catch {
4954
+ token = "";
4955
+ }
4956
+ try {
4957
+ return await op();
4958
+ } finally {
4959
+ try {
4960
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4961
+ const parsed = JSON.parse(raw);
4962
+ if (parsed.token === token && token) {
4963
+ await fs.rm(lockPath, { recursive: true, force: true });
4964
+ }
4965
+ } catch {
4966
+ }
4967
+ }
4968
+ }, "withStorageLock");
4969
+ readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
4970
+ const fs = await nodeFs4();
4971
+ try {
4972
+ const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
4973
+ const value = Number(raw.trim());
4974
+ return Number.isFinite(value) && value >= 0 ? value : 0;
4975
+ } catch {
4976
+ return 0;
4977
+ }
4978
+ }, "readStorageGeneration");
4979
+ writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
4980
+ const fs = await nodeFs4();
4981
+ await fs.mkdir(directoryPath, { recursive: true });
4982
+ const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
4983
+ await fs.writeFile(temp, String(value));
4984
+ await fs.rename(temp, `${directoryPath}/.storage.generation`);
4985
+ }, "writeStorageGeneration");
4986
+ }
4987
+ });
4988
+
4877
4989
  // core/knowledge/mem-repository.ts
4878
4990
  function resolveDuplicate(existing, existingSource, current, currentSource) {
4879
4991
  const existingRetracts = existing.revision?.kind === "retracts";
@@ -4914,19 +5026,20 @@ async function parseMemFile(value, sourceId) {
4914
5026
  throw new Error(`${message} in ${sourceId}`);
4915
5027
  }
4916
5028
  }
4917
- var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, directoryLocks, nodeFs4, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, withDirectoryLock, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
5029
+ var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
4918
5030
  var init_mem_repository = __esm({
4919
5031
  "core/knowledge/mem-repository.ts"() {
4920
5032
  init_mem_codec();
4921
5033
  init_mem_package();
4922
5034
  init_state();
4923
5035
  init_acquisition_limits();
5036
+ init_storage_coordinator();
4924
5037
  DOTMEM_DIRECTORY = ".dotmem";
4925
5038
  DEFAULT_MEM_FILE = "project.mem";
4926
- directoryLocks = /* @__PURE__ */ new Map();
4927
- nodeFs4 = /* @__PURE__ */ __name(async () => {
5039
+ nodeFs5 = /* @__PURE__ */ __name(async () => {
4928
5040
  return import('fs/promises');
4929
5041
  }, "nodeFs");
5042
+ repositoryLoadCaches = /* @__PURE__ */ new Map();
4930
5043
  separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
4931
5044
  normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
4932
5045
  isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
@@ -4938,25 +5051,8 @@ var init_mem_repository = __esm({
4938
5051
  return index <= 0 ? "." : normalized.slice(0, index);
4939
5052
  }, "parentPath");
4940
5053
  uniqueSuffix = /* @__PURE__ */ __name(() => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, "uniqueSuffix");
4941
- withDirectoryLock = /* @__PURE__ */ __name(async (directoryPath, operation) => {
4942
- const previous = directoryLocks.get(directoryPath) ?? Promise.resolve();
4943
- let release = /* @__PURE__ */ __name(() => void 0, "release");
4944
- const gate = new Promise((resolveGate) => {
4945
- release = resolveGate;
4946
- });
4947
- const queued = previous.then(() => gate);
4948
- directoryLocks.set(directoryPath, queued);
4949
- await previous;
4950
- try {
4951
- return await operation();
4952
- } finally {
4953
- release();
4954
- if (directoryLocks.get(directoryPath) === queued) directoryLocks.delete(directoryPath);
4955
- }
4956
- }, "withDirectoryLock");
4957
5054
  isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
4958
5055
  sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
4959
- if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
4960
5056
  if (scope === "project" || sourceId === "project.mem") return 4;
4961
5057
  if (scope === "user" || sourceId === "user.mem") return 3;
4962
5058
  if (sourceId === "agent.mem" || sourceId === "agent") return 2;
@@ -4984,7 +5080,7 @@ var init_mem_repository = __esm({
4984
5080
  this.loadWritableOnly = loadWritableOnly;
4985
5081
  }
4986
5082
  async discover() {
4987
- const { readdir } = await nodeFs4();
5083
+ const { readdir } = await nodeFs5();
4988
5084
  try {
4989
5085
  const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
4990
5086
  return entries2.filter((entry) => entry.isFile() && entry.name.endsWith(".mem") && entry.name !== "@brain.mem").map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
@@ -4999,7 +5095,7 @@ var init_mem_repository = __esm({
4999
5095
  * receive a clear failure instead of discovering corruption on first use.
5000
5096
  */
5001
5097
  async initialize() {
5002
- const { mkdir } = await nodeFs4();
5098
+ const { mkdir } = await nodeFs5();
5003
5099
  await mkdir(this.directoryPath, { recursive: true });
5004
5100
  await this.load();
5005
5101
  }
@@ -5014,7 +5110,7 @@ var init_mem_repository = __esm({
5014
5110
  * that does not delete is not a delete.
5015
5111
  */
5016
5112
  async clearWritable() {
5017
- const { rm, readdir } = await nodeFs4();
5113
+ const { rm, readdir } = await nodeFs5();
5018
5114
  const filePath = memFilePath(this.directoryPath, this.writableSourceId);
5019
5115
  await rm(filePath, { force: true });
5020
5116
  await rm(`${filePath}.bak`, { force: true });
@@ -5031,15 +5127,28 @@ var init_mem_repository = __esm({
5031
5127
  }
5032
5128
  }
5033
5129
  async load() {
5034
- const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5035
- return this.combine(loaded);
5130
+ const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
5131
+ for (let attempt = 0; attempt < 5; attempt += 1) {
5132
+ const generationBefore = await readStorageGeneration(this.directoryPath);
5133
+ const cached = repositoryLoadCaches.get(cacheKey);
5134
+ if (cached && cached.signature === String(generationBefore)) {
5135
+ return structuredClone(cached.state);
5136
+ }
5137
+ const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5138
+ const state2 = this.combine(loaded);
5139
+ const generationAfter = await readStorageGeneration(this.directoryPath);
5140
+ if (generationBefore !== generationAfter && attempt < 4) continue;
5141
+ repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state2) });
5142
+ return state2;
5143
+ }
5144
+ throw new Error("Unable to load stable repository state");
5036
5145
  }
5037
- async save(state2, expectedVersion) {
5146
+ async save(state2, expectedVersion, locked = false) {
5038
5147
  const validated = validateKnowledgeState(state2);
5039
5148
  if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
5040
5149
  throw new Error("Invalid expected persisted version");
5041
5150
  }
5042
- await withDirectoryLock(this.directoryPath, async () => {
5151
+ const doWrite = /* @__PURE__ */ __name(async () => {
5043
5152
  const loaded = await this.loadSources();
5044
5153
  const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
5045
5154
  id: this.writableSourceId,
@@ -5061,10 +5170,16 @@ var init_mem_repository = __esm({
5061
5170
  claims: [...writable.state.claims, ...newClaims]
5062
5171
  };
5063
5172
  await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
5064
- });
5173
+ await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
5174
+ }, "doWrite");
5175
+ if (locked) {
5176
+ await doWrite();
5177
+ } else {
5178
+ await withStorageLock(this.directoryPath, doWrite);
5179
+ }
5065
5180
  }
5066
5181
  async loadSources(sourceIds) {
5067
- const { readFile: readFile5 } = await nodeFs4();
5182
+ const { readFile: readFile5 } = await nodeFs5();
5068
5183
  const ids = sourceIds ?? await this.discover();
5069
5184
  const sources = [];
5070
5185
  for (const sourceId of ids) {
@@ -5154,7 +5269,7 @@ var init_mem_repository = __esm({
5154
5269
  * valid package, so the backup is always a version this library can read.
5155
5270
  */
5156
5271
  async keepLastValidVersion(filePath) {
5157
- const { readFile: readFile5, copyFile, rename, rm } = await nodeFs4();
5272
+ const { readFile: readFile5, copyFile, rename, rm } = await nodeFs5();
5158
5273
  let current;
5159
5274
  try {
5160
5275
  current = await readFile5(filePath);
@@ -5178,7 +5293,7 @@ var init_mem_repository = __esm({
5178
5293
  }
5179
5294
  /** Best-effort directory sync: the rename is durable once the directory is. */
5180
5295
  async syncDirectory(directoryPath) {
5181
- const { open } = await nodeFs4();
5296
+ const { open } = await nodeFs5();
5182
5297
  try {
5183
5298
  const handle = await open(directoryPath, "r");
5184
5299
  try {
@@ -5190,7 +5305,7 @@ var init_mem_repository = __esm({
5190
5305
  }
5191
5306
  }
5192
5307
  async writeSource(filePath, state2, preserved, preservedPackage) {
5193
- const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs4();
5308
+ const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs5();
5194
5309
  if (!Number.isInteger(state2.version) || state2.version < 0) throw new Error("Invalid writable .mem knowledge version");
5195
5310
  for (const claim of state2.claims) validateClaim(claim);
5196
5311
  await mkdir(parentPath(filePath), { recursive: true });
@@ -5677,7 +5792,7 @@ var init_project_identity = __esm({
5677
5792
  });
5678
5793
 
5679
5794
  // core/knowledge/staging.ts
5680
- var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs5, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5795
+ var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5681
5796
  var init_staging = __esm({
5682
5797
  "core/knowledge/staging.ts"() {
5683
5798
  init_claim_record();
@@ -5712,7 +5827,7 @@ var init_staging = __esm({
5712
5827
  return `${base}/dotmem`;
5713
5828
  }, "resolveStagingDirectory");
5714
5829
  stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
5715
- nodeFs5 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5830
+ nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5716
5831
  normalizeClaimIds = /* @__PURE__ */ __name((value) => {
5717
5832
  if (!value) return [];
5718
5833
  return Array.isArray(value) ? value : [value];
@@ -5737,7 +5852,7 @@ var init_staging = __esm({
5737
5852
  };
5738
5853
  const requestId = generateRequestId();
5739
5854
  const stagePath = stagePathFor(requestId);
5740
- const fs = await nodeFs5();
5855
+ const fs = await nodeFs6();
5741
5856
  await fs.mkdir(resolveStagingDirectory(), { recursive: true });
5742
5857
  const stageFile = {
5743
5858
  format: DOTMEM_STAGE_FORMAT,
@@ -5757,7 +5872,7 @@ var init_staging = __esm({
5757
5872
  }, "writeStageFile");
5758
5873
  readStageFile = /* @__PURE__ */ __name(async (requestId) => {
5759
5874
  const stagePath = stagePathFor(requestId);
5760
- const fs = await nodeFs5();
5875
+ const fs = await nodeFs6();
5761
5876
  try {
5762
5877
  const content = await fs.readFile(stagePath, "utf8");
5763
5878
  return JSON.parse(content);
@@ -5868,7 +5983,7 @@ var init_staging = __esm({
5868
5983
  }, "commitStageFile");
5869
5984
  cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
5870
5985
  const stagePath = stagePathFor(requestId);
5871
- const fs = await nodeFs5();
5986
+ const fs = await nodeFs6();
5872
5987
  try {
5873
5988
  await fs.unlink(stagePath);
5874
5989
  } catch (error) {
@@ -7055,7 +7170,7 @@ var init_note_types = __esm({
7055
7170
 
7056
7171
  // package.json
7057
7172
  var package_default = {
7058
- version: "0.0.8"};
7173
+ version: "0.0.9"};
7059
7174
 
7060
7175
  // core/memory/journal.ts
7061
7176
  init_env();
@@ -8210,10 +8325,12 @@ init_project_identity();
8210
8325
  init_project_anchor();
8211
8326
  init_acquisition_boundary();
8212
8327
  init_claim_record();
8328
+ init_state();
8213
8329
  init_router();
8214
8330
  init_store_queue();
8215
8331
  init_staging();
8216
8332
  init_user_paths();
8333
+ init_storage_coordinator();
8217
8334
  init_mem_repository();
8218
8335
  function normalizeClaimIds3(value) {
8219
8336
  if (!value) return [];
@@ -8291,23 +8408,27 @@ var acquire = {
8291
8408
  );
8292
8409
  }
8293
8410
  const revision = revisionFromAcquire2(opts);
8411
+ const repository = target.repository;
8294
8412
  await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
8295
- const state2 = await target.runtime.select([]);
8296
- await target.runtime.update({
8297
- basedOnVersion: state2.context.knowledgeVersion,
8298
- claims: [buildClaimRecord({
8299
- id: opts.id,
8300
- knowledge: boundary.knowledge,
8301
- applicability: opts.applicability,
8302
- evidence: opts.evidence,
8303
- epistemicType: opts.epistemicType,
8304
- provenance: opts.provenance,
8305
- acquiredAt: opts.acquiredAt,
8306
- revision,
8307
- scope,
8308
- sourceProject,
8309
- kind
8310
- })]
8413
+ await withStorageLock(repository.directoryPath, async () => {
8414
+ const state2 = await repository.load();
8415
+ const next = updateKnowledge(state2, {
8416
+ basedOnVersion: state2.version,
8417
+ claims: [buildClaimRecord({
8418
+ id: opts.id,
8419
+ knowledge: boundary.knowledge,
8420
+ applicability: opts.applicability,
8421
+ evidence: opts.evidence,
8422
+ epistemicType: opts.epistemicType,
8423
+ provenance: opts.provenance,
8424
+ acquiredAt: opts.acquiredAt,
8425
+ revision,
8426
+ scope,
8427
+ sourceProject,
8428
+ kind
8429
+ })]
8430
+ });
8431
+ await repository.save(next, state2.version, true);
8311
8432
  });
8312
8433
  });
8313
8434
  const verification = await target.repository.load();