@x12i/memorix-service 3.3.0 → 3.4.0
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/README.md +23 -6
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +4 -0
- package/dist/app.js.map +1 -1
- package/dist/blob-store/config.d.ts +31 -0
- package/dist/blob-store/config.d.ts.map +1 -0
- package/dist/blob-store/config.js +68 -0
- package/dist/blob-store/config.js.map +1 -0
- package/dist/blob-store/disk.d.ts +10 -0
- package/dist/blob-store/disk.d.ts.map +1 -0
- package/dist/blob-store/disk.js +104 -0
- package/dist/blob-store/disk.js.map +1 -0
- package/dist/blob-store/index.d.ts +6 -0
- package/dist/blob-store/index.d.ts.map +1 -0
- package/dist/blob-store/index.js +6 -0
- package/dist/blob-store/index.js.map +1 -0
- package/dist/blob-store/memory.d.ts +4 -0
- package/dist/blob-store/memory.d.ts.map +1 -0
- package/dist/blob-store/memory.js +47 -0
- package/dist/blob-store/memory.js.map +1 -0
- package/dist/blob-store/s3.d.ts +52 -0
- package/dist/blob-store/s3.d.ts.map +1 -0
- package/dist/blob-store/s3.js +151 -0
- package/dist/blob-store/s3.js.map +1 -0
- package/dist/blob-store/types.d.ts +30 -0
- package/dist/blob-store/types.d.ts.map +1 -0
- package/dist/blob-store/types.js +38 -0
- package/dist/blob-store/types.js.map +1 -0
- package/dist/catalog-store-cli.d.ts.map +1 -1
- package/dist/catalog-store-cli.js +35 -4
- package/dist/catalog-store-cli.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/index.d.ts +3 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +3 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/instance-store.d.ts +5 -0
- package/dist/integrations/instance-store.d.ts.map +1 -0
- package/dist/integrations/instance-store.js +327 -0
- package/dist/integrations/instance-store.js.map +1 -0
- package/dist/integrations/types.d.ts +92 -0
- package/dist/integrations/types.d.ts.map +1 -0
- package/dist/integrations/types.js +40 -0
- package/dist/integrations/types.js.map +1 -0
- package/dist/magit/magit-pack-service.d.ts +187 -3
- package/dist/magit/magit-pack-service.d.ts.map +1 -1
- package/dist/magit/magit-pack-service.js +401 -8
- package/dist/magit/magit-pack-service.js.map +1 -1
- package/dist/openapi/openapi.json +1348 -2
- package/dist/platform.d.ts +18 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +61 -0
- package/dist/platform.js.map +1 -1
- package/dist/restricted-store/crypto.d.ts +10 -0
- package/dist/restricted-store/crypto.d.ts.map +1 -0
- package/dist/restricted-store/crypto.js +43 -0
- package/dist/restricted-store/crypto.js.map +1 -0
- package/dist/restricted-store/index.d.ts +4 -0
- package/dist/restricted-store/index.d.ts.map +1 -0
- package/dist/restricted-store/index.js +4 -0
- package/dist/restricted-store/index.js.map +1 -0
- package/dist/restricted-store/object-store.d.ts +12 -0
- package/dist/restricted-store/object-store.d.ts.map +1 -0
- package/dist/restricted-store/object-store.js +233 -0
- package/dist/restricted-store/object-store.js.map +1 -0
- package/dist/restricted-store/types.d.ts +78 -0
- package/dist/restricted-store/types.d.ts.map +1 -0
- package/dist/restricted-store/types.js +12 -0
- package/dist/restricted-store/types.js.map +1 -0
- package/dist/routes/integrations.d.ts +5 -0
- package/dist/routes/integrations.d.ts.map +1 -0
- package/dist/routes/integrations.js +126 -0
- package/dist/routes/integrations.js.map +1 -0
- package/dist/routes/metadata.d.ts.map +1 -1
- package/dist/routes/metadata.js +328 -4
- package/dist/routes/metadata.js.map +1 -1
- package/dist/routes/restricted-store.d.ts +5 -0
- package/dist/routes/restricted-store.d.ts.map +1 -0
- package/dist/routes/restricted-store.js +117 -0
- package/dist/routes/restricted-store.js.map +1 -0
- package/openapi/openapi.json +1348 -2
- package/package.json +3 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FG-A3 restricted / encrypted object store.
|
|
3
|
+
* Metadata (redacted) in Mongo; full body only in BlobObjectStore (s3|disk).
|
|
4
|
+
* Never lands full body into Memory raw or abstracts.
|
|
5
|
+
*/
|
|
6
|
+
import type { BlobBackendKind } from "../blob-store/types.js";
|
|
7
|
+
export declare const RESTRICTED_COLLECTIONS: {
|
|
8
|
+
readonly objects: "restricted_objects";
|
|
9
|
+
readonly audits: "restricted_access_audits";
|
|
10
|
+
};
|
|
11
|
+
export type RestrictedClassification = "restricted" | "config-backup";
|
|
12
|
+
export type RestrictedObjectMeta = {
|
|
13
|
+
orgId: string;
|
|
14
|
+
objectId: string;
|
|
15
|
+
classification: RestrictedClassification;
|
|
16
|
+
/** Content hash of plaintext body (sha256 hex). */
|
|
17
|
+
contentHash: string;
|
|
18
|
+
sizeBytes: number;
|
|
19
|
+
deviceId?: string;
|
|
20
|
+
vdom?: string;
|
|
21
|
+
sourceRunId?: string;
|
|
22
|
+
contentType?: string;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
createdBy?: string;
|
|
25
|
+
/** Blob key within org partition. */
|
|
26
|
+
blobKey: string;
|
|
27
|
+
blobBackend: BlobBackendKind;
|
|
28
|
+
/** true when body was AES-GCM wrapped (on-prem KEK). */
|
|
29
|
+
encryptedAtRest: boolean;
|
|
30
|
+
/** Optional retention expiry ISO. */
|
|
31
|
+
retainUntil?: string | null;
|
|
32
|
+
version?: string;
|
|
33
|
+
};
|
|
34
|
+
export type RestrictedPutInput = {
|
|
35
|
+
classification: RestrictedClassification;
|
|
36
|
+
/** Raw body as base64 (JSON API). */
|
|
37
|
+
bodyBase64: string;
|
|
38
|
+
deviceId?: string;
|
|
39
|
+
vdom?: string;
|
|
40
|
+
sourceRunId?: string;
|
|
41
|
+
contentType?: string;
|
|
42
|
+
version?: string;
|
|
43
|
+
createdBy?: string;
|
|
44
|
+
retainUntil?: string | null;
|
|
45
|
+
/** Optional client-supplied object id; default random. */
|
|
46
|
+
objectId?: string;
|
|
47
|
+
};
|
|
48
|
+
export type RestrictedAccessAudit = {
|
|
49
|
+
auditId: string;
|
|
50
|
+
orgId: string;
|
|
51
|
+
objectId: string;
|
|
52
|
+
contentHash: string;
|
|
53
|
+
at: string;
|
|
54
|
+
actor?: string;
|
|
55
|
+
reason?: string;
|
|
56
|
+
action: "content-get" | "put" | "delete" | "purge";
|
|
57
|
+
};
|
|
58
|
+
export type RestrictedObjectStore = {
|
|
59
|
+
put(input: RestrictedPutInput): Promise<RestrictedObjectMeta>;
|
|
60
|
+
getMeta(objectId: string): Promise<RestrictedObjectMeta | null>;
|
|
61
|
+
/** Break-glass body read — caller must authorize + audit. */
|
|
62
|
+
getContent(objectId: string): Promise<{
|
|
63
|
+
meta: RestrictedObjectMeta;
|
|
64
|
+
body: Buffer;
|
|
65
|
+
} | null>;
|
|
66
|
+
delete(objectId: string): Promise<boolean>;
|
|
67
|
+
purgeExpired(now?: Date): Promise<number>;
|
|
68
|
+
recordAccessAudit(entry: Omit<RestrictedAccessAudit, "auditId" | "orgId" | "at"> & {
|
|
69
|
+
at?: string;
|
|
70
|
+
}): Promise<RestrictedAccessAudit>;
|
|
71
|
+
listAccessAudits(opts?: {
|
|
72
|
+
objectId?: string;
|
|
73
|
+
limit?: number;
|
|
74
|
+
}): Promise<RestrictedAccessAudit[]>;
|
|
75
|
+
};
|
|
76
|
+
export declare const BREAK_GLASS_HEADER = "x-memorix-break-glass";
|
|
77
|
+
export declare const BREAK_GLASS_REASON_HEADER = "x-memorix-break-glass-reason";
|
|
78
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/restricted-store/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FG-A3 restricted / encrypted object store.
|
|
3
|
+
* Metadata (redacted) in Mongo; full body only in BlobObjectStore (s3|disk).
|
|
4
|
+
* Never lands full body into Memory raw or abstracts.
|
|
5
|
+
*/
|
|
6
|
+
export const RESTRICTED_COLLECTIONS = {
|
|
7
|
+
objects: "restricted_objects",
|
|
8
|
+
audits: "restricted_access_audits",
|
|
9
|
+
};
|
|
10
|
+
export const BREAK_GLASS_HEADER = "x-memorix-break-glass";
|
|
11
|
+
export const BREAK_GLASS_REASON_HEADER = "x-memorix-break-glass-reason";
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/restricted-store/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,OAAO,EAAE,oBAAoB;IAC7B,MAAM,EAAE,0BAA0B;CAC1B,CAAC;AA0EX,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAC1D,MAAM,CAAC,MAAM,yBAAyB,GAAG,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import type { MemorixPlatform } from "../platform.js";
|
|
3
|
+
/** Stage −1 integration instance registry. */
|
|
4
|
+
export declare function registerIntegrationRoutes(app: FastifyInstance, platform: MemorixPlatform): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=integrations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrations.d.ts","sourceRoot":"","sources":["../../src/routes/integrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAoCtD,8CAA8C;AAC9C,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,IAAI,CAAC,CAyHf"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { ServiceError } from "../errors.js";
|
|
2
|
+
import { hasRegistrySecretPayload, } from "../integrations/index.js";
|
|
3
|
+
import { resolveConnectorOpsOrg } from "./connector-ops.js";
|
|
4
|
+
function header(headers, name) {
|
|
5
|
+
const v = headers[name] ?? headers[name.toLowerCase()];
|
|
6
|
+
if (Array.isArray(v))
|
|
7
|
+
return v[0];
|
|
8
|
+
return typeof v === "string" ? v : undefined;
|
|
9
|
+
}
|
|
10
|
+
function mapStoreError(err) {
|
|
11
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12
|
+
const code = err.code;
|
|
13
|
+
if (code === "ETAG_MISMATCH" || /etag mismatch/i.test(msg)) {
|
|
14
|
+
throw new ServiceError("ETAG_MISMATCH", msg, 412);
|
|
15
|
+
}
|
|
16
|
+
if (/already exists/i.test(msg)) {
|
|
17
|
+
throw new ServiceError("CONFLICT", msg, 409);
|
|
18
|
+
}
|
|
19
|
+
if (/secret|credentialRef is required|connectorId is required/i.test(msg)) {
|
|
20
|
+
throw new ServiceError(msg.includes("secret") ? "PAYLOAD_REJECTED" : "VALIDATION_ERROR", msg, 400);
|
|
21
|
+
}
|
|
22
|
+
throw new ServiceError("VALIDATION_ERROR", msg, 400);
|
|
23
|
+
}
|
|
24
|
+
/** Stage −1 integration instance registry. */
|
|
25
|
+
export async function registerIntegrationRoutes(app, platform) {
|
|
26
|
+
const prefix = "/api/integrations/v1";
|
|
27
|
+
app.get(`${prefix}/orgs/:orgId/instances`, async (req) => {
|
|
28
|
+
const orgId = resolveConnectorOpsOrg(req);
|
|
29
|
+
const q = (req.query ?? {});
|
|
30
|
+
const connectorId = typeof q.connectorId === "string" ? q.connectorId.trim() : undefined;
|
|
31
|
+
const enabled = q.enabled === "true" ? true : q.enabled === "false" ? false : undefined;
|
|
32
|
+
return platform.integrationInstancesFor(orgId).list({
|
|
33
|
+
...(connectorId ? { connectorId } : {}),
|
|
34
|
+
...(enabled != null ? { enabled } : {}),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
app.post(`${prefix}/orgs/:orgId/instances`, async (req, reply) => {
|
|
38
|
+
const orgId = resolveConnectorOpsOrg(req);
|
|
39
|
+
const body = (req.body ?? {});
|
|
40
|
+
if (hasRegistrySecretPayload(body)) {
|
|
41
|
+
throw new ServiceError("PAYLOAD_REJECTED", "instance payload must not contain secrets; credentialRef only", 400);
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const created = await platform
|
|
45
|
+
.integrationInstancesFor(orgId)
|
|
46
|
+
.create(body);
|
|
47
|
+
return reply.code(201).send(created);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
mapStoreError(err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
app.get(`${prefix}/orgs/:orgId/instances/:instanceId`, async (req) => {
|
|
54
|
+
const orgId = resolveConnectorOpsOrg(req);
|
|
55
|
+
const params = req.params;
|
|
56
|
+
const instanceId = params.instanceId?.trim() ?? "";
|
|
57
|
+
if (!instanceId) {
|
|
58
|
+
throw new ServiceError("VALIDATION_ERROR", "instanceId required", 400);
|
|
59
|
+
}
|
|
60
|
+
const doc = await platform
|
|
61
|
+
.integrationInstancesFor(orgId)
|
|
62
|
+
.get(instanceId);
|
|
63
|
+
if (!doc) {
|
|
64
|
+
throw new ServiceError("NOT_FOUND", "integration instance not found", 404);
|
|
65
|
+
}
|
|
66
|
+
return doc;
|
|
67
|
+
});
|
|
68
|
+
app.put(`${prefix}/orgs/:orgId/instances/:instanceId`, async (req) => {
|
|
69
|
+
const orgId = resolveConnectorOpsOrg(req);
|
|
70
|
+
const params = req.params;
|
|
71
|
+
const instanceId = params.instanceId?.trim() ?? "";
|
|
72
|
+
if (!instanceId) {
|
|
73
|
+
throw new ServiceError("VALIDATION_ERROR", "instanceId required", 400);
|
|
74
|
+
}
|
|
75
|
+
const body = (req.body ?? {});
|
|
76
|
+
if (hasRegistrySecretPayload(body)) {
|
|
77
|
+
throw new ServiceError("PAYLOAD_REJECTED", "instance payload must not contain secrets; credentialRef only", 400);
|
|
78
|
+
}
|
|
79
|
+
const ifMatch = header(req.headers, "if-match") ??
|
|
80
|
+
header(req.headers, "If-Match") ??
|
|
81
|
+
null;
|
|
82
|
+
try {
|
|
83
|
+
return await platform
|
|
84
|
+
.integrationInstancesFor(orgId)
|
|
85
|
+
.put(instanceId, body, { ifMatch });
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
mapStoreError(err);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
app.delete(`${prefix}/orgs/:orgId/instances/:instanceId`, async (req, reply) => {
|
|
92
|
+
const orgId = resolveConnectorOpsOrg(req);
|
|
93
|
+
const params = req.params;
|
|
94
|
+
const instanceId = params.instanceId?.trim() ?? "";
|
|
95
|
+
if (!instanceId) {
|
|
96
|
+
throw new ServiceError("VALIDATION_ERROR", "instanceId required", 400);
|
|
97
|
+
}
|
|
98
|
+
const ifMatch = header(req.headers, "if-match") ??
|
|
99
|
+
header(req.headers, "If-Match") ??
|
|
100
|
+
null;
|
|
101
|
+
try {
|
|
102
|
+
const ok = await platform
|
|
103
|
+
.integrationInstancesFor(orgId)
|
|
104
|
+
.delete(instanceId, { ifMatch });
|
|
105
|
+
if (!ok) {
|
|
106
|
+
throw new ServiceError("NOT_FOUND", "integration instance not found", 404);
|
|
107
|
+
}
|
|
108
|
+
return reply.code(204).send();
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
if (err instanceof ServiceError)
|
|
112
|
+
throw err;
|
|
113
|
+
mapStoreError(err);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
app.get(`${prefix}/orgs/:orgId/mutations`, async (req) => {
|
|
117
|
+
const orgId = resolveConnectorOpsOrg(req);
|
|
118
|
+
const q = (req.query ?? {});
|
|
119
|
+
const limit = q.limit != null ? Number(q.limit) : undefined;
|
|
120
|
+
const items = await platform
|
|
121
|
+
.integrationInstancesFor(orgId)
|
|
122
|
+
.listMutations({ limit });
|
|
123
|
+
return { orgId, mutations: items };
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=integrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrations.js","sourceRoot":"","sources":["../../src/routes/integrations.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/routes/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAQ/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAsMtD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/routes/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAQ/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAsMtD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,IAAI,CAAC,CAq8Cf"}
|
package/dist/routes/metadata.js
CHANGED
|
@@ -806,10 +806,10 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
806
806
|
}
|
|
807
807
|
}
|
|
808
808
|
const note = saved.published === false
|
|
809
|
-
? "
|
|
809
|
+
? "Published locally but not to the shared catalog (published=false)."
|
|
810
810
|
: saved.published === true
|
|
811
|
-
? "
|
|
812
|
-
: "
|
|
811
|
+
? "Published to the shared package catalog."
|
|
812
|
+
: "Published to the package catalog.";
|
|
813
813
|
return {
|
|
814
814
|
packed: true,
|
|
815
815
|
kind,
|
|
@@ -917,7 +917,7 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
917
917
|
checksum: fetched.checksum,
|
|
918
918
|
targetOrgId: scope.orgId,
|
|
919
919
|
result,
|
|
920
|
-
note: "
|
|
920
|
+
note: "Installed from the shared package catalog into this organization. Organization is the install target only.",
|
|
921
921
|
};
|
|
922
922
|
});
|
|
923
923
|
app.post("/api/metadata/packages/diff", async (req) => {
|
|
@@ -976,6 +976,330 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
976
976
|
note: "Compare package versions or version vs installed. Not env→env copy.",
|
|
977
977
|
};
|
|
978
978
|
});
|
|
979
|
+
// Magit 2.14 — package channels & release sets (Mode A only; fail closed if unset)
|
|
980
|
+
app.get("/api/metadata/packages/capabilities", async () => {
|
|
981
|
+
const magit = requireMagit(platform);
|
|
982
|
+
const caps = magit.packageCatalogCapabilities();
|
|
983
|
+
return {
|
|
984
|
+
...caps,
|
|
985
|
+
note: "Safe capability flags for Packages UI. Never includes tokens or store selector.",
|
|
986
|
+
};
|
|
987
|
+
});
|
|
988
|
+
app.post("/api/metadata/packages/channels/promote", async (req) => {
|
|
989
|
+
const magit = requireMagit(platform);
|
|
990
|
+
const body = req.body;
|
|
991
|
+
const kind = parseKind(body.kind);
|
|
992
|
+
const id = body.id?.trim();
|
|
993
|
+
const channel = body.channel?.trim();
|
|
994
|
+
const version = body.version?.trim();
|
|
995
|
+
if (!id)
|
|
996
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
997
|
+
if (!channel)
|
|
998
|
+
throw new ServiceError("VALIDATION", "channel required", 400);
|
|
999
|
+
if (!version)
|
|
1000
|
+
throw new ServiceError("VALIDATION", "version required", 400);
|
|
1001
|
+
const result = await magit.setPackageChannel({
|
|
1002
|
+
kind,
|
|
1003
|
+
id,
|
|
1004
|
+
channel,
|
|
1005
|
+
version,
|
|
1006
|
+
message: body.message?.trim() || undefined,
|
|
1007
|
+
expectedVersion: body.expectedVersion,
|
|
1008
|
+
});
|
|
1009
|
+
return {
|
|
1010
|
+
...result,
|
|
1011
|
+
note: "Channel pointer updated. Packages do not move; promote does not install into organizations.",
|
|
1012
|
+
};
|
|
1013
|
+
});
|
|
1014
|
+
app.get("/api/metadata/packages/:kind/:id/channels", async (req) => {
|
|
1015
|
+
const magit = requireMagit(platform);
|
|
1016
|
+
const kind = parseKind(req.params.kind);
|
|
1017
|
+
const id = String(req.params.id ?? "").trim();
|
|
1018
|
+
if (!id)
|
|
1019
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
1020
|
+
const channels = await magit.listPackageChannels({ kind, id });
|
|
1021
|
+
return {
|
|
1022
|
+
kind,
|
|
1023
|
+
id,
|
|
1024
|
+
channels,
|
|
1025
|
+
note: "Known channel heads for this package (shared catalog).",
|
|
1026
|
+
};
|
|
1027
|
+
});
|
|
1028
|
+
app.get("/api/metadata/packages/:kind/:id/channels/:channel", async (req) => {
|
|
1029
|
+
const magit = requireMagit(platform);
|
|
1030
|
+
const kind = parseKind(req.params.kind);
|
|
1031
|
+
const id = String(req.params.id ?? "").trim();
|
|
1032
|
+
const channel = String(req.params.channel ?? "").trim();
|
|
1033
|
+
if (!id)
|
|
1034
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
1035
|
+
if (!channel)
|
|
1036
|
+
throw new ServiceError("VALIDATION", "channel required", 400);
|
|
1037
|
+
const result = await magit.resolvePackageChannel({ kind, id, channel });
|
|
1038
|
+
return {
|
|
1039
|
+
...result,
|
|
1040
|
+
note: "Resolved after Magit sync (fresh channel head).",
|
|
1041
|
+
};
|
|
1042
|
+
});
|
|
1043
|
+
app.get("/api/metadata/packages/:kind/:id/channels/:channel/history", async (req) => {
|
|
1044
|
+
const magit = requireMagit(platform);
|
|
1045
|
+
const kind = parseKind(req.params.kind);
|
|
1046
|
+
const id = String(req.params.id ?? "").trim();
|
|
1047
|
+
const channel = String(req.params.channel ?? "").trim();
|
|
1048
|
+
if (!id)
|
|
1049
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
1050
|
+
if (!channel)
|
|
1051
|
+
throw new ServiceError("VALIDATION", "channel required", 400);
|
|
1052
|
+
const history = await magit.packageChannelHistory({ kind, id, channel });
|
|
1053
|
+
return {
|
|
1054
|
+
kind,
|
|
1055
|
+
id,
|
|
1056
|
+
channel,
|
|
1057
|
+
history,
|
|
1058
|
+
note: "Package channel history — promote a previous version to roll back the pointer.",
|
|
1059
|
+
};
|
|
1060
|
+
});
|
|
1061
|
+
app.post("/api/metadata/packages/releases/pack", async (req) => {
|
|
1062
|
+
const magit = requireMagit(platform);
|
|
1063
|
+
const body = req.body;
|
|
1064
|
+
const members = (body.members ?? []).map((m) => ({
|
|
1065
|
+
kind: parseKind(m.kind),
|
|
1066
|
+
id: String(m.id ?? "").trim(),
|
|
1067
|
+
version: String(m.version ?? "").trim(),
|
|
1068
|
+
}));
|
|
1069
|
+
if (members.length === 0) {
|
|
1070
|
+
throw new ServiceError("VALIDATION", "members must list at least one { kind, id, version }", 400);
|
|
1071
|
+
}
|
|
1072
|
+
for (const m of members) {
|
|
1073
|
+
if (!m.id || !m.version) {
|
|
1074
|
+
throw new ServiceError("VALIDATION", "each member needs kind, id, and version", 400);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const result = await magit.packRelease({
|
|
1078
|
+
members,
|
|
1079
|
+
message: body.message?.trim() || undefined,
|
|
1080
|
+
});
|
|
1081
|
+
return {
|
|
1082
|
+
...result,
|
|
1083
|
+
note: "Immutable release set (content-addressed). Promote via release channel. Install separately into a target organization.",
|
|
1084
|
+
};
|
|
1085
|
+
});
|
|
1086
|
+
app.post("/api/metadata/packages/releases/channels/promote", async (req) => {
|
|
1087
|
+
const magit = requireMagit(platform);
|
|
1088
|
+
const body = req.body;
|
|
1089
|
+
const channel = body.channel?.trim();
|
|
1090
|
+
const releaseId = body.releaseId?.trim();
|
|
1091
|
+
if (!channel)
|
|
1092
|
+
throw new ServiceError("VALIDATION", "channel required", 400);
|
|
1093
|
+
if (!releaseId) {
|
|
1094
|
+
throw new ServiceError("VALIDATION", "releaseId required", 400);
|
|
1095
|
+
}
|
|
1096
|
+
const result = await magit.setReleaseChannel({
|
|
1097
|
+
channel,
|
|
1098
|
+
releaseId,
|
|
1099
|
+
message: body.message?.trim() || undefined,
|
|
1100
|
+
expectedReleaseId: body.expectedReleaseId,
|
|
1101
|
+
});
|
|
1102
|
+
return {
|
|
1103
|
+
...result,
|
|
1104
|
+
note: "Release channel updated in one write (atomic set promote). Does not install into organizations.",
|
|
1105
|
+
};
|
|
1106
|
+
});
|
|
1107
|
+
app.get("/api/metadata/packages/releases/channels", async () => {
|
|
1108
|
+
const magit = requireMagit(platform);
|
|
1109
|
+
const channels = await magit.listReleaseChannels();
|
|
1110
|
+
return {
|
|
1111
|
+
channels,
|
|
1112
|
+
note: "Release channel heads with member sets.",
|
|
1113
|
+
};
|
|
1114
|
+
});
|
|
1115
|
+
app.get("/api/metadata/packages/releases/channels/:channel", async (req) => {
|
|
1116
|
+
const magit = requireMagit(platform);
|
|
1117
|
+
const channel = String(req.params.channel ?? "").trim();
|
|
1118
|
+
if (!channel)
|
|
1119
|
+
throw new ServiceError("VALIDATION", "channel required", 400);
|
|
1120
|
+
const result = await magit.resolveReleaseChannel({ channel });
|
|
1121
|
+
return {
|
|
1122
|
+
...result,
|
|
1123
|
+
note: "Resolved release channel with full member set (Magit syncs first).",
|
|
1124
|
+
};
|
|
1125
|
+
});
|
|
1126
|
+
app.get("/api/metadata/packages/releases/channels/:channel/history", async (req) => {
|
|
1127
|
+
const magit = requireMagit(platform);
|
|
1128
|
+
const channel = String(req.params.channel ?? "").trim();
|
|
1129
|
+
if (!channel)
|
|
1130
|
+
throw new ServiceError("VALIDATION", "channel required", 400);
|
|
1131
|
+
const history = await magit.releaseChannelHistory({ channel });
|
|
1132
|
+
return {
|
|
1133
|
+
channel,
|
|
1134
|
+
history,
|
|
1135
|
+
note: "Rollback list: promote a previous releaseId to move the channel back.",
|
|
1136
|
+
};
|
|
1137
|
+
});
|
|
1138
|
+
/**
|
|
1139
|
+
* Atomic install of a release set into the target org.
|
|
1140
|
+
* Body: { confirm:true, channel? | releaseId?, message? }
|
|
1141
|
+
* Installs members with agent-first ordering; rolls back prior installs on failure.
|
|
1142
|
+
*/
|
|
1143
|
+
app.post("/api/metadata/packages/releases/install", async (req) => {
|
|
1144
|
+
const scope = resolveScope(req);
|
|
1145
|
+
const magit = requireMagit(platform);
|
|
1146
|
+
const body = req.body;
|
|
1147
|
+
if (!body?.confirm) {
|
|
1148
|
+
throw new ServiceError("CONFIRMATION_REQUIRED", "explicit confirm:true required for release install", 400);
|
|
1149
|
+
}
|
|
1150
|
+
if (!scope.orgId?.trim()) {
|
|
1151
|
+
throw new ServiceError("VALIDATION", "x-memorix-org-id required — organization is the install target", 400);
|
|
1152
|
+
}
|
|
1153
|
+
let members;
|
|
1154
|
+
let releaseId;
|
|
1155
|
+
let channel = null;
|
|
1156
|
+
if (body.channel?.trim()) {
|
|
1157
|
+
channel = body.channel.trim();
|
|
1158
|
+
const live = await magit.resolveReleaseChannel({ channel });
|
|
1159
|
+
releaseId = live.releaseId;
|
|
1160
|
+
members = live.release.members;
|
|
1161
|
+
}
|
|
1162
|
+
else if (body.releaseId?.trim()) {
|
|
1163
|
+
releaseId = body.releaseId.trim();
|
|
1164
|
+
// Resolve via channel history is not available for bare releaseId —
|
|
1165
|
+
// pack response members must be re-fetched through channel or re-cut.
|
|
1166
|
+
// Prefer channel path; for releaseId-only, resolve from listReleaseChannels.
|
|
1167
|
+
const listed = await magit.listReleaseChannels();
|
|
1168
|
+
const match = listed.find((c) => c.releaseId === releaseId);
|
|
1169
|
+
if (match) {
|
|
1170
|
+
members = match.release.members;
|
|
1171
|
+
channel = match.channel;
|
|
1172
|
+
}
|
|
1173
|
+
else {
|
|
1174
|
+
throw new ServiceError("VALIDATION", "releaseId not found on any release channel; pass channel to install from a channel head", 400);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
else {
|
|
1178
|
+
throw new ServiceError("VALIDATION", "channel or releaseId required", 400);
|
|
1179
|
+
}
|
|
1180
|
+
if (members.length === 0) {
|
|
1181
|
+
throw new ServiceError("VALIDATION", "release has no members", 400);
|
|
1182
|
+
}
|
|
1183
|
+
// Install agent members first so connector/service merges have a host pack.
|
|
1184
|
+
const ordered = [...members].sort((a, b) => {
|
|
1185
|
+
const rank = (k) => k === "agent" ? 0 : k === "connector" ? 1 : 2;
|
|
1186
|
+
return rank(a.kind) - rank(b.kind);
|
|
1187
|
+
});
|
|
1188
|
+
const priors = [];
|
|
1189
|
+
const installed = [];
|
|
1190
|
+
const packs = installedPacks();
|
|
1191
|
+
try {
|
|
1192
|
+
for (const m of ordered) {
|
|
1193
|
+
const kind = parseKind(m.kind);
|
|
1194
|
+
const key = `${kind}:${m.id}`;
|
|
1195
|
+
let agentId = body.agentIdByMember?.[key]?.trim() ||
|
|
1196
|
+
(kind === "agent" ? m.id : "");
|
|
1197
|
+
if (!agentId) {
|
|
1198
|
+
try {
|
|
1199
|
+
agentId = resolveOwnerAgent(packs, kind, m.id, undefined);
|
|
1200
|
+
}
|
|
1201
|
+
catch {
|
|
1202
|
+
agentId = kind === "agent" ? m.id : "";
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
if (!agentId) {
|
|
1206
|
+
throw new ServiceError("VALIDATION", `agentId required to install ${kind}/${m.id}`, 400);
|
|
1207
|
+
}
|
|
1208
|
+
const priorPack = packs[agentId]
|
|
1209
|
+
? structuredClone(packs[agentId])
|
|
1210
|
+
: null;
|
|
1211
|
+
priors.push({ agentId, pack: priorPack });
|
|
1212
|
+
const sliceAgentId = catalogAgentId(kind, m.id, agentId);
|
|
1213
|
+
const fetched = await magit.fetchPackage({
|
|
1214
|
+
kind,
|
|
1215
|
+
id: m.id,
|
|
1216
|
+
version: m.version,
|
|
1217
|
+
});
|
|
1218
|
+
let installPack = packagePayloadToPack(sliceAgentId, fetched.payload);
|
|
1219
|
+
if (kind === "connector" || kind === "service") {
|
|
1220
|
+
const live = packs[agentId] ?? emptyPackForRestore(agentId);
|
|
1221
|
+
if (kind === "connector") {
|
|
1222
|
+
const keepRefs = new Set((installPack.sources ?? [])
|
|
1223
|
+
.map((s) => s && typeof s === "object" && "connectorRef" in s
|
|
1224
|
+
? String(s.connectorRef ?? "")
|
|
1225
|
+
: "")
|
|
1226
|
+
.filter(Boolean));
|
|
1227
|
+
const otherSources = (live.sources ?? []).filter((s) => {
|
|
1228
|
+
const ref = s && typeof s === "object" && "connectorRef" in s
|
|
1229
|
+
? String(s.connectorRef ?? "")
|
|
1230
|
+
: "";
|
|
1231
|
+
return !keepRefs.has(ref);
|
|
1232
|
+
});
|
|
1233
|
+
installPack = {
|
|
1234
|
+
...live,
|
|
1235
|
+
agentId,
|
|
1236
|
+
sources: [...otherSources, ...(installPack.sources ?? [])],
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
const svcId = m.id;
|
|
1241
|
+
const otherServices = (live.services ?? []).filter((s) => !(s &&
|
|
1242
|
+
typeof s === "object" &&
|
|
1243
|
+
"id" in s &&
|
|
1244
|
+
String(s.id) === svcId));
|
|
1245
|
+
installPack = {
|
|
1246
|
+
...live,
|
|
1247
|
+
agentId,
|
|
1248
|
+
services: [...otherServices, ...(installPack.services ?? [])],
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
installPack = stripOrgRuntimeSourcesFromPack(installPack);
|
|
1253
|
+
const validation = validateMetadataPack(installPack);
|
|
1254
|
+
if (!validation.ok) {
|
|
1255
|
+
throw new ServiceError("VALIDATION", `release member ${kind}/${m.id} failed validation`, 400, { issues: validation.issues });
|
|
1256
|
+
}
|
|
1257
|
+
const message = body.message?.trim() ||
|
|
1258
|
+
`Install release ${releaseId.slice(0, 12)} member ${kind}/${m.id}`;
|
|
1259
|
+
const result = platform.metadata.install(installPack, {
|
|
1260
|
+
reinstall: true,
|
|
1261
|
+
magitCommit: m.version,
|
|
1262
|
+
magitRelease: releaseId,
|
|
1263
|
+
source: "rollback",
|
|
1264
|
+
message,
|
|
1265
|
+
environment: "live",
|
|
1266
|
+
});
|
|
1267
|
+
if (!result.ok) {
|
|
1268
|
+
throw new ServiceError("VALIDATION", result.issues.map((i) => i.message).join("; ") ||
|
|
1269
|
+
`install failed for ${kind}/${m.id}`, 400);
|
|
1270
|
+
}
|
|
1271
|
+
installed.push({ kind, id: m.id, version: m.version, agentId });
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
catch (err) {
|
|
1275
|
+
// Best-effort rollback to prior packs for successfully installed members.
|
|
1276
|
+
for (let i = priors.length - 1; i >= 0; i--) {
|
|
1277
|
+
const prior = priors[i];
|
|
1278
|
+
if (prior.pack) {
|
|
1279
|
+
try {
|
|
1280
|
+
platform.metadata.install(prior.pack, {
|
|
1281
|
+
reinstall: true,
|
|
1282
|
+
source: "rollback",
|
|
1283
|
+
message: `Rollback after failed release install ${releaseId.slice(0, 12)}`,
|
|
1284
|
+
environment: "live",
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
catch {
|
|
1288
|
+
// keep original error
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
throw err;
|
|
1293
|
+
}
|
|
1294
|
+
return {
|
|
1295
|
+
installed: true,
|
|
1296
|
+
releaseId,
|
|
1297
|
+
channel,
|
|
1298
|
+
targetOrgId: scope.orgId,
|
|
1299
|
+
members: installed,
|
|
1300
|
+
note: "Atomic release install into the target organization. On failure, prior packs are restored.",
|
|
1301
|
+
};
|
|
1302
|
+
});
|
|
979
1303
|
}
|
|
980
1304
|
function emptyPackForRestore(agentId) {
|
|
981
1305
|
return {
|