@hasna/mementos 0.14.80 → 0.14.82

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/index.js CHANGED
@@ -50488,6 +50488,98 @@ function getMementosPackageVersion() {
50488
50488
  // src/db/memory-project-link.ts
50489
50489
  init_schema();
50490
50490
 
50491
+ // src/project-registration/types.ts
50492
+ var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
50493
+ var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
50494
+ var MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION = 1;
50495
+ var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
50496
+ var MEMENTOS_PROJECT_RESOURCE_ROUTE = "mementos.project-resources.v1";
50497
+ var MEMENTOS_PROJECT_RESOURCE_KINDS = [
50498
+ "project",
50499
+ "knowledge",
50500
+ "memory",
50501
+ "session"
50502
+ ];
50503
+
50504
+ class MementosProjectRegistrationError extends Error {
50505
+ code;
50506
+ details;
50507
+ constructor(code, message, details = {}) {
50508
+ super(message);
50509
+ this.code = code;
50510
+ this.details = details;
50511
+ this.name = "MementosProjectRegistrationError";
50512
+ }
50513
+ }
50514
+
50515
+ // src/project-registration/identity.ts
50516
+ var MEMENTOS_PROJECT_AUTHORITY_ENV = {
50517
+ authorityId: "MEMENTOS_PROJECT_AUTHORITY_ID",
50518
+ tenantId: "MEMENTOS_PROJECT_TENANT_ID",
50519
+ corpusId: "MEMENTOS_PROJECT_CORPUS_ID"
50520
+ };
50521
+ function configuredValue(override, envKey) {
50522
+ return override?.trim() || process.env[envKey]?.trim() || null;
50523
+ }
50524
+
50525
+ class MementosProjectAuthorityIdentityError extends Error {
50526
+ missing_env;
50527
+ code = "MEMENTOS_PROJECT_AUTHORITY_UNCONFIGURED";
50528
+ constructor(missing_env) {
50529
+ super("Mementos project authority identity is not configured; set " + missing_env.join(", "));
50530
+ this.missing_env = missing_env;
50531
+ this.name = "MementosProjectAuthorityIdentityError";
50532
+ }
50533
+ }
50534
+ function resolveMementosProjectAuthorityIdentity(options = {}) {
50535
+ const authorityId = configuredValue(options.authorityId, MEMENTOS_PROJECT_AUTHORITY_ENV.authorityId);
50536
+ const tenantId = configuredValue(options.tenantId, MEMENTOS_PROJECT_AUTHORITY_ENV.tenantId);
50537
+ const corpusId = configuredValue(options.corpusId, MEMENTOS_PROJECT_AUTHORITY_ENV.corpusId);
50538
+ if (!authorityId || !tenantId || !corpusId) {
50539
+ const missingEnv = [];
50540
+ if (!authorityId)
50541
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.authorityId);
50542
+ if (!tenantId)
50543
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.tenantId);
50544
+ if (!corpusId)
50545
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.corpusId);
50546
+ throw new MementosProjectAuthorityIdentityError(missingEnv);
50547
+ }
50548
+ return {
50549
+ authority_id: authorityId,
50550
+ tenant_id: tenantId,
50551
+ corpus_id: corpusId
50552
+ };
50553
+ }
50554
+ function buildMementosProjectRegistrationCapability(options = {}) {
50555
+ const identity = resolveMementosProjectAuthorityIdentity(options);
50556
+ return {
50557
+ authority: "mementos",
50558
+ route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
50559
+ package_version: options.packageVersion ?? getMementosPackageVersion(),
50560
+ ...identity,
50561
+ supported_resources: ["project"],
50562
+ conditional_create: true,
50563
+ immutable_receipts: true,
50564
+ exact_terminal_lookup: true,
50565
+ exact_readback: true,
50566
+ conditional_inverse: true,
50567
+ ambiguous_outcome_reconciliation: true,
50568
+ guarded_update: true,
50569
+ guarded_update_route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
50570
+ no_write_dry_run: true,
50571
+ expected_revision_compare_and_swap: true,
50572
+ caller_idempotency: true,
50573
+ exact_inverse_rollback: true,
50574
+ project_resource_enumeration: true,
50575
+ project_resource_route: MEMENTOS_PROJECT_RESOURCE_ROUTE,
50576
+ project_resource_kinds: ["project", "knowledge", "memory", "session"],
50577
+ stable_keyset_pagination: true,
50578
+ explicit_membership_only: true
50579
+ };
50580
+ }
50581
+
50582
+ // src/db/memory-project-link.ts
50491
50583
  class MemoryProjectLinkError extends Error {
50492
50584
  code;
50493
50585
  details;
@@ -50498,12 +50590,17 @@ class MemoryProjectLinkError extends Error {
50498
50590
  this.name = "MemoryProjectLinkError";
50499
50591
  }
50500
50592
  }
50501
- var LINK_AUTHORITY = {
50502
- authority_id: "mementos",
50503
- tenant_id: "default",
50504
- corpus_id: "default"
50505
- };
50506
50593
  var BOUNDED_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
50594
+ function linkAuthority() {
50595
+ try {
50596
+ return resolveMementosProjectAuthorityIdentity();
50597
+ } catch (error) {
50598
+ if (error instanceof MementosProjectAuthorityIdentityError) {
50599
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH", error.message, { authority_code: error.code, missing_env: error.missing_env });
50600
+ }
50601
+ throw error;
50602
+ }
50603
+ }
50507
50604
  function canonicalize(value) {
50508
50605
  if (Array.isArray(value))
50509
50606
  return value.map(canonicalize);
@@ -50611,7 +50708,8 @@ function receiptFromRow(row) {
50611
50708
  };
50612
50709
  }
50613
50710
  function assertIdentity(identity) {
50614
- if (identity.authority_id !== LINK_AUTHORITY.authority_id || identity.tenant_id !== LINK_AUTHORITY.tenant_id || identity.corpus_id !== LINK_AUTHORITY.corpus_id) {
50711
+ const expectedIdentity = linkAuthority();
50712
+ if (identity.authority_id !== expectedIdentity.authority_id || identity.tenant_id !== expectedIdentity.tenant_id || identity.corpus_id !== expectedIdentity.corpus_id) {
50615
50713
  throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH", "memory project link does not match this authority, tenant, and corpus");
50616
50714
  }
50617
50715
  }
@@ -51088,7 +51186,7 @@ function rollbackMemoryProjectLink(memoryId, request, db) {
51088
51186
  };
51089
51187
  });
51090
51188
  }
