@x12i/memorix-service 3.0.6 → 3.2.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 +8 -1
- package/dist/ai-services/associate-prep.d.ts +37 -0
- package/dist/ai-services/associate-prep.d.ts.map +1 -0
- package/dist/ai-services/associate-prep.js +208 -0
- package/dist/ai-services/associate-prep.js.map +1 -0
- package/dist/ai-services/chunk-markdown.d.ts +22 -0
- package/dist/ai-services/chunk-markdown.d.ts.map +1 -0
- package/dist/ai-services/chunk-markdown.js +159 -0
- package/dist/ai-services/chunk-markdown.js.map +1 -0
- package/dist/ai-services/client.d.ts +31 -0
- package/dist/ai-services/client.d.ts.map +1 -0
- package/dist/ai-services/client.js +104 -0
- package/dist/ai-services/client.js.map +1 -0
- package/dist/ai-services/config.d.ts +17 -0
- package/dist/ai-services/config.d.ts.map +1 -0
- package/dist/ai-services/config.js +44 -0
- package/dist/ai-services/config.js.map +1 -0
- package/dist/ai-services/normalize-output.d.ts +29 -0
- package/dist/ai-services/normalize-output.d.ts.map +1 -0
- package/dist/ai-services/normalize-output.js +428 -0
- package/dist/ai-services/normalize-output.js.map +1 -0
- package/dist/ai-services/skill-execution.d.ts +24 -0
- package/dist/ai-services/skill-execution.d.ts.map +1 -0
- package/dist/ai-services/skill-execution.js +74 -0
- package/dist/ai-services/skill-execution.js.map +1 -0
- package/dist/ai-services/types.d.ts +62 -0
- package/dist/ai-services/types.d.ts.map +1 -0
- package/dist/ai-services/types.js +3 -0
- package/dist/ai-services/types.js.map +1 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +41 -6
- package/dist/app.js.map +1 -1
- package/dist/assist/ask.d.ts +35 -0
- package/dist/assist/ask.d.ts.map +1 -0
- package/dist/assist/ask.js +346 -0
- package/dist/assist/ask.js.map +1 -0
- package/dist/assist/facts.d.ts +5 -0
- package/dist/assist/facts.d.ts.map +1 -0
- package/dist/assist/facts.js +237 -0
- package/dist/assist/facts.js.map +1 -0
- package/dist/assist/merge-ask-proposal.d.ts +9 -0
- package/dist/assist/merge-ask-proposal.d.ts.map +1 -0
- package/dist/assist/merge-ask-proposal.js +57 -0
- package/dist/assist/merge-ask-proposal.js.map +1 -0
- package/dist/assist/types.d.ts +60 -0
- package/dist/assist/types.d.ts.map +1 -0
- package/dist/assist/types.js +57 -0
- package/dist/assist/types.js.map +1 -0
- package/dist/assist/validate.d.ts +4 -0
- package/dist/assist/validate.d.ts.map +1 -0
- package/dist/assist/validate.js +319 -0
- package/dist/assist/validate.js.map +1 -0
- package/dist/cli.js +29 -6
- package/dist/cli.js.map +1 -1
- package/dist/connector-catalog.d.ts +68 -0
- package/dist/connector-catalog.d.ts.map +1 -0
- package/dist/connector-catalog.js +258 -0
- package/dist/connector-catalog.js.map +1 -0
- package/dist/connector-role.d.ts +22 -0
- package/dist/connector-role.d.ts.map +1 -0
- package/dist/connector-role.js +71 -0
- package/dist/connector-role.js.map +1 -0
- package/dist/destination-runtime.d.ts +8 -0
- package/dist/destination-runtime.d.ts.map +1 -0
- package/dist/destination-runtime.js +11 -0
- package/dist/destination-runtime.js.map +1 -0
- package/dist/execution-target.d.ts +141 -0
- package/dist/execution-target.d.ts.map +1 -0
- package/dist/execution-target.js +853 -0
- package/dist/execution-target.js.map +1 -0
- package/dist/generic-connectors.d.ts +41 -0
- package/dist/generic-connectors.d.ts.map +1 -0
- package/dist/generic-connectors.js +158 -0
- package/dist/generic-connectors.js.map +1 -0
- package/dist/isolation.d.ts +1 -1
- package/dist/isolation.d.ts.map +1 -1
- package/dist/jobs-scope.d.ts +21 -0
- package/dist/jobs-scope.d.ts.map +1 -0
- package/dist/jobs-scope.js +83 -0
- package/dist/jobs-scope.js.map +1 -0
- package/dist/journey/collect.js +2 -2
- package/dist/journey/collect.js.map +1 -1
- package/dist/journey/coverage.js +3 -3
- package/dist/journey/coverage.js.map +1 -1
- package/dist/known-packs.d.ts +1 -1
- package/dist/known-packs.d.ts.map +1 -1
- package/dist/known-packs.js +1 -1
- package/dist/known-packs.js.map +1 -1
- package/dist/live.d.ts +13 -1
- package/dist/live.d.ts.map +1 -1
- package/dist/live.js +866 -2
- package/dist/live.js.map +1 -1
- package/dist/magit/magit-pack-service.d.ts +112 -0
- package/dist/magit/magit-pack-service.d.ts.map +1 -1
- package/dist/magit/magit-pack-service.js +494 -12
- package/dist/magit/magit-pack-service.js.map +1 -1
- package/dist/magit/pack-to-patchset.d.ts +20 -1
- package/dist/magit/pack-to-patchset.d.ts.map +1 -1
- package/dist/magit/pack-to-patchset.js +82 -9
- package/dist/magit/pack-to-patchset.js.map +1 -1
- package/dist/magit/package-catalog-bridge.d.ts +58 -0
- package/dist/magit/package-catalog-bridge.d.ts.map +1 -0
- package/dist/magit/package-catalog-bridge.js +220 -0
- package/dist/magit/package-catalog-bridge.js.map +1 -0
- package/dist/magit/proposal-to-pack.d.ts +22 -0
- package/dist/magit/proposal-to-pack.d.ts.map +1 -0
- package/dist/magit/proposal-to-pack.js +68 -0
- package/dist/magit/proposal-to-pack.js.map +1 -0
- package/dist/mongo-metadata-store.d.ts.map +1 -1
- package/dist/mongo-metadata-store.js +9 -3
- package/dist/mongo-metadata-store.js.map +1 -1
- package/dist/openapi/openapi.json +6262 -3139
- package/dist/org-sources.d.ts +14 -0
- package/dist/org-sources.d.ts.map +1 -0
- package/dist/org-sources.js +31 -0
- package/dist/org-sources.js.map +1 -0
- package/dist/pipeline-compile.d.ts +67 -1
- package/dist/pipeline-compile.d.ts.map +1 -1
- package/dist/pipeline-compile.js +455 -29
- package/dist/pipeline-compile.js.map +1 -1
- package/dist/pipeline-invoke.d.ts +63 -0
- package/dist/pipeline-invoke.d.ts.map +1 -0
- package/dist/pipeline-invoke.js +114 -0
- package/dist/pipeline-invoke.js.map +1 -0
- package/dist/pipeline-ports.d.ts +13 -0
- package/dist/pipeline-ports.d.ts.map +1 -0
- package/dist/pipeline-ports.js +104 -0
- package/dist/pipeline-ports.js.map +1 -0
- package/dist/platform.d.ts +47 -3
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +309 -26
- package/dist/platform.js.map +1 -1
- package/dist/routes/ai-services.d.ts +4 -0
- package/dist/routes/ai-services.d.ts.map +1 -0
- package/dist/routes/ai-services.js +239 -0
- package/dist/routes/ai-services.js.map +1 -0
- package/dist/routes/assist.d.ts +4 -0
- package/dist/routes/assist.d.ts.map +1 -0
- package/dist/routes/assist.js +75 -0
- package/dist/routes/assist.js.map +1 -0
- package/dist/routes/connector-invocations.d.ts.map +1 -1
- package/dist/routes/connector-invocations.js +18 -21
- package/dist/routes/connector-invocations.js.map +1 -1
- package/dist/routes/data-journey.js +11 -11
- package/dist/routes/data-journey.js.map +1 -1
- package/dist/routes/intelligence.d.ts.map +1 -1
- package/dist/routes/intelligence.js +19 -1
- package/dist/routes/intelligence.js.map +1 -1
- package/dist/routes/jobs.d.ts +4 -0
- package/dist/routes/jobs.d.ts.map +1 -0
- package/dist/routes/jobs.js +888 -0
- package/dist/routes/jobs.js.map +1 -0
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +278 -80
- package/dist/routes/memory.js.map +1 -1
- package/dist/routes/metadata.d.ts.map +1 -1
- package/dist/routes/metadata.js +770 -21
- package/dist/routes/metadata.js.map +1 -1
- package/dist/routes/pipelines.d.ts.map +1 -1
- package/dist/routes/pipelines.js +98 -22
- package/dist/routes/pipelines.js.map +1 -1
- package/dist/routes/relationships.d.ts.map +1 -1
- package/dist/routes/relationships.js +109 -2
- package/dist/routes/relationships.js.map +1 -1
- package/dist/routes/simulation.d.ts +7 -0
- package/dist/routes/simulation.d.ts.map +1 -0
- package/dist/routes/simulation.js +22 -0
- package/dist/routes/simulation.js.map +1 -0
- package/dist/runx/client.d.ts +18 -0
- package/dist/runx/client.d.ts.map +1 -0
- package/dist/runx/client.js +59 -0
- package/dist/runx/client.js.map +1 -0
- package/dist/simulation.d.ts +26 -0
- package/dist/simulation.d.ts.map +1 -0
- package/dist/simulation.js +46 -0
- package/dist/simulation.js.map +1 -0
- package/openapi/openapi.json +6262 -3139
- package/package.json +18 -8
package/dist/routes/metadata.js
CHANGED
|
@@ -2,6 +2,127 @@ import { METADATA_KIND_IDS, METADATA_KIND_REGISTRY, validateMetadataPack, } from
|
|
|
2
2
|
import { resolveScope } from "../request-scope.js";
|
|
3
3
|
import { ServiceError } from "../errors.js";
|
|
4
4
|
import { listKnownPackIds, loadKnownMetadataPack } from "../known-packs.js";
|
|
5
|
+
import { stripOrgRuntimeSourcesFromPack } from "../org-sources.js";
|
|
6
|
+
import { PACKAGE_CATALOG_ORG, associationsFromPack, buildPackagePayload, catalogAgentId, discoverPackageCatalog, packageVersionMessage, parsePackageMessage, releaseAssociations, resolveOwnerAgent, } from "../magit/package-catalog-bridge.js";
|
|
7
|
+
function requireMagit(platform) {
|
|
8
|
+
if (!platform.magit) {
|
|
9
|
+
throw new ServiceError("MAGIT_UNAVAILABLE", "metadata versioning requires a durable Mongo connection (set METADATA_MAGIT=0 to disable)", 503);
|
|
10
|
+
}
|
|
11
|
+
return platform.magit;
|
|
12
|
+
}
|
|
13
|
+
function lineageFromAgent(platform, agentId) {
|
|
14
|
+
const ledger = platform.metadata.getLedger();
|
|
15
|
+
const latest = [...ledger].reverse().find((e) => e.agentId === agentId);
|
|
16
|
+
if (!latest)
|
|
17
|
+
return null;
|
|
18
|
+
return {
|
|
19
|
+
checksum: latest.checksum,
|
|
20
|
+
installedAt: latest.installedAt,
|
|
21
|
+
magitCommit: latest.magitCommit ?? null,
|
|
22
|
+
magitRelease: latest.magitRelease ?? null,
|
|
23
|
+
source: latest.source,
|
|
24
|
+
message: latest.message,
|
|
25
|
+
environment: latest.environment,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Install packs for multiple agents from one Magit commit.
|
|
30
|
+
* On failure, restore already-applied agents to their prior state.
|
|
31
|
+
*/
|
|
32
|
+
async function installAgentsAtomically(platform, magit, opts) {
|
|
33
|
+
const priors = [];
|
|
34
|
+
for (const agentId of opts.agentIds) {
|
|
35
|
+
const lineage = lineageFromAgent(platform, agentId);
|
|
36
|
+
const exported = platform.metadata.exportMetadata([agentId]);
|
|
37
|
+
priors.push({
|
|
38
|
+
agentId,
|
|
39
|
+
magitCommit: lineage?.magitCommit ?? null,
|
|
40
|
+
pack: exported.packs[agentId] ?? null,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const ledgerEntries = [];
|
|
44
|
+
const applied = [];
|
|
45
|
+
try {
|
|
46
|
+
for (const agentId of opts.agentIds) {
|
|
47
|
+
const loaded = await magit.packAtCommit(opts.orgId, agentId, opts.commit);
|
|
48
|
+
const validation = validateMetadataPack(loaded.pack);
|
|
49
|
+
if (!validation.ok) {
|
|
50
|
+
throw new ServiceError("VALIDATION", `pack at commit failed validation for ${agentId}: ${validation.issues
|
|
51
|
+
.map((i) => i.message)
|
|
52
|
+
.join("; ")}`, 400, { issues: validation.issues, agentId });
|
|
53
|
+
}
|
|
54
|
+
const result = platform.metadata.install(loaded.pack, {
|
|
55
|
+
reinstall: true,
|
|
56
|
+
magitCommit: opts.commit,
|
|
57
|
+
...(opts.magitRelease ? { magitRelease: opts.magitRelease } : {}),
|
|
58
|
+
source: opts.source,
|
|
59
|
+
message: opts.message,
|
|
60
|
+
environment: "live",
|
|
61
|
+
});
|
|
62
|
+
if (!result.ok || !result.ledgerEntry) {
|
|
63
|
+
throw new ServiceError("VALIDATION", result.issues.map((i) => i.message).join("; ") ||
|
|
64
|
+
`bundle install failed for ${agentId}`, 400);
|
|
65
|
+
}
|
|
66
|
+
ledgerEntries.push(result.ledgerEntry);
|
|
67
|
+
applied.push(agentId);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
installed: true,
|
|
71
|
+
rolledBack: false,
|
|
72
|
+
commit: opts.commit,
|
|
73
|
+
ledgerEntries,
|
|
74
|
+
restoredAgentIds: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const restoredAgentIds = [];
|
|
79
|
+
for (const agentId of [...applied].reverse()) {
|
|
80
|
+
const prior = priors.find((p) => p.agentId === agentId);
|
|
81
|
+
if (!prior)
|
|
82
|
+
continue;
|
|
83
|
+
try {
|
|
84
|
+
if (prior.magitCommit) {
|
|
85
|
+
const loaded = await magit.packAtCommit(opts.orgId, agentId, prior.magitCommit);
|
|
86
|
+
platform.metadata.install(loaded.pack, {
|
|
87
|
+
reinstall: true,
|
|
88
|
+
magitCommit: prior.magitCommit,
|
|
89
|
+
source: "rollback",
|
|
90
|
+
message: `Rollback after failed bundle install (${agentId})`,
|
|
91
|
+
environment: "live",
|
|
92
|
+
});
|
|
93
|
+
restoredAgentIds.push(agentId);
|
|
94
|
+
}
|
|
95
|
+
else if (prior.pack) {
|
|
96
|
+
platform.metadata.install(prior.pack, {
|
|
97
|
+
reinstall: true,
|
|
98
|
+
source: "rollback",
|
|
99
|
+
message: `Rollback after failed bundle install (${agentId})`,
|
|
100
|
+
environment: "live",
|
|
101
|
+
});
|
|
102
|
+
restoredAgentIds.push(agentId);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* best-effort restore */
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
110
|
+
if (err instanceof ServiceError) {
|
|
111
|
+
throw new ServiceError(err.code, `${message} (rolled back: ${restoredAgentIds.join(",") || "none"})`, err.statusCode, {
|
|
112
|
+
rolledBack: true,
|
|
113
|
+
restoredAgentIds,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
installed: false,
|
|
118
|
+
rolledBack: restoredAgentIds.length > 0,
|
|
119
|
+
commit: opts.commit,
|
|
120
|
+
ledgerEntries: [],
|
|
121
|
+
restoredAgentIds,
|
|
122
|
+
error: message,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
5
126
|
export async function registerMetadataRoutes(app, platform) {
|
|
6
127
|
app.get("/api/metadata/packs", async (req) => {
|
|
7
128
|
const scope = resolveScope(req);
|
|
@@ -15,13 +136,15 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
15
136
|
const scope = resolveScope(req);
|
|
16
137
|
const q = req.query;
|
|
17
138
|
const agentId = (q.agentId?.trim() || scope.agentIds[0]);
|
|
18
|
-
|
|
139
|
+
const raw = platform.metadata.getRaw(agentId);
|
|
140
|
+
const lineage = lineageFromAgent(platform, agentId);
|
|
141
|
+
const exported = platform.metadata.exportMetadata([agentId]);
|
|
142
|
+
const pack = exported.packs[agentId] ?? null;
|
|
143
|
+
return { scope, agentId, raw, lineage, pack };
|
|
19
144
|
});
|
|
20
145
|
app.get("/api/metadata/effective", async (req) => {
|
|
21
146
|
const scope = resolveScope(req);
|
|
22
147
|
const resolved = platform.metadata.resolveEffective(scope.agentIds);
|
|
23
|
-
// Contract: `effective` is always the EffectiveDefinition[] array.
|
|
24
|
-
// Inheritance / resolve diagnostics live beside it (never nested as the array).
|
|
25
148
|
return {
|
|
26
149
|
scope,
|
|
27
150
|
ok: resolved.ok,
|
|
@@ -36,7 +159,6 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
36
159
|
note: "agentIds select effective metadata only; effective is the resolved definition array",
|
|
37
160
|
};
|
|
38
161
|
});
|
|
39
|
-
/** List built-in kinds + effective custom kind-declarations for the scope. */
|
|
40
162
|
app.get("/api/metadata/kinds", async (req) => {
|
|
41
163
|
const scope = resolveScope(req);
|
|
42
164
|
const effective = platform.metadata.resolveEffective(scope.agentIds);
|
|
@@ -70,6 +192,18 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
70
192
|
const scope = resolveScope(req);
|
|
71
193
|
return { scope, result: platform.metadata.resolveAbstracts(scope.agentIds) };
|
|
72
194
|
});
|
|
195
|
+
app.get("/api/metadata/topology", async (req) => {
|
|
196
|
+
const scope = resolveScope(req);
|
|
197
|
+
const topology = platform.metadata.buildTopology(scope.agentIds);
|
|
198
|
+
return {
|
|
199
|
+
scope,
|
|
200
|
+
ok: topology.ok,
|
|
201
|
+
nodes: topology.nodes,
|
|
202
|
+
edges: topology.edges,
|
|
203
|
+
issues: topology.issues,
|
|
204
|
+
note: "Projected concrete topology from effective metadata; author relationships on abstracts",
|
|
205
|
+
};
|
|
206
|
+
});
|
|
73
207
|
app.post("/api/metadata/validate", async (req) => {
|
|
74
208
|
const scope = resolveScope(req);
|
|
75
209
|
const body = req.body;
|
|
@@ -84,17 +218,68 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
84
218
|
if (!body?.confirm) {
|
|
85
219
|
throw new ServiceError("CONFIRMATION_REQUIRED", "explicit confirm:true required for metadata install", 400);
|
|
86
220
|
}
|
|
87
|
-
const
|
|
221
|
+
const agentHint = body.pack?.agentId?.trim() ||
|
|
222
|
+
body.packId?.trim() ||
|
|
223
|
+
scope.agentIds[0] ||
|
|
224
|
+
"";
|
|
225
|
+
let pack = body.pack;
|
|
226
|
+
let magitCommit = body.commit?.trim() || undefined;
|
|
227
|
+
const proposalId = body.proposalId?.trim();
|
|
228
|
+
if (magitCommit) {
|
|
229
|
+
const magit = requireMagit(platform);
|
|
230
|
+
if (!agentHint) {
|
|
231
|
+
throw new ServiceError("VALIDATION", "agentId required with commit", 400);
|
|
232
|
+
}
|
|
233
|
+
const loaded = await magit.packAtCommit(scope.orgId, agentHint, magitCommit);
|
|
234
|
+
pack = loaded.pack;
|
|
235
|
+
magitCommit = loaded.commit;
|
|
236
|
+
}
|
|
237
|
+
else if (!pack && body.packId) {
|
|
238
|
+
pack = loadKnownMetadataPack(body.packId);
|
|
239
|
+
}
|
|
88
240
|
if (!pack)
|
|
89
|
-
throw new ServiceError("VALIDATION", "pack or
|
|
241
|
+
throw new ServiceError("VALIDATION", "pack, packId, or commit required", 400);
|
|
242
|
+
// Org folder sources are never pack/Magit content — strip before save/install.
|
|
243
|
+
pack = stripOrgRuntimeSourcesFromPack(pack);
|
|
90
244
|
const validation = validateMetadataPack(pack);
|
|
91
245
|
if (!validation.ok) {
|
|
92
246
|
return { scope, result: validation, installed: false, published: false };
|
|
93
247
|
}
|
|
94
|
-
const
|
|
248
|
+
const versionMessage = body.versionMessage?.trim() ||
|
|
249
|
+
(proposalId
|
|
250
|
+
? `Install proposal ${proposalId}`
|
|
251
|
+
: magitCommit
|
|
252
|
+
? `Install ${pack.agentId} @ ${magitCommit.slice(0, 12)}`
|
|
253
|
+
: `Install ${pack.agentId}`);
|
|
254
|
+
if (!magitCommit && platform.magit) {
|
|
255
|
+
try {
|
|
256
|
+
const saved = await platform.magit.saveVersion(scope.orgId, pack, {
|
|
257
|
+
message: versionMessage,
|
|
258
|
+
});
|
|
259
|
+
magitCommit = saved.commit;
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
// Fail closed when Magit is enabled — never silent checksum-only install.
|
|
263
|
+
throw err instanceof ServiceError
|
|
264
|
+
? err
|
|
265
|
+
: new ServiceError("MAGIT_UNAVAILABLE", err instanceof Error ? err.message : String(err), 503);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const source = proposalId
|
|
269
|
+
? "proposal-install"
|
|
270
|
+
: "install";
|
|
271
|
+
const result = platform.metadata.install(pack, {
|
|
272
|
+
reinstall: body.reinstall,
|
|
273
|
+
source,
|
|
274
|
+
message: versionMessage,
|
|
275
|
+
environment: "live",
|
|
276
|
+
...(magitCommit ? { magitCommit } : {}),
|
|
277
|
+
});
|
|
95
278
|
return {
|
|
96
279
|
scope,
|
|
97
280
|
packId: body.packId ?? pack.agentId,
|
|
281
|
+
proposalId: proposalId || undefined,
|
|
282
|
+
commit: magitCommit,
|
|
98
283
|
result,
|
|
99
284
|
installed: Boolean(result.ok),
|
|
100
285
|
published: false,
|
|
@@ -107,24 +292,221 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
107
292
|
note: "intelligence proposals are never auto-installed",
|
|
108
293
|
};
|
|
109
294
|
});
|
|
295
|
+
app.post("/api/metadata/proposals/:id/stage", async (req) => {
|
|
296
|
+
const scope = resolveScope(req);
|
|
297
|
+
const magit = requireMagit(platform);
|
|
298
|
+
const { id: proposalId } = req.params;
|
|
299
|
+
const body = (req.body ?? {});
|
|
300
|
+
let proposal = body.proposal
|
|
301
|
+
? {
|
|
302
|
+
proposalId: body.proposal.proposalId?.trim() || proposalId,
|
|
303
|
+
metadataKind: body.proposal.metadataKind,
|
|
304
|
+
metadataId: body.proposal.metadataId,
|
|
305
|
+
draft: body.proposal.draft,
|
|
306
|
+
scope: body.proposal.scope,
|
|
307
|
+
}
|
|
308
|
+
: null;
|
|
309
|
+
if (!proposal) {
|
|
310
|
+
if (!platform.client) {
|
|
311
|
+
throw new ServiceError("DURABLE_STORE_REQUIRED", "proposal staging requires durable intelligence store or body.proposal", 503);
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const stored = await platform
|
|
315
|
+
.intelligenceArtifactsFor(scope.orgId)
|
|
316
|
+
.getProposal(proposalId);
|
|
317
|
+
if (!stored) {
|
|
318
|
+
throw new ServiceError("NOT_FOUND", `proposal not found: ${proposalId}`, 404);
|
|
319
|
+
}
|
|
320
|
+
proposal = {
|
|
321
|
+
proposalId: stored.proposalId,
|
|
322
|
+
metadataKind: stored.metadataKind,
|
|
323
|
+
metadataId: stored.metadataId,
|
|
324
|
+
draft: stored.draft,
|
|
325
|
+
scope: stored.scope,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
if (err instanceof ServiceError)
|
|
330
|
+
throw err;
|
|
331
|
+
throw new ServiceError("VALIDATION", `failed to load proposal ${proposalId}: ${err instanceof Error ? err.message : String(err)}`, 400);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const agentId = body.agentId?.trim() ||
|
|
335
|
+
proposal.scope?.agentIds?.[0]?.trim() ||
|
|
336
|
+
scope.agentIds[0] ||
|
|
337
|
+
"";
|
|
338
|
+
if (!agentId)
|
|
339
|
+
throw new ServiceError("VALIDATION", "agentId required", 400);
|
|
340
|
+
const exported = platform.metadata.exportMetadata([agentId]);
|
|
341
|
+
const basePack = exported.packs[agentId] ?? null;
|
|
342
|
+
const staged = await magit.importProposal(scope.orgId, {
|
|
343
|
+
agentId,
|
|
344
|
+
proposal,
|
|
345
|
+
basePack,
|
|
346
|
+
});
|
|
347
|
+
const validation = validateMetadataPack(staged.pack);
|
|
348
|
+
return {
|
|
349
|
+
scope,
|
|
350
|
+
proposalId: proposal.proposalId,
|
|
351
|
+
agentId,
|
|
352
|
+
commit: staged.commit,
|
|
353
|
+
checksum: staged.checksum,
|
|
354
|
+
reused: staged.reused,
|
|
355
|
+
pack: staged.pack,
|
|
356
|
+
validation,
|
|
357
|
+
installed: false,
|
|
358
|
+
note: "stage saves a Magit version only — live metadata unchanged until explicit install",
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
app.post("/api/metadata/install/bundle", async (req) => {
|
|
362
|
+
const scope = resolveScope(req);
|
|
363
|
+
const magit = requireMagit(platform);
|
|
364
|
+
const body = req.body;
|
|
365
|
+
if (!body?.confirm) {
|
|
366
|
+
throw new ServiceError("CONFIRMATION_REQUIRED", "explicit confirm:true required for bundle install", 400);
|
|
367
|
+
}
|
|
368
|
+
const commit = body.commit?.trim();
|
|
369
|
+
if (!commit)
|
|
370
|
+
throw new ServiceError("VALIDATION", "commit required", 400);
|
|
371
|
+
const agentIds = body.agentIds?.map((a) => a.trim()).filter(Boolean) ?? [...scope.agentIds];
|
|
372
|
+
if (!agentIds.length) {
|
|
373
|
+
throw new ServiceError("VALIDATION", "agentIds required", 400);
|
|
374
|
+
}
|
|
375
|
+
const message = body.message?.trim() ||
|
|
376
|
+
`Bundle install ${agentIds.join("+")} @ ${commit.slice(0, 12)}`;
|
|
377
|
+
const release = body.release?.trim();
|
|
378
|
+
const result = await installAgentsAtomically(platform, magit, {
|
|
379
|
+
orgId: scope.orgId,
|
|
380
|
+
agentIds,
|
|
381
|
+
commit,
|
|
382
|
+
message,
|
|
383
|
+
source: release ? "publish" : "bundle-install",
|
|
384
|
+
magitRelease: release,
|
|
385
|
+
});
|
|
386
|
+
return { scope, ...result };
|
|
387
|
+
});
|
|
388
|
+
app.post("/api/metadata/publish/plan", async (req) => {
|
|
389
|
+
const scope = resolveScope(req);
|
|
390
|
+
const magit = requireMagit(platform);
|
|
391
|
+
const body = req.body;
|
|
392
|
+
const release = body.release?.trim();
|
|
393
|
+
const commit = body.commit?.trim();
|
|
394
|
+
if (!release)
|
|
395
|
+
throw new ServiceError("VALIDATION", "release required", 400);
|
|
396
|
+
if (!commit)
|
|
397
|
+
throw new ServiceError("VALIDATION", "commit required", 400);
|
|
398
|
+
const agentIds = body.agentIds?.map((a) => a.trim()).filter(Boolean) ??
|
|
399
|
+
(body.agentId?.trim() ? [body.agentId.trim()] : scope.agentIds);
|
|
400
|
+
const plan = await magit.publishPlan(scope.orgId, {
|
|
401
|
+
release,
|
|
402
|
+
commit,
|
|
403
|
+
agentIds,
|
|
404
|
+
});
|
|
405
|
+
return { scope, plan, note: "publish plan is read-only; confirm via POST /api/metadata/publish" };
|
|
406
|
+
});
|
|
110
407
|
app.post("/api/metadata/publish", async (req) => {
|
|
111
408
|
const scope = resolveScope(req);
|
|
112
409
|
const body = req.body;
|
|
113
410
|
if (!body?.confirm) {
|
|
114
411
|
throw new ServiceError("CONFIRMATION_REQUIRED", "explicit confirm:true required for metadata publish", 400);
|
|
115
412
|
}
|
|
116
|
-
|
|
413
|
+
const magit = requireMagit(platform);
|
|
414
|
+
const release = body.release?.trim();
|
|
415
|
+
const commit = body.commit?.trim();
|
|
416
|
+
if (!release)
|
|
417
|
+
throw new ServiceError("VALIDATION", "release required", 400);
|
|
418
|
+
if (!commit)
|
|
419
|
+
throw new ServiceError("VALIDATION", "commit required", 400);
|
|
420
|
+
const agentIds = body.agentIds?.map((a) => a.trim()).filter(Boolean) ??
|
|
421
|
+
(body.agentId?.trim() ? [body.agentId.trim()] : scope.agentIds);
|
|
422
|
+
if (!agentIds.length) {
|
|
423
|
+
throw new ServiceError("VALIDATION", "agentIds required", 400);
|
|
424
|
+
}
|
|
425
|
+
const plan = await magit.publishPlan(scope.orgId, { release, commit, agentIds });
|
|
426
|
+
if (body.planHash?.trim() && body.planHash.trim() !== plan.planHash) {
|
|
427
|
+
throw new ServiceError("VALIDATION", "planHash mismatch — re-run publish plan", 409);
|
|
428
|
+
}
|
|
429
|
+
const now = new Date().toISOString();
|
|
430
|
+
const message = body.message?.trim() || `Publish ${release}`;
|
|
431
|
+
const applied = await installAgentsAtomically(platform, magit, {
|
|
432
|
+
orgId: scope.orgId,
|
|
433
|
+
agentIds,
|
|
434
|
+
commit,
|
|
435
|
+
message,
|
|
436
|
+
source: agentIds.length > 1 ? "bundle-install" : "publish",
|
|
437
|
+
magitRelease: release,
|
|
438
|
+
});
|
|
439
|
+
if (!applied.installed) {
|
|
440
|
+
throw new ServiceError("VALIDATION", applied.error || "publish install failed", 400, { restoredAgentIds: applied.restoredAgentIds });
|
|
441
|
+
}
|
|
442
|
+
await magit.recordRelease(scope.orgId, {
|
|
443
|
+
name: release,
|
|
444
|
+
commit,
|
|
445
|
+
agentIds,
|
|
446
|
+
createdAt: now,
|
|
447
|
+
message,
|
|
448
|
+
});
|
|
449
|
+
return {
|
|
450
|
+
scope,
|
|
451
|
+
published: true,
|
|
452
|
+
release,
|
|
453
|
+
commit,
|
|
454
|
+
planHash: plan.planHash,
|
|
455
|
+
ledgerEntries: applied.ledgerEntries,
|
|
456
|
+
note: "publish installed packs reconstructed from Magit commit",
|
|
457
|
+
};
|
|
117
458
|
});
|
|
118
|
-
|
|
119
|
-
app.post("/api/metadata/versions", async (req) => {
|
|
459
|
+
app.post("/api/metadata/rollback", async (req) => {
|
|
120
460
|
const scope = resolveScope(req);
|
|
121
|
-
|
|
122
|
-
|
|
461
|
+
const magit = requireMagit(platform);
|
|
462
|
+
const body = req.body;
|
|
463
|
+
if (!body?.confirm) {
|
|
464
|
+
throw new ServiceError("CONFIRMATION_REQUIRED", "explicit confirm:true required for metadata rollback", 400);
|
|
123
465
|
}
|
|
466
|
+
const agentId = (body.agentId?.trim() || scope.agentIds[0]);
|
|
467
|
+
const commit = body.commit?.trim();
|
|
468
|
+
if (!commit)
|
|
469
|
+
throw new ServiceError("VALIDATION", "commit required", 400);
|
|
470
|
+
const integrity = await magit.diff(scope.orgId, agentId, {
|
|
471
|
+
from: "live",
|
|
472
|
+
to: commit,
|
|
473
|
+
});
|
|
474
|
+
const loaded = await magit.packAtCommit(scope.orgId, agentId, commit);
|
|
475
|
+
const restoredPack = stripOrgRuntimeSourcesFromPack(loaded.pack);
|
|
476
|
+
const validation = validateMetadataPack(restoredPack);
|
|
477
|
+
if (!validation.ok) {
|
|
478
|
+
throw new ServiceError("VALIDATION", "pack at commit failed validation", 400, { issues: validation.issues });
|
|
479
|
+
}
|
|
480
|
+
const message = body.message?.trim() || `Restore ${agentId} to ${commit.slice(0, 12)}`;
|
|
481
|
+
const result = platform.metadata.install(restoredPack, {
|
|
482
|
+
reinstall: true,
|
|
483
|
+
magitCommit: commit,
|
|
484
|
+
magitRelease: undefined,
|
|
485
|
+
source: "rollback",
|
|
486
|
+
message,
|
|
487
|
+
environment: "live",
|
|
488
|
+
});
|
|
489
|
+
if (!result.ok) {
|
|
490
|
+
throw new ServiceError("VALIDATION", result.issues.map((i) => i.message).join("; ") || "restore install failed", 400);
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
scope,
|
|
494
|
+
restored: true,
|
|
495
|
+
agentId,
|
|
496
|
+
commit,
|
|
497
|
+
integrity,
|
|
498
|
+
result,
|
|
499
|
+
note: "restore installed pack reconstructed from Magit commit",
|
|
500
|
+
};
|
|
501
|
+
});
|
|
502
|
+
app.post("/api/metadata/versions", async (req) => {
|
|
503
|
+
const scope = resolveScope(req);
|
|
504
|
+
const magit = requireMagit(platform);
|
|
124
505
|
const body = req.body;
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
506
|
+
const rawPack = body.pack ?? (body.packId ? loadKnownMetadataPack(body.packId) : undefined);
|
|
507
|
+
if (!rawPack)
|
|
127
508
|
throw new ServiceError("VALIDATION", "pack or packId required", 400);
|
|
509
|
+
const pack = stripOrgRuntimeSourcesFromPack(rawPack);
|
|
128
510
|
const message = body.message?.trim();
|
|
129
511
|
if (!message) {
|
|
130
512
|
throw new ServiceError("VALIDATION", "message required", 400);
|
|
@@ -133,7 +515,7 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
133
515
|
if (!validation.ok) {
|
|
134
516
|
return { scope, result: validation, saved: false };
|
|
135
517
|
}
|
|
136
|
-
const saved = await
|
|
518
|
+
const saved = await magit.saveVersion(scope.orgId, pack, { message });
|
|
137
519
|
return {
|
|
138
520
|
scope,
|
|
139
521
|
saved: true,
|
|
@@ -141,23 +523,69 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
141
523
|
checksum: saved.checksum,
|
|
142
524
|
reused: saved.reused,
|
|
143
525
|
agentId: pack.agentId,
|
|
144
|
-
note: "save version is not install; live metadata unchanged",
|
|
526
|
+
note: "save version is not install; live metadata unchanged; org folder sources stripped",
|
|
145
527
|
};
|
|
146
528
|
});
|
|
147
|
-
/** List saved pack versions for an agent (Magit proposal ledger + HEAD). */
|
|
148
529
|
app.get("/api/metadata/versions", async (req) => {
|
|
149
530
|
const scope = resolveScope(req);
|
|
150
|
-
|
|
151
|
-
throw new ServiceError("MAGIT_UNAVAILABLE", "metadata versioning requires METADATA_MAGIT=1 and org isolation", 503);
|
|
152
|
-
}
|
|
531
|
+
const magit = requireMagit(platform);
|
|
153
532
|
const q = req.query;
|
|
154
533
|
const agentId = (q.agentId?.trim() || scope.agentIds[0]);
|
|
534
|
+
// Lazy backfill: checksum-only installs get an initial Magit snapshot once.
|
|
535
|
+
const lineage = lineageFromAgent(platform, agentId);
|
|
536
|
+
if (lineage && !lineage.magitCommit) {
|
|
537
|
+
const pack = platform.metadata.exportMetadata([agentId]).packs[agentId];
|
|
538
|
+
if (pack) {
|
|
539
|
+
const saved = await magit.backfillLineage(scope.orgId, pack);
|
|
540
|
+
platform.stampAgentLineage(agentId, { magitCommit: saved.commit });
|
|
541
|
+
platform.appendInstallLedger({
|
|
542
|
+
agentId,
|
|
543
|
+
checksum: lineage.checksum,
|
|
544
|
+
installedAt: new Date().toISOString(),
|
|
545
|
+
definitionCount: 0,
|
|
546
|
+
noop: true,
|
|
547
|
+
magitCommit: saved.commit,
|
|
548
|
+
source: "install",
|
|
549
|
+
message: "backfill: initial snapshot",
|
|
550
|
+
environment: "live",
|
|
551
|
+
previousChecksum: lineage.checksum,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
155
555
|
const maxCount = q.maxCount ? Number(q.maxCount) : undefined;
|
|
156
|
-
const versions = await
|
|
556
|
+
const versions = await magit.history(scope.orgId, agentId, {
|
|
157
557
|
maxCount: Number.isFinite(maxCount) ? maxCount : undefined,
|
|
158
558
|
});
|
|
159
559
|
return { scope, agentId, versions };
|
|
160
560
|
});
|
|
561
|
+
app.get("/api/metadata/versions/:commit", async (req) => {
|
|
562
|
+
const scope = resolveScope(req);
|
|
563
|
+
const magit = requireMagit(platform);
|
|
564
|
+
const { commit } = req.params;
|
|
565
|
+
const q = req.query;
|
|
566
|
+
const agentId = (q.agentId?.trim() || scope.agentIds[0]);
|
|
567
|
+
const loaded = await magit.packAtCommit(scope.orgId, agentId, commit);
|
|
568
|
+
return {
|
|
569
|
+
scope,
|
|
570
|
+
agentId,
|
|
571
|
+
commit: loaded.commit,
|
|
572
|
+
checksum: loaded.checksum,
|
|
573
|
+
pack: loaded.pack,
|
|
574
|
+
};
|
|
575
|
+
});
|
|
576
|
+
app.post("/api/metadata/diff", async (req) => {
|
|
577
|
+
const scope = resolveScope(req);
|
|
578
|
+
const magit = requireMagit(platform);
|
|
579
|
+
const body = req.body;
|
|
580
|
+
const agentId = (body.agentId?.trim() || scope.agentIds[0]);
|
|
581
|
+
const from = body.from?.trim();
|
|
582
|
+
const to = body.to?.trim();
|
|
583
|
+
if (!from || !to) {
|
|
584
|
+
throw new ServiceError("VALIDATION", "from and to required", 400);
|
|
585
|
+
}
|
|
586
|
+
const diff = await magit.diff(scope.orgId, agentId, { from, to });
|
|
587
|
+
return { scope, ...diff };
|
|
588
|
+
});
|
|
161
589
|
app.get("/api/metadata/definition", async (req) => {
|
|
162
590
|
const scope = resolveScope(req);
|
|
163
591
|
const q = req.query;
|
|
@@ -167,5 +595,326 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
167
595
|
const def = platform.metadata.getDefinition(scope.agentIds, q.kind, q.id);
|
|
168
596
|
return { scope, definition: def };
|
|
169
597
|
});
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
// Package catalog bridge (FR-CPS interim — org-free Pack; org required on Restore)
|
|
600
|
+
// Spec: .operational/magit-central-package-storage-FR.md
|
|
601
|
+
// ---------------------------------------------------------------------------
|
|
602
|
+
function installedPacks() {
|
|
603
|
+
return platform.metadata.exportMetadata().packs;
|
|
604
|
+
}
|
|
605
|
+
function parseKind(raw) {
|
|
606
|
+
const k = typeof raw === "string" ? raw.trim() : "";
|
|
607
|
+
if (k === "agent" || k === "connector" || k === "service")
|
|
608
|
+
return k;
|
|
609
|
+
throw new ServiceError("VALIDATION", "kind must be agent | connector | service", 400);
|
|
610
|
+
}
|
|
611
|
+
app.get("/api/metadata/packages", async (req) => {
|
|
612
|
+
requireMagit(platform);
|
|
613
|
+
const q = req.query;
|
|
614
|
+
const kindFilter = q.kind?.trim() && q.kind.trim() !== "all"
|
|
615
|
+
? parseKind(q.kind)
|
|
616
|
+
: undefined;
|
|
617
|
+
const catalog = discoverPackageCatalog(installedPacks()).filter((e) => kindFilter ? e.kind === kindFilter : true);
|
|
618
|
+
return {
|
|
619
|
+
packages: catalog,
|
|
620
|
+
interim: true,
|
|
621
|
+
catalogOrg: PACKAGE_CATALOG_ORG,
|
|
622
|
+
note: "Interim local catalog from installed packs. Central org-free Magit storage: FR-CPS.",
|
|
623
|
+
};
|
|
624
|
+
});
|
|
625
|
+
app.get("/api/metadata/packages/:kind/:id/associations", async (req) => {
|
|
626
|
+
requireMagit(platform);
|
|
627
|
+
const kind = parseKind(req.params.kind);
|
|
628
|
+
const id = String(req.params.id ?? "").trim();
|
|
629
|
+
if (!id)
|
|
630
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
631
|
+
const packs = installedPacks();
|
|
632
|
+
const q = req.query;
|
|
633
|
+
const agentId = resolveOwnerAgent(packs, kind, id, q.agentId);
|
|
634
|
+
const pack = packs[agentId];
|
|
635
|
+
if (!pack) {
|
|
636
|
+
throw new ServiceError("NOT_FOUND", `No installed pack for agent ${agentId}`, 404);
|
|
637
|
+
}
|
|
638
|
+
return {
|
|
639
|
+
kind,
|
|
640
|
+
id,
|
|
641
|
+
agentId,
|
|
642
|
+
associations: associationsFromPack(pack),
|
|
643
|
+
interim: true,
|
|
644
|
+
};
|
|
645
|
+
});
|
|
646
|
+
app.get("/api/metadata/packages/:kind/:id/versions", async (req) => {
|
|
647
|
+
const magit = requireMagit(platform);
|
|
648
|
+
const kind = parseKind(req.params.kind);
|
|
649
|
+
const id = String(req.params.id ?? "").trim();
|
|
650
|
+
if (!id)
|
|
651
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
652
|
+
const q = req.query;
|
|
653
|
+
const packs = installedPacks();
|
|
654
|
+
let ownerAgentId;
|
|
655
|
+
try {
|
|
656
|
+
ownerAgentId = resolveOwnerAgent(packs, kind, id, q.agentId);
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
ownerAgentId = kind === "agent" ? id : q.agentId?.trim() || "";
|
|
660
|
+
}
|
|
661
|
+
const sliceAgentId = catalogAgentId(kind, id, ownerAgentId || undefined);
|
|
662
|
+
const maxCount = q.maxCount ? Number(q.maxCount) : 50;
|
|
663
|
+
const versions = await magit.history(PACKAGE_CATALOG_ORG, sliceAgentId, {
|
|
664
|
+
maxCount: Number.isFinite(maxCount) ? maxCount : 50,
|
|
665
|
+
});
|
|
666
|
+
const filtered = versions.map((v) => {
|
|
667
|
+
const parsed = parsePackageMessage(v.message);
|
|
668
|
+
return {
|
|
669
|
+
...v,
|
|
670
|
+
packageKind: parsed.kind ?? kind,
|
|
671
|
+
packageId: parsed.id ?? id,
|
|
672
|
+
mode: parsed.mode,
|
|
673
|
+
displayMessage: parsed.message || v.message,
|
|
674
|
+
};
|
|
675
|
+
});
|
|
676
|
+
return {
|
|
677
|
+
kind,
|
|
678
|
+
id,
|
|
679
|
+
agentId: ownerAgentId || id,
|
|
680
|
+
catalogAgentId: sliceAgentId,
|
|
681
|
+
versions: filtered,
|
|
682
|
+
interim: true,
|
|
683
|
+
catalogOrg: PACKAGE_CATALOG_ORG,
|
|
684
|
+
note: "Versions from interim package catalog Magit repo (not runtime org).",
|
|
685
|
+
};
|
|
686
|
+
});
|
|
687
|
+
app.post("/api/metadata/packages/pack", async (req) => {
|
|
688
|
+
const magit = requireMagit(platform);
|
|
689
|
+
const body = req.body;
|
|
690
|
+
const kind = parseKind(body.kind);
|
|
691
|
+
const id = body.id?.trim();
|
|
692
|
+
if (!id)
|
|
693
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
694
|
+
const message = body.message?.trim();
|
|
695
|
+
if (!message)
|
|
696
|
+
throw new ServiceError("VALIDATION", "message required", 400);
|
|
697
|
+
const packs = installedPacks();
|
|
698
|
+
let agentId;
|
|
699
|
+
try {
|
|
700
|
+
agentId = resolveOwnerAgent(packs, kind, id, body.agentId);
|
|
701
|
+
}
|
|
702
|
+
catch (e) {
|
|
703
|
+
throw new ServiceError("NOT_FOUND", e instanceof Error ? e.message : String(e), 404);
|
|
704
|
+
}
|
|
705
|
+
const live = packs[agentId];
|
|
706
|
+
if (!live) {
|
|
707
|
+
throw new ServiceError("NOT_FOUND", `No installed pack for agent ${agentId}`, 404);
|
|
708
|
+
}
|
|
709
|
+
const mode = kind === "agent" ? (body.mode === "narrow" ? "narrow" : "full") : undefined;
|
|
710
|
+
const payload = buildPackagePayload(live, {
|
|
711
|
+
kind,
|
|
712
|
+
id,
|
|
713
|
+
mode,
|
|
714
|
+
includeConnectorIds: body.includeConnectorIds,
|
|
715
|
+
includeServiceIds: body.includeServiceIds,
|
|
716
|
+
});
|
|
717
|
+
const validation = validateMetadataPack(payload);
|
|
718
|
+
if (!validation.ok) {
|
|
719
|
+
return { packed: false, result: validation, interim: true };
|
|
720
|
+
}
|
|
721
|
+
const saved = await magit.saveVersion(PACKAGE_CATALOG_ORG, payload, {
|
|
722
|
+
message: packageVersionMessage(kind, id, message, mode),
|
|
723
|
+
});
|
|
724
|
+
const associations = kind === "agent" && mode
|
|
725
|
+
? releaseAssociations(live, mode, body.includeConnectorIds, body.includeServiceIds)
|
|
726
|
+
: undefined;
|
|
727
|
+
// Full agent pack also snapshots selected connector/service packages.
|
|
728
|
+
const bundled = [];
|
|
729
|
+
if (kind === "agent" && mode === "full" && associations) {
|
|
730
|
+
for (const connectorId of associations.connectorIds) {
|
|
731
|
+
const slice = buildPackagePayload(live, { kind: "connector", id: connectorId });
|
|
732
|
+
const ok = validateMetadataPack(slice);
|
|
733
|
+
if (!ok.ok)
|
|
734
|
+
continue;
|
|
735
|
+
const b = await magit.saveVersion(PACKAGE_CATALOG_ORG, slice, {
|
|
736
|
+
message: packageVersionMessage("connector", connectorId, `bundled with agent ${id}: ${message}`),
|
|
737
|
+
});
|
|
738
|
+
bundled.push({ kind: "connector", id: connectorId, commit: b.commit });
|
|
739
|
+
}
|
|
740
|
+
for (const serviceId of associations.serviceIds) {
|
|
741
|
+
const slice = buildPackagePayload(live, { kind: "service", id: serviceId });
|
|
742
|
+
const ok = validateMetadataPack(slice);
|
|
743
|
+
if (!ok.ok)
|
|
744
|
+
continue;
|
|
745
|
+
const b = await magit.saveVersion(PACKAGE_CATALOG_ORG, slice, {
|
|
746
|
+
message: packageVersionMessage("service", serviceId, `bundled with agent ${id}: ${message}`),
|
|
747
|
+
});
|
|
748
|
+
bundled.push({ kind: "service", id: serviceId, commit: b.commit });
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return {
|
|
752
|
+
packed: true,
|
|
753
|
+
kind,
|
|
754
|
+
id,
|
|
755
|
+
agentId,
|
|
756
|
+
mode: mode ?? null,
|
|
757
|
+
associations,
|
|
758
|
+
bundled,
|
|
759
|
+
commit: saved.commit,
|
|
760
|
+
version: saved.commit,
|
|
761
|
+
checksum: saved.checksum,
|
|
762
|
+
reused: saved.reused,
|
|
763
|
+
interim: true,
|
|
764
|
+
catalogOrg: PACKAGE_CATALOG_ORG,
|
|
765
|
+
note: "Pack stored in interim Magit catalog (not live). Central storage: FR-CPS.",
|
|
766
|
+
};
|
|
767
|
+
});
|
|
768
|
+
app.post("/api/metadata/packages/restore", async (req) => {
|
|
769
|
+
const scope = resolveScope(req);
|
|
770
|
+
const magit = requireMagit(platform);
|
|
771
|
+
const body = req.body;
|
|
772
|
+
if (!body?.confirm) {
|
|
773
|
+
throw new ServiceError("CONFIRMATION_REQUIRED", "explicit confirm:true required for package restore", 400);
|
|
774
|
+
}
|
|
775
|
+
if (!scope.orgId?.trim()) {
|
|
776
|
+
throw new ServiceError("VALIDATION", "x-memorix-org-id required — org is the restore target, not package identity", 400);
|
|
777
|
+
}
|
|
778
|
+
const kind = parseKind(body.kind);
|
|
779
|
+
const id = body.id?.trim();
|
|
780
|
+
if (!id)
|
|
781
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
782
|
+
const version = (body.version ?? body.commit)?.trim();
|
|
783
|
+
if (!version)
|
|
784
|
+
throw new ServiceError("VALIDATION", "version required", 400);
|
|
785
|
+
const packs = installedPacks();
|
|
786
|
+
let agentId;
|
|
787
|
+
try {
|
|
788
|
+
agentId = resolveOwnerAgent(packs, kind, id, body.agentId);
|
|
789
|
+
}
|
|
790
|
+
catch {
|
|
791
|
+
agentId = kind === "agent" ? id : body.agentId?.trim() || "";
|
|
792
|
+
}
|
|
793
|
+
if (!agentId) {
|
|
794
|
+
throw new ServiceError("VALIDATION", "agentId required to restore connector/service into an agent pack", 400);
|
|
795
|
+
}
|
|
796
|
+
const sliceAgentId = catalogAgentId(kind, id, agentId);
|
|
797
|
+
const loaded = await magit.packAtCommit(PACKAGE_CATALOG_ORG, sliceAgentId, version);
|
|
798
|
+
let installPack = loaded.pack;
|
|
799
|
+
// Connector/service restore merges slice into current live agent pack.
|
|
800
|
+
if (kind === "connector" || kind === "service") {
|
|
801
|
+
const live = packs[agentId] ?? emptyPackForRestore(agentId);
|
|
802
|
+
if (kind === "connector") {
|
|
803
|
+
const keepRefs = new Set((loaded.pack.sources ?? [])
|
|
804
|
+
.map((s) => s && typeof s === "object" && "connectorRef" in s
|
|
805
|
+
? String(s.connectorRef ?? "")
|
|
806
|
+
: "")
|
|
807
|
+
.filter(Boolean));
|
|
808
|
+
const otherSources = (live.sources ?? []).filter((s) => {
|
|
809
|
+
const ref = s && typeof s === "object" && "connectorRef" in s
|
|
810
|
+
? String(s.connectorRef ?? "")
|
|
811
|
+
: "";
|
|
812
|
+
return !keepRefs.has(ref);
|
|
813
|
+
});
|
|
814
|
+
installPack = {
|
|
815
|
+
...live,
|
|
816
|
+
agentId,
|
|
817
|
+
sources: [...otherSources, ...(loaded.pack.sources ?? [])],
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
const svcId = id;
|
|
822
|
+
const otherServices = (live.services ?? []).filter((s) => !(s && typeof s === "object" && "id" in s && String(s.id) === svcId));
|
|
823
|
+
installPack = {
|
|
824
|
+
...live,
|
|
825
|
+
agentId,
|
|
826
|
+
services: [...otherServices, ...(loaded.pack.services ?? [])],
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
installPack = stripOrgRuntimeSourcesFromPack(installPack);
|
|
831
|
+
const validation = validateMetadataPack(installPack);
|
|
832
|
+
if (!validation.ok) {
|
|
833
|
+
throw new ServiceError("VALIDATION", "pack at version failed validation", 400, { issues: validation.issues });
|
|
834
|
+
}
|
|
835
|
+
const message = body.message?.trim() ||
|
|
836
|
+
`Restore ${kind}:${id} @ ${version.slice(0, 12)} into org ${scope.orgId}`;
|
|
837
|
+
const result = platform.metadata.install(installPack, {
|
|
838
|
+
reinstall: true,
|
|
839
|
+
magitCommit: version,
|
|
840
|
+
magitRelease: undefined,
|
|
841
|
+
source: "rollback",
|
|
842
|
+
message,
|
|
843
|
+
environment: "live",
|
|
844
|
+
});
|
|
845
|
+
if (!result.ok) {
|
|
846
|
+
throw new ServiceError("VALIDATION", result.issues.map((i) => i.message).join("; ") || "restore install failed", 400);
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
restored: true,
|
|
850
|
+
kind,
|
|
851
|
+
id,
|
|
852
|
+
agentId,
|
|
853
|
+
version,
|
|
854
|
+
targetOrgId: scope.orgId,
|
|
855
|
+
result,
|
|
856
|
+
interim: true,
|
|
857
|
+
catalogOrg: PACKAGE_CATALOG_ORG,
|
|
858
|
+
note: "Restored from interim Magit catalog into this environment. Org is restore target only.",
|
|
859
|
+
};
|
|
860
|
+
});
|
|
861
|
+
app.post("/api/metadata/packages/diff", async (req) => {
|
|
862
|
+
const magit = requireMagit(platform);
|
|
863
|
+
const body = req.body;
|
|
864
|
+
const kind = parseKind(body.kind);
|
|
865
|
+
const id = body.id?.trim();
|
|
866
|
+
if (!id)
|
|
867
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
868
|
+
const from = body.from?.trim();
|
|
869
|
+
const to = body.to?.trim();
|
|
870
|
+
if (!from || !to) {
|
|
871
|
+
throw new ServiceError("VALIDATION", "from and to required", 400);
|
|
872
|
+
}
|
|
873
|
+
const packs = installedPacks();
|
|
874
|
+
let agentId;
|
|
875
|
+
try {
|
|
876
|
+
agentId = resolveOwnerAgent(packs, kind, id, body.agentId);
|
|
877
|
+
}
|
|
878
|
+
catch {
|
|
879
|
+
agentId = kind === "agent" ? id : body.agentId?.trim() || id;
|
|
880
|
+
}
|
|
881
|
+
const sliceAgentId = catalogAgentId(kind, id, agentId);
|
|
882
|
+
// "installed" sentinel → live for Magit diff (same Mongo metadata DB)
|
|
883
|
+
const fromRef = from === "installed" ? "live" : from;
|
|
884
|
+
const toRef = to === "installed" ? "live" : to;
|
|
885
|
+
const diff = await magit.diff(PACKAGE_CATALOG_ORG, sliceAgentId, {
|
|
886
|
+
from: fromRef,
|
|
887
|
+
to: toRef,
|
|
888
|
+
});
|
|
889
|
+
return {
|
|
890
|
+
...diff,
|
|
891
|
+
kind,
|
|
892
|
+
id,
|
|
893
|
+
agentId,
|
|
894
|
+
catalogAgentId: sliceAgentId,
|
|
895
|
+
interim: true,
|
|
896
|
+
note: "Compare package versions or version vs installed. Not env→env copy.",
|
|
897
|
+
};
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
function emptyPackForRestore(agentId) {
|
|
901
|
+
return {
|
|
902
|
+
format: "memorix-metadata/1",
|
|
903
|
+
agentId,
|
|
904
|
+
agents: [],
|
|
905
|
+
objectTypes: [],
|
|
906
|
+
abstractTypes: [],
|
|
907
|
+
abstractBindings: [],
|
|
908
|
+
relationshipDefinitions: [],
|
|
909
|
+
mappings: [],
|
|
910
|
+
views: [],
|
|
911
|
+
writes: [],
|
|
912
|
+
sources: [],
|
|
913
|
+
services: [],
|
|
914
|
+
pipelines: [],
|
|
915
|
+
schedules: [],
|
|
916
|
+
kindDeclarations: [],
|
|
917
|
+
extensions: {},
|
|
918
|
+
};
|
|
170
919
|
}
|
|
171
920
|
//# sourceMappingURL=metadata.js.map
|