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.js CHANGED
@@ -2401,6 +2401,7 @@ var init_policy_store = __esm({
2401
2401
  if (entry.target !== void 0) built.target = entry.target;
2402
2402
  if (entry.reason !== void 0) built.reason = entry.reason;
2403
2403
  if (entry.detail !== void 0) built.detail = entry.detail;
2404
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2404
2405
  return built;
2405
2406
  }, "validatePolicyAuditEntry");
2406
2407
  validatePolicyDocument = /* @__PURE__ */ __name((value) => {
@@ -2478,6 +2479,7 @@ var init_policy_store = __esm({
2478
2479
  if (entry.target !== void 0) built.target = entry.target;
2479
2480
  if (entry.reason !== void 0) built.reason = entry.reason;
2480
2481
  if (entry.detail !== void 0) built.detail = entry.detail;
2482
+ if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
2481
2483
  return built;
2482
2484
  }, "buildAuditEntry");
2483
2485
  boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
@@ -2490,9 +2492,10 @@ var init_policy_store = __esm({
2490
2492
  for (let index = entries2.length - 1; index >= 0; index--) {
2491
2493
  const entry = entries2[index];
2492
2494
  const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
2493
- if (activeLinked || budget > 0) {
2495
+ const mandatory = entry.mandatory === true;
2496
+ if (activeLinked || mandatory || budget > 0) {
2494
2497
  kept.unshift(entry);
2495
- if (!activeLinked) budget--;
2498
+ if (!activeLinked && !mandatory) budget--;
2496
2499
  }
2497
2500
  }
2498
2501
  return kept;
@@ -2533,6 +2536,12 @@ var init_policy_store = __esm({
2533
2536
  try {
2534
2537
  await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
2535
2538
  `, "utf8");
2539
+ const handle = await fs.open(temporary, "r+");
2540
+ try {
2541
+ await handle.sync();
2542
+ } finally {
2543
+ await handle.close();
2544
+ }
2536
2545
  await fs.rename(temporary, filePath);
2537
2546
  } catch (error) {
2538
2547
  await fs.rm(temporary, { force: true }).catch(() => void 0);
@@ -4868,6 +4877,109 @@ var init_mem_package = __esm({
4868
4877
  }
4869
4878
  });
4870
4879
 
4880
+ // core/storage-coordinator/index.ts
4881
+ var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
4882
+ var init_storage_coordinator = __esm({
4883
+ "core/storage-coordinator/index.ts"() {
4884
+ nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
4885
+ withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
4886
+ const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
4887
+ const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
4888
+ const timeoutMs = opts.timeoutMs ?? 3e4;
4889
+ const staleMs = opts.staleMs ?? 6e4;
4890
+ const lockName = opts.lockName ?? ".storage.lock";
4891
+ const lockPath = `${directoryPath}/${lockName}`;
4892
+ const fs = await nodeFs4();
4893
+ const started = Date.now();
4894
+ await fs.mkdir(directoryPath, { recursive: true });
4895
+ while (true) {
4896
+ try {
4897
+ await fs.mkdir(lockPath);
4898
+ const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
4899
+ await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
4900
+ break;
4901
+ } catch (error) {
4902
+ const code = error.code;
4903
+ if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
4904
+ let stale = false;
4905
+ let processDead = false;
4906
+ try {
4907
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4908
+ const parsed = JSON.parse(raw);
4909
+ const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
4910
+ if (typeof parsed.pid === "number") {
4911
+ try {
4912
+ process.kill(parsed.pid, 0);
4913
+ } catch {
4914
+ processDead = true;
4915
+ }
4916
+ } else {
4917
+ processDead = true;
4918
+ }
4919
+ if (ageStale || processDead) stale = true;
4920
+ } catch {
4921
+ stale = true;
4922
+ }
4923
+ if (stale) {
4924
+ if (processDead) {
4925
+ try {
4926
+ await fs.rm(lockPath, { recursive: true, force: true });
4927
+ continue;
4928
+ } catch {
4929
+ }
4930
+ }
4931
+ try {
4932
+ await fs.rmdir(lockPath);
4933
+ continue;
4934
+ } catch {
4935
+ }
4936
+ }
4937
+ if (Date.now() - started > timeoutMs) {
4938
+ throw new Error(`Storage lock timeout for ${directoryPath}`);
4939
+ }
4940
+ await new Promise((resolve) => setTimeout(resolve, 25));
4941
+ }
4942
+ }
4943
+ let token = "";
4944
+ try {
4945
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4946
+ token = JSON.parse(raw).token ?? "";
4947
+ } catch {
4948
+ token = "";
4949
+ }
4950
+ try {
4951
+ return await op();
4952
+ } finally {
4953
+ try {
4954
+ const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
4955
+ const parsed = JSON.parse(raw);
4956
+ if (parsed.token === token && token) {
4957
+ await fs.rm(lockPath, { recursive: true, force: true });
4958
+ }
4959
+ } catch {
4960
+ }
4961
+ }
4962
+ }, "withStorageLock");
4963
+ readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
4964
+ const fs = await nodeFs4();
4965
+ try {
4966
+ const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
4967
+ const value = Number(raw.trim());
4968
+ return Number.isFinite(value) && value >= 0 ? value : 0;
4969
+ } catch {
4970
+ return 0;
4971
+ }
4972
+ }, "readStorageGeneration");
4973
+ writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
4974
+ const fs = await nodeFs4();
4975
+ await fs.mkdir(directoryPath, { recursive: true });
4976
+ const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
4977
+ await fs.writeFile(temp, String(value));
4978
+ await fs.rename(temp, `${directoryPath}/.storage.generation`);
4979
+ }, "writeStorageGeneration");
4980
+ }
4981
+ });
4982
+
4871
4983
  // core/knowledge/mem-repository.ts
4872
4984
  function resolveDuplicate(existing, existingSource, current, currentSource) {
4873
4985
  const existingRetracts = existing.revision?.kind === "retracts";
@@ -4908,19 +5020,20 @@ async function parseMemFile(value, sourceId) {
4908
5020
  throw new Error(`${message} in ${sourceId}`);
4909
5021
  }
4910
5022
  }
4911
- var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, directoryLocks, nodeFs4, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, withDirectoryLock, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
5023
+ var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
4912
5024
  var init_mem_repository = __esm({
4913
5025
  "core/knowledge/mem-repository.ts"() {
4914
5026
  init_mem_codec();
4915
5027
  init_mem_package();
4916
5028
  init_state();
4917
5029
  init_acquisition_limits();
5030
+ init_storage_coordinator();
4918
5031
  DOTMEM_DIRECTORY = ".dotmem";
4919
5032
  DEFAULT_MEM_FILE = "project.mem";
4920
- directoryLocks = /* @__PURE__ */ new Map();
4921
- nodeFs4 = /* @__PURE__ */ __name(async () => {
5033
+ nodeFs5 = /* @__PURE__ */ __name(async () => {
4922
5034
  return import('fs/promises');
4923
5035
  }, "nodeFs");
5036
+ repositoryLoadCaches = /* @__PURE__ */ new Map();
4924
5037
  separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
4925
5038
  normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
4926
5039
  isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
@@ -4932,25 +5045,8 @@ var init_mem_repository = __esm({
4932
5045
  return index <= 0 ? "." : normalized.slice(0, index);
4933
5046
  }, "parentPath");
4934
5047
  uniqueSuffix = /* @__PURE__ */ __name(() => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, "uniqueSuffix");
4935
- withDirectoryLock = /* @__PURE__ */ __name(async (directoryPath, operation) => {
4936
- const previous = directoryLocks.get(directoryPath) ?? Promise.resolve();
4937
- let release = /* @__PURE__ */ __name(() => void 0, "release");
4938
- const gate = new Promise((resolveGate) => {
4939
- release = resolveGate;
4940
- });
4941
- const queued = previous.then(() => gate);
4942
- directoryLocks.set(directoryPath, queued);
4943
- await previous;
4944
- try {
4945
- return await operation();
4946
- } finally {
4947
- release();
4948
- if (directoryLocks.get(directoryPath) === queued) directoryLocks.delete(directoryPath);
4949
- }
4950
- }, "withDirectoryLock");
4951
5048
  isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
4952
5049
  sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
4953
- if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
4954
5050
  if (scope === "project" || sourceId === "project.mem") return 4;
4955
5051
  if (scope === "user" || sourceId === "user.mem") return 3;
4956
5052
  if (sourceId === "agent.mem" || sourceId === "agent") return 2;
@@ -4978,7 +5074,7 @@ var init_mem_repository = __esm({
4978
5074
  this.loadWritableOnly = loadWritableOnly;
4979
5075
  }
4980
5076
  async discover() {
4981
- const { readdir } = await nodeFs4();
5077
+ const { readdir } = await nodeFs5();
4982
5078
  try {
4983
5079
  const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
4984
5080
  return entries2.filter((entry) => entry.isFile() && entry.name.endsWith(".mem") && entry.name !== "@brain.mem").map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
@@ -4993,7 +5089,7 @@ var init_mem_repository = __esm({
4993
5089
  * receive a clear failure instead of discovering corruption on first use.
4994
5090
  */
4995
5091
  async initialize() {
4996
- const { mkdir } = await nodeFs4();
5092
+ const { mkdir } = await nodeFs5();
4997
5093
  await mkdir(this.directoryPath, { recursive: true });
4998
5094
  await this.load();
4999
5095
  }
@@ -5008,7 +5104,7 @@ var init_mem_repository = __esm({
5008
5104
  * that does not delete is not a delete.
5009
5105
  */
5010
5106
  async clearWritable() {
5011
- const { rm, readdir } = await nodeFs4();
5107
+ const { rm, readdir } = await nodeFs5();
5012
5108
  const filePath = memFilePath(this.directoryPath, this.writableSourceId);
5013
5109
  await rm(filePath, { force: true });
5014
5110
  await rm(`${filePath}.bak`, { force: true });
@@ -5025,15 +5121,28 @@ var init_mem_repository = __esm({
5025
5121
  }
5026
5122
  }
5027
5123
  async load() {
5028
- const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5029
- return this.combine(loaded);
5124
+ const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
5125
+ for (let attempt = 0; attempt < 5; attempt += 1) {
5126
+ const generationBefore = await readStorageGeneration(this.directoryPath);
5127
+ const cached = repositoryLoadCaches.get(cacheKey);
5128
+ if (cached && cached.signature === String(generationBefore)) {
5129
+ return structuredClone(cached.state);
5130
+ }
5131
+ const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
5132
+ const state3 = this.combine(loaded);
5133
+ const generationAfter = await readStorageGeneration(this.directoryPath);
5134
+ if (generationBefore !== generationAfter && attempt < 4) continue;
5135
+ repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state3) });
5136
+ return state3;
5137
+ }
5138
+ throw new Error("Unable to load stable repository state");
5030
5139
  }
5031
- async save(state3, expectedVersion) {
5140
+ async save(state3, expectedVersion, locked = false) {
5032
5141
  const validated = validateKnowledgeState(state3);
5033
5142
  if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
5034
5143
  throw new Error("Invalid expected persisted version");
5035
5144
  }
5036
- await withDirectoryLock(this.directoryPath, async () => {
5145
+ const doWrite = /* @__PURE__ */ __name(async () => {
5037
5146
  const loaded = await this.loadSources();
5038
5147
  const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
5039
5148
  id: this.writableSourceId,
@@ -5055,10 +5164,16 @@ var init_mem_repository = __esm({
5055
5164
  claims: [...writable.state.claims, ...newClaims]
5056
5165
  };
5057
5166
  await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
5058
- });
5167
+ await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
5168
+ }, "doWrite");
5169
+ if (locked) {
5170
+ await doWrite();
5171
+ } else {
5172
+ await withStorageLock(this.directoryPath, doWrite);
5173
+ }
5059
5174
  }
5060
5175
  async loadSources(sourceIds) {
5061
- const { readFile: readFile4 } = await nodeFs4();
5176
+ const { readFile: readFile4 } = await nodeFs5();
5062
5177
  const ids = sourceIds ?? await this.discover();
5063
5178
  const sources = [];
5064
5179
  for (const sourceId of ids) {
@@ -5148,7 +5263,7 @@ var init_mem_repository = __esm({
5148
5263
  * valid package, so the backup is always a version this library can read.
5149
5264
  */
5150
5265
  async keepLastValidVersion(filePath) {
5151
- const { readFile: readFile4, copyFile, rename, rm } = await nodeFs4();
5266
+ const { readFile: readFile4, copyFile, rename, rm } = await nodeFs5();
5152
5267
  let current;
5153
5268
  try {
5154
5269
  current = await readFile4(filePath);
@@ -5172,7 +5287,7 @@ var init_mem_repository = __esm({
5172
5287
  }
5173
5288
  /** Best-effort directory sync: the rename is durable once the directory is. */
5174
5289
  async syncDirectory(directoryPath) {
5175
- const { open } = await nodeFs4();
5290
+ const { open } = await nodeFs5();
5176
5291
  try {
5177
5292
  const handle = await open(directoryPath, "r");
5178
5293
  try {
@@ -5184,7 +5299,7 @@ var init_mem_repository = __esm({
5184
5299
  }
5185
5300
  }
5186
5301
  async writeSource(filePath, state3, preserved, preservedPackage) {
5187
- const { mkdir, rename, rm, open, readFile: readFile4 } = await nodeFs4();
5302
+ const { mkdir, rename, rm, open, readFile: readFile4 } = await nodeFs5();
5188
5303
  if (!Number.isInteger(state3.version) || state3.version < 0) throw new Error("Invalid writable .mem knowledge version");
5189
5304
  for (const claim of state3.claims) validateClaim(claim);
5190
5305
  await mkdir(parentPath(filePath), { recursive: true });
@@ -5671,7 +5786,7 @@ var init_project_identity = __esm({
5671
5786
  });
5672
5787
 
5673
5788
  // core/knowledge/staging.ts
5674
- var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs5, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5789
+ var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
5675
5790
  var init_staging = __esm({
5676
5791
  "core/knowledge/staging.ts"() {
5677
5792
  init_claim_record();
@@ -5706,7 +5821,7 @@ var init_staging = __esm({
5706
5821
  return `${base}/dotmem`;
5707
5822
  }, "resolveStagingDirectory");
5708
5823
  stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
5709
- nodeFs5 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5824
+ nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
5710
5825
  normalizeClaimIds = /* @__PURE__ */ __name((value) => {
5711
5826
  if (!value) return [];
5712
5827
  return Array.isArray(value) ? value : [value];
@@ -5731,7 +5846,7 @@ var init_staging = __esm({
5731
5846
  };
5732
5847
  const requestId = generateRequestId();
5733
5848
  const stagePath = stagePathFor(requestId);
5734
- const fs = await nodeFs5();
5849
+ const fs = await nodeFs6();
5735
5850
  await fs.mkdir(resolveStagingDirectory(), { recursive: true });
5736
5851
  const stageFile = {
5737
5852
  format: DOTMEM_STAGE_FORMAT,
@@ -5751,7 +5866,7 @@ var init_staging = __esm({
5751
5866
  }, "writeStageFile");
5752
5867
  readStageFile = /* @__PURE__ */ __name(async (requestId) => {
5753
5868
  const stagePath = stagePathFor(requestId);
5754
- const fs = await nodeFs5();
5869
+ const fs = await nodeFs6();
5755
5870
  try {
5756
5871
  const content = await fs.readFile(stagePath, "utf8");
5757
5872
  return JSON.parse(content);
@@ -5862,7 +5977,7 @@ var init_staging = __esm({
5862
5977
  }, "commitStageFile");
5863
5978
  cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
5864
5979
  const stagePath = stagePathFor(requestId);
5865
- const fs = await nodeFs5();
5980
+ const fs = await nodeFs6();
5866
5981
  try {
5867
5982
  await fs.unlink(stagePath);
5868
5983
  } catch (error) {
@@ -8227,10 +8342,12 @@ init_project_identity();
8227
8342
  init_project_anchor();
8228
8343
  init_acquisition_boundary();
8229
8344
  init_claim_record();
8345
+ init_state();
8230
8346
  init_router();
8231
8347
  init_store_queue();
8232
8348
  init_staging();
8233
8349
  init_user_paths();
8350
+ init_storage_coordinator();
8234
8351
  init_mem_repository();
8235
8352
  function normalizeClaimIds3(value) {
8236
8353
  if (!value) return [];
@@ -8308,23 +8425,27 @@ var acquire = {
8308
8425
  );
8309
8426
  }
8310
8427
  const revision = revisionFromAcquire2(opts);
8428
+ const repository = target.repository;
8311
8429
  await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
8312
- const state3 = await target.runtime.select([]);
8313
- await target.runtime.update({
8314
- basedOnVersion: state3.context.knowledgeVersion,
8315
- claims: [buildClaimRecord({
8316
- id: opts.id,
8317
- knowledge: boundary.knowledge,
8318
- applicability: opts.applicability,
8319
- evidence: opts.evidence,
8320
- epistemicType: opts.epistemicType,
8321
- provenance: opts.provenance,
8322
- acquiredAt: opts.acquiredAt,
8323
- revision,
8324
- scope,
8325
- sourceProject,
8326
- kind
8327
- })]
8430
+ await withStorageLock(repository.directoryPath, async () => {
8431
+ const state3 = await repository.load();
8432
+ const next = updateKnowledge(state3, {
8433
+ basedOnVersion: state3.version,
8434
+ claims: [buildClaimRecord({
8435
+ id: opts.id,
8436
+ knowledge: boundary.knowledge,
8437
+ applicability: opts.applicability,
8438
+ evidence: opts.evidence,
8439
+ epistemicType: opts.epistemicType,
8440
+ provenance: opts.provenance,
8441
+ acquiredAt: opts.acquiredAt,
8442
+ revision,
8443
+ scope,
8444
+ sourceProject,
8445
+ kind
8446
+ })]
8447
+ });
8448
+ await repository.save(next, state3.version, true);
8328
8449
  });
8329
8450
  });
8330
8451
  const verification = await target.repository.load();