@hasna/mementos 0.14.81 → 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.
@@ -52908,13 +52908,36 @@ function buildOpenApiDocument(version2) {
52908
52908
  required: true,
52909
52909
  schema: { type: "string" }
52910
52910
  }));
52911
+ if (route.method === "GET" && route.path === "/api/projects/:id/resources") {
52912
+ params.push({
52913
+ name: "limit",
52914
+ in: "query",
52915
+ required: false,
52916
+ schema: { type: "integer", minimum: 1, maximum: 1000, default: 100 }
52917
+ }, {
52918
+ name: "cursor",
52919
+ in: "query",
52920
+ required: false,
52921
+ schema: { type: "string" }
52922
+ }, {
52923
+ name: "resource_kinds",
52924
+ in: "query",
52925
+ required: false,
52926
+ description: "Comma-separated subset of project, knowledge, memory, session",
52927
+ schema: { type: "string" }
52928
+ });
52929
+ }
52930
+ const successSchema = route.method === "GET" && route.path === "/api/projects/:id/resources" ? { $ref: "#/components/schemas/MementosProjectResourcePage" } : route.method === "GET" && route.path === "/api/projects/:id/resources/:kind/:resource_id" ? { $ref: "#/components/schemas/MementosProjectResourceExactResult" } : undefined;
52911
52931
  paths[p] = paths[p] ?? {};