51091
- function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = LINK_AUTHORITY, db) {
51189
+ function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = linkAuthority(), db) {
51092
51190
  assertIdentity(identity);
51093
51191
  assertBoundedIdentifier(memoryId, "memory_id");
51094
51192
  assertBoundedIdentifier(receiptId, "receipt_id");
@@ -51553,25 +51651,6 @@ function focusFilterSQL(agentId, projectId) {
51553
51651
  init_database();
51554
51652
  init_api_mode();
51555
51653
  import { createHash as createHash2 } from "crypto";
51556
-
51557
- // src/project-registration/types.ts
51558
- var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
51559
- var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
51560
- var MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION = 1;
51561
- var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
51562
-
51563
- class MementosProjectRegistrationError extends Error {
51564
- code;
51565
- details;
51566
- constructor(code, message, details = {}) {
51567
- super(message);
51568
- this.code = code;
51569
- this.details = details;
51570
- this.name = "MementosProjectRegistrationError";
51571
- }
51572
- }
51573
-
51574
- // src/db/projects.ts
51575
51654
  function parseProjectRow2(row) {
51576
51655
  return {
51577
51656
  id: row["id"],
@@ -51605,12 +51684,17 @@ class ProjectGuardedUpdateError extends Error {
51605
51684
  this.name = "ProjectGuardedUpdateError";
51606
51685
  }
51607
51686
  }
51608
- var PROJECT_UPDATE_AUTHORITY = {
51609
- authority_id: "mementos",
51610
- tenant_id: "default",
51611
- corpus_id: "default"
51612
- };
51613
51687
  var BOUNDED_IDENTIFIER2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
51688
+ function projectUpdateAuthority() {
51689
+ try {
51690
+ return resolveMementosProjectAuthorityIdentity();
51691
+ } catch (error) {
51692
+ if (error instanceof MementosProjectAuthorityIdentityError) {
51693
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", error.message, { authority_code: error.code, missing_env: error.missing_env });
51694
+ }
51695
+ throw error;
51696
+ }
51697
+ }
51614
51698
  function canonicalizeProjectUpdateValue(value) {
51615
51699
  if (Array.isArray(value))
51616
51700
  return value.map(canonicalizeProjectUpdateValue);
@@ -51683,8 +51767,8 @@ function normalizeProjectUpdateInput(input) {
51683
51767
  }
51684
51768
  return normalized;
51685
51769
  }
51686
- function assertProjectUpdateIdentity(identity) {
51687
- if (identity.authority_id !== PROJECT_UPDATE_AUTHORITY.authority_id || identity.tenant_id !== PROJECT_UPDATE_AUTHORITY.tenant_id || identity.corpus_id !== PROJECT_UPDATE_AUTHORITY.corpus_id) {
51770
+ function assertProjectUpdateIdentity(identity, expectedIdentity = projectUpdateAuthority()) {
51771
+ if (identity.authority_id !== expectedIdentity.authority_id || identity.tenant_id !== expectedIdentity.tenant_id || identity.corpus_id !== expectedIdentity.corpus_id) {
51688
51772
  throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", "guarded project update does not match this authority, tenant, and corpus");
51689
51773
  }
51690
51774
  }
@@ -51693,8 +51777,8 @@ function assertBoundedIdentifier2(value, field) {
51693
51777
  throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
51694
51778
  }
51695
51779
  }
51696
- function assertProjectUpdateRequest(request) {
51697
- assertProjectUpdateIdentity(request);
51780
+ function assertProjectUpdateRequest(request, expectedIdentity = projectUpdateAuthority()) {
51781
+ assertProjectUpdateIdentity(request, expectedIdentity);
51698
51782
  assertBoundedIdentifier2(request.operation_id, "operation_id");
51699
51783
  assertBoundedIdentifier2(request.step_id, "step_id");
51700
51784
  assertBoundedIdentifier2(request.idempotency_key, "idempotency_key");
@@ -51807,7 +51891,7 @@ function makeProjectUpdateReceipt(input) {
51807
51891
  target_id: input.target_id,
51808
51892
  expected_revision: input.request.expected_revision,
51809
51893
  result_revision: input.after_project.updated_at,
51810
- result_digest: digestProjectUpdateValue(input.after_project),
51894
+ result_digest: input.result_digest ?? digestProjectUpdateValue(input.after_project),
51811
51895
  accepted_receipt_id: input.accepted_receipt_id ?? null,
51812
51896
  before_project: input.before_project,
51813
51897
  after_project: input.after_project,
@@ -51871,8 +51955,8 @@ function listProjects(db) {
51871
51955
  const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
51872
51956
  return rows.map(parseProjectRow2);
51873
51957
  }
51874
- function previewProjectUpdate(id, request, db) {
51875
- assertProjectUpdateRequest(request);
51958
+ function previewProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority()) {
51959
+ assertProjectUpdateRequest(request, expectedIdentity);
51876
51960
  const normalized = normalizeProjectUpdateInput(request.updates);
51877
51961
  if (!db && isApiMode()) {
51878
51962
  const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: true });
@@ -51894,8 +51978,8 @@ function previewProjectUpdate(id, request, db) {
51894
51978
  receipt: null
51895
51979
  };
51896
51980
  }
51897
- function applyProjectUpdate(id, request, db) {
51898
- assertProjectUpdateRequest(request);
51981
+ function applyProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
51982
+ assertProjectUpdateRequest(request, expectedIdentity);
51899
51983
  const normalized = normalizeProjectUpdateInput(request.updates);
51900
51984
  if (!db && isApiMode()) {
51901
51985
  const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: false });
@@ -51956,14 +52040,15 @@ function applyProjectUpdate(id, request, db) {
51956
52040
  request_digest: requestDigest,
51957
52041
  target_id: id,
51958
52042
  before_project: before,
51959
- after_project: readback
52043
+ after_project: readback,
52044
+ result_digest: resultDigestForProject?.(readback)
51960
52045
  });
51961
52046
  insertProjectUpdateReceipt(d, receipt);
51962
52047
  return { dry_run: false, applied: true, project: readback, receipt };
51963
52048
  });
51964
52049
  }
51965
- function rollbackProjectUpdate(id, request, db) {
51966
- assertProjectUpdateRequest(request);
52050
+ function rollbackProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
52051
+ assertProjectUpdateRequest(request, expectedIdentity);
51967
52052
  assertBoundedIdentifier2(request.accepted_receipt_id, "accepted_receipt_id");
51968
52053
  if (!db && isApiMode()) {
51969
52054
  const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-rollback`, request);
@@ -52029,14 +52114,15 @@ function rollbackProjectUpdate(id, request, db) {
52029
52114
  target_id: id,
52030
52115
  before_project: current,
52031
52116
  after_project: readback,
52117
+ result_digest: resultDigestForProject?.(readback),
52032
52118
  accepted_receipt_id: accepted.receipt_id
52033
52119
  });
52034
52120
  insertProjectUpdateReceipt(d, receipt);
52035
52121
  return { dry_run: false, applied: true, project: readback, receipt };
52036
52122
  });
52037
52123
  }
52038
- function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHORITY, db) {
52039
- assertProjectUpdateIdentity(identity);
52124
+ function getProjectUpdateReceipt(id, receiptId, identity = projectUpdateAuthority(), db, expectedIdentity = projectUpdateAuthority()) {
52125
+ assertProjectUpdateIdentity(identity, expectedIdentity);
52040
52126
  if (!db && isApiMode()) {
52041
52127
  const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/update-receipts/lookup`, { ...identity, receipt_id: receiptId });
52042
52128
  return data;
@@ -52178,9 +52264,9 @@ function assertWithinBounds(value, bounds, startedAt) {
52178
52264
  }
52179
52265
  return { response_bytes: bytes, elapsed_ms: elapsed };
52180
52266
  }
52181
- function withResponseControl(receipt, bounds, startedAt) {
52267
+ function withBoundedResponseControl(payload, bounds, startedAt) {
52182
52268
  const result = {
52183
- receipt,
52269
+ ...payload,
52184
52270
  response_control: {
52185
52271
  response_byte_limit: bounds.response_byte_limit,
52186
52272
  time_budget_ms: bounds.time_budget_ms,
@@ -52203,6 +52289,9 @@ function withResponseControl(receipt, bounds, startedAt) {
52203
52289
  result.response_control.elapsed_ms = measured.elapsed_ms;
52204
52290
  return result;
52205
52291
  }
52292
+ function withResponseControl(receipt, bounds, startedAt) {
52293
+ return withBoundedResponseControl({ receipt }, bounds, startedAt);
52294
+ }
52206
52295
  function requireString(value, field, options = {}) {
52207
52296
  const min = options.min ?? 1;
52208
52297
  const max = options.max ?? 512;
@@ -52229,6 +52318,80 @@ function ownedPath(target) {
52229
52318
  }
52230
52319
  return path;
52231
52320
  }
52321
+ function guardedAuthorityIdentity(capability) {
52322
+ return {
52323
+ authority_id: capability.authority_id,
52324
+ tenant_id: capability.tenant_id,
52325
+ corpus_id: capability.corpus_id
52326
+ };
52327
+ }
52328
+ function assertGuardedAuthorityRequest(targetId, request, capability) {
52329
+ assertBounds(request);
52330
+ requireString(targetId, "target_id", { min: 8, max: 128, pattern: OPERATION_PATTERN });
52331
+ if (request.authority !== "mementos" || request.authority_route !== MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE || request.package_version !== capability.package_version || request.authority_id !== capability.authority_id || request.tenant_id !== capability.tenant_id || request.corpus_id !== capability.corpus_id) {
52332
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "guarded project request does not match this authority capability identity");
52333
+ }
52334
+ return guardedAuthorityIdentity(capability);
52335
+ }
52336
+ function assertGuardedOperationFields(request) {
52337
+ requireString(request.operation_id, "operation_id", {
52338
+ min: 8,
52339
+ max: 128,
52340
+ pattern: OPERATION_PATTERN
52341
+ });
52342
+ requireString(request.step_id, "step_id", {
52343
+ min: 8,
52344
+ max: 128,
52345
+ pattern: OPERATION_PATTERN
52346
+ });
52347
+ requireString(request.idempotency_key, "idempotency_key", {
52348
+ min: 8,
52349
+ max: 128,
52350
+ pattern: OPERATION_PATTERN
52351
+ });
52352
+ requireString(request.expected_revision, "expected_revision", { max: 128 });
52353
+ }
52354
+ function assertGuardedUpdateRequest(targetId, request, capability) {
52355
+ const identity = assertGuardedAuthorityRequest(targetId, request, capability);
52356
+ assertGuardedOperationFields(request);
52357
+ if (!request.updates || typeof request.updates !== "object") {
52358
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "guarded project updates must contain exactly one private path handle");
52359
+ }
52360
+ exactKeys(request.updates, ["path"], "updates");
52361
+ return { identity, path: ownedPath(request.updates.path) };
52362
+ }
52363
+ function publicGuardedUpdateReceipt(receipt, capability) {
52364
+ return {
52365
+ receipt_id: receipt.receipt_id,
52366
+ authority: "mementos",
52367
+ route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
52368
+ package_version: capability.package_version,
52369
+ authority_id: capability.authority_id,
52370
+ tenant_id: capability.tenant_id,
52371
+ corpus_id: capability.corpus_id,
52372
+ operation_id: receipt.operation_id,
52373
+ step_id: receipt.step_id,
52374
+ direction: receipt.direction,
52375
+ idempotency_key: receipt.idempotency_key,
52376
+ request_digest: receipt.request_digest,
52377
+ outcome: "accepted",
52378
+ target_id: receipt.target_id,
52379
+ expected_revision: receipt.expected_revision,
52380
+ result_revision: receipt.result_revision,
52381
+ result_digest: receipt.result_digest,
52382
+ accepted_receipt_id: receipt.accepted_receipt_id,
52383
+ created_at: receipt.created_at
52384
+ };
52385
+ }
52386
+ function guardedProjectError(cause) {
52387
+ if (cause instanceof MementosProjectRegistrationError)
52388
+ throw cause;
52389
+ if (cause instanceof ProjectGuardedUpdateError) {
52390
+ const code = cause.code === "PROJECT_UPDATE_INVALID_INPUT" ? "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT" : cause.code === "PROJECT_UPDATE_AUTHORITY_MISMATCH" ? "MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH" : cause.code === "PROJECT_UPDATE_NOT_FOUND" ? "MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND" : cause.code === "PROJECT_UPDATE_RECEIPT_NOT_FOUND" ? "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND" : "MEMENTOS_PROJECT_REGISTRATION_CONFLICT";
52391
+ throw new MementosProjectRegistrationError(code, "guarded project operation was rejected without exposing private project data", { project_update_code: cause.code });
52392
+ }
52393
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "guarded project operation failed before a bounded public result was available");
52394
+ }
52232
52395
  function normalizedCallDigest(request) {
52233
52396
  return digestMementosProjectRegistrationValue({
52234
52397
  authority_route: request.authority_route,
@@ -52600,26 +52763,7 @@ class PackageOwnedMementosProjectRegistrationAuthority {
52600
52763
  this.db = db;
52601
52764
  this.now = options.now ?? (() => new Date().toISOString());
52602
52765
  this.faultInjector = options.faultInjector;
52603
- this.capabilityValue = {
52604
- authority: "mementos",
52605
- route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
52606
- package_version: options.packageVersion ?? getMementosPackageVersion(),
52607
- authority_id: options.authorityId ?? "mementos",
52608
- tenant_id: options.tenantId ?? "default",
52609
- corpus_id: options.corpusId ?? "default",
52610
- supported_resources: ["project"],
52611
- conditional_create: true,
52612
- immutable_receipts: true,
52613
- exact_terminal_lookup: true,
52614
- exact_readback: true,
52615
- conditional_inverse: true,
52616
- ambiguous_outcome_reconciliation: true,
52617
- guarded_update: true,
52618
- no_write_dry_run: true,
52619
- expected_revision_compare_and_swap: true,
52620
- caller_idempotency: true,
52621
- exact_inverse_rollback: true
52622
- };
52766
+ this.capabilityValue = buildMementosProjectRegistrationCapability(options);
52623
52767
  }
52624
52768
  fault(point, request) {
52625
52769
  this.faultInjector?.(point, {
@@ -52676,6 +52820,90 @@ class PackageOwnedMementosProjectRegistrationAuthority {
52676
52820
  supported_resources: ["project"]
52677
52821
  };
52678
52822
  }
52823
+ boundedGuardedUpdateResult(targetId2, project, receipt, bounds, startedAt) {
52824
+ if (!receipt || project.id !== targetId2 || receipt.target_id !== targetId2) {
52825
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "guarded project operation did not return its exact immutable receipt");
52826
+ }
52827
+ if (project.updated_at !== receipt.result_revision) {
52828
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "guarded project result revision did not match its immutable receipt");
52829
+ }
52830
+ const record = {
52831
+ target_id: targetId2,
52832
+ revision: receipt.result_revision,
52833
+ digest: receipt.result_digest
52834
+ };
52835
+ return withBoundedResponseControl({
52836
+ dry_run: false,
52837
+ applied: true,
52838
+ record,
52839
+ receipt: publicGuardedUpdateReceipt(receipt, this.capabilityValue)
52840
+ }, bounds, startedAt);
52841
+ }
52842
+ async guardedUpdateProject(targetId2, request) {
52843
+ const startedAt = Date.now();
52844
+ const { identity, path } = assertGuardedUpdateRequest(targetId2, request, this.capabilityValue);
52845
+ try {
52846
+ const result = applyProjectUpdate(targetId2, {
52847
+ ...identity,
52848
+ operation_id: request.operation_id,
52849
+ step_id: request.step_id,
52850
+ idempotency_key: request.idempotency_key,
52851
+ expected_revision: request.expected_revision,
52852
+ updates: { path }
52853
+ }, this.db, identity, (project) => projectRecord(this.db, project).digest);
52854
+ return this.boundedGuardedUpdateResult(targetId2, result.project, result.receipt, request, startedAt);
52855
+ } catch (cause) {
52856
+ return guardedProjectError(cause);
52857
+ }
52858
+ }
52859
+ async getGuardedProjectUpdateReceipt(targetId2, receiptId2, request) {
52860
+ const startedAt = Date.now();
52861
+ const identity = assertGuardedAuthorityRequest(targetId2, request, this.capabilityValue);
52862
+ requireString(receiptId2, "receipt_id", {
52863
+ min: 8,
52864
+ max: 128,
52865
+ pattern: OPERATION_PATTERN
52866
+ });
52867
+ try {
52868
+ const receipt = getProjectUpdateReceipt(targetId2, receiptId2, identity, this.db, identity);
52869
+ return withBoundedResponseControl({
52870
+ receipt: publicGuardedUpdateReceipt(receipt, this.capabilityValue)
52871
+ }, request, startedAt);
52872
+ } catch (cause) {
52873
+ return guardedProjectError(cause);
52874
+ }
52875
+ }
52876
+ async rollbackGuardedProjectUpdate(targetId2, request) {
52877
+ const startedAt = Date.now();
52878
+ const identity = assertGuardedAuthorityRequest(targetId2, request, this.capabilityValue);
52879
+ assertGuardedOperationFields(request);
52880
+ if (!request.accepted_receipt || typeof request.accepted_receipt !== "object") {
52881
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional rollback requires the exact sanitized accepted receipt");
52882
+ }
52883
+ requireString(request.accepted_receipt.receipt_id, "accepted_receipt.receipt_id", {
52884
+ min: 8,
52885
+ max: 128,
52886
+ pattern: OPERATION_PATTERN
52887
+ });
52888
+ try {
52889
+ const internalAccepted = getProjectUpdateReceipt(targetId2, request.accepted_receipt.receipt_id, identity, this.db, identity);
52890
+ const accepted = publicGuardedUpdateReceipt(internalAccepted, this.capabilityValue);
52891
+ if (accepted.direction !== "forward" || accepted.target_id !== targetId2 || request.expected_revision !== accepted.result_revision || canonicalMementosProjectRegistrationJson(request.accepted_receipt) !== canonicalMementosProjectRegistrationJson(accepted)) {
52892
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional rollback receipt does not exactly match the accepted forward update");
52893
+ }
52894
+ const result = rollbackProjectUpdate(targetId2, {
52895
+ ...identity,
52896
+ operation_id: request.operation_id,
52897
+ step_id: request.step_id,
52898
+ idempotency_key: request.idempotency_key,
52899
+ expected_revision: request.expected_revision,
52900
+ accepted_receipt_id: accepted.receipt_id
52901
+ }, this.db, identity, (project) => projectRecord(this.db, project).digest);
52902
+ return this.boundedGuardedUpdateResult(targetId2, result.project, result.receipt, request, startedAt);
52903
+ } catch (cause) {
52904
+ return guardedProjectError(cause);
52905
+ }
52906
+ }
52679
52907
  async create(request) {
52680
52908
  const startedAt = Date.now();
52681
52909
  const path = assertForwardRequest2(request, this.capabilityValue);
@@ -52772,9 +53000,9 @@ class PackageOwnedMementosProjectRegistrationAuthority {
52772
53000
  throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
52773
53001
  }
52774
53002
  requireString(request.target_id, "target_id", {
52775
- min: 51,
52776
- max: 51,
52777
- pattern: PROJECT_ID_PATTERN
53003
+ min: 8,
53004
+ max: 128,
53005
+ pattern: OPERATION_PATTERN
52778
53006
  });
52779
53007
  const path = ownedPath(request.target);
52780
53008
  const project = getProjectByExactId3(this.db, request.target_id);
@@ -53033,6 +53261,31 @@ function fromWireReadRequest(body) {
53033
53261
  target: new WirePathHandle(canonicalPath)
53034
53262
  };
53035
53263
  }
53264
+ function fromWireGuardedUpdateRequest(body) {
53265
+ const { target_id: targetId2, updates, ...request } = body;
53266
+ if (typeof targetId2 !== "string") {
53267
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target_id is required on the private guarded-update transport");
53268
+ }
53269
+ if (!updates || typeof updates !== "object" || Array.isArray(updates) || typeof updates["path"] !== "string") {
53270
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "updates.path is required on the private guarded-update transport");
53271
+ }
53272
+ return {
53273
+ targetId: targetId2,
53274
+ request: {
53275
+ ...request,
53276
+ updates: {
53277
+ path: new WirePathHandle(String(updates["path"]))
53278
+ }
53279
+ }
53280
+ };
53281
+ }
53282
+ function exactWireIdentifier(body, field) {
53283
+ const value = body[field];
53284
+ if (typeof value !== "string") {
53285
+ throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is required on the guarded-update transport`);
53286
+ }
53287
+ return value;
53288
+ }
53036
53289
  async function handleMementosProjectRegistrationHttpRequest(request, url, authority, basePath = "/v1/project-registration") {
53037
53290
  const path = url.pathname;
53038
53291
  if (path !== basePath && !path.startsWith(`${basePath}/`))
@@ -53067,6 +53320,21 @@ async function handleMementosProjectRegistrationHttpRequest(request, url, author
53067
53320
  if (action === "verify-inverse") {
53068
53321
  return json({ verification: await authority.verifyInverse(fromWireRequest(body)) });
53069
53322
  }
53323
+ if (action === "projects/guarded-update") {
53324
+ const guarded = fromWireGuardedUpdateRequest(body);
53325
+ return json(await authority.guardedUpdateProject(guarded.targetId, guarded.request));
53326
+ }
53327
+ if (action === "projects/update-receipts/lookup") {
53328
+ const targetId2 = exactWireIdentifier(body, "target_id");
53329
+ const receiptId2 = exactWireIdentifier(body, "receipt_id");
53330
+ const { target_id: _targetId, receipt_id: _receiptId, ...lookup } = body;
53331
+ return json(await authority.getGuardedProjectUpdateReceipt(targetId2, receiptId2, lookup));
53332
+ }
53333
+ if (action === "projects/guarded-rollback") {
53334
+ const targetId2 = exactWireIdentifier(body, "target_id");
53335
+ const { target_id: _targetId, ...rollback } = body;
53336
+ return json(await authority.rollbackGuardedProjectUpdate(targetId2, rollback));
53337
+ }
53070
53338
  return json({
53071
53339
  error: "unknown Mementos project-registration route",
53072
53340
  code: "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
@@ -53099,6 +53367,15 @@ function toWireRequest(request) {
53099
53367
  canonical_path: extractPath(target)
53100
53368
  };
53101
53369
  }
53370
+ function toWireGuardedUpdateRequest(targetId2, request) {
53371
+ return {
53372
+ ...request,
53373
+ target_id: targetId2,
53374
+ updates: {
53375
+ path: extractPath(request.updates.path)
53376
+ }
53377
+ };
53378
+ }
53102
53379
 
53103
53380
  class MementosProjectRegistrationHttpClient {
53104
53381
  authority = "mementos";
@@ -53160,10 +53437,333 @@ class MementosProjectRegistrationHttpClient {
53160
53437
  });
53161
53438
  return body.verification;
53162
53439
  }
53440
+ async guardedUpdateProject(targetId2, request) {
53441
+ return this.request("/projects/guarded-update", {
53442
+ method: "POST",
53443
+ body: JSON.stringify(toWireGuardedUpdateRequest(targetId2, request))
53444
+ });
53445
+ }
53446
+ async getGuardedProjectUpdateReceipt(targetId2, receiptId2, request) {
53447
+ return this.request("/projects/update-receipts/lookup", {
53448
+ method: "POST",
53449
+ body: JSON.stringify({ ...request, target_id: targetId2, receipt_id: receiptId2 })
53450
+ });
53451
+ }
53452
+ async rollbackGuardedProjectUpdate(targetId2, request) {
53453
+ return this.request("/projects/guarded-rollback", {
53454
+ method: "POST",
53455
+ body: JSON.stringify({ ...request, target_id: targetId2 })
53456
+ });
53457
+ }
53163
53458
  }
53164
53459
  function createMementosProjectRegistrationHttpClient(options) {
53165
53460
  return new MementosProjectRegistrationHttpClient(options);
53166
53461
  }
53462
+ // src/project-registration/project-resources.ts
53463
+ init_api_mode();
53464
+ init_database();
53465
+ var DEFAULT_PAGE_LIMIT = 100;
53466
+ var MAX_PAGE_LIMIT = 1000;
53467
+ var CURSOR_SCHEMA = "mementos.project-resources.cursor.v1";
53468
+
53469
+ class MementosProjectResourceError extends Error {
53470
+ code;
53471
+ details;
53472
+ constructor(code, message, details = {}) {
53473
+ super(message);
53474
+ this.code = code;
53475
+ this.details = details;
53476
+ this.name = "MementosProjectResourceError";
53477
+ }
53478
+ }
53479
+ function timestamp(value) {
53480
+ return value instanceof Date ? value.toISOString() : String(value);
53481
+ }
53482
+ function normalizeSqlValue(value) {
53483
+ if (value instanceof Date)
53484
+ return value.toISOString();
53485
+ if (Array.isArray(value))
53486
+ return value.map(normalizeSqlValue);
53487
+ if (!value || typeof value !== "object")
53488
+ return value;
53489
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, normalizeSqlValue(item)]));
53490
+ }
53491
+ function exactProject(db, projectId) {
53492
+ const row = db.get("SELECT id, name, path, description, memory_prefix, created_at, updated_at FROM projects WHERE id = ? LIMIT 1", projectId);
53493
+ if (!row) {
53494
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_PROJECT_NOT_FOUND", `Mementos project not found: ${projectId}`, { project_id: projectId });
53495
+ }
53496
+ return row;
53497
+ }
53498
+ function resourceKey(resource) {
53499
+ const rank = MEMENTOS_PROJECT_RESOURCE_KINDS.indexOf(resource.resource_kind);
53500
+ return `${String(rank).padStart(2, "0")}:${resource.stable_id}`;
53501
+ }
53502
+ function projectResource(project) {
53503
+ const normalized = normalizeSqlValue(project);
53504
+ return {
53505
+ authority: "mementos",
53506
+ source_package: "@hasna/mementos",
53507
+ project_id: project.id,
53508
+ resource_kind: "project",
53509
+ stable_id: project.id,
53510
+ revision: timestamp(project.updated_at),
53511
+ digest: digestMementosProjectRegistrationValue(normalized),
53512
+ membership: "project_aggregate"
53513
+ };
53514
+ }
53515
+ function memoryResources(db, projectId) {
53516
+ const rows = db.all("SELECT * FROM memories WHERE project_id = ? ORDER BY id ASC", projectId);
53517
+ return rows.map((row) => {
53518
+ const memory = normalizeSqlValue(parseMemoryRow(row));
53519
+ return {
53520
+ authority: "mementos",
53521
+ source_package: "@hasna/mementos",
53522
+ project_id: projectId,
53523
+ resource_kind: row["category"] === "knowledge" ? "knowledge" : "memory",
53524
+ stable_id: String(row["id"]),
53525
+ revision: timestamp(row["updated_at"]),
53526
+ digest: digestMementosProjectRegistrationValue(memory),
53527
+ membership: "explicit_project_id_or_focus"
53528
+ };
53529
+ });
53530
+ }
53531
+ function sessionResources(db, projectId) {
53532
+ const rows = db.all("SELECT * FROM session_memory_jobs WHERE project_id = ? ORDER BY id ASC", projectId);
53533
+ return rows.map((row) => {
53534
+ const normalized = {
53535
+ id: String(row["id"]),
53536
+ session_id: String(row["session_id"]),
53537
+ agent_id: row["agent_id"] === null ? null : String(row["agent_id"] ?? "") || null,
53538
+ project_id: row["project_id"] === null ? null : String(row["project_id"] ?? "") || null,
53539
+ source: String(row["source"]),
53540
+ status: String(row["status"]),
53541
+ transcript: String(row["transcript"]),
53542
+ chunk_count: Number(row["chunk_count"]),
53543
+ memories_extracted: Number(row["memories_extracted"]),
53544
+ error: row["error"] === null ? null : String(row["error"] ?? "") || null,
53545
+ metadata: typeof row["metadata"] === "string" ? JSON.parse(row["metadata"] || "{}") : normalizeSqlValue(row["metadata"] ?? {}),
53546
+ created_at: timestamp(row["created_at"]),
53547
+ started_at: row["started_at"] === null ? null : timestamp(row["started_at"]),
53548
+ completed_at: row["completed_at"] === null ? null : timestamp(row["completed_at"])
53549
+ };
53550
+ return {
53551
+ authority: "mementos",
53552
+ source_package: "@hasna/mementos",
53553
+ project_id: projectId,
53554
+ resource_kind: "session",
53555
+ stable_id: String(row["id"]),
53556
+ revision: timestamp(row["completed_at"] ?? row["started_at"] ?? row["created_at"]),
53557
+ digest: digestMementosProjectRegistrationValue(normalized),
53558
+ membership: "explicit_project_id_or_focus"
53559
+ };
53560
+ });
53561
+ }
53562
+ function normalizeResourceKinds(value) {
53563
+ if (!value)
53564
+ return [...MEMENTOS_PROJECT_RESOURCE_KINDS];
53565
+ if (value.length === 0) {
53566
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "resource_kinds must contain at least one supported resource kind");
53567
+ }
53568
+ const requested = new Set(value);
53569
+ for (const kind of requested) {
53570
+ if (!MEMENTOS_PROJECT_RESOURCE_KINDS.includes(kind)) {
53571
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `Unsupported Mementos project resource kind: ${kind}`);
53572
+ }
53573
+ }
53574
+ return MEMENTOS_PROJECT_RESOURCE_KINDS.filter((kind) => requested.has(kind));
53575
+ }
53576
+ function normalizeLimit(value) {
53577
+ const limit = value ?? DEFAULT_PAGE_LIMIT;
53578
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_PAGE_LIMIT) {
53579
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `limit must be an integer between 1 and ${MAX_PAGE_LIMIT}`);
53580
+ }
53581
+ return limit;
53582
+ }
53583
+ function encodeCursor(cursor) {
53584
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
53585
+ }
53586
+ function decodeCursor(raw) {
53587
+ try {
53588
+ const parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
53589
+ if (parsed.schema !== CURSOR_SCHEMA || typeof parsed.project_id !== "string" || typeof parsed.collection_revision !== "string" || !Array.isArray(parsed.resource_kinds) || typeof parsed.after_key !== "string") {
53590
+ throw new Error("invalid cursor shape");
53591
+ }
53592
+ return parsed;
53593
+ } catch {
53594
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "cursor is not a valid Mementos project-resource cursor");
53595
+ }
53596
+ }
53597
+ function localPopulation(projectId, db, resourceKinds) {
53598
+ const project = exactProject(db, projectId);
53599
+ const selected = new Set(resourceKinds);
53600
+ const resources = [
53601
+ ...selected.has("project") ? [projectResource(project)] : [],
53602
+ ...memoryResources(db, projectId).filter((resource) => selected.has(resource.resource_kind)),
53603
+ ...selected.has("session") ? sessionResources(db, projectId) : []
53604
+ ].sort((left, right) => resourceKey(left).localeCompare(resourceKey(right)));
53605
+ const collectionRevision = digestMementosProjectRegistrationValue({
53606
+ schema: MEMENTOS_PROJECT_RESOURCE_ROUTE,
53607
+ project_id: projectId,
53608
+ project_revision: timestamp(project.updated_at),
53609
+ resource_kinds: resourceKinds,
53610
+ resources: resources.map((resource) => ({
53611
+ resource_kind: resource.resource_kind,
53612
+ stable_id: resource.stable_id,
53613
+ revision: resource.revision,
53614
+ digest: resource.digest
53615
+ }))
53616
+ });
53617
+ return { project, resources, collectionRevision };
53618
+ }
53619
+ function readMementosProjectResourcePage(projectId, options = {}, db, authorityOptions = {}) {
53620
+ const resourceKinds = normalizeResourceKinds(options.resource_kinds);
53621
+ const limit = normalizeLimit(options.limit);
53622
+ if (!db && isApiMode()) {
53623
+ const { data } = apiJson("GET", `/projects/${encodeURIComponent(projectId)}/resources${toQuery({
53624
+ limit,
53625
+ cursor: options.cursor ?? undefined,
53626
+ resource_kinds: resourceKinds.join(",")
53627
+ })}`);
53628
+ return data;
53629
+ }
53630
+ const d = db ?? getDatabase();
53631
+ const { project, resources, collectionRevision } = localPopulation(projectId, d, resourceKinds);
53632
+ let start = 0;
53633
+ if (options.cursor) {
53634
+ const cursor = decodeCursor(options.cursor);
53635
+ if (cursor.project_id !== projectId || JSON.stringify(cursor.resource_kinds) !== JSON.stringify(resourceKinds)) {
53636
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "cursor does not belong to this project and resource-kind selection");
53637
+ }
53638
+ if (cursor.collection_revision !== collectionRevision) {
53639
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource collection changed; restart from the first page", {
53640
+ cursor_collection_revision: cursor.collection_revision,
53641
+ current_collection_revision: collectionRevision
53642
+ });
53643
+ }
53644
+ const afterIndex = resources.findIndex((resource) => resourceKey(resource) === cursor.after_key);
53645
+ if (afterIndex < 0) {
53646
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource cursor no longer names a member; restart from the first page");
53647
+ }
53648
+ start = afterIndex + 1;
53649
+ }
53650
+ const pageResources = resources.slice(start, start + limit);
53651
+ const hasMore = start + pageResources.length < resources.length;
53652
+ const nextCursor = hasMore && pageResources.length > 0 ? encodeCursor({
53653
+ schema: CURSOR_SCHEMA,
53654
+ project_id: projectId,
53655
+ collection_revision: collectionRevision,
53656
+ resource_kinds: resourceKinds,
53657
+ after_key: resourceKey(pageResources[pageResources.length - 1])
53658
+ }) : null;
53659
+ const capability = buildMementosProjectRegistrationCapability(authorityOptions);
53660
+ return {
53661
+ schema: "mementos.project-resources.v1",
53662
+ authority: {
53663
+ authority: capability.authority,
53664
+ authority_id: capability.authority_id,
53665
+ tenant_id: capability.tenant_id,
53666
+ corpus_id: capability.corpus_id,
53667
+ package_version: capability.package_version
53668
+ },
53669
+ project_id: projectId,
53670
+ project_revision: timestamp(project.updated_at),
53671
+ collection_revision: collectionRevision,
53672
+ resource_kinds: resourceKinds,
53673
+ resources: pageResources,
53674
+ count: pageResources.length,
53675
+ total: resources.length,
53676
+ limit,
53677
+ cursor: options.cursor ?? null,
53678
+ next_cursor: nextCursor,
53679
+ has_more: hasMore,
53680
+ complete: true,
53681
+ truncated: false
53682
+ };
53683
+ }
53684
+ function readAllMementosProjectResources(projectId, options = {}, db, authorityOptions = {}) {
53685
+ const pageSize = normalizeLimit(options.page_size);
53686
+ let cursor = null;
53687
+ let first = null;
53688
+ const resources = [];
53689
+ const seen = new Set;
53690
+ do {
53691
+ const page = readMementosProjectResourcePage(projectId, {
53692
+ limit: pageSize,
53693
+ cursor,
53694
+ resource_kinds: options.resource_kinds
53695
+ }, db, authorityOptions);
53696
+ if (!first)
53697
+ first = page;
53698
+ if (page.collection_revision !== first.collection_revision || page.total !== first.total || JSON.stringify(page.resource_kinds) !== JSON.stringify(first.resource_kinds)) {
53699
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource collection changed during complete traversal");
53700
+ }
53701
+ for (const resource of page.resources) {
53702
+ const key = resourceKey(resource);
53703
+ if (seen.has(key)) {
53704
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", `Mementos project resource traversal returned duplicate stable ID: ${key}`);
53705
+ }
53706
+ seen.add(key);
53707
+ resources.push(resource);
53708
+ }
53709
+ if (page.has_more && !page.next_cursor) {
53710
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", "Mementos project resource page claimed more results without a continuation cursor");
53711
+ }
53712
+ cursor = page.next_cursor;
53713
+ } while (cursor);
53714
+ if (!first) {
53715
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", "Mementos project resource traversal returned no first page");
53716
+ }
53717
+ if (resources.length !== first.total) {
53718
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INCOMPLETE", `Mementos project resource traversal returned ${resources.length} of ${first.total} resources`);
53719
+ }
53720
+ return {
53721
+ ...first,
53722
+ resources,
53723
+ count: resources.length,
53724
+ total: resources.length,
53725
+ limit: pageSize,
53726
+ cursor: null,
53727
+ next_cursor: null,
53728
+ has_more: false,
53729
+ complete: true,
53730
+ truncated: false
53731
+ };
53732
+ }
53733
+ function getMementosProjectResourceExact(projectId, resourceKind, stableId, db, authorityOptions = {}) {
53734
+ if (!MEMENTOS_PROJECT_RESOURCE_KINDS.includes(resourceKind)) {
53735
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `Unsupported Mementos project resource kind: ${resourceKind}`);
53736
+ }
53737
+ if (!db && isApiMode()) {
53738
+ const { data } = apiJson("GET", `/projects/${encodeURIComponent(projectId)}/resources/${encodeURIComponent(resourceKind)}/${encodeURIComponent(stableId)}`);
53739
+ return data;
53740
+ }
53741
+ const d = db ?? getDatabase();
53742
+ const { project, resources, collectionRevision } = localPopulation(projectId, d, [
53743
+ resourceKind
53744
+ ]);
53745
+ const resource = resources.find((candidate) => candidate.stable_id === stableId);
53746
+ if (!resource) {
53747
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_NOT_FOUND", `Mementos ${resourceKind} resource not found in project ${projectId}: ${stableId}`, { project_id: projectId, resource_kind: resourceKind, stable_id: stableId });
53748
+ }
53749
+ const capability = buildMementosProjectRegistrationCapability(authorityOptions);
53750
+ return {
53751
+ schema: "mementos.project-resource.v1",
53752
+ authority: {
53753
+ authority: capability.authority,
53754
+ authority_id: capability.authority_id,
53755
+ tenant_id: capability.tenant_id,
53756
+ corpus_id: capability.corpus_id,
53757
+ package_version: capability.package_version
53758
+ },
53759
+ project_id: projectId,
53760
+ project_revision: timestamp(project.updated_at),
53761
+ collection_revision: collectionRevision,
53762
+ resource,
53763
+ complete: true,
53764
+ truncated: false
53765
+ };
53766
+ }
53167
53767
  // src/db/machines.ts
53168
53768
  init_database();
53169
53769
  import { hostname, platform } from "os";
@@ -53288,13 +53888,13 @@ function createEntity(input, db) {
53288
53888
  return data;
53289
53889
  }
53290
53890
  const d = db || getDatabase();
53291
- const timestamp = now();
53891
+ const timestamp2 = now();
53292
53892
  const metadataJson = JSON.stringify(input.metadata || {});
53293
53893
  const existing = d.query(`SELECT * FROM entities
53294
53894
  WHERE name = ? AND type = ? AND COALESCE(project_id, '') = ?`).get(input.name, input.type, input.project_id || "");
53295
53895
  if (existing) {
53296
53896
  const sets = ["updated_at = ?"];
53297
- const params = [timestamp];
53897
+ const params = [timestamp2];
53298
53898
  if (input.description !== undefined) {
53299
53899
  sets.push("description = ?");
53300
53900
  params.push(input.description);
@@ -53317,8 +53917,8 @@ function createEntity(input, db) {
53317
53917
  input.description || null,
53318
53918
  metadataJson,
53319
53919
  input.project_id || null,
53320
- timestamp,
53321
- timestamp
53920
+ timestamp2,
53921
+ timestamp2
53322
53922
  ]);
53323
53923
  hookRegistry.runHooks("PostEntityCreate", {
53324
53924
  entityId: id,
@@ -54146,7 +54746,7 @@ function memoryResource(memory) {
54146
54746
  tags: memory.tags
54147
54747
  };
54148
54748
  }
54149
- function projectResource(projectId, name, externalId) {
54749
+ function projectResource2(projectId, name, externalId) {
54150
54750
  return {
54151
54751
  kind: "project",
54152
54752
  id: projectId,
@@ -54252,7 +54852,7 @@ function createMementosProjectPanel(projectRef, options = {}) {
54252
54852
  actionResource("mementos:save", "Save project memory")
54253
54853
  ],
54254
54854
  resourceRefs: [
54255
- projectResource(projectId, project?.name ?? projectRef, project?.id ?? projectRef),
54855
+ projectResource2(projectId, project?.name ?? projectRef, project?.id ?? projectRef),
54256
54856
  ...memories.slice(0, limit).map(memoryResource)
54257
54857
  ],
54258
54858
  renderFragment: {
@@ -54854,29 +55454,29 @@ function enforceQuotas(config, db) {
54854
55454
  }
54855
55455
  function archiveStale(staleDays, db) {
54856
55456
  const d = db || getDatabase();
54857
- const timestamp = now();
55457
+ const timestamp2 = now();
54858
55458
  const cutoff = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000).toISOString();
54859
55459
  const archiveWhere = `status = 'active' AND pinned = 0 AND COALESCE(accessed_at, created_at) < ?`;
54860
55460
  const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${archiveWhere}`).get(cutoff).c;
54861
55461
  if (count > 0) {
54862
- d.run(`UPDATE memories SET status = 'archived', updated_at = ? WHERE ${archiveWhere}`, [timestamp, cutoff]);
55462
+ d.run(`UPDATE memories SET status = 'archived', updated_at = ? WHERE ${archiveWhere}`, [timestamp2, cutoff]);
54863
55463
  }
54864
55464
  return count;
54865
55465
  }
54866
55466
  function archiveUnused(days, db) {
54867
55467
  const d = db || getDatabase();
54868
- const timestamp = now();
55468
+ const timestamp2 = now();
54869
55469
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
54870
55470
  const unusedWhere = `status = 'active' AND pinned = 0 AND access_count = 0 AND created_at < ?`;
54871
55471
  const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${unusedWhere}`).get(cutoff).c;
54872
55472
  if (count > 0) {
54873
- d.run(`UPDATE memories SET status = 'archived', updated_at = ? WHERE ${unusedWhere}`, [timestamp, cutoff]);
55473
+ d.run(`UPDATE memories SET status = 'archived', updated_at = ? WHERE ${unusedWhere}`, [timestamp2, cutoff]);
54874
55474
  }
54875
55475
  return count;
54876
55476
  }
54877
55477
  function deprioritizeStale(days, db) {
54878
55478
  const d = db || getDatabase();
54879
- const timestamp = now();
55479
+ const timestamp2 = now();
54880
55480
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
54881
55481
  const deprioWhere = `status = 'active' AND pinned = 0 AND importance > 1 AND COALESCE(accessed_at, updated_at) < ?`;
54882
55482
  const count = d.query(`SELECT COUNT(*) as c FROM memories WHERE ${deprioWhere}`).get(cutoff).c;
@@ -54885,7 +55485,7 @@ function deprioritizeStale(days, db) {
54885
55485
  SET importance = importance - 1,
54886
55486
  version = version + 1,
54887
55487
  updated_at = ?
54888
- WHERE ${deprioWhere}`, [timestamp, cutoff]);
55488
+ WHERE ${deprioWhere}`, [timestamp2, cutoff]);
54889
55489
  }
54890
55490
  return count;
54891
55491
  }
@@ -54909,9 +55509,9 @@ function getAgentSyncDir(agentName) {
54909
55509
  }
54910
55510
  return dir;
54911
55511
  }
54912
- function setHighWaterMark(agentDir, timestamp) {
55512
+ function setHighWaterMark(agentDir, timestamp2) {
54913
55513
  const markFile = join6(agentDir, ".highwatermark");
54914
- writeFileSync4(markFile, timestamp, "utf-8");
55514
+ writeFileSync4(markFile, timestamp2, "utf-8");
54915
55515
  }
54916
55516
  function resolveConflict(local, remote, resolution) {
54917
55517
  switch (resolution) {
@@ -55231,13 +55831,13 @@ function buildConflictKey(key, sourceMachine, updatedAt) {
55231
55831
  return `${key}__conflict__${machineSegment}__${timestampSegment || "0"}`;
55232
55832
  }
55233
55833
  function buildConflictClone(loser, sourceMachine, winnerId) {
55234
- const timestamp = new Date().toISOString();
55834
+ const timestamp2 = new Date().toISOString();
55235
55835
  const tags = new Set(ensureArrayValue(loser["tags"]));
55236
55836
  tags.add("sync-conflict");
55237
55837
  tags.add(`source_machine:${sourceMachine}`);
55238
55838
  const metadata = ensureObjectValue(loser["metadata"]);
55239
55839
  metadata["sync_conflict"] = true;
55240
- metadata["conflict_detected_at"] = timestamp;
55840
+ metadata["conflict_detected_at"] = timestamp2;
55241
55841
  metadata["conflict_original_id"] = loser["id"];
55242
55842
  metadata["conflict_winner_id"] = winnerId;
55243
55843
  metadata["conflict_source_machine"] = sourceMachine;
@@ -55249,10 +55849,10 @@ function buildConflictClone(loser, sourceMachine, winnerId) {
55249
55849
  metadata: JSON.stringify(metadata),
55250
55850
  access_count: 0,
55251
55851
  version: 1,
55252
- created_at: timestamp,
55253
- updated_at: timestamp,
55852
+ created_at: timestamp2,
55853
+ updated_at: timestamp2,
55254
55854
  ..."accessed_at" in loser ? { accessed_at: null } : {},
55255
- ..."ingested_at" in loser ? { ingested_at: timestamp } : {}
55855
+ ..."ingested_at" in loser ? { ingested_at: timestamp2 } : {}
55256
55856
  }, sourceMachine);
55257
55857
  }
55258
55858
  function insertConflictCloneIfMissing(db, clone) {
@@ -55502,13 +56102,13 @@ function createRelation(input, db) {
55502
56102
  }
55503
56103
  const d = db || getDatabase();
55504
56104
  const id = shortUuid();
55505
- const timestamp = now();
56105
+ const timestamp2 = now();
55506
56106
  const weight = input.weight ?? 1;
55507
56107
  const metadata = JSON.stringify(input.metadata ?? {});
55508
56108
  d.run(`INSERT INTO relations (id, source_entity_id, target_entity_id, relation_type, weight, metadata, created_at)
55509
56109
  VALUES (?, ?, ?, ?, ?, ?, ?)
55510
56110
  ON CONFLICT(source_entity_id, target_entity_id, relation_type)
55511
- DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp]);
56111
+ DO UPDATE SET weight = excluded.weight, metadata = excluded.metadata`, [id, input.source_entity_id, input.target_entity_id, input.relation_type, weight, metadata, timestamp2]);
55512
56112
  const row = d.query(`SELECT * FROM relations
55513
56113
  WHERE source_entity_id = ? AND target_entity_id = ? AND relation_type = ?`).get(input.source_entity_id, input.target_entity_id, input.relation_type);
55514
56114
  const relation = parseRelationRow(row);
@@ -55685,7 +56285,7 @@ function parseMemoryLink(row) {
55685
56285
  function createMemoryLink(input, db) {
55686
56286
  const d = db || getDatabase();
55687
56287
  const id = shortUuid();
55688
- const timestamp = now();
56288
+ const timestamp2 = now();
55689
56289
  d.run(`INSERT OR IGNORE INTO memory_links (id, source_memory_id, target_memory_id, relation_type, run_id, metadata, created_at)
55690
56290
  VALUES (?, ?, ?, ?, ?, ?, ?)`, [
55691
56291
  id,
@@ -55694,7 +56294,7 @@ function createMemoryLink(input, db) {
55694
56294
  input.relation_type,
55695
56295
  input.run_id ?? null,
55696
56296
  JSON.stringify(input.metadata ?? {}),
55697
- timestamp
56297
+ timestamp2
55698
56298
  ]);
55699
56299
  const row = d.query(`SELECT * FROM memory_links
55700
56300
  WHERE source_memory_id = ? AND target_memory_id = ? AND relation_type = ? AND COALESCE(run_id, '') = ?
@@ -57983,6 +58583,7 @@ export {
57983
58583
  rollbackMemoryProjectLink,
57984
58584
  resolveProjectId,
57985
58585
  resolvePartialId,
58586
+ resolveMementosProjectAuthorityIdentity,
57986
58587
  resetDatabase,
57987
58588
  renameMachine,
57988
58589
  releaseResourceLocks,
@@ -57995,6 +58596,8 @@ export {
57995
58596
  registerAgent,
57996
58597
  reflectOnTrajectory,
57997
58598
  redactSecrets,
58599
+ readMementosProjectResourcePage,
58600
+ readAllMementosProjectResources,
57998
58601
  pushStorageChanges,
57999
58602
  pullStorageChanges,
58000
58603
  providerRegistry,
@@ -58055,6 +58658,7 @@ export {
58055
58658
  getMemoriesForEntity,
58056
58659
  getMemoriesByKey,
58057
58660
  getMementosStorageStatus,
58661
+ getMementosProjectResourceExact,
58058
58662
  getMachine,
58059
58663
  getFocus,
58060
58664
  getFallbackSyncTargetMachine,
@@ -58109,6 +58713,7 @@ export {
58109
58713
  canonicalMementosProjectRegistrationJson,
58110
58714
  bulkLinkEntities,
58111
58715
  bulkDeleteMemories,
58716
+ buildMementosProjectRegistrationCapability,
58112
58717
  buildFocusFilter,
58113
58718
  archiveUnused,
58114
58719
  archiveStale,
@@ -58131,17 +58736,22 @@ export {
58131
58736
  MemoryLockConflictError,
58132
58737
  MemoryInjector,
58133
58738
  MemoryExpiredError,
58739
+ MementosProjectResourceError,
58134
58740
  MementosProjectRegistrationHttpClient,
58135
58741
  MementosProjectRegistrationError,
58742
+ MementosProjectAuthorityIdentityError,
58136
58743
  MEMORY_PROJECT_LINK_RECEIPT_COLUMNS,
58137
58744
  MEMENTOS_STORAGE_TABLES,
58138
58745
  MEMENTOS_STORAGE_FALLBACK_ENV,
58139
58746
  MEMENTOS_STORAGE_ENV,
58747
+ MEMENTOS_PROJECT_RESOURCE_ROUTE,
58748
+ MEMENTOS_PROJECT_RESOURCE_KINDS,
58140
58749
  MEMENTOS_PROJECT_REGISTRATION_SCHEMA_VERSION,
58141
58750
  MEMENTOS_PROJECT_REGISTRATION_ROUTE,
58142
58751
  MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE,
58143
58752
  MEMENTOS_PROJECT_REFERENCE_SURFACES,
58144
58753
  MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
58754
+ MEMENTOS_PROJECT_AUTHORITY_ENV,
58145
58755
  MEMENTOS_MEMORY_PROJECT_LINK_ROUTE,
58146
58756
  InvalidScopeError,
58147
58757
  EntityNotFoundError,