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/global.cjs CHANGED
@@ -2405,6 +2405,7 @@ var init_policy_store = __esm({
2405
2405
  if (entry.target !== void 0) built.target = entry.target;
2406
2406
  if (entry.reason !== void 0) built.reason = entry.reason;
2407
2407
  if (entry.detail !== void 0) built.detail = entry.detail;
2408
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2408
2409
  return built;
2409
2410
  }, "validatePolicyAuditEntry");
2410
2411
  validatePolicyDocument = /* @__PURE__ */ __name((value) => {
@@ -2482,6 +2483,7 @@ var init_policy_store = __esm({
2482
2483
  if (entry.target !== void 0) built.target = entry.target;
2483
2484
  if (entry.reason !== void 0) built.reason = entry.reason;
2484
2485
  if (entry.detail !== void 0) built.detail = entry.detail;
2486
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2485
2487
  return built;
2486
2488
  }, "buildAuditEntry");
2487
2489
  boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
@@ -2494,9 +2496,10 @@ var init_policy_store = __esm({
2494
2496
  for (let index = entries2.length - 1; index >= 0; index--) {
2495
2497
  const entry = entries2[index];
2496
2498
  const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
2497
- if (activeLinked || budget > 0) {
2499
+ const mandatory = entry.mandatory === true;
2500
+ if (activeLinked || mandatory || budget > 0) {
2498
2501
  kept.unshift(entry);
2499
- if (!activeLinked) budget--;
2502
+ if (!activeLinked && !mandatory) budget--;
2500
2503
  }
2501
2504
  }
2502
2505
  return kept;
@@ -2537,6 +2540,12 @@ var init_policy_store = __esm({
2537
2540
  try {
2538
2541
  await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
2539
2542
  `, "utf8");
2543
+ const handle = await fs.open(temporary, "r+");
2544
+ try {
2545
+ await handle.sync();
2546
+ } finally {
2547
+ await handle.close();
2548
+ }
2540
2549
  await fs.rename(temporary, filePath);
2541
2550
  } catch (error) {
2542
2551
  await fs.rm(temporary, { force: true }).catch(() => void 0);
@@ -4872,6 +4881,109 @@ var init_mem_package = __esm({
4872
4881
  }
4873
4882
  });
4874
4883
 
4884
+ // core/storage-coordinator/index.ts
4885
+ var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
4886
+ var init_storage_coordinator = __esm({
4887
+ "core/storage-coordinator/index.ts"() {
4888
+ nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
4889
+ withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
4890
+ const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
4891
+ const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
4892
+ const timeoutMs = opts.timeoutMs ?? 3e4;
4893
+ const staleMs = opts.staleMs ?? 6e4;
4894
+ const lockName = opts.lockName ?? ".storage.lock";
4895
+ const lockPath = `${directoryPath}/${lockName}`;
4896
+ const fs = await nodeFs4();
4897
+ const started = Date.now();
4898
+ await fs.mkdir(directoryPath, { recursive: true });
4899
+ while (true) {
4900
+ try {
4901
+ await fs.mkdir(lockPath);
4902
+ const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4903
+ await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
4904
+ break;
4905
+ } catch (error) {
4906
+ const code = error.code;
4907
+ if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
4908
+ let stale = false;
4909
+ let processDead = false;
4910
+ try {
4911
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4912
+ const parsed = JSON.parse(raw);
4913
+ const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
4914
+ if (typeof parsed.pid === "number") {
4915
+ try {
4916
+ process.kill(parsed.pid, 0);
4917
+ } catch {
4918
+ processDead = true;
4919
+ }
4920
+ } else {
4921
+ processDead = true;
4922
+ }
4923
+ if (ageStale || processDead) stale = true;
4924
+ } catch {
4925
+ stale = true;
4926
+ }
4927
+ if (stale) {
4928
+ if (processDead) {
4929
+ try {
4930
+ await fs.rm(lockPath, { recursive: true, force: true });
4931
+ continue;
4932
+ } catch {
4933
+ }
4934
+ }
4935
+ try {
4936
+ await fs.rmdir(lockPath);
4937
+ continue;
4938
+ } catch {
4939
+ }
4940
+ }
4941
+ if (Date.now() - started > timeoutMs) {
4942
+ throw new Error(`Storage lock timeout for ${directoryPath}`);
4943
+ }
4944
+ await new Promise((resolve) => setTimeout(resolve, 25));
4945
+ }
4946
+ }
4947
+ let token = "";
4948
+ try {
4949
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4950
+ token = JSON.parse(raw).token ?? "";
4951
+ } catch {
4952
+ token = "";
4953
+ }
4954
+ try {
4955
+ return await op();
4956
+ } finally {
4957
+ try {
4958
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4959
+ const parsed = JSON.parse(raw);
4960
+ if (parsed.token === token && token) {
4961
+ await fs.rm(lockPath, { recursive: true, force: true });
4962
+ }
4963
+ } catch {
4964
+ }
4965
+ }
4966
+ }, "withStorageLock");
4967
+ readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
4968
+ const fs = await nodeFs4();
4969
+ try {
4970
+ const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
4971
+ const value = Number(raw.trim());
4972
+ return Number.isFinite(value) && value >= 0 ? value : 0;
4973
+ } catch {
4974
+ return 0;
4975
+ }
4976
+ }, "readStorageGeneration");
4977
+ writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
4978
+ const fs = await nodeFs4();
4979
+ await fs.mkdir(directoryPath, { recursive: true });
4980
+ const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
4981
+ await fs.writeFile(temp, String(value));
4982
+ await fs.rename(temp, `${directoryPath}/.storage.generation`);
4983
+ }, "writeStorageGeneration");
4984
+ }
4985
+ });
4986
+
4875
4987
  // core/knowledge/mem-repository.ts
4876
4988
  function resolveDuplicate(existing, existingSource, current, currentSource) {
4877
4989
  const existingRetracts = existing.revision?.kind === "retracts";
@@ -4912,19 +5024,20 @@ async function parseMemFile(value, sourceId) {
4912
5024
  throw new Error(`${message} in ${sourceId}`);
4913
5025
  }
4914
5026
  }
4915
- var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, directoryLocks, nodeFs4, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, withDirectoryLock, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
5027
+ var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
4916
5028
  var init_mem_repository = __esm({
4917
5029
  "core/knowledge/mem-repository.ts"() {
4918
5030
  init_mem_codec();
4919
5031
  init_mem_package();
4920
5032
  init_state();
4921
5033
  init_acquisition_limits();
5034
+ init_storage_coordinator();
4922
5035
  DOTMEM_DIRECTORY = ".dotmem";
4923
5036
  DEFAULT_MEM_FILE = "project.mem";
4924
- directoryLocks = /* @__PURE__ */ new Map();
4925
- nodeFs4 = /* @__PURE__ */ __name(async () => {
5037
+ nodeFs5 = /* @__PURE__ */ __name(async () => {
4926
5038
  return import('fs/promises');
4927
5039
  }, "nodeFs");
5040
+ repositoryLoadCaches = /* @__PURE__ */ new Map();
4928
5041
  separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
4929
5042
  normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
4930
5043
  isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
@@ -4936,25 +5049,8 @@ var init_mem_repository = __esm({
4936
5049
  return index <= 0 ? "." : normalized.slice(0, index);
4937
5050
  }, "parentPath");
4938
5051
  uniqueSuffix = /* @__PURE__ */ __name(() => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, "uniqueSuffix");
4939
- withDirectoryLock = /* @__PURE__ */ __name(async (directoryPath, operation) => {
4940
- const previous = directoryLocks.get(directoryPath) ?? Promise.resolve();
4941
- let release = /* @__PURE__ */ __name(() => void 0, "release");
4942
- const gate = new Promise((resolveGate) => {
4943
- release = resolveGate;
4944
- });
4945
- const queued = previous.then(() => gate);
4946
- directoryLocks.set(directoryPath, queued);
4947
- await previous;
4948
- try {
4949
- return await operation();
4950
- } finally {
4951
- release();
4952
- if (directoryLocks.get(directoryPath) === queued) directoryLocks.delete(directoryPath);
4953
- }
4954
- }, "withDirectoryLock");
4955
5052
  isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
4956
5053
  sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
4957
- if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
4958
5054
  if (scope === "project" || sourceId === "project.mem") return 4;
4959
5055
  if (scope === "user" || sourceId === "user.mem") return 3;
4960
5056
  if (sourceId === "agent.mem" || sourceId === "agent") return 2;
@@ -4982,7 +5078,7 @@ var init_mem_repository = __esm({
4982
5078
  this.loadWritableOnly = loadWritableOnly;
4983
5079
  }
4984
5080
  async discover() {
4985
- const { readdir } = await nodeFs4();
5081
+ const { readdir } = await nodeFs5();
4986
5082
  try {
4987
5083
  const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
4988
5084
  return entries2.filter((entry) => entry.isFile() && entry.name.endsWith(".mem") && entry.name !== "@brain.mem").map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
@@ -4997,7 +5093,7 @@ var init_mem_repository = __esm({
4997
5093
  * receive a clear failure instead of discovering corruption on first use.
4998
5094
  */
4999
5095
  async initialize() {
5000
- const { mkdir } = await nodeFs4();
5096
+ const { mkdir } = await nodeFs5();
5001
5097
  await mkdir(this.directoryPath, { recursive: true });
5002
5098
  await this.load();
5003
5099
  }
@@ -5012,7 +5108,7 @@ var init_mem_repository = __esm({
5012
5108
  * that does not delete is not a delete.
5013
5109
  */
5014
5110
  async clearWritable() {
5015
- const { rm, readdir } = await nodeFs4();
5111
+ const { rm, readdir } = await nodeFs5();
5016
5112
  const filePath = memFilePath(this.directoryPath, this.writableSourceId);
5017
5113
  await rm(filePath, { force: true });
5018
5114
  await rm(`${filePath}.bak`, { force: true });
@@ -5029,15 +5125,28 @@ var init_mem_repository = __esm({
5029
5125
  }
5030
5126
  }
5031
5127
  async load() {
5032
- const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5033
- return this.combine(loaded);
5128
+ const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
5129
+ for (let attempt = 0; attempt < 5; attempt += 1) {
5130
+ const generationBefore = await readStorageGeneration(this.directoryPath);
5131
+ const cached = repositoryLoadCaches.get(cacheKey);
5132
+ if (cached && cached.signature === String(generationBefore)) {
5133
+ return structuredClone(cached.state);
5134
+ }
5135
+ const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5136
+ const state3 = this.combine(loaded);
5137
+ const generationAfter = await readStorageGeneration(this.directoryPath);
5138
+ if (generationBefore !== generationAfter && attempt < 4) continue;
5139
+ repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state3) });
5140
+ return state3;
5141
+ }
5142
+ throw new Error("Unable to load stable repository state");
5034
5143
  }
5035
- async save(state3, expectedVersion) {
5144
+ async save(state3, expectedVersion, locked = false) {
5036
5145
  const validated = validateKnowledgeState(state3);
5037
5146
  if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
5038
5147
  throw new Error("Invalid expected persisted version");
5039
5148
  }
5040
- await withDirectoryLock(this.directoryPath, async () => {
5149
+ const doWrite = /* @__PURE__ */ __name(async () => {
5041
5150
  const loaded = await this.loadSources();
5042
5151
  const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
5043
5152
  id: this.writableSourceId,
@@ -5059,10 +5168,16 @@ var init_mem_repository = __esm({
5059
5168
  claims: [...writable.state.claims, ...newClaims]
5060
5169
  };
5061
5170
  await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
5062
- });
5171
+ await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
5172
+ }, "doWrite");
5173
+ if (locked) {
5174
+ await doWrite();
5175
+ } else {
5176
+ await withStorageLock(this.directoryPath, doWrite);
5177
+ }
5063
5178
  }
5064
5179
  async loadSources(sourceIds) {
5065
- const { readFile: readFile4 } = await nodeFs4();
5180
+ const { readFile: readFile4 } = await nodeFs5();
5066
5181
  const ids = sourceIds ?? await this.discover();
5067
5182
  const sources = [];
5068
5183
  for (const sourceId of ids) {
@@ -5152,7 +5267,7 @@ var init_mem_repository = __esm({
5152
5267
  * valid package, so the backup is always a version this library can read.
5153
5268
  */
5154
5269
  async keepLastValidVersion(filePath) {
5155
- const { readFile: readFile4, copyFile, rename, rm } = await nodeFs4();
5270
+ const { readFile: readFile4, copyFile, rename, rm } = await nodeFs5();
5156
5271
  let current;
5157
5272
  try {
5158
5273
  current = await readFile4(filePath);
@@ -5176,7 +5291,7 @@ var init_mem_repository = __esm({
5176
5291
  }
5177
5292
  /** Best-effort directory sync: the rename is durable once the directory is. */
5178
5293
  async syncDirectory(directoryPath) {
5179
- const { open } = await nodeFs4();
5294
+ const { open } = await nodeFs5();
5180
5295
  try {
5181
5296
  const handle = await open(directoryPath, "r");
5182
5297
  try {
@@ -5188,7 +5303,7 @@ var init_mem_repository = __esm({
5188
5303
  }
5189
5304
  }
5190
5305
  async writeSource(filePath, state3, preserved, preservedPackage) {
5191
- const { mkdir, rename, rm, open, readFile: readFile4 } = await nodeFs4();
5306
+ const { mkdir, rename, rm, open, readFile: readFile4 } = await nodeFs5();
5192
5307
  if (!Number.isInteger(state3.version) || state3.version < 0) throw new Error("Invalid writable .mem knowledge version");
5193
5308
  for (const claim of state3.claims) validateClaim(claim);
5194
5309
  await mkdir(parentPath(filePath), { recursive: true });
@@ -5675,7 +5790,7 @@ var init_project_identity = __esm({
5675
5790
  });
5676
5791
 
5677
5792
  // core/knowledge/staging.ts
5678
- var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs5, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5793
+ var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5679
5794
  var init_staging = __esm({
5680
5795
  "core/knowledge/staging.ts"() {
5681
5796
  init_claim_record();
@@ -5710,7 +5825,7 @@ var init_staging = __esm({
5710
5825
  return `${base}/dotmem`;
5711
5826
  }, "resolveStagingDirectory");
5712
5827
  stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
5713
- nodeFs5 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5828
+ nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5714
5829
  normalizeClaimIds = /* @__PURE__ */ __name((value) => {
5715
5830
  if (!value) return [];
5716
5831
  return Array.isArray(value) ? value : [value];
@@ -5735,7 +5850,7 @@ var init_staging = __esm({
5735
5850
  };
5736
5851
  const requestId = generateRequestId();
5737
5852
  const stagePath = stagePathFor(requestId);
5738
- const fs = await nodeFs5();
5853
+ const fs = await nodeFs6();
5739
5854
  await fs.mkdir(resolveStagingDirectory(), { recursive: true });
5740
5855
  const stageFile = {
5741
5856
  format: DOTMEM_STAGE_FORMAT,
@@ -5755,7 +5870,7 @@ var init_staging = __esm({
5755
5870
  }, "writeStageFile");
5756
5871
  readStageFile = /* @__PURE__ */ __name(async (requestId) => {
5757
5872
  const stagePath = stagePathFor(requestId);
5758
- const fs = await nodeFs5();
5873
+ const fs = await nodeFs6();
5759
5874
  try {
5760
5875
  const content = await fs.readFile(stagePath, "utf8");
5761
5876
  return JSON.parse(content);
@@ -5866,7 +5981,7 @@ var init_staging = __esm({
5866
5981
  }, "commitStageFile");
5867
5982
  cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
5868
5983
  const stagePath = stagePathFor(requestId);
5869
- const fs = await nodeFs5();
5984
+ const fs = await nodeFs6();
5870
5985
  try {
5871
5986
  await fs.unlink(stagePath);
5872
5987
  } catch (error) {
@@ -8231,10 +8346,12 @@ init_project_identity();
8231
8346
  init_project_anchor();
8232
8347
  init_acquisition_boundary();
8233
8348
  init_claim_record();
8349
+ init_state();
8234
8350
  init_router();
8235
8351
  init_store_queue();
8236
8352
  init_staging();
8237
8353
  init_user_paths();
8354
+ init_storage_coordinator();
8238
8355
  init_mem_repository();
8239
8356
  function normalizeClaimIds3(value) {
8240
8357
  if (!value) return [];
@@ -8312,23 +8429,27 @@ var acquire = {
8312
8429
  );
8313
8430
  }
8314
8431
  const revision = revisionFromAcquire2(opts);
8432
+ const repository = target.repository;
8315
8433
  await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
8316
- const state3 = await target.runtime.select([]);
8317
- await target.runtime.update({
8318
- basedOnVersion: state3.context.knowledgeVersion,
8319
- claims: [buildClaimRecord({
8320
- id: opts.id,
8321
- knowledge: boundary.knowledge,
8322
- applicability: opts.applicability,
8323
- evidence: opts.evidence,
8324
- epistemicType: opts.epistemicType,
8325
- provenance: opts.provenance,
8326
- acquiredAt: opts.acquiredAt,
8327
- revision,
8328
- scope,
8329
- sourceProject,
8330
- kind
8331
- })]
8434
+ await withStorageLock(repository.directoryPath, async () => {
8435
+ const state3 = await repository.load();
8436
+ const next = updateKnowledge(state3, {
8437
+ basedOnVersion: state3.version,
8438
+ claims: [buildClaimRecord({
8439
+ id: opts.id,
8440
+ knowledge: boundary.knowledge,
8441
+ applicability: opts.applicability,
8442
+ evidence: opts.evidence,
8443
+ epistemicType: opts.epistemicType,
8444
+ provenance: opts.provenance,
8445
+ acquiredAt: opts.acquiredAt,
8446
+ revision,
8447
+ scope,
8448
+ sourceProject,
8449
+ kind
8450
+ })]
8451
+ });
8452
+ await repository.save(next, state3.version, true);
8332
8453
  });
8333
8454
  });
8334
8455
  const verification = await target.repository.load();