52912
52932
  paths[p][method] = {
52913
52933
  summary: `${route.method} ${p}`,
52914
52934
  operationId: `${method}_${p.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_|_$/g, "")}`,
52915
52935
  ...params.length ? { parameters: params } : {},
52916
52936
  responses: {
52917
- "200": { description: "OK" },
52937
+ "200": {
52938
+ description: "OK",
52939
+ ...successSchema ? { content: { "application/json": { schema: successSchema } } } : {}
52940
+ },
52918
52941
  "401": { description: "Unauthorized" },
52919
52942
  "403": { description: "Forbidden" },
52920
52943
  "404": { description: "Not found" }
@@ -52933,6 +52956,124 @@ function buildOpenApiDocument(version2) {
52933
52956
  securitySchemes: {
52934
52957
  bearerAuth: { type: "http", scheme: "bearer" },
52935
52958
  apiKeyAuth: { type: "apiKey", in: "header", name: "x-api-key" }
52959
+ },
52960
+ schemas: {
52961
+ MementosProjectResource: {
52962
+ type: "object",
52963
+ additionalProperties: false,
52964
+ required: [
52965
+ "authority",
52966
+ "source_package",
52967
+ "project_id",
52968
+ "resource_kind",
52969
+ "stable_id",
52970
+ "revision",
52971
+ "digest",
52972
+ "membership"
52973
+ ],
52974
+ properties: {
52975
+ authority: { const: "mementos" },
52976
+ source_package: { const: "@hasna/mementos" },
52977
+ project_id: { type: "string" },
52978
+ resource_kind: {
52979
+ type: "string",
52980
+ enum: ["project", "knowledge", "memory", "session"]
52981
+ },
52982
+ stable_id: { type: "string" },
52983
+ revision: { type: "string" },
52984
+ digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
52985
+ membership: {
52986
+ type: "string",
52987
+ enum: ["project_aggregate", "explicit_project_id_or_focus"]
52988
+ }
52989
+ }
52990
+ },
52991
+ MementosProjectResourceAuthority: {
52992
+ type: "object",
52993
+ additionalProperties: false,
52994
+ required: [
52995
+ "authority",
52996
+ "authority_id",
52997
+ "tenant_id",
52998
+ "corpus_id",
52999
+ "package_version"
53000
+ ],
53001
+ properties: {
53002
+ authority: { const: "mementos" },
53003
+ authority_id: { type: "string" },
53004
+ tenant_id: { type: "string" },
53005
+ corpus_id: { type: "string" },
53006
+ package_version: { type: "string" }
53007
+ }
53008
+ },
53009
+ MementosProjectResourcePage: {
53010
+ type: "object",
53011
+ additionalProperties: false,
53012
+ required: [
53013
+ "schema",
53014
+ "authority",
53015
+ "project_id",
53016
+ "project_revision",
53017
+ "collection_revision",
53018
+ "resource_kinds",
53019
+ "resources",
53020
+ "count",
53021
+ "total",
53022
+ "limit",
53023
+ "cursor",
53024
+ "next_cursor",
53025
+ "has_more",
53026
+ "complete",
53027
+ "truncated"
53028
+ ],
53029
+ properties: {
53030
+ schema: { const: "mementos.project-resources.v1" },
53031
+ authority: { $ref: "#/components/schemas/MementosProjectResourceAuthority" },
53032
+ project_id: { type: "string" },
53033
+ project_revision: { type: "string" },
53034
+ collection_revision: { type: "string", pattern: "^[0-9a-f]{64}$" },
53035
+ resource_kinds: {
53036
+ type: "array",
53037
+ items: { type: "string", enum: ["project", "knowledge", "memory", "session"] }
53038
+ },
53039
+ resources: {
53040
+ type: "array",
53041
+ items: { $ref: "#/components/schemas/MementosProjectResource" }
53042
+ },
53043
+ count: { type: "integer", minimum: 0 },
53044
+ total: { type: "integer", minimum: 0 },
53045
+ limit: { type: "integer", minimum: 1, maximum: 1000 },
53046
+ cursor: { type: ["string", "null"] },
53047
+ next_cursor: { type: ["string", "null"] },
53048
+ has_more: { type: "boolean" },
53049
+ complete: { const: true },
53050
+ truncated: { const: false }
53051
+ }
53052
+ },
53053
+ MementosProjectResourceExactResult: {
53054
+ type: "object",
53055
+ additionalProperties: false,
53056
+ required: [
53057
+ "schema",
53058
+ "authority",
53059
+ "project_id",
53060
+ "project_revision",
53061
+ "collection_revision",
53062
+ "resource",
53063
+ "complete",
53064
+ "truncated"
53065
+ ],
53066
+ properties: {
53067
+ schema: { const: "mementos.project-resource.v1" },
53068
+ authority: { $ref: "#/components/schemas/MementosProjectResourceAuthority" },
53069
+ project_id: { type: "string" },
53070
+ project_revision: { type: "string" },
53071
+ collection_revision: { type: "string", pattern: "^[0-9a-f]{64}$" },
53072
+ resource: { $ref: "#/components/schemas/MementosProjectResource" },
53073
+ complete: { const: true },
53074
+ truncated: { const: false }
53075
+ }
53076
+ }
52936
53077
  }
52937
53078
  },
52938
53079
  security: [{ bearerAuth: [] }, { apiKeyAuth: [] }],
@@ -56419,6 +56560,97 @@ function getMementosPackageVersion() {
56419
56560
  // src/db/memory-project-link.ts
56420
56561
  init_schema();
56421
56562
 
56563
+ // src/project-registration/types.ts
56564
+ var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
56565
+ var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
56566
+ var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
56567
+ var MEMENTOS_PROJECT_RESOURCE_ROUTE = "mementos.project-resources.v1";
56568
+ var MEMENTOS_PROJECT_RESOURCE_KINDS = [
56569
+ "project",
56570
+ "knowledge",
56571
+ "memory",
56572
+ "session"
56573
+ ];
56574
+
56575
+ class MementosProjectRegistrationError extends Error {
56576
+ code;
56577
+ details;
56578
+ constructor(code, message, details = {}) {
56579
+ super(message);
56580
+ this.code = code;
56581
+ this.details = details;
56582
+ this.name = "MementosProjectRegistrationError";
56583
+ }
56584
+ }
56585
+
56586
+ // src/project-registration/identity.ts
56587
+ var MEMENTOS_PROJECT_AUTHORITY_ENV = {
56588
+ authorityId: "MEMENTOS_PROJECT_AUTHORITY_ID",
56589
+ tenantId: "MEMENTOS_PROJECT_TENANT_ID",
56590
+ corpusId: "MEMENTOS_PROJECT_CORPUS_ID"
56591
+ };
56592
+ function configuredValue(override, envKey) {
56593
+ return override?.trim() || process.env[envKey]?.trim() || null;
56594
+ }
56595
+
56596
+ class MementosProjectAuthorityIdentityError extends Error {
56597
+ missing_env;
56598
+ code = "MEMENTOS_PROJECT_AUTHORITY_UNCONFIGURED";
56599
+ constructor(missing_env) {
56600
+ super("Mementos project authority identity is not configured; set " + missing_env.join(", "));
56601
+ this.missing_env = missing_env;
56602
+ this.name = "MementosProjectAuthorityIdentityError";
56603
+ }
56604
+ }
56605
+ function resolveMementosProjectAuthorityIdentity(options = {}) {
56606
+ const authorityId = configuredValue(options.authorityId, MEMENTOS_PROJECT_AUTHORITY_ENV.authorityId);
56607
+ const tenantId = configuredValue(options.tenantId, MEMENTOS_PROJECT_AUTHORITY_ENV.tenantId);
56608
+ const corpusId = configuredValue(options.corpusId, MEMENTOS_PROJECT_AUTHORITY_ENV.corpusId);
56609
+ if (!authorityId || !tenantId || !corpusId) {
56610
+ const missingEnv = [];
56611
+ if (!authorityId)
56612
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.authorityId);
56613
+ if (!tenantId)
56614
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.tenantId);
56615
+ if (!corpusId)
56616
+ missingEnv.push(MEMENTOS_PROJECT_AUTHORITY_ENV.corpusId);
56617
+ throw new MementosProjectAuthorityIdentityError(missingEnv);
56618
+ }
56619
+ return {
56620
+ authority_id: authorityId,
56621
+ tenant_id: tenantId,
56622
+ corpus_id: corpusId
56623
+ };
56624
+ }
56625
+ function buildMementosProjectRegistrationCapability(options = {}) {
56626
+ const identity = resolveMementosProjectAuthorityIdentity(options);
56627
+ return {
56628
+ authority: "mementos",
56629
+ route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
56630
+ package_version: options.packageVersion ?? getMementosPackageVersion(),
56631
+ ...identity,
56632
+ supported_resources: ["project"],
56633
+ conditional_create: true,
56634
+ immutable_receipts: true,
56635
+ exact_terminal_lookup: true,
56636
+ exact_readback: true,
56637
+ conditional_inverse: true,
56638
+ ambiguous_outcome_reconciliation: true,
56639
+ guarded_update: true,
56640
+ guarded_update_route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
56641
+ no_write_dry_run: true,
56642
+ expected_revision_compare_and_swap: true,
56643
+ caller_idempotency: true,
56644
+ exact_inverse_rollback: true,
56645
+ project_resource_enumeration: true,
56646
+ project_resource_route: MEMENTOS_PROJECT_RESOURCE_ROUTE,
56647
+ project_resource_kinds: ["project", "knowledge", "memory", "session"],
56648
+ stable_keyset_pagination: true,
56649
+ explicit_membership_only: true
56650
+ };
56651
+ }
56652
+
56653
+ // src/db/memory-project-link.ts
56422
56654
  class MemoryProjectLinkError extends Error {
56423
56655
  code;
56424
56656
  details;
@@ -56429,12 +56661,17 @@ class MemoryProjectLinkError extends Error {
56429
56661
  this.name = "MemoryProjectLinkError";
56430
56662
  }
56431
56663
  }
56432
- var LINK_AUTHORITY = {
56433
- authority_id: "mementos",
56434
- tenant_id: "default",
56435
- corpus_id: "default"
56436
- };
56437
56664
  var BOUNDED_IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
56665
+ function linkAuthority() {
56666
+ try {
56667
+ return resolveMementosProjectAuthorityIdentity();
56668
+ } catch (error) {
56669
+ if (error instanceof MementosProjectAuthorityIdentityError) {
56670
+ throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH", error.message, { authority_code: error.code, missing_env: error.missing_env });
56671
+ }
56672
+ throw error;
56673
+ }
56674
+ }
56438
56675
  function canonicalize(value) {
56439
56676
  if (Array.isArray(value))
56440
56677
  return value.map(canonicalize);
@@ -56542,7 +56779,8 @@ function receiptFromRow(row) {
56542
56779
  };
56543
56780
  }
56544
56781
  function assertIdentity(identity) {
56545
- if (identity.authority_id !== LINK_AUTHORITY.authority_id || identity.tenant_id !== LINK_AUTHORITY.tenant_id || identity.corpus_id !== LINK_AUTHORITY.corpus_id) {
56782
+ const expectedIdentity = linkAuthority();
56783
+ if (identity.authority_id !== expectedIdentity.authority_id || identity.tenant_id !== expectedIdentity.tenant_id || identity.corpus_id !== expectedIdentity.corpus_id) {
56546
56784
  throw new MemoryProjectLinkError("MEMORY_PROJECT_LINK_AUTHORITY_MISMATCH", "memory project link does not match this authority, tenant, and corpus");
56547
56785
  }
56548
56786
  }
@@ -57019,7 +57257,7 @@ function rollbackMemoryProjectLink(memoryId, request, db) {
57019
57257
  };
57020
57258
  });
