@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.
- package/dist/cli/commands/memory-cmd-crud.d.ts.map +1 -1
- package/dist/cli/commands/project.d.ts.map +1 -1
- package/dist/cli/index.js +547 -72
- package/dist/db/memory-project-link.d.ts.map +1 -1
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/index.js +460 -81
- package/dist/project-registration/authority.d.ts.map +1 -1
- package/dist/project-registration/identity.d.ts +20 -0
- package/dist/project-registration/identity.d.ts.map +1 -0
- package/dist/project-registration/index.d.ts +5 -2
- package/dist/project-registration/index.d.ts.map +1 -1
- package/dist/project-registration/project-resources.d.ts +22 -0
- package/dist/project-registration/project-resources.d.ts.map +1 -0
- package/dist/project-registration/types.d.ts +52 -0
- package/dist/project-registration/types.d.ts.map +1 -1
- package/dist/project-registration.js +1488 -55
- package/dist/sdk/index.d.ts +57 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +56 -0
- package/dist/server/index.js +644 -145
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/test-support/project-authority-identity.d.ts +8 -0
- package/dist/test-support/project-authority-identity.d.ts.map +1 -0
- package/package.json +2 -2
package/dist/server/index.js
CHANGED
|
@@ -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": {
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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, '') = ?
|