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/mcp.js CHANGED
@@ -2403,6 +2403,7 @@ var init_policy_store = __esm({
2403
2403
  if (entry.target !== void 0) built.target = entry.target;
2404
2404
  if (entry.reason !== void 0) built.reason = entry.reason;
2405
2405
  if (entry.detail !== void 0) built.detail = entry.detail;
2406
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2406
2407
  return built;
2407
2408
  }, "validatePolicyAuditEntry");
2408
2409
  validatePolicyDocument = /* @__PURE__ */ __name((value) => {
@@ -2480,6 +2481,7 @@ var init_policy_store = __esm({
2480
2481
  if (entry.target !== void 0) built.target = entry.target;
2481
2482
  if (entry.reason !== void 0) built.reason = entry.reason;
2482
2483
  if (entry.detail !== void 0) built.detail = entry.detail;
2484
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2483
2485
  return built;
2484
2486
  }, "buildAuditEntry");
2485
2487
  boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
@@ -2492,9 +2494,10 @@ var init_policy_store = __esm({
2492
2494
  for (let index = entries2.length - 1; index >= 0; index--) {
2493
2495
  const entry = entries2[index];
2494
2496
  const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
2495
- if (activeLinked || budget > 0) {
2497
+ const mandatory = entry.mandatory === true;
2498
+ if (activeLinked || mandatory || budget > 0) {
2496
2499
  kept.unshift(entry);
2497
- if (!activeLinked) budget--;
2500
+ if (!activeLinked && !mandatory) budget--;
2498
2501
  }
2499
2502
  }
2500
2503
  return kept;
@@ -2535,6 +2538,12 @@ var init_policy_store = __esm({
2535
2538
  try {
2536
2539
  await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
2537
2540
  `, "utf8");
2541
+ const handle = await fs.open(temporary, "r+");
2542
+ try {
2543
+ await handle.sync();
2544
+ } finally {
2545
+ await handle.close();
2546
+ }
2538
2547
  await fs.rename(temporary, filePath);
2539
2548
  } catch (error) {
2540
2549
  await fs.rm(temporary, { force: true }).catch(() => void 0);
@@ -4871,6 +4880,109 @@ var init_mem_package = __esm({
4871
4880
  }
4872
4881
  });
4873
4882
 
4883
+ // core/storage-coordinator/index.ts
4884
+ var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
4885
+ var init_storage_coordinator = __esm({
4886
+ "core/storage-coordinator/index.ts"() {
4887
+ nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
4888
+ withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
4889
+ const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
4890
+ const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
4891
+ const timeoutMs = opts.timeoutMs ?? 3e4;
4892
+ const staleMs = opts.staleMs ?? 6e4;
4893
+ const lockName = opts.lockName ?? ".storage.lock";
4894
+ const lockPath = `${directoryPath}/${lockName}`;
4895
+ const fs = await nodeFs4();
4896
+ const started = Date.now();
4897
+ await fs.mkdir(directoryPath, { recursive: true });
4898
+ while (true) {
4899
+ try {
4900
+ await fs.mkdir(lockPath);
4901
+ const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4902
+ await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
4903
+ break;
4904
+ } catch (error) {
4905
+ const code = error.code;
4906
+ if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
4907
+ let stale = false;
4908
+ let processDead = false;
4909
+ try {
4910
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4911
+ const parsed = JSON.parse(raw);
4912
+ const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
4913
+ if (typeof parsed.pid === "number") {
4914
+ try {
4915
+ process.kill(parsed.pid, 0);
4916
+ } catch {
4917
+ processDead = true;
4918
+ }
4919
+ } else {
4920
+ processDead = true;
4921
+ }
4922
+ if (ageStale || processDead) stale = true;
4923
+ } catch {
4924
+ stale = true;
4925
+ }
4926
+ if (stale) {
4927
+ if (processDead) {
4928
+ try {
4929
+ await fs.rm(lockPath, { recursive: true, force: true });
4930
+ continue;
4931
+ } catch {
4932
+ }
4933
+ }
4934
+ try {
4935
+ await fs.rmdir(lockPath);
4936
+ continue;
4937
+ } catch {
4938
+ }
4939
+ }
4940
+ if (Date.now() - started > timeoutMs) {
4941
+ throw new Error(`Storage lock timeout for ${directoryPath}`);
4942
+ }
4943
+ await new Promise((resolve) => setTimeout(resolve, 25));
4944
+ }
4945
+ }
4946
+ let token = "";
4947
+ try {
4948
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4949
+ token = JSON.parse(raw).token ?? "";
4950
+ } catch {
4951
+ token = "";
4952
+ }
4953
+ try {
4954
+ return await op();
4955
+ } finally {
4956
+ try {
4957
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4958
+ const parsed = JSON.parse(raw);
4959
+ if (parsed.token === token && token) {
4960
+ await fs.rm(lockPath, { recursive: true, force: true });
4961
+ }
4962
+ } catch {
4963
+ }
4964
+ }
4965
+ }, "withStorageLock");
4966
+ readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
4967
+ const fs = await nodeFs4();
4968
+ try {
4969
+ const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
4970
+ const value = Number(raw.trim());
4971
+ return Number.isFinite(value) && value >= 0 ? value : 0;
4972
+ } catch {
4973
+ return 0;
4974
+ }
4975
+ }, "readStorageGeneration");
4976
+ writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
4977
+ const fs = await nodeFs4();
4978
+ await fs.mkdir(directoryPath, { recursive: true });
4979
+ const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
4980
+ await fs.writeFile(temp, String(value));
4981
+ await fs.rename(temp, `${directoryPath}/.storage.generation`);
4982
+ }, "writeStorageGeneration");
4983
+ }
4984
+ });
4985
+
4874
4986
  // core/knowledge/mem-repository.ts
4875
4987
  function resolveDuplicate(existing, existingSource, current, currentSource) {
4876
4988
  const existingRetracts = existing.revision?.kind === "retracts";
@@ -4911,19 +5023,20 @@ async function parseMemFile(value, sourceId) {
4911
5023
  throw new Error(`${message} in ${sourceId}`);
4912
5024
  }
4913
5025
  }
4914
- var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, directoryLocks, nodeFs4, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, withDirectoryLock, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
5026
+ var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
4915
5027
  var init_mem_repository = __esm({
4916
5028
  "core/knowledge/mem-repository.ts"() {
4917
5029
  init_mem_codec();
4918
5030
  init_mem_package();
4919
5031
  init_state();
4920
5032
  init_acquisition_limits();
5033
+ init_storage_coordinator();
4921
5034
  DOTMEM_DIRECTORY = ".dotmem";
4922
5035
  DEFAULT_MEM_FILE = "project.mem";
4923
- directoryLocks = /* @__PURE__ */ new Map();
4924
- nodeFs4 = /* @__PURE__ */ __name(async () => {
5036
+ nodeFs5 = /* @__PURE__ */ __name(async () => {
4925
5037
  return import('fs/promises');
4926
5038
  }, "nodeFs");
5039
+ repositoryLoadCaches = /* @__PURE__ */ new Map();
4927
5040
  separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
4928
5041
  normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
4929
5042
  isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
@@ -4935,25 +5048,8 @@ var init_mem_repository = __esm({
4935
5048
  return index <= 0 ? "." : normalized.slice(0, index);
4936
5049
  }, "parentPath");
4937
5050
  uniqueSuffix = /* @__PURE__ */ __name(() => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, "uniqueSuffix");
4938
- withDirectoryLock = /* @__PURE__ */ __name(async (directoryPath, operation) => {
4939
- const previous = directoryLocks.get(directoryPath) ?? Promise.resolve();
4940
- let release = /* @__PURE__ */ __name(() => void 0, "release");
4941
- const gate = new Promise((resolveGate) => {
4942
- release = resolveGate;
4943
- });
4944
- const queued = previous.then(() => gate);
4945
- directoryLocks.set(directoryPath, queued);
4946
- await previous;
4947
- try {
4948
- return await operation();
4949
- } finally {
4950
- release();
4951
- if (directoryLocks.get(directoryPath) === queued) directoryLocks.delete(directoryPath);
4952
- }
4953
- }, "withDirectoryLock");
4954
5051
  isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
4955
5052
  sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
4956
- if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
4957
5053
  if (scope === "project" || sourceId === "project.mem") return 4;
4958
5054
  if (scope === "user" || sourceId === "user.mem") return 3;
4959
5055
  if (sourceId === "agent.mem" || sourceId === "agent") return 2;
@@ -4981,7 +5077,7 @@ var init_mem_repository = __esm({
4981
5077
  this.loadWritableOnly = loadWritableOnly;
4982
5078
  }
4983
5079
  async discover() {
4984
- const { readdir } = await nodeFs4();
5080
+ const { readdir } = await nodeFs5();
4985
5081
  try {
4986
5082
  const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
4987
5083
  return entries2.filter((entry) => entry.isFile() && entry.name.endsWith(".mem") && entry.name !== "@brain.mem").map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
@@ -4996,7 +5092,7 @@ var init_mem_repository = __esm({
4996
5092
  * receive a clear failure instead of discovering corruption on first use.
4997
5093
  */
4998
5094
  async initialize() {
4999
- const { mkdir } = await nodeFs4();
5095
+ const { mkdir } = await nodeFs5();
5000
5096
  await mkdir(this.directoryPath, { recursive: true });
5001
5097
  await this.load();
5002
5098
  }
@@ -5011,7 +5107,7 @@ var init_mem_repository = __esm({
5011
5107
  * that does not delete is not a delete.
5012
5108
  */
5013
5109
  async clearWritable() {
5014
- const { rm, readdir } = await nodeFs4();
5110
+ const { rm, readdir } = await nodeFs5();
5015
5111
  const filePath = memFilePath(this.directoryPath, this.writableSourceId);
5016
5112
  await rm(filePath, { force: true });
5017
5113
  await rm(`${filePath}.bak`, { force: true });
@@ -5028,15 +5124,28 @@ var init_mem_repository = __esm({
5028
5124
  }
5029
5125
  }
5030
5126
  async load() {
5031
- const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5032
- return this.combine(loaded);
5127
+ const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
5128
+ for (let attempt = 0; attempt < 5; attempt += 1) {
5129
+ const generationBefore = await readStorageGeneration(this.directoryPath);
5130
+ const cached = repositoryLoadCaches.get(cacheKey);
5131
+ if (cached && cached.signature === String(generationBefore)) {
5132
+ return structuredClone(cached.state);
5133
+ }
5134
+ const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5135
+ const state2 = this.combine(loaded);
5136
+ const generationAfter = await readStorageGeneration(this.directoryPath);
5137
+ if (generationBefore !== generationAfter && attempt < 4) continue;
5138
+ repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state2) });
5139
+ return state2;
5140
+ }
5141
+ throw new Error("Unable to load stable repository state");
5033
5142
  }
5034
- async save(state2, expectedVersion) {
5143
+ async save(state2, expectedVersion, locked = false) {
5035
5144
  const validated = validateKnowledgeState(state2);
5036
5145
  if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
5037
5146
  throw new Error("Invalid expected persisted version");
5038
5147
  }
5039
- await withDirectoryLock(this.directoryPath, async () => {
5148
+ const doWrite = /* @__PURE__ */ __name(async () => {
5040
5149
  const loaded = await this.loadSources();
5041
5150
  const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
5042
5151
  id: this.writableSourceId,
@@ -5058,10 +5167,16 @@ var init_mem_repository = __esm({
5058
5167
  claims: [...writable.state.claims, ...newClaims]
5059
5168
  };
5060
5169
  await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
5061
- });
5170
+ await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
5171
+ }, "doWrite");
5172
+ if (locked) {
5173
+ await doWrite();
5174
+ } else {
5175
+ await withStorageLock(this.directoryPath, doWrite);
5176
+ }
5062
5177
  }
5063
5178
  async loadSources(sourceIds) {
5064
- const { readFile: readFile5 } = await nodeFs4();
5179
+ const { readFile: readFile5 } = await nodeFs5();
5065
5180
  const ids = sourceIds ?? await this.discover();
5066
5181
  const sources = [];
5067
5182
  for (const sourceId of ids) {
@@ -5151,7 +5266,7 @@ var init_mem_repository = __esm({
5151
5266
  * valid package, so the backup is always a version this library can read.
5152
5267
  */
5153
5268
  async keepLastValidVersion(filePath) {
5154
- const { readFile: readFile5, copyFile, rename, rm } = await nodeFs4();
5269
+ const { readFile: readFile5, copyFile, rename, rm } = await nodeFs5();
5155
5270
  let current;
5156
5271
  try {
5157
5272
  current = await readFile5(filePath);
@@ -5175,7 +5290,7 @@ var init_mem_repository = __esm({
5175
5290
  }
5176
5291
  /** Best-effort directory sync: the rename is durable once the directory is. */
5177
5292
  async syncDirectory(directoryPath) {
5178
- const { open } = await nodeFs4();
5293
+ const { open } = await nodeFs5();
5179
5294
  try {
5180
5295
  const handle = await open(directoryPath, "r");
5181
5296
  try {
@@ -5187,7 +5302,7 @@ var init_mem_repository = __esm({
5187
5302
  }
5188
5303
  }
5189
5304
  async writeSource(filePath, state2, preserved, preservedPackage) {
5190
- const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs4();
5305
+ const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs5();
5191
5306
  if (!Number.isInteger(state2.version) || state2.version < 0) throw new Error("Invalid writable .mem knowledge version");
5192
5307
  for (const claim of state2.claims) validateClaim(claim);
5193
5308
  await mkdir(parentPath(filePath), { recursive: true });
@@ -5674,7 +5789,7 @@ var init_project_identity = __esm({
5674
5789
  });
5675
5790
 
5676
5791
  // core/knowledge/staging.ts
5677
- var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs5, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5792
+ var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5678
5793
  var init_staging = __esm({
5679
5794
  "core/knowledge/staging.ts"() {
5680
5795
  init_claim_record();
@@ -5709,7 +5824,7 @@ var init_staging = __esm({
5709
5824
  return `${base}/dotmem`;
5710
5825
  }, "resolveStagingDirectory");
5711
5826
  stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
5712
- nodeFs5 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5827
+ nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5713
5828
  normalizeClaimIds = /* @__PURE__ */ __name((value) => {
5714
5829
  if (!value) return [];
5715
5830
  return Array.isArray(value) ? value : [value];
@@ -5734,7 +5849,7 @@ var init_staging = __esm({
5734
5849
  };
5735
5850
  const requestId = generateRequestId();
5736
5851
  const stagePath = stagePathFor(requestId);
5737
- const fs = await nodeFs5();
5852
+ const fs = await nodeFs6();
5738
5853
  await fs.mkdir(resolveStagingDirectory(), { recursive: true });
5739
5854
  const stageFile = {
5740
5855
  format: DOTMEM_STAGE_FORMAT,
@@ -5754,7 +5869,7 @@ var init_staging = __esm({
5754
5869
  }, "writeStageFile");
5755
5870
  readStageFile = /* @__PURE__ */ __name(async (requestId) => {
5756
5871
  const stagePath = stagePathFor(requestId);
5757
- const fs = await nodeFs5();
5872
+ const fs = await nodeFs6();
5758
5873
  try {
5759
5874
  const content = await fs.readFile(stagePath, "utf8");
5760
5875
  return JSON.parse(content);
@@ -5865,7 +5980,7 @@ var init_staging = __esm({
5865
5980
  }, "commitStageFile");
5866
5981
  cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
5867
5982
  const stagePath = stagePathFor(requestId);
5868
- const fs = await nodeFs5();
5983
+ const fs = await nodeFs6();
5869
5984
  try {
5870
5985
  await fs.unlink(stagePath);
5871
5986
  } catch (error) {
@@ -7052,7 +7167,7 @@ var init_note_types = __esm({
7052
7167
 
7053
7168
  // package.json
7054
7169
  var package_default = {
7055
- version: "0.0.8"};
7170
+ version: "0.0.9"};
7056
7171
 
7057
7172
  // core/memory/journal.ts
7058
7173
  init_env();
@@ -8207,10 +8322,12 @@ init_project_identity();
8207
8322
  init_project_anchor();
8208
8323
  init_acquisition_boundary();
8209
8324
  init_claim_record();
8325
+ init_state();
8210
8326
  init_router();
8211
8327
  init_store_queue();
8212
8328
  init_staging();
8213
8329
  init_user_paths();
8330
+ init_storage_coordinator();
8214
8331
  init_mem_repository();
8215
8332
  function normalizeClaimIds3(value) {
8216
8333
  if (!value) return [];
@@ -8288,23 +8405,27 @@ var acquire = {
8288
8405
  );
8289
8406
  }
8290
8407
  const revision = revisionFromAcquire2(opts);
8408
+ const repository = target.repository;
8291
8409
  await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
8292
- const state2 = await target.runtime.select([]);
8293
- await target.runtime.update({
8294
- basedOnVersion: state2.context.knowledgeVersion,
8295
- claims: [buildClaimRecord({
8296
- id: opts.id,
8297
- knowledge: boundary.knowledge,
8298
- applicability: opts.applicability,
8299
- evidence: opts.evidence,
8300
- epistemicType: opts.epistemicType,
8301
- provenance: opts.provenance,
8302
- acquiredAt: opts.acquiredAt,
8303
- revision,
8304
- scope,
8305
- sourceProject,
8306
- kind
8307
- })]
8410
+ await withStorageLock(repository.directoryPath, async () => {
8411
+ const state2 = await repository.load();
8412
+ const next = updateKnowledge(state2, {
8413
+ basedOnVersion: state2.version,
8414
+ claims: [buildClaimRecord({
8415
+ id: opts.id,
8416
+ knowledge: boundary.knowledge,
8417
+ applicability: opts.applicability,
8418
+ evidence: opts.evidence,
8419
+ epistemicType: opts.epistemicType,
8420
+ provenance: opts.provenance,
8421
+ acquiredAt: opts.acquiredAt,
8422
+ revision,
8423
+ scope,
8424
+ sourceProject,
8425
+ kind
8426
+ })]
8427
+ });
8428
+ await repository.save(next, state2.version, true);
8308
8429
  });
8309
8430
  });
8310
8431
  const verification = await target.repository.load();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dotmem",
3
3
  "codeName": "dotmem",
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "description": "The AI memory layer for agents and apps: acquired experience, discovery, context projection, continuity, and logic engine. Built on Memorio's state+store foundation.",