57021
57259
  }
57022
- function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = LINK_AUTHORITY, db) {
57260
+ function getMemoryProjectLinkReceipt(memoryId, receiptId, identity = linkAuthority(), db) {
57023
57261
  assertIdentity(identity);
57024
57262
  assertBoundedIdentifier(memoryId, "memory_id");
57025
57263
  assertBoundedIdentifier(receiptId, "receipt_id");
@@ -57507,24 +57745,6 @@ addRoute("POST", "/api/locks/clean", () => {
57507
57745
  init_database();
57508
57746
  init_api_mode();
57509
57747
  import { createHash as createHash2 } from "crypto";
57510
-
57511
- // src/project-registration/types.ts
57512
- var MEMENTOS_PROJECT_REGISTRATION_ROUTE = "mementos.project-registration.v1";
57513
- var MEMENTOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1";
57514
- var MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE = "mementos.project-guarded-update.v1";
57515
-
57516
- class MementosProjectRegistrationError extends Error {
57517
- code;
57518
- details;
57519
- constructor(code, message, details = {}) {
57520
- super(message);
57521
- this.code = code;
57522
- this.details = details;
57523
- this.name = "MementosProjectRegistrationError";
57524
- }
57525
- }
57526
-
57527
- // src/db/projects.ts
57528
57748
  function parseProjectRow2(row) {
57529
57749
  return {
57530
57750
  id: row["id"],
@@ -57546,12 +57766,17 @@ class ProjectGuardedUpdateError extends Error {
57546
57766
  this.name = "ProjectGuardedUpdateError";
57547
57767
  }
57548
57768
  }
57549
- var PROJECT_UPDATE_AUTHORITY = {
57550
- authority_id: "mementos",
57551
- tenant_id: "default",
57552
- corpus_id: "default"
57553
- };
57554
57769
  var BOUNDED_IDENTIFIER2 = /^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/;
57770
+ function projectUpdateAuthority() {
57771
+ try {
57772
+ return resolveMementosProjectAuthorityIdentity();
57773
+ } catch (error) {
57774
+ if (error instanceof MementosProjectAuthorityIdentityError) {
57775
+ throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", error.message, { authority_code: error.code, missing_env: error.missing_env });
57776
+ }
57777
+ throw error;
57778
+ }
57779
+ }
57555
57780
  function canonicalizeProjectUpdateValue(value) {
57556
57781
  if (Array.isArray(value))
57557
57782
  return value.map(canonicalizeProjectUpdateValue);
@@ -57624,7 +57849,7 @@ function normalizeProjectUpdateInput(input) {
57624
57849
  }
57625
57850
  return normalized;
57626
57851
  }
57627
- function assertProjectUpdateIdentity(identity, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
57852
+ function assertProjectUpdateIdentity(identity, expectedIdentity = projectUpdateAuthority()) {
57628
57853
  if (identity.authority_id !== expectedIdentity.authority_id || identity.tenant_id !== expectedIdentity.tenant_id || identity.corpus_id !== expectedIdentity.corpus_id) {
57629
57854
  throw new ProjectGuardedUpdateError("PROJECT_UPDATE_AUTHORITY_MISMATCH", "guarded project update does not match this authority, tenant, and corpus");
57630
57855
  }
@@ -57634,7 +57859,7 @@ function assertBoundedIdentifier2(value, field) {
57634
57859
  throw new ProjectGuardedUpdateError("PROJECT_UPDATE_INVALID_INPUT", `${field} must be an 8-128 character bounded identifier`);
57635
57860
  }
57636
57861
  }
57637
- function assertProjectUpdateRequest(request, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
57862
+ function assertProjectUpdateRequest(request, expectedIdentity = projectUpdateAuthority()) {
57638
57863
  assertProjectUpdateIdentity(request, expectedIdentity);
57639
57864
  assertBoundedIdentifier2(request.operation_id, "operation_id");
57640
57865
  assertBoundedIdentifier2(request.step_id, "step_id");
@@ -57812,7 +58037,7 @@ function listProjects(db) {
57812
58037
  const rows = d.query("SELECT * FROM projects ORDER BY updated_at DESC").all();
57813
58038
  return rows.map(parseProjectRow2);
57814
58039
  }
57815
- function previewProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
58040
+ function previewProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority()) {
57816
58041
  assertProjectUpdateRequest(request, expectedIdentity);
57817
58042
  const normalized = normalizeProjectUpdateInput(request.updates);
57818
58043
  if (!db && isApiMode()) {
@@ -57835,7 +58060,7 @@ function previewProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE
57835
58060
  receipt: null
57836
58061
  };
57837
58062
  }
57838
- function applyProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY, resultDigestForProject) {
58063
+ function applyProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
57839
58064
  assertProjectUpdateRequest(request, expectedIdentity);
57840
58065
  const normalized = normalizeProjectUpdateInput(request.updates);
57841
58066
  if (!db && isApiMode()) {
@@ -57904,7 +58129,7 @@ function applyProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_A
57904
58129
  return { dry_run: false, applied: true, project: readback, receipt };
57905
58130
  });
57906
58131
  }
57907
- function rollbackProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY, resultDigestForProject) {
58132
+ function rollbackProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
57908
58133
  assertProjectUpdateRequest(request, expectedIdentity);
57909
58134
  assertBoundedIdentifier2(request.accepted_receipt_id, "accepted_receipt_id");
57910
58135
  if (!db && isApiMode()) {
@@ -57978,7 +58203,7 @@ function rollbackProjectUpdate(id, request, db, expectedIdentity = PROJECT_UPDAT
57978
58203
  return { dry_run: false, applied: true, project: readback, receipt };
57979
58204
  });
57980
58205
  }
57981
- function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHORITY, db, expectedIdentity = PROJECT_UPDATE_AUTHORITY) {
58206
+ function getProjectUpdateReceipt(id, receiptId, identity = projectUpdateAuthority(), db, expectedIdentity = projectUpdateAuthority()) {
57982
58207
  assertProjectUpdateIdentity(identity, expectedIdentity);
57983
58208
  if (!db && isApiMode()) {
57984
58209
  const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/update-receipts/lookup`, { ...identity, receipt_id: receiptId });
@@ -57992,91 +58217,6 @@ function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHOR
57992
58217
  return receipt;
57993
58218
  }
57994
58219
 
57995
- // src/server/routes/projects.ts
57996
- init_router();
57997
- addRoute("GET", "/api/projects", (_req, url) => {
57998
- const q = getSearchParams(url);
57999
- const projects = listProjects();
58000
- if (q["fields"]) {
58001
- const fields = q["fields"].split(",").map((f) => f.trim());
58002
- const filtered = projects.map((p) => Object.fromEntries(fields.map((f) => [f, p[f]]).filter(([, v]) => v !== undefined)));
58003
- return json({ projects: filtered, count: filtered.length });
58004
- }
58005
- return json({ projects, count: projects.length });
58006
- });
58007
- addRoute("POST", "/api/projects", async (req) => {
58008
- const body = await readJson(req);
58009
- if (!body || !body["name"] || !body["path"]) {
58010
- return errorResponse("Missing required fields: name, path", 400);
58011
- }
58012
- const project = registerProject(body["name"], body["path"], body["description"], body["memory_prefix"]);
58013
- return json(project, 201);
58014
- });
58015
- addRoute("GET", "/api/projects/:id", (_req, _url, params) => {
58016
- const project = getProject(params["id"]);
58017
- if (!project)
58018
- return errorResponse("Project not found", 404);
58019
- return json(project);
58020
- });
58021
- function guardedUpdateError(error) {
58022
- const status = error.code === "PROJECT_UPDATE_AUTHORITY_MISMATCH" ? 403 : error.code === "PROJECT_UPDATE_NOT_FOUND" || error.code === "PROJECT_UPDATE_RECEIPT_NOT_FOUND" ? 404 : error.code === "PROJECT_UPDATE_INVALID_INPUT" ? 400 : 409;
58023
- return errorResponse(error.message, status, { code: error.code, ...error.details });
58024
- }
58025
- addRoute("PATCH", "/api/projects/:id", () => errorResponse("Unguarded project updates are disabled; use POST /projects/:id/guarded-update", 428));
58026
- addRoute("POST", "/api/projects/:id/guarded-update", async (req, _url, params) => {
58027
- const body = await readJson(req);
58028
- if (!body)
58029
- return errorResponse("Invalid JSON body", 400);
58030
- try {
58031
- const request = body;
58032
- return json(request.dry_run ? previewProjectUpdate(params["id"], request) : applyProjectUpdate(params["id"], request));
58033
- } catch (error) {
58034
- if (error instanceof ProjectGuardedUpdateError)
58035
- return guardedUpdateError(error);
58036
- throw error;
58037
- }
58038
- });
58039
- addRoute("POST", "/api/projects/:id/guarded-rollback", async (req, _url, params) => {
58040
- const body = await readJson(req);
58041
- if (!body)
58042
- return errorResponse("Invalid JSON body", 400);
58043
- try {
58044
- return json(rollbackProjectUpdate(params["id"], body));
58045
- } catch (error) {
58046
- if (error instanceof ProjectGuardedUpdateError)
58047
- return guardedUpdateError(error);
58048
- throw error;
58049
- }
58050
- });
58051
- addRoute("POST", "/api/projects/:id/update-receipts/lookup", async (req, _url, params) => {
58052
- const body = await readJson(req);
58053
- if (!body || typeof body["receipt_id"] !== "string") {
58054
- return errorResponse("receipt_id is required", 400);
58055
- }
58056
- try {
58057
- const identity = {
58058
- authority_id: String(body["authority_id"] ?? ""),
58059
- tenant_id: String(body["tenant_id"] ?? ""),
58060
- corpus_id: String(body["corpus_id"] ?? "")
58061
- };
58062
- return json(getProjectUpdateReceipt(params["id"], body["receipt_id"], identity));
58063
- } catch (error) {
58064
- if (error instanceof ProjectGuardedUpdateError)
58065
- return guardedUpdateError(error);
58066
- throw error;
58067
- }
58068
- });
58069
- addRoute("GET", "/api/projects/:id/agents", (_req, _url, params) => {
58070
- const project = getProject(params["id"]);
58071
- if (!project)
58072
- return errorResponse("Project not found", 404);
58073
- const agents = listAgentsByProject(project.id);
58074
- return json({ agents, count: agents.length });
58075
- });
58076
-
58077
- // src/server/routes/project-registration.ts
58078
- init_database();
58079
-
58080
58220
  // src/project-registration/authority.ts
58081
58221
  import { createHash as createHash3 } from "crypto";
58082
58222
  import { resolve as resolve3 } from "path";
@@ -58706,27 +58846,7 @@ class PackageOwnedMementosProjectRegistrationAuthority {
58706
58846
  this.db = db;
58707
58847
  this.now = options.now ?? (() => new Date().toISOString());
58708
58848
  this.faultInjector = options.faultInjector;
58709
- this.capabilityValue = {
58710
- authority: "mementos",
58711
- route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
58712
- package_version: options.packageVersion ?? getMementosPackageVersion(),
58713
- authority_id: options.authorityId ?? "mementos",
58714
- tenant_id: options.tenantId ?? "default",
58715
- corpus_id: options.corpusId ?? "default",
58716
- supported_resources: ["project"],
58717
- conditional_create: true,
58718
- immutable_receipts: true,
58719
- exact_terminal_lookup: true,
58720
- exact_readback: true,
58721
- conditional_inverse: true,
58722
- ambiguous_outcome_reconciliation: true,
58723
- guarded_update: true,
58724
- guarded_update_route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
58725
- no_write_dry_run: true,
58726
- expected_revision_compare_and_swap: true,
58727
- caller_idempotency: true,
58728
- exact_inverse_rollback: true
58729
- };
58849
+ this.capabilityValue = buildMementosProjectRegistrationCapability(options);
58730
58850
  }
58731
58851
  fault(point, request) {
58732
58852
  this.faultInjector?.(point, {
@@ -59316,7 +59436,386 @@ async function handleMementosProjectRegistrationHttpRequest(request, url, author
59316
59436
  }, 500);
59317
59437
  }
59318
59438
  }
59439
+ // src/project-registration/project-resources.ts
59440
+ init_api_mode();
59441
+ init_database();
59442
+ init_memories();
59443
+ var DEFAULT_PAGE_LIMIT = 100;
59444
+ var MAX_PAGE_LIMIT = 1000;
59445
+ var CURSOR_SCHEMA = "mementos.project-resources.cursor.v1";
59446
+
59447
+ class MementosProjectResourceError extends Error {
59448
+ code;
59449
+ details;
59450
+ constructor(code, message, details = {}) {
59451
+ super(message);
59452
+ this.code = code;
59453
+ this.details = details;
59454
+ this.name = "MementosProjectResourceError";
59455
+ }
59456
+ }
59457
+ function timestamp(value) {
59458
+ return value instanceof Date ? value.toISOString() : String(value);
59459
+ }
59460
+ function normalizeSqlValue(value) {
59461
+ if (value instanceof Date)
59462
+ return value.toISOString();
59463
+ if (Array.isArray(value))
59464
+ return value.map(normalizeSqlValue);
59465
+ if (!value || typeof value !== "object")
59466
+ return value;
59467
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, normalizeSqlValue(item)]));
59468
+ }
59469
+ function exactProject(db, projectId) {
59470
+ const row = db.get("SELECT id, name, path, description, memory_prefix, created_at, updated_at FROM projects WHERE id = ? LIMIT 1", projectId);
59471
+ if (!row) {
59472
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_PROJECT_NOT_FOUND", `Mementos project not found: ${projectId}`, { project_id: projectId });
59473
+ }
59474
+ return row;
59475
+ }
59476
+ function resourceKey(resource) {
59477
+ const rank = MEMENTOS_PROJECT_RESOURCE_KINDS.indexOf(resource.resource_kind);
59478
+ return `${String(rank).padStart(2, "0")}:${resource.stable_id}`;
59479
+ }
59480
+ function projectResource(project) {
59481
+ const normalized = normalizeSqlValue(project);
59482
+ return {
59483
+ authority: "mementos",
59484
+ source_package: "@hasna/mementos",
59485
+ project_id: project.id,
59486
+ resource_kind: "project",
59487
+ stable_id: project.id,
59488
+ revision: timestamp(project.updated_at),
59489
+ digest: digestMementosProjectRegistrationValue(normalized),
59490
+ membership: "project_aggregate"
59491
+ };
59492
+ }
59493
+ function memoryResources(db, projectId) {
59494
+ const rows = db.all("SELECT * FROM memories WHERE project_id = ? ORDER BY id ASC", projectId);
59495
+ return rows.map((row) => {
59496
+ const memory = normalizeSqlValue(parseMemoryRow(row));
59497
+ return {
59498
+ authority: "mementos",
59499
+ source_package: "@hasna/mementos",
59500
+ project_id: projectId,
59501
+ resource_kind: row["category"] === "knowledge" ? "knowledge" : "memory",
59502
+ stable_id: String(row["id"]),
59503
+ revision: timestamp(row["updated_at"]),
59504
+ digest: digestMementosProjectRegistrationValue(memory),
59505
+ membership: "explicit_project_id_or_focus"
59506
+ };
59507
+ });
59508
+ }
59509
+ function sessionResources(db, projectId) {
59510
+ const rows = db.all("SELECT * FROM session_memory_jobs WHERE project_id = ? ORDER BY id ASC", projectId);
59511
+ return rows.map((row) => {
59512
+ const normalized = {
59513
+ id: String(row["id"]),
59514
+ session_id: String(row["session_id"]),
59515
+ agent_id: row["agent_id"] === null ? null : String(row["agent_id"] ?? "") || null,
59516
+ project_id: row["project_id"] === null ? null : String(row["project_id"] ?? "") || null,
59517
+ source: String(row["source"]),
59518
+ status: String(row["status"]),
59519
+ transcript: String(row["transcript"]),
59520
+ chunk_count: Number(row["chunk_count"]),
59521
+ memories_extracted: Number(row["memories_extracted"]),
59522
+ error: row["error"] === null ? null : String(row["error"] ?? "") || null,
59523
+ metadata: typeof row["metadata"] === "string" ? JSON.parse(row["metadata"] || "{}") : normalizeSqlValue(row["metadata"] ?? {}),
59524
+ created_at: timestamp(row["created_at"]),
59525
+ started_at: row["started_at"] === null ? null : timestamp(row["started_at"]),
59526
+ completed_at: row["completed_at"] === null ? null : timestamp(row["completed_at"])
59527
+ };
59528
+ return {
59529
+ authority: "mementos",
59530
+ source_package: "@hasna/mementos",
59531
+ project_id: projectId,
59532
+ resource_kind: "session",
59533
+ stable_id: String(row["id"]),
59534
+ revision: timestamp(row["completed_at"] ?? row["started_at"] ?? row["created_at"]),
59535
+ digest: digestMementosProjectRegistrationValue(normalized),
59536
+ membership: "explicit_project_id_or_focus"
59537
+ };
59538
+ });
59539
+ }
59540
+ function normalizeResourceKinds(value) {
59541
+ if (!value)
59542
+ return [...MEMENTOS_PROJECT_RESOURCE_KINDS];
59543
+ if (value.length === 0) {
59544
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "resource_kinds must contain at least one supported resource kind");
59545
+ }
59546
+ const requested = new Set(value);
59547
+ for (const kind of requested) {
59548
+ if (!MEMENTOS_PROJECT_RESOURCE_KINDS.includes(kind)) {
59549
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `Unsupported Mementos project resource kind: ${kind}`);
59550
+ }
59551
+ }
59552
+ return MEMENTOS_PROJECT_RESOURCE_KINDS.filter((kind) => requested.has(kind));
59553
+ }
59554
+ function normalizeLimit(value) {
59555
+ const limit = value ?? DEFAULT_PAGE_LIMIT;
59556
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_PAGE_LIMIT) {
59557
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `limit must be an integer between 1 and ${MAX_PAGE_LIMIT}`);
59558
+ }
59559
+ return limit;
59560
+ }
59561
+ function encodeCursor(cursor) {
59562
+ return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
59563
+ }
59564
+ function decodeCursor(raw) {
59565
+ try {
59566
+ const parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
59567
+ 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") {
59568
+ throw new Error("invalid cursor shape");
59569
+ }
59570
+ return parsed;
59571
+ } catch {
59572
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "cursor is not a valid Mementos project-resource cursor");
59573
+ }
59574
+ }
59575
+ function localPopulation(projectId, db, resourceKinds) {
59576
+ const project = exactProject(db, projectId);
59577
+ const selected = new Set(resourceKinds);
59578
+ const resources = [
59579
+ ...selected.has("project") ? [projectResource(project)] : [],
59580
+ ...memoryResources(db, projectId).filter((resource) => selected.has(resource.resource_kind)),
59581
+ ...selected.has("session") ? sessionResources(db, projectId) : []
59582
+ ].sort((left, right) => resourceKey(left).localeCompare(resourceKey(right)));
59583
+ const collectionRevision = digestMementosProjectRegistrationValue({
59584
+ schema: MEMENTOS_PROJECT_RESOURCE_ROUTE,
59585
+ project_id: projectId,
59586
+ project_revision: timestamp(project.updated_at),
59587
+ resource_kinds: resourceKinds,
59588
+ resources: resources.map((resource) => ({
59589
+ resource_kind: resource.resource_kind,
59590
+ stable_id: resource.stable_id,
59591
+ revision: resource.revision,
59592
+ digest: resource.digest
59593
+ }))
59594
+ });
59595
+ return { project, resources, collectionRevision };
59596
+ }
59597
+ function readMementosProjectResourcePage(projectId, options = {}, db, authorityOptions = {}) {
59598
+ const resourceKinds = normalizeResourceKinds(options.resource_kinds);
59599
+ const limit = normalizeLimit(options.limit);
59600
+ if (!db && isApiMode()) {
59601
+ const { data } = apiJson("GET", `/projects/${encodeURIComponent(projectId)}/resources${toQuery({
59602
+ limit,
59603
+ cursor: options.cursor ?? undefined,
59604
+ resource_kinds: resourceKinds.join(",")
59605
+ })}`);
59606
+ return data;
59607
+ }
59608
+ const d = db ?? getDatabase();
59609
+ const { project, resources, collectionRevision } = localPopulation(projectId, d, resourceKinds);
59610
+ let start = 0;
59611
+ if (options.cursor) {
59612
+ const cursor = decodeCursor(options.cursor);
59613
+ if (cursor.project_id !== projectId || JSON.stringify(cursor.resource_kinds) !== JSON.stringify(resourceKinds)) {
59614
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", "cursor does not belong to this project and resource-kind selection");
59615
+ }
59616
+ if (cursor.collection_revision !== collectionRevision) {
59617
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource collection changed; restart from the first page", {
59618
+ cursor_collection_revision: cursor.collection_revision,
59619
+ current_collection_revision: collectionRevision
59620
+ });
59621
+ }
59622
+ const afterIndex = resources.findIndex((resource) => resourceKey(resource) === cursor.after_key);
59623
+ if (afterIndex < 0) {
59624
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_COLLECTION_CHANGED", "Mementos project resource cursor no longer names a member; restart from the first page");
59625
+ }
59626
+ start = afterIndex + 1;
59627
+ }
59628
+ const pageResources = resources.slice(start, start + limit);
59629
+ const hasMore = start + pageResources.length < resources.length;
59630
+ const nextCursor = hasMore && pageResources.length > 0 ? encodeCursor({
59631
+ schema: CURSOR_SCHEMA,
59632
+ project_id: projectId,
59633
+ collection_revision: collectionRevision,
59634
+ resource_kinds: resourceKinds,
59635
+ after_key: resourceKey(pageResources[pageResources.length - 1])
59636
+ }) : null;
59637
+ const capability = buildMementosProjectRegistrationCapability(authorityOptions);
59638
+ return {
59639
+ schema: "mementos.project-resources.v1",
59640
+ authority: {
59641
+ authority: capability.authority,
59642
+ authority_id: capability.authority_id,
59643
+ tenant_id: capability.tenant_id,
59644
+ corpus_id: capability.corpus_id,
59645
+ package_version: capability.package_version
59646
+ },
59647
+ project_id: projectId,
59648
+ project_revision: timestamp(project.updated_at),
59649
+ collection_revision: collectionRevision,
59650
+ resource_kinds: resourceKinds,
59651
+ resources: pageResources,
59652
+ count: pageResources.length,
59653
+ total: resources.length,
59654
+ limit,
59655
+ cursor: options.cursor ?? null,
59656
+ next_cursor: nextCursor,
59657
+ has_more: hasMore,
59658
+ complete: true,
59659
+ truncated: false
59660
+ };
59661
+ }
59662
+ function getMementosProjectResourceExact(projectId, resourceKind, stableId, db, authorityOptions = {}) {
59663
+ if (!MEMENTOS_PROJECT_RESOURCE_KINDS.includes(resourceKind)) {
59664
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `Unsupported Mementos project resource kind: ${resourceKind}`);
59665
+ }
59666
+ if (!db && isApiMode()) {
59667
+ const { data } = apiJson("GET", `/projects/${encodeURIComponent(projectId)}/resources/${encodeURIComponent(resourceKind)}/${encodeURIComponent(stableId)}`);
59668
+ return data;
59669
+ }
59670
+ const d = db ?? getDatabase();
59671
+ const { project, resources, collectionRevision } = localPopulation(projectId, d, [
59672
+ resourceKind
59673
+ ]);
59674
+ const resource = resources.find((candidate) => candidate.stable_id === stableId);
59675
+ if (!resource) {
59676
+ 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 });
59677
+ }
59678
+ const capability = buildMementosProjectRegistrationCapability(authorityOptions);
59679
+ return {
59680
+ schema: "mementos.project-resource.v1",
59681
+ authority: {
59682
+ authority: capability.authority,
59683
+ authority_id: capability.authority_id,
59684
+ tenant_id: capability.tenant_id,
59685
+ corpus_id: capability.corpus_id,
59686
+ package_version: capability.package_version
59687
+ },
59688
+ project_id: projectId,
59689
+ project_revision: timestamp(project.updated_at),
59690
+ collection_revision: collectionRevision,
59691
+ resource,
59692
+ complete: true,
59693
+ truncated: false
59694
+ };
59695
+ }
59696
+ // src/server/routes/projects.ts
59697
+ init_router();
59698
+ addRoute("GET", "/api/projects", (_req, url) => {
59699
+ const q = getSearchParams(url);
59700
+ const projects = listProjects();
59701
+ if (q["fields"]) {
59702
+ const fields = q["fields"].split(",").map((f) => f.trim());
59703
+ const filtered = projects.map((p) => Object.fromEntries(fields.map((f) => [f, p[f]]).filter(([, v]) => v !== undefined)));
59704
+ return json({ projects: filtered, count: filtered.length });
59705
+ }
59706
+ return json({ projects, count: projects.length });
59707
+ });
59708
+ addRoute("POST", "/api/projects", async (req) => {
59709
+ const body = await readJson(req);
59710
+ if (!body || !body["name"] || !body["path"]) {
59711
+ return errorResponse("Missing required fields: name, path", 400);
59712
+ }
59713
+ const project = registerProject(body["name"], body["path"], body["description"], body["memory_prefix"]);
59714
+ return json(project, 201);
59715
+ });
59716
+ addRoute("GET", "/api/projects/:id", (_req, _url, params) => {
59717
+ const project = getProject(params["id"]);
59718
+ if (!project)
59719
+ return errorResponse("Project not found", 404);
59720
+ return json(project);
59721
+ });
59722
+ function projectResourceError(error) {
59723
+ const status = error.code === "MEMENTOS_PROJECT_RESOURCE_PROJECT_NOT_FOUND" || error.code === "MEMENTOS_PROJECT_RESOURCE_NOT_FOUND" ? 404 : error.code === "MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT" ? 400 : 409;
59724
+ return errorResponse(error.message, status, { code: error.code, ...error.details });
59725
+ }
59726
+ function parseResourceKinds(raw) {
59727
+ if (raw === undefined)
59728
+ return;
59729
+ const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
59730
+ for (const value of values) {
59731
+ if (!MEMENTOS_PROJECT_RESOURCE_KINDS.includes(value)) {
59732
+ throw new MementosProjectResourceError("MEMENTOS_PROJECT_RESOURCE_INVALID_INPUT", `Unsupported Mementos project resource kind: ${value}`);
59733
+ }
59734
+ }
59735
+ return values;
59736
+ }
59737
+ addRoute("GET", "/api/projects/:id/resources", (_req, url, params) => {
59738
+ const q = getSearchParams(url);
59739
+ try {
59740
+ const parsedLimit = q["limit"] === undefined ? undefined : Number(q["limit"]);
59741
+ return json(readMementosProjectResourcePage(params["id"], {
59742
+ limit: parsedLimit,
59743
+ cursor: q["cursor"],
59744
+ resource_kinds: parseResourceKinds(q["resource_kinds"])
59745
+ }));
59746
+ } catch (error) {
59747
+ if (error instanceof MementosProjectResourceError)
59748
+ return projectResourceError(error);
59749
+ throw error;
59750
+ }
59751
+ });
59752
+ addRoute("GET", "/api/projects/:id/resources/:kind/:resource_id", (_req, _url, params) => {
59753
+ try {
59754
+ return json(getMementosProjectResourceExact(params["id"], params["kind"], params["resource_id"]));
59755
+ } catch (error) {
59756
+ if (error instanceof MementosProjectResourceError)
59757
+ return projectResourceError(error);
59758
+ throw error;
59759
+ }
59760
+ });
59761
+ function guardedUpdateError(error) {
59762
+ const status = error.code === "PROJECT_UPDATE_AUTHORITY_MISMATCH" ? 403 : error.code === "PROJECT_UPDATE_NOT_FOUND" || error.code === "PROJECT_UPDATE_RECEIPT_NOT_FOUND" ? 404 : error.code === "PROJECT_UPDATE_INVALID_INPUT" ? 400 : 409;
59763
+ return errorResponse(error.message, status, { code: error.code, ...error.details });
59764
+ }
59765
+ addRoute("PATCH", "/api/projects/:id", () => errorResponse("Unguarded project updates are disabled; use POST /projects/:id/guarded-update", 428));
59766
+ addRoute("POST", "/api/projects/:id/guarded-update", async (req, _url, params) => {
59767
+ const body = await readJson(req);
59768
+ if (!body)
59769
+ return errorResponse("Invalid JSON body", 400);
59770
+ try {
59771
+ const request = body;
59772
+ return json(request.dry_run ? previewProjectUpdate(params["id"], request) : applyProjectUpdate(params["id"], request));
59773
+ } catch (error) {
59774
+ if (error instanceof ProjectGuardedUpdateError)
59775
+ return guardedUpdateError(error);
59776
+ throw error;
59777
+ }
59778
+ });
59779
+ addRoute("POST", "/api/projects/:id/guarded-rollback", async (req, _url, params) => {
59780
+ const body = await readJson(req);
59781
+ if (!body)
59782
+ return errorResponse("Invalid JSON body", 400);
59783
+ try {
59784
+ return json(rollbackProjectUpdate(params["id"], body));
59785
+ } catch (error) {
59786
+ if (error instanceof ProjectGuardedUpdateError)
59787
+ return guardedUpdateError(error);
59788
+ throw error;
59789
+ }
59790
+ });
59791
+ addRoute("POST", "/api/projects/:id/update-receipts/lookup", async (req, _url, params) => {
59792
+ const body = await readJson(req);
59793
+ if (!body || typeof body["receipt_id"] !== "string") {
59794
+ return errorResponse("receipt_id is required", 400);
59795
+ }
59796
+ try {
59797
+ const identity = {
59798
+ authority_id: String(body["authority_id"] ?? ""),
59799
+ tenant_id: String(body["tenant_id"] ?? ""),
59800
+ corpus_id: String(body["corpus_id"] ?? "")
59801
+ };
59802
+ return json(getProjectUpdateReceipt(params["id"], body["receipt_id"], identity));
59803
+ } catch (error) {
59804
+ if (error instanceof ProjectGuardedUpdateError)
59805
+ return guardedUpdateError(error);
59806
+ throw error;
59807
+ }
59808
+ });
59809
+ addRoute("GET", "/api/projects/:id/agents", (_req, _url, params) => {
59810
+ const project = getProject(params["id"]);
59811
+ if (!project)
59812
+ return errorResponse("Project not found", 404);
59813
+ const agents = listAgentsByProject(project.id);
59814
+ return json({ agents, count: agents.length });
59815
+ });
59816
+
59319
59817
  // src/server/routes/project-registration.ts
59818
+ init_database();
59320
59819
  init_router();
59321
59820
  var handle = async (request, url) => {
59322
59821
  const basePath = url.pathname.startsWith("/v1/") ? "/v1/project-registration" : "/api/project-registration";
@@ -60976,7 +61475,7 @@ function parseMemoryLink(row) {
60976
61475
  function createMemoryLink(input, db) {
60977
61476
  const d = db || getDatabase();
60978
61477
  const id = shortUuid();
60979
- const timestamp = now();
61478
+ const timestamp2 = now();
60980
61479
  d.run(`INSERT OR IGNORE INTO memory_links (id, source_memory_id, target_memory_id, relation_type, run_id, metadata, created_at)
60981
61480
  VALUES (?, ?, ?, ?, ?, ?, ?)`, [
60982
61481
  id,
@@ -60985,7 +61484,7 @@ function createMemoryLink(input, db) {
60985
61484
  input.relation_type,
60986
61485
  input.run_id ?? null,
60987
61486
  JSON.stringify(input.metadata ?? {}),
60988
- timestamp
61487
+ timestamp2
60989
61488
  ]);
60990
61489
  const row = d.query(`SELECT * FROM memory_links
60991
61490
  WHERE source_memory_id = ? AND target_memory_id = ? AND relation_type = ? AND COALESCE(run_id, '') = ?