@hasna/mementos 0.14.80 → 0.14.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +557 -80
- package/dist/db/memory-project-link.d.ts.map +1 -1
- package/dist/db/projects.d.ts +4 -4
- package/dist/db/projects.d.ts.map +1 -1
- package/dist/index.js +703 -93
- package/dist/project-registration/authority.d.ts +5 -1
- package/dist/project-registration/authority.d.ts.map +1 -1
- package/dist/project-registration/http.d.ts +4 -1
- package/dist/project-registration/http.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 +125 -0
- package/dist/project-registration/types.d.ts.map +1 -1
- package/dist/project-registration.js +4959 -327
- 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 +863 -157
- 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,8 +57849,8 @@ function normalizeProjectUpdateInput(input) {
|
|
|
57624
57849
|
}
|
|
57625
57850
|
return normalized;
|
|
57626
57851
|
}
|
|
57627
|
-
function assertProjectUpdateIdentity(identity) {
|
|
57628
|
-
if (identity.authority_id !==
|
|
57852
|
+
function assertProjectUpdateIdentity(identity, expectedIdentity = projectUpdateAuthority()) {
|
|
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
|
}
|
|
57631
57856
|
}
|
|
@@ -57634,8 +57859,8 @@ 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) {
|
|
57638
|
-
assertProjectUpdateIdentity(request);
|
|
57862
|
+
function assertProjectUpdateRequest(request, expectedIdentity = projectUpdateAuthority()) {
|
|
57863
|
+
assertProjectUpdateIdentity(request, expectedIdentity);
|
|
57639
57864
|
assertBoundedIdentifier2(request.operation_id, "operation_id");
|
|
57640
57865
|
assertBoundedIdentifier2(request.step_id, "step_id");
|
|
57641
57866
|
assertBoundedIdentifier2(request.idempotency_key, "idempotency_key");
|
|
@@ -57748,7 +57973,7 @@ function makeProjectUpdateReceipt(input) {
|
|
|
57748
57973
|
target_id: input.target_id,
|
|
57749
57974
|
expected_revision: input.request.expected_revision,
|
|
57750
57975
|
result_revision: input.after_project.updated_at,
|
|
57751
|
-
result_digest: digestProjectUpdateValue(input.after_project),
|
|
57976
|
+
result_digest: input.result_digest ?? digestProjectUpdateValue(input.after_project),
|
|
57752
57977
|
accepted_receipt_id: input.accepted_receipt_id ?? null,
|
|
57753
57978
|
before_project: input.before_project,
|
|
57754
57979
|
after_project: input.after_project,
|
|
@@ -57812,8 +58037,8 @@ 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) {
|
|
57816
|
-
assertProjectUpdateRequest(request);
|
|
58040
|
+
function previewProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority()) {
|
|
58041
|
+
assertProjectUpdateRequest(request, expectedIdentity);
|
|
57817
58042
|
const normalized = normalizeProjectUpdateInput(request.updates);
|
|
57818
58043
|
if (!db && isApiMode()) {
|
|
57819
58044
|
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: true });
|
|
@@ -57835,8 +58060,8 @@ function previewProjectUpdate(id, request, db) {
|
|
|
57835
58060
|
receipt: null
|
|
57836
58061
|
};
|
|
57837
58062
|
}
|
|
57838
|
-
function applyProjectUpdate(id, request, db) {
|
|
57839
|
-
assertProjectUpdateRequest(request);
|
|
58063
|
+
function applyProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
|
|
58064
|
+
assertProjectUpdateRequest(request, expectedIdentity);
|
|
57840
58065
|
const normalized = normalizeProjectUpdateInput(request.updates);
|
|
57841
58066
|
if (!db && isApiMode()) {
|
|
57842
58067
|
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-update`, { ...request, updates: normalized, dry_run: false });
|
|
@@ -57897,14 +58122,15 @@ function applyProjectUpdate(id, request, db) {
|
|
|
57897
58122
|
request_digest: requestDigest,
|
|
57898
58123
|
target_id: id,
|
|
57899
58124
|
before_project: before,
|
|
57900
|
-
after_project: readback
|
|
58125
|
+
after_project: readback,
|
|
58126
|
+
result_digest: resultDigestForProject?.(readback)
|
|
57901
58127
|
});
|
|
57902
58128
|
insertProjectUpdateReceipt(d, receipt);
|
|
57903
58129
|
return { dry_run: false, applied: true, project: readback, receipt };
|
|
57904
58130
|
});
|
|
57905
58131
|
}
|
|
57906
|
-
function rollbackProjectUpdate(id, request, db) {
|
|
57907
|
-
assertProjectUpdateRequest(request);
|
|
58132
|
+
function rollbackProjectUpdate(id, request, db, expectedIdentity = projectUpdateAuthority(), resultDigestForProject) {
|
|
58133
|
+
assertProjectUpdateRequest(request, expectedIdentity);
|
|
57908
58134
|
assertBoundedIdentifier2(request.accepted_receipt_id, "accepted_receipt_id");
|
|
57909
58135
|
if (!db && isApiMode()) {
|
|
57910
58136
|
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/guarded-rollback`, request);
|
|
@@ -57970,14 +58196,15 @@ function rollbackProjectUpdate(id, request, db) {
|
|
|
57970
58196
|
target_id: id,
|
|
57971
58197
|
before_project: current,
|
|
57972
58198
|
after_project: readback,
|
|
58199
|
+
result_digest: resultDigestForProject?.(readback),
|
|
57973
58200
|
accepted_receipt_id: accepted.receipt_id
|
|
57974
58201
|
});
|
|
57975
58202
|
insertProjectUpdateReceipt(d, receipt);
|
|
57976
58203
|
return { dry_run: false, applied: true, project: readback, receipt };
|
|
57977
58204
|
});
|
|
57978
58205
|
}
|
|
57979
|
-
function getProjectUpdateReceipt(id, receiptId, identity =
|
|
57980
|
-
assertProjectUpdateIdentity(identity);
|
|
58206
|
+
function getProjectUpdateReceipt(id, receiptId, identity = projectUpdateAuthority(), db, expectedIdentity = projectUpdateAuthority()) {
|
|
58207
|
+
assertProjectUpdateIdentity(identity, expectedIdentity);
|
|
57981
58208
|
if (!db && isApiMode()) {
|
|
57982
58209
|
const { data } = apiJson("POST", `/projects/${encodeURIComponent(id)}/update-receipts/lookup`, { ...identity, receipt_id: receiptId });
|
|
57983
58210
|
return data;
|
|
@@ -57990,91 +58217,6 @@ function getProjectUpdateReceipt(id, receiptId, identity = PROJECT_UPDATE_AUTHOR
|
|
|
57990
58217
|
return receipt;
|
|
57991
58218
|
}
|
|
57992
58219
|
|
|
57993
|
-
// src/server/routes/projects.ts
|
|
57994
|
-
init_router();
|
|
57995
|
-
addRoute("GET", "/api/projects", (_req, url) => {
|
|
57996
|
-
const q = getSearchParams(url);
|
|
57997
|
-
const projects = listProjects();
|
|
57998
|
-
if (q["fields"]) {
|
|
57999
|
-
const fields = q["fields"].split(",").map((f) => f.trim());
|
|
58000
|
-
const filtered = projects.map((p) => Object.fromEntries(fields.map((f) => [f, p[f]]).filter(([, v]) => v !== undefined)));
|
|
58001
|
-
return json({ projects: filtered, count: filtered.length });
|
|
58002
|
-
}
|
|
58003
|
-
return json({ projects, count: projects.length });
|
|
58004
|
-
});
|
|
58005
|
-
addRoute("POST", "/api/projects", async (req) => {
|
|
58006
|
-
const body = await readJson(req);
|
|
58007
|
-
if (!body || !body["name"] || !body["path"]) {
|
|
58008
|
-
return errorResponse("Missing required fields: name, path", 400);
|
|
58009
|
-
}
|
|
58010
|
-
const project = registerProject(body["name"], body["path"], body["description"], body["memory_prefix"]);
|
|
58011
|
-
return json(project, 201);
|
|
58012
|
-
});
|
|
58013
|
-
addRoute("GET", "/api/projects/:id", (_req, _url, params) => {
|
|
58014
|
-
const project = getProject(params["id"]);
|
|
58015
|
-
if (!project)
|
|
58016
|
-
return errorResponse("Project not found", 404);
|
|
58017
|
-
return json(project);
|
|
58018
|
-
});
|
|
58019
|
-
function guardedUpdateError(error) {
|
|
58020
|
-
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;
|
|
58021
|
-
return errorResponse(error.message, status, { code: error.code, ...error.details });
|
|
58022
|
-
}
|
|
58023
|
-
addRoute("PATCH", "/api/projects/:id", () => errorResponse("Unguarded project updates are disabled; use POST /projects/:id/guarded-update", 428));
|
|
58024
|
-
addRoute("POST", "/api/projects/:id/guarded-update", async (req, _url, params) => {
|
|
58025
|
-
const body = await readJson(req);
|
|
58026
|
-
if (!body)
|
|
58027
|
-
return errorResponse("Invalid JSON body", 400);
|
|
58028
|
-
try {
|
|
58029
|
-
const request = body;
|
|
58030
|
-
return json(request.dry_run ? previewProjectUpdate(params["id"], request) : applyProjectUpdate(params["id"], request));
|
|
58031
|
-
} catch (error) {
|
|
58032
|
-
if (error instanceof ProjectGuardedUpdateError)
|
|
58033
|
-
return guardedUpdateError(error);
|
|
58034
|
-
throw error;
|
|
58035
|
-
}
|
|
58036
|
-
});
|
|
58037
|
-
addRoute("POST", "/api/projects/:id/guarded-rollback", async (req, _url, params) => {
|
|
58038
|
-
const body = await readJson(req);
|
|
58039
|
-
if (!body)
|
|
58040
|
-
return errorResponse("Invalid JSON body", 400);
|
|
58041
|
-
try {
|
|
58042
|
-
return json(rollbackProjectUpdate(params["id"], body));
|
|
58043
|
-
} catch (error) {
|
|
58044
|
-
if (error instanceof ProjectGuardedUpdateError)
|
|
58045
|
-
return guardedUpdateError(error);
|
|
58046
|
-
throw error;
|
|
58047
|
-
}
|
|
58048
|
-
});
|
|
58049
|
-
addRoute("POST", "/api/projects/:id/update-receipts/lookup", async (req, _url, params) => {
|
|
58050
|
-
const body = await readJson(req);
|
|
58051
|
-
if (!body || typeof body["receipt_id"] !== "string") {
|
|
58052
|
-
return errorResponse("receipt_id is required", 400);
|
|
58053
|
-
}
|
|
58054
|
-
try {
|
|
58055
|
-
const identity = {
|
|
58056
|
-
authority_id: String(body["authority_id"] ?? ""),
|
|
58057
|
-
tenant_id: String(body["tenant_id"] ?? ""),
|
|
58058
|
-
corpus_id: String(body["corpus_id"] ?? "")
|
|
58059
|
-
};
|
|
58060
|
-
return json(getProjectUpdateReceipt(params["id"], body["receipt_id"], identity));
|
|
58061
|
-
} catch (error) {
|
|
58062
|
-
if (error instanceof ProjectGuardedUpdateError)
|
|
58063
|
-
return guardedUpdateError(error);
|
|
58064
|
-
throw error;
|
|
58065
|
-
}
|
|
58066
|
-
});
|
|
58067
|
-
addRoute("GET", "/api/projects/:id/agents", (_req, _url, params) => {
|
|
58068
|
-
const project = getProject(params["id"]);
|
|
58069
|
-
if (!project)
|
|
58070
|
-
return errorResponse("Project not found", 404);
|
|
58071
|
-
const agents = listAgentsByProject(project.id);
|
|
58072
|
-
return json({ agents, count: agents.length });
|
|
58073
|
-
});
|
|
58074
|
-
|
|
58075
|
-
// src/server/routes/project-registration.ts
|
|
58076
|
-
init_database();
|
|
58077
|
-
|
|
58078
58220
|
// src/project-registration/authority.ts
|
|
58079
58221
|
import { createHash as createHash3 } from "crypto";
|
|
58080
58222
|
import { resolve as resolve3 } from "path";
|
|
@@ -58205,9 +58347,9 @@ function assertWithinBounds(value, bounds, startedAt) {
|
|
|
58205
58347
|
}
|
|
58206
58348
|
return { response_bytes: bytes, elapsed_ms: elapsed };
|
|
58207
58349
|
}
|
|
58208
|
-
function
|
|
58350
|
+
function withBoundedResponseControl(payload, bounds, startedAt) {
|
|
58209
58351
|
const result = {
|
|
58210
|
-
|
|
58352
|
+
...payload,
|
|
58211
58353
|
response_control: {
|
|
58212
58354
|
response_byte_limit: bounds.response_byte_limit,
|
|
58213
58355
|
time_budget_ms: bounds.time_budget_ms,
|
|
@@ -58230,6 +58372,9 @@ function withResponseControl(receipt, bounds, startedAt) {
|
|
|
58230
58372
|
result.response_control.elapsed_ms = measured.elapsed_ms;
|
|
58231
58373
|
return result;
|
|
58232
58374
|
}
|
|
58375
|
+
function withResponseControl(receipt, bounds, startedAt) {
|
|
58376
|
+
return withBoundedResponseControl({ receipt }, bounds, startedAt);
|
|
58377
|
+
}
|
|
58233
58378
|
function requireString(value, field, options = {}) {
|
|
58234
58379
|
const min = options.min ?? 1;
|
|
58235
58380
|
const max = options.max ?? 512;
|
|
@@ -58256,6 +58401,80 @@ function ownedPath(target) {
|
|
|
58256
58401
|
}
|
|
58257
58402
|
return path;
|
|
58258
58403
|
}
|
|
58404
|
+
function guardedAuthorityIdentity(capability) {
|
|
58405
|
+
return {
|
|
58406
|
+
authority_id: capability.authority_id,
|
|
58407
|
+
tenant_id: capability.tenant_id,
|
|
58408
|
+
corpus_id: capability.corpus_id
|
|
58409
|
+
};
|
|
58410
|
+
}
|
|
58411
|
+
function assertGuardedAuthorityRequest(targetId, request, capability) {
|
|
58412
|
+
assertBounds(request);
|
|
58413
|
+
requireString(targetId, "target_id", { min: 8, max: 128, pattern: OPERATION_PATTERN });
|
|
58414
|
+
if (request.authority !== "mementos" || request.authority_route !== MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE || request.package_version !== capability.package_version || request.authority_id !== capability.authority_id || request.tenant_id !== capability.tenant_id || request.corpus_id !== capability.corpus_id) {
|
|
58415
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH", "guarded project request does not match this authority capability identity");
|
|
58416
|
+
}
|
|
58417
|
+
return guardedAuthorityIdentity(capability);
|
|
58418
|
+
}
|
|
58419
|
+
function assertGuardedOperationFields(request) {
|
|
58420
|
+
requireString(request.operation_id, "operation_id", {
|
|
58421
|
+
min: 8,
|
|
58422
|
+
max: 128,
|
|
58423
|
+
pattern: OPERATION_PATTERN
|
|
58424
|
+
});
|
|
58425
|
+
requireString(request.step_id, "step_id", {
|
|
58426
|
+
min: 8,
|
|
58427
|
+
max: 128,
|
|
58428
|
+
pattern: OPERATION_PATTERN
|
|
58429
|
+
});
|
|
58430
|
+
requireString(request.idempotency_key, "idempotency_key", {
|
|
58431
|
+
min: 8,
|
|
58432
|
+
max: 128,
|
|
58433
|
+
pattern: OPERATION_PATTERN
|
|
58434
|
+
});
|
|
58435
|
+
requireString(request.expected_revision, "expected_revision", { max: 128 });
|
|
58436
|
+
}
|
|
58437
|
+
function assertGuardedUpdateRequest(targetId, request, capability) {
|
|
58438
|
+
const identity = assertGuardedAuthorityRequest(targetId, request, capability);
|
|
58439
|
+
assertGuardedOperationFields(request);
|
|
58440
|
+
if (!request.updates || typeof request.updates !== "object") {
|
|
58441
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "guarded project updates must contain exactly one private path handle");
|
|
58442
|
+
}
|
|
58443
|
+
exactKeys(request.updates, ["path"], "updates");
|
|
58444
|
+
return { identity, path: ownedPath(request.updates.path) };
|
|
58445
|
+
}
|
|
58446
|
+
function publicGuardedUpdateReceipt(receipt, capability) {
|
|
58447
|
+
return {
|
|
58448
|
+
receipt_id: receipt.receipt_id,
|
|
58449
|
+
authority: "mementos",
|
|
58450
|
+
route: MEMENTOS_PROJECT_GUARDED_UPDATE_ROUTE,
|
|
58451
|
+
package_version: capability.package_version,
|
|
58452
|
+
authority_id: capability.authority_id,
|
|
58453
|
+
tenant_id: capability.tenant_id,
|
|
58454
|
+
corpus_id: capability.corpus_id,
|
|
58455
|
+
operation_id: receipt.operation_id,
|
|
58456
|
+
step_id: receipt.step_id,
|
|
58457
|
+
direction: receipt.direction,
|
|
58458
|
+
idempotency_key: receipt.idempotency_key,
|
|
58459
|
+
request_digest: receipt.request_digest,
|
|
58460
|
+
outcome: "accepted",
|
|
58461
|
+
target_id: receipt.target_id,
|
|
58462
|
+
expected_revision: receipt.expected_revision,
|
|
58463
|
+
result_revision: receipt.result_revision,
|
|
58464
|
+
result_digest: receipt.result_digest,
|
|
58465
|
+
accepted_receipt_id: receipt.accepted_receipt_id,
|
|
58466
|
+
created_at: receipt.created_at
|
|
58467
|
+
};
|
|
58468
|
+
}
|
|
58469
|
+
function guardedProjectError(cause) {
|
|
58470
|
+
if (cause instanceof MementosProjectRegistrationError)
|
|
58471
|
+
throw cause;
|
|
58472
|
+
if (cause instanceof ProjectGuardedUpdateError) {
|
|
58473
|
+
const code = cause.code === "PROJECT_UPDATE_INVALID_INPUT" ? "MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT" : cause.code === "PROJECT_UPDATE_AUTHORITY_MISMATCH" ? "MEMENTOS_PROJECT_REGISTRATION_CAPABILITY_MISMATCH" : cause.code === "PROJECT_UPDATE_NOT_FOUND" ? "MEMENTOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND" : cause.code === "PROJECT_UPDATE_RECEIPT_NOT_FOUND" ? "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND" : "MEMENTOS_PROJECT_REGISTRATION_CONFLICT";
|
|
58474
|
+
throw new MementosProjectRegistrationError(code, "guarded project operation was rejected without exposing private project data", { project_update_code: cause.code });
|
|
58475
|
+
}
|
|
58476
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "guarded project operation failed before a bounded public result was available");
|
|
58477
|
+
}
|
|
58259
58478
|
function normalizedCallDigest(request) {
|
|
58260
58479
|
return digestMementosProjectRegistrationValue({
|
|
58261
58480
|
authority_route: request.authority_route,
|
|
@@ -58627,26 +58846,7 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
58627
58846
|
this.db = db;
|
|
58628
58847
|
this.now = options.now ?? (() => new Date().toISOString());
|
|
58629
58848
|
this.faultInjector = options.faultInjector;
|
|
58630
|
-
this.capabilityValue =
|
|
58631
|
-
authority: "mementos",
|
|
58632
|
-
route: MEMENTOS_PROJECT_REGISTRATION_ROUTE,
|
|
58633
|
-
package_version: options.packageVersion ?? getMementosPackageVersion(),
|
|
58634
|
-
authority_id: options.authorityId ?? "mementos",
|
|
58635
|
-
tenant_id: options.tenantId ?? "default",
|
|
58636
|
-
corpus_id: options.corpusId ?? "default",
|
|
58637
|
-
supported_resources: ["project"],
|
|
58638
|
-
conditional_create: true,
|
|
58639
|
-
immutable_receipts: true,
|
|
58640
|
-
exact_terminal_lookup: true,
|
|
58641
|
-
exact_readback: true,
|
|
58642
|
-
conditional_inverse: true,
|
|
58643
|
-
ambiguous_outcome_reconciliation: true,
|
|
58644
|
-
guarded_update: true,
|
|
58645
|
-
no_write_dry_run: true,
|
|
58646
|
-
expected_revision_compare_and_swap: true,
|
|
58647
|
-
caller_idempotency: true,
|
|
58648
|
-
exact_inverse_rollback: true
|
|
58649
|
-
};
|
|
58849
|
+
this.capabilityValue = buildMementosProjectRegistrationCapability(options);
|
|
58650
58850
|
}
|
|
58651
58851
|
fault(point, request) {
|
|
58652
58852
|
this.faultInjector?.(point, {
|
|
@@ -58703,6 +58903,90 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
58703
58903
|
supported_resources: ["project"]
|
|
58704
58904
|
};
|
|
58705
58905
|
}
|
|
58906
|
+
boundedGuardedUpdateResult(targetId2, project, receipt, bounds, startedAt) {
|
|
58907
|
+
if (!receipt || project.id !== targetId2 || receipt.target_id !== targetId2) {
|
|
58908
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "guarded project operation did not return its exact immutable receipt");
|
|
58909
|
+
}
|
|
58910
|
+
if (project.updated_at !== receipt.result_revision) {
|
|
58911
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_ATOMICITY_UNAVAILABLE", "guarded project result revision did not match its immutable receipt");
|
|
58912
|
+
}
|
|
58913
|
+
const record = {
|
|
58914
|
+
target_id: targetId2,
|
|
58915
|
+
revision: receipt.result_revision,
|
|
58916
|
+
digest: receipt.result_digest
|
|
58917
|
+
};
|
|
58918
|
+
return withBoundedResponseControl({
|
|
58919
|
+
dry_run: false,
|
|
58920
|
+
applied: true,
|
|
58921
|
+
record,
|
|
58922
|
+
receipt: publicGuardedUpdateReceipt(receipt, this.capabilityValue)
|
|
58923
|
+
}, bounds, startedAt);
|
|
58924
|
+
}
|
|
58925
|
+
async guardedUpdateProject(targetId2, request) {
|
|
58926
|
+
const startedAt = Date.now();
|
|
58927
|
+
const { identity, path } = assertGuardedUpdateRequest(targetId2, request, this.capabilityValue);
|
|
58928
|
+
try {
|
|
58929
|
+
const result = applyProjectUpdate(targetId2, {
|
|
58930
|
+
...identity,
|
|
58931
|
+
operation_id: request.operation_id,
|
|
58932
|
+
step_id: request.step_id,
|
|
58933
|
+
idempotency_key: request.idempotency_key,
|
|
58934
|
+
expected_revision: request.expected_revision,
|
|
58935
|
+
updates: { path }
|
|
58936
|
+
}, this.db, identity, (project) => projectRecord(this.db, project).digest);
|
|
58937
|
+
return this.boundedGuardedUpdateResult(targetId2, result.project, result.receipt, request, startedAt);
|
|
58938
|
+
} catch (cause) {
|
|
58939
|
+
return guardedProjectError(cause);
|
|
58940
|
+
}
|
|
58941
|
+
}
|
|
58942
|
+
async getGuardedProjectUpdateReceipt(targetId2, receiptId2, request) {
|
|
58943
|
+
const startedAt = Date.now();
|
|
58944
|
+
const identity = assertGuardedAuthorityRequest(targetId2, request, this.capabilityValue);
|
|
58945
|
+
requireString(receiptId2, "receipt_id", {
|
|
58946
|
+
min: 8,
|
|
58947
|
+
max: 128,
|
|
58948
|
+
pattern: OPERATION_PATTERN
|
|
58949
|
+
});
|
|
58950
|
+
try {
|
|
58951
|
+
const receipt = getProjectUpdateReceipt(targetId2, receiptId2, identity, this.db, identity);
|
|
58952
|
+
return withBoundedResponseControl({
|
|
58953
|
+
receipt: publicGuardedUpdateReceipt(receipt, this.capabilityValue)
|
|
58954
|
+
}, request, startedAt);
|
|
58955
|
+
} catch (cause) {
|
|
58956
|
+
return guardedProjectError(cause);
|
|
58957
|
+
}
|
|
58958
|
+
}
|
|
58959
|
+
async rollbackGuardedProjectUpdate(targetId2, request) {
|
|
58960
|
+
const startedAt = Date.now();
|
|
58961
|
+
const identity = assertGuardedAuthorityRequest(targetId2, request, this.capabilityValue);
|
|
58962
|
+
assertGuardedOperationFields(request);
|
|
58963
|
+
if (!request.accepted_receipt || typeof request.accepted_receipt !== "object") {
|
|
58964
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional rollback requires the exact sanitized accepted receipt");
|
|
58965
|
+
}
|
|
58966
|
+
requireString(request.accepted_receipt.receipt_id, "accepted_receipt.receipt_id", {
|
|
58967
|
+
min: 8,
|
|
58968
|
+
max: 128,
|
|
58969
|
+
pattern: OPERATION_PATTERN
|
|
58970
|
+
});
|
|
58971
|
+
try {
|
|
58972
|
+
const internalAccepted = getProjectUpdateReceipt(targetId2, request.accepted_receipt.receipt_id, identity, this.db, identity);
|
|
58973
|
+
const accepted = publicGuardedUpdateReceipt(internalAccepted, this.capabilityValue);
|
|
58974
|
+
if (accepted.direction !== "forward" || accepted.target_id !== targetId2 || request.expected_revision !== accepted.result_revision || canonicalMementosProjectRegistrationJson(request.accepted_receipt) !== canonicalMementosProjectRegistrationJson(accepted)) {
|
|
58975
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "conditional rollback receipt does not exactly match the accepted forward update");
|
|
58976
|
+
}
|
|
58977
|
+
const result = rollbackProjectUpdate(targetId2, {
|
|
58978
|
+
...identity,
|
|
58979
|
+
operation_id: request.operation_id,
|
|
58980
|
+
step_id: request.step_id,
|
|
58981
|
+
idempotency_key: request.idempotency_key,
|
|
58982
|
+
expected_revision: request.expected_revision,
|
|
58983
|
+
accepted_receipt_id: accepted.receipt_id
|
|
58984
|
+
}, this.db, identity, (project) => projectRecord(this.db, project).digest);
|
|
58985
|
+
return this.boundedGuardedUpdateResult(targetId2, result.project, result.receipt, request, startedAt);
|
|
58986
|
+
} catch (cause) {
|
|
58987
|
+
return guardedProjectError(cause);
|
|
58988
|
+
}
|
|
58989
|
+
}
|
|
58706
58990
|
async create(request) {
|
|
58707
58991
|
const startedAt = Date.now();
|
|
58708
58992
|
const path = assertForwardRequest2(request, this.capabilityValue);
|
|
@@ -58799,9 +59083,9 @@ class PackageOwnedMementosProjectRegistrationAuthority {
|
|
|
58799
59083
|
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "resource_kind must be project");
|
|
58800
59084
|
}
|
|
58801
59085
|
requireString(request.target_id, "target_id", {
|
|
58802
|
-
min:
|
|
58803
|
-
max:
|
|
58804
|
-
pattern:
|
|
59086
|
+
min: 8,
|
|
59087
|
+
max: 128,
|
|
59088
|
+
pattern: OPERATION_PATTERN
|
|
58805
59089
|
});
|
|
58806
59090
|
const path = ownedPath(request.target);
|
|
58807
59091
|
const project = getProjectByExactId3(this.db, request.target_id);
|
|
@@ -59059,6 +59343,31 @@ function fromWireReadRequest(body) {
|
|
|
59059
59343
|
target: new WirePathHandle(canonicalPath)
|
|
59060
59344
|
};
|
|
59061
59345
|
}
|
|
59346
|
+
function fromWireGuardedUpdateRequest(body) {
|
|
59347
|
+
const { target_id: targetId2, updates, ...request } = body;
|
|
59348
|
+
if (typeof targetId2 !== "string") {
|
|
59349
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "target_id is required on the private guarded-update transport");
|
|
59350
|
+
}
|
|
59351
|
+
if (!updates || typeof updates !== "object" || Array.isArray(updates) || typeof updates["path"] !== "string") {
|
|
59352
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", "updates.path is required on the private guarded-update transport");
|
|
59353
|
+
}
|
|
59354
|
+
return {
|
|
59355
|
+
targetId: targetId2,
|
|
59356
|
+
request: {
|
|
59357
|
+
...request,
|
|
59358
|
+
updates: {
|
|
59359
|
+
path: new WirePathHandle(String(updates["path"]))
|
|
59360
|
+
}
|
|
59361
|
+
}
|
|
59362
|
+
};
|
|
59363
|
+
}
|
|
59364
|
+
function exactWireIdentifier(body, field) {
|
|
59365
|
+
const value = body[field];
|
|
59366
|
+
if (typeof value !== "string") {
|
|
59367
|
+
throw new MementosProjectRegistrationError("MEMENTOS_PROJECT_REGISTRATION_INVALID_INPUT", `${field} is required on the guarded-update transport`);
|
|
59368
|
+
}
|
|
59369
|
+
return value;
|
|
59370
|
+
}
|
|
59062
59371
|
async function handleMementosProjectRegistrationHttpRequest(request, url, authority, basePath = "/v1/project-registration") {
|
|
59063
59372
|
const path = url.pathname;
|
|
59064
59373
|
if (path !== basePath && !path.startsWith(`${basePath}/`))
|
|
@@ -59093,6 +59402,21 @@ async function handleMementosProjectRegistrationHttpRequest(request, url, author
|
|
|
59093
59402
|
if (action === "verify-inverse") {
|
|
59094
59403
|
return json2({ verification: await authority.verifyInverse(fromWireRequest(body)) });
|
|
59095
59404
|
}
|
|
59405
|
+
if (action === "projects/guarded-update") {
|
|
59406
|
+
const guarded = fromWireGuardedUpdateRequest(body);
|
|
59407
|
+
return json2(await authority.guardedUpdateProject(guarded.targetId, guarded.request));
|
|
59408
|
+
}
|
|
59409
|
+
if (action === "projects/update-receipts/lookup") {
|
|
59410
|
+
const targetId2 = exactWireIdentifier(body, "target_id");
|
|
59411
|
+
const receiptId2 = exactWireIdentifier(body, "receipt_id");
|
|
59412
|
+
const { target_id: _targetId, receipt_id: _receiptId, ...lookup } = body;
|
|
59413
|
+
return json2(await authority.getGuardedProjectUpdateReceipt(targetId2, receiptId2, lookup));
|
|
59414
|
+
}
|
|
59415
|
+
if (action === "projects/guarded-rollback") {
|
|
59416
|
+
const targetId2 = exactWireIdentifier(body, "target_id");
|
|
59417
|
+
const { target_id: _targetId, ...rollback } = body;
|
|
59418
|
+
return json2(await authority.rollbackGuardedProjectUpdate(targetId2, rollback));
|
|
59419
|
+
}
|
|
59096
59420
|
return json2({
|
|
59097
59421
|
error: "unknown Mementos project-registration route",
|
|
59098
59422
|
code: "MEMENTOS_PROJECT_REGISTRATION_RECEIPT_NOT_FOUND"
|
|
@@ -59112,7 +59436,386 @@ async function handleMementosProjectRegistrationHttpRequest(request, url, author
|
|
|
59112
59436
|
}, 500);
|
|
59113
59437
|
}
|
|
59114
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
|
+
|
|
59115
59817
|
// src/server/routes/project-registration.ts
|
|
59818
|
+
init_database();
|
|
59116
59819
|
init_router();
|
|
59117
59820
|
var handle = async (request, url) => {
|
|
59118
59821
|
const basePath = url.pathname.startsWith("/v1/") ? "/v1/project-registration" : "/api/project-registration";
|
|
@@ -59129,6 +59832,9 @@ addRoute("POST", "/api/project-registration/receipts/lookup", handle);
|
|
|
59129
59832
|
addRoute("POST", "/api/project-registration/read-exact", handle);
|
|
59130
59833
|
addRoute("POST", "/api/project-registration/compensate", handle);
|
|
59131
59834
|
addRoute("POST", "/api/project-registration/verify-inverse", handle);
|
|
59835
|
+
addRoute("POST", "/api/project-registration/projects/guarded-update", handle);
|
|
59836
|
+
addRoute("POST", "/api/project-registration/projects/update-receipts/lookup", handle);
|
|
59837
|
+
addRoute("POST", "/api/project-registration/projects/guarded-rollback", handle);
|
|
59132
59838
|
|
|
59133
59839
|
// src/server/routes/entities.ts
|
|
59134
59840
|
init_entities();
|
|
@@ -60769,7 +61475,7 @@ function parseMemoryLink(row) {
|
|
|
60769
61475
|
function createMemoryLink(input, db) {
|
|
60770
61476
|
const d = db || getDatabase();
|
|
60771
61477
|
const id = shortUuid();
|
|
60772
|
-
const
|
|
61478
|
+
const timestamp2 = now();
|
|
60773
61479
|
d.run(`INSERT OR IGNORE INTO memory_links (id, source_memory_id, target_memory_id, relation_type, run_id, metadata, created_at)
|
|
60774
61480
|
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
|
|
60775
61481
|
id,
|
|
@@ -60778,7 +61484,7 @@ function createMemoryLink(input, db) {
|
|
|
60778
61484
|
input.relation_type,
|
|
60779
61485
|
input.run_id ?? null,
|
|
60780
61486
|
JSON.stringify(input.metadata ?? {}),
|
|
60781
|
-
|
|
61487
|
+
timestamp2
|
|
60782
61488
|
]);
|
|
60783
61489
|
const row = d.query(`SELECT * FROM memory_links
|
|
60784
61490
|
WHERE source_memory_id = ? AND target_memory_id = ? AND relation_type = ? AND COALESCE(run_id, '') = ?
|