@x12i/memorix-service 3.2.1 → 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 +61 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +8 -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/catalog-service.d.ts +48 -0
- package/dist/catalog/catalog-service.d.ts.map +1 -0
- package/dist/catalog/catalog-service.js +397 -0
- package/dist/catalog/catalog-service.js.map +1 -0
- package/dist/catalog/package-list-source.d.ts +35 -0
- package/dist/catalog/package-list-source.d.ts.map +1 -0
- package/dist/catalog/package-list-source.js +47 -0
- package/dist/catalog/package-list-source.js.map +1 -0
- package/dist/catalog/types.d.ts +65 -0
- package/dist/catalog/types.d.ts.map +1 -0
- package/dist/catalog/types.js +16 -0
- package/dist/catalog/types.js.map +1 -0
- package/dist/catalog-store-cli.d.ts +20 -0
- package/dist/catalog-store-cli.d.ts.map +1 -0
- package/dist/catalog-store-cli.js +237 -0
- package/dist/catalog-store-cli.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +48 -18
- package/dist/cli.js.map +1 -1
- package/dist/connector-ops/attempt-store.d.ts +5 -0
- package/dist/connector-ops/attempt-store.d.ts.map +1 -0
- package/dist/connector-ops/attempt-store.js +186 -0
- package/dist/connector-ops/attempt-store.js.map +1 -0
- package/dist/connector-ops/index.d.ts +5 -0
- package/dist/connector-ops/index.d.ts.map +1 -0
- package/dist/connector-ops/index.js +5 -0
- package/dist/connector-ops/index.js.map +1 -0
- package/dist/connector-ops/override-store.d.ts +7 -0
- package/dist/connector-ops/override-store.d.ts.map +1 -0
- package/dist/connector-ops/override-store.js +123 -0
- package/dist/connector-ops/override-store.js.map +1 -0
- package/dist/connector-ops/sanitize.d.ts +16 -0
- package/dist/connector-ops/sanitize.d.ts.map +1 -0
- package/dist/connector-ops/sanitize.js +48 -0
- package/dist/connector-ops/sanitize.js.map +1 -0
- package/dist/connector-ops/types.d.ts +76 -0
- package/dist/connector-ops/types.d.ts.map +1 -0
- package/dist/connector-ops/types.js +12 -0
- package/dist/connector-ops/types.js.map +1 -0
- package/dist/effective-listing.d.ts +11 -0
- package/dist/effective-listing.d.ts.map +1 -0
- package/dist/effective-listing.js +41 -0
- package/dist/effective-listing.js.map +1 -0
- package/dist/errors.d.ts +5 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -3
- package/dist/errors.js.map +1 -1
- package/dist/execution-target.d.ts +16 -0
- package/dist/execution-target.d.ts.map +1 -1
- package/dist/execution-target.js +54 -0
- package/dist/execution-target.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- 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 +436 -70
- package/dist/magit/magit-pack-service.d.ts.map +1 -1
- package/dist/magit/magit-pack-service.js +949 -202
- package/dist/magit/magit-pack-service.js.map +1 -1
- package/dist/magit/pack-to-patchset.d.ts +21 -0
- package/dist/magit/pack-to-patchset.d.ts.map +1 -1
- package/dist/magit/pack-to-patchset.js +29 -0
- package/dist/magit/pack-to-patchset.js.map +1 -1
- package/dist/magit/package-catalog-bridge.d.ts +4 -14
- package/dist/magit/package-catalog-bridge.d.ts.map +1 -1
- package/dist/magit/package-catalog-bridge.js +2 -21
- package/dist/magit/package-catalog-bridge.js.map +1 -1
- package/dist/mgc-packages.d.ts +4 -3
- package/dist/mgc-packages.d.ts.map +1 -1
- package/dist/mgc-packages.js +4 -3
- package/dist/mgc-packages.js.map +1 -1
- package/dist/openapi/openapi.json +3060 -135
- package/dist/optional-companion-url.d.ts +72 -0
- package/dist/optional-companion-url.d.ts.map +1 -0
- package/dist/optional-companion-url.js +148 -0
- package/dist/optional-companion-url.js.map +1 -0
- package/dist/platform.d.ts +29 -0
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +138 -2
- 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/catalog.d.ts +4 -0
- package/dist/routes/catalog.d.ts.map +1 -0
- package/dist/routes/catalog.js +103 -0
- package/dist/routes/catalog.js.map +1 -0
- package/dist/routes/connector-ops.d.ts +12 -0
- package/dist/routes/connector-ops.d.ts.map +1 -0
- package/dist/routes/connector-ops.js +160 -0
- package/dist/routes/connector-ops.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/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +9 -2
- package/dist/routes/memory.js.map +1 -1
- package/dist/routes/metadata.d.ts.map +1 -1
- package/dist/routes/metadata.js +459 -186
- package/dist/routes/metadata.js.map +1 -1
- package/dist/routes/pipelines.d.ts.map +1 -1
- package/dist/routes/pipelines.js +3 -2
- package/dist/routes/pipelines.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 +3060 -135
- package/package.json +31 -7
package/dist/routes/metadata.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { METADATA_KIND_IDS, METADATA_KIND_REGISTRY, validateMetadataPack, } from "@x12i/memorix-metadata";
|
|
2
2
|
import { resolveScope } from "../request-scope.js";
|
|
3
|
+
import { resolveEffectiveForListing } from "../effective-listing.js";
|
|
3
4
|
import { ServiceError } from "../errors.js";
|
|
4
5
|
import { listKnownPackIds, loadKnownMetadataPack } from "../known-packs.js";
|
|
5
6
|
import { stripOrgRuntimeSourcesFromPack } from "../org-sources.js";
|
|
6
|
-
import {
|
|
7
|
+
import { associationsFromPack, buildPackagePayload, catalogAgentId, discoverPackageCatalog, releaseAssociations, resolveOwnerAgent, } from "../magit/package-catalog-bridge.js";
|
|
8
|
+
import { packagePayloadToPack } from "../magit/pack-to-patchset.js";
|
|
9
|
+
import { createCatalogService } from "../catalog/catalog-service.js";
|
|
10
|
+
import { collapseCatalogVersions, parsePackagesSource, selectPackagesBySource, } from "../catalog/package-list-source.js";
|
|
11
|
+
import { isMagitCatalogKind } from "../catalog/types.js";
|
|
7
12
|
function requireMagit(platform) {
|
|
8
13
|
if (!platform.magit) {
|
|
9
14
|
throw new ServiceError("MAGIT_UNAVAILABLE", "metadata versioning requires a durable Mongo connection (set METADATA_MAGIT=0 to disable)", 503);
|
|
@@ -144,7 +149,7 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
144
149
|
});
|
|
145
150
|
app.get("/api/metadata/effective", async (req) => {
|
|
146
151
|
const scope = resolveScope(req);
|
|
147
|
-
const resolved = platform.metadata.resolveEffective(scope.agentIds);
|
|
152
|
+
const resolved = resolveEffectiveForListing((ids) => platform.metadata.resolveEffective(ids), scope.agentIds);
|
|
148
153
|
return {
|
|
149
154
|
scope,
|
|
150
155
|
ok: resolved.ok,
|
|
@@ -156,12 +161,12 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
156
161
|
issues: resolved.issues ?? [],
|
|
157
162
|
referenceIssues: resolved.referenceIssues ?? [],
|
|
158
163
|
},
|
|
159
|
-
note: "agentIds select effective metadata only;
|
|
164
|
+
note: "agentIds select effective metadata only; Any/* fans out and unions when multi-agent joint resolve conflicts",
|
|
160
165
|
};
|
|
161
166
|
});
|
|
162
167
|
app.get("/api/metadata/kinds", async (req) => {
|
|
163
168
|
const scope = resolveScope(req);
|
|
164
|
-
const effective = platform.metadata.resolveEffective(scope.agentIds);
|
|
169
|
+
const effective = resolveEffectiveForListing((ids) => platform.metadata.resolveEffective(ids), scope.agentIds);
|
|
165
170
|
const customDeclarations = (effective.effective ?? []).filter((e) => e.kind === "kind-declarations");
|
|
166
171
|
return {
|
|
167
172
|
scope,
|
|
@@ -184,7 +189,7 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
184
189
|
app.get("/api/metadata/kinds/:kind", async (req) => {
|
|
185
190
|
const scope = resolveScope(req);
|
|
186
191
|
const { kind } = req.params;
|
|
187
|
-
const effective = platform.metadata.resolveEffective(scope.agentIds);
|
|
192
|
+
const effective = resolveEffectiveForListing((ids) => platform.metadata.resolveEffective(ids), scope.agentIds);
|
|
188
193
|
const items = (effective.effective ?? []).filter((e) => e.kind === kind);
|
|
189
194
|
return { scope, kind, items };
|
|
190
195
|
});
|
|
@@ -596,8 +601,8 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
596
601
|
return { scope, definition: def };
|
|
597
602
|
});
|
|
598
603
|
// ---------------------------------------------------------------------------
|
|
599
|
-
//
|
|
600
|
-
//
|
|
604
|
+
// Central package catalog (org-free Pack/List/Fetch; org required
|
|
605
|
+
// only on Restore as the install target)
|
|
601
606
|
// ---------------------------------------------------------------------------
|
|
602
607
|
function installedPacks() {
|
|
603
608
|
return platform.metadata.exportMetadata().packs;
|
|
@@ -608,95 +613,52 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
608
613
|
return k;
|
|
609
614
|
throw new ServiceError("VALIDATION", "kind must be agent | connector | service", 400);
|
|
610
615
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const h = req.headers;
|
|
614
|
-
const pick = (name) => {
|
|
615
|
-
const v = h[name] ?? h[name.toLowerCase()];
|
|
616
|
-
if (Array.isArray(v))
|
|
617
|
-
return v[0]?.trim() || undefined;
|
|
618
|
-
return typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
619
|
-
};
|
|
620
|
-
const subject = pick("x-memorix-actor") ??
|
|
621
|
-
pick("x-actor-id") ??
|
|
622
|
-
pick("x-authix-subject");
|
|
623
|
-
return subject ? { subject } : undefined;
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* Magit published catalog and/or packable live installs.
|
|
627
|
-
* Query: source=catalog|packable|all (default all); optional kind filter.
|
|
628
|
-
* packages = Magit published (distinct kind+id, latest version row as summary).
|
|
629
|
-
* packable = installed live (for Publish-from-this-env).
|
|
630
|
-
* No publisher "ours only" filter until Magit G1–G3 ship.
|
|
631
|
-
*/
|
|
616
|
+
// Local packable by default. source=catalog proxies Magit inventory collapse;
|
|
617
|
+
// source=all returns both planes. Global product inventory is GET /api/catalog.
|
|
632
618
|
app.get("/api/metadata/packages", async (req) => {
|
|
633
619
|
const magit = requireMagit(platform);
|
|
634
620
|
const q = req.query;
|
|
635
621
|
const kindFilter = q.kind?.trim() && q.kind.trim() !== "all"
|
|
636
622
|
? parseKind(q.kind)
|
|
637
623
|
: undefined;
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const listed = await magit.listCatalogPackages({
|
|
624
|
+
const source = parsePackagesSource(q.source);
|
|
625
|
+
const packable = discoverPackageCatalog(installedPacks()).filter((e) => kindFilter ? e.kind === kindFilter : true);
|
|
626
|
+
let catalogRows = [];
|
|
627
|
+
if (source === "catalog" || source === "all") {
|
|
628
|
+
const catalog = createCatalogService({
|
|
629
|
+
magit,
|
|
630
|
+
metadata: platform.metadata,
|
|
631
|
+
connectorRegistry: platform.connectorRegistry,
|
|
632
|
+
genericConnectors: platform.genericConnectors,
|
|
633
|
+
});
|
|
634
|
+
const versions = await catalog.listMagitVersionRows({
|
|
650
635
|
kind: kindFilter,
|
|
651
|
-
maxCount: 500,
|
|
652
636
|
});
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (prev?.createdAt && prev.createdAt >= v.createdAt)
|
|
662
|
-
continue;
|
|
663
|
-
byKey.set(key, {
|
|
664
|
-
kind: v.kind,
|
|
665
|
-
id: v.id,
|
|
666
|
-
// Owner agent is live concern; catalog packages are org-free.
|
|
667
|
-
agentId: v.kind === "agent" ? v.id : "",
|
|
668
|
-
label: v.kind === "agent"
|
|
669
|
-
? v.id
|
|
670
|
-
: `${v.kind}:${v.id}${v.version ? ` @ ${v.version.slice(0, 12)}` : ""}`,
|
|
671
|
-
version: v.version,
|
|
672
|
-
checksum: v.checksum,
|
|
673
|
-
createdAt: v.createdAt,
|
|
674
|
-
mode: v.mode,
|
|
675
|
-
associations: v.associations,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
packages = [...byKey.values()].sort((a, b) => a.kind.localeCompare(b.kind) || a.id.localeCompare(b.id));
|
|
679
|
-
}
|
|
680
|
-
const packable = wantPackable
|
|
681
|
-
? discoverPackageCatalog(installedPacks()).filter((e) => kindFilter ? e.kind === kindFilter : true)
|
|
682
|
-
: [];
|
|
683
|
-
// Backward-compat: when clients only read `packages`, prefer catalog when requested.
|
|
684
|
-
// For source=packable only, packages[] mirrors packable so older clients still work.
|
|
685
|
-
if (source === "packable") {
|
|
686
|
-
packages = packable.map((e) => ({
|
|
687
|
-
kind: e.kind,
|
|
688
|
-
id: e.id,
|
|
689
|
-
agentId: e.agentId,
|
|
690
|
-
label: e.label,
|
|
637
|
+
catalogRows = versions
|
|
638
|
+
.filter((v) => isMagitCatalogKind(v.kind))
|
|
639
|
+
.map((v) => ({
|
|
640
|
+
kind: v.kind,
|
|
641
|
+
id: v.id,
|
|
642
|
+
version: v.version,
|
|
643
|
+
createdAt: v.createdAt,
|
|
644
|
+
checksum: v.checksum,
|
|
691
645
|
}));
|
|
692
646
|
}
|
|
693
|
-
|
|
694
|
-
|
|
647
|
+
const selected = selectPackagesBySource({
|
|
648
|
+
source,
|
|
649
|
+
packages: collapseCatalogVersions(catalogRows),
|
|
695
650
|
packable,
|
|
651
|
+
});
|
|
652
|
+
const note = source === "packable"
|
|
653
|
+
? "Package identities discoverable from packs installed in this environment."
|
|
654
|
+
: source === "catalog"
|
|
655
|
+
? "Collapsed Magit catalog identities (latest version per kind+id). Prefer GET /api/catalog for product inventory."
|
|
656
|
+
: "packages=Magit catalog (collapsed); packable=installed pack identities.";
|
|
657
|
+
return {
|
|
658
|
+
packages: selected.packages,
|
|
659
|
+
...(source === "all" ? { packable: selected.packable } : {}),
|
|
696
660
|
source,
|
|
697
|
-
|
|
698
|
-
catalogOrg: PACKAGE_CATALOG_ORG,
|
|
699
|
-
note: "packages = Magit published catalog; packable = live installs for Publish. Publisher-scoped catalog awaits Magit G1–G3 (.operational/magit-package-ownership-catalog-FR.md).",
|
|
661
|
+
note,
|
|
700
662
|
};
|
|
701
663
|
});
|
|
702
664
|
app.get("/api/metadata/packages/:kind/:id/associations", async (req) => {
|
|
@@ -719,6 +681,8 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
719
681
|
associations: associationsFromPack(pack),
|
|
720
682
|
};
|
|
721
683
|
});
|
|
684
|
+
// FR-CPS-3/5 — flat catalog listing. Org-free: kind + id only, no agentId
|
|
685
|
+
// resolution needed (a catalog package isn't owned by any installed pack).
|
|
722
686
|
app.get("/api/metadata/packages/:kind/:id/versions", async (req) => {
|
|
723
687
|
const magit = requireMagit(platform);
|
|
724
688
|
const kind = parseKind(req.params.kind);
|
|
@@ -726,44 +690,42 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
726
690
|
if (!id)
|
|
727
691
|
throw new ServiceError("VALIDATION", "id required", 400);
|
|
728
692
|
const q = req.query;
|
|
729
|
-
const packs = installedPacks();
|
|
730
|
-
let ownerAgentId;
|
|
731
|
-
try {
|
|
732
|
-
ownerAgentId = resolveOwnerAgent(packs, kind, id, q.agentId);
|
|
733
|
-
}
|
|
734
|
-
catch {
|
|
735
|
-
ownerAgentId = kind === "agent" ? id : q.agentId?.trim() || "";
|
|
736
|
-
}
|
|
737
693
|
const maxCount = q.maxCount ? Number(q.maxCount) : 50;
|
|
738
|
-
const
|
|
694
|
+
const versions = await magit.listPackages({
|
|
739
695
|
kind,
|
|
740
696
|
id,
|
|
741
697
|
maxCount: Number.isFinite(maxCount) ? maxCount : 50,
|
|
742
698
|
});
|
|
743
|
-
const versions = listed.items.map((v) => {
|
|
744
|
-
const parsed = parsePackageMessage(v.message);
|
|
745
|
-
return {
|
|
746
|
-
commit: v.version,
|
|
747
|
-
agentId: ownerAgentId || id,
|
|
748
|
-
checksum: v.checksum,
|
|
749
|
-
message: v.message,
|
|
750
|
-
createdAt: v.createdAt,
|
|
751
|
-
source: "save-version",
|
|
752
|
-
packageKind: (parsed.kind ?? v.kind),
|
|
753
|
-
packageId: parsed.id ?? v.id,
|
|
754
|
-
mode: parsed.mode ?? v.mode,
|
|
755
|
-
displayMessage: parsed.message || v.message,
|
|
756
|
-
associations: v.associations,
|
|
757
|
-
};
|
|
758
|
-
});
|
|
759
699
|
return {
|
|
760
700
|
kind,
|
|
761
701
|
id,
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
702
|
+
// `commit` aliases `version` for existing HTTP callers written against
|
|
703
|
+
// the pre-cutover org-scoped history shape (e.g. Studio's
|
|
704
|
+
// PackVersionsPage.tsx keys/renders list rows by `.commit`). `version`
|
|
705
|
+
// is the FR-CPS-4/5 field name and the one new callers should use.
|
|
706
|
+
versions: versions.map((v) => ({ ...v, commit: v.version })),
|
|
707
|
+
note: "Versions from the central Magit package catalog.",
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
// FR-CPS-5 — fetch a package version's payload directly (§7 of the FR).
|
|
711
|
+
// Org header is ignored: catalog reads are org-free.
|
|
712
|
+
app.get("/api/metadata/packages/:kind/:id/versions/:version", async (req) => {
|
|
713
|
+
const magit = requireMagit(platform);
|
|
714
|
+
const kind = parseKind(req.params.kind);
|
|
715
|
+
const id = String(req.params.id ?? "").trim();
|
|
716
|
+
if (!id)
|
|
717
|
+
throw new ServiceError("VALIDATION", "id required", 400);
|
|
718
|
+
const version = String(req.params.version ?? "").trim();
|
|
719
|
+
if (!version)
|
|
720
|
+
throw new ServiceError("VALIDATION", "version required", 400);
|
|
721
|
+
const fetched = await magit.fetchPackage({ kind, id, version });
|
|
722
|
+
return {
|
|
723
|
+
kind,
|
|
724
|
+
id,
|
|
725
|
+
version,
|
|
726
|
+
checksum: fetched.checksum,
|
|
727
|
+
payload: fetched.payload,
|
|
728
|
+
associations: fetched.associations,
|
|
767
729
|
};
|
|
768
730
|
});
|
|
769
731
|
app.post("/api/metadata/packages/pack", async (req) => {
|
|
@@ -788,11 +750,7 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
788
750
|
if (!live) {
|
|
789
751
|
throw new ServiceError("NOT_FOUND", `No installed pack for agent ${agentId}`, 404);
|
|
790
752
|
}
|
|
791
|
-
const mode = kind === "agent"
|
|
792
|
-
? body.mode === "narrow"
|
|
793
|
-
? "narrow"
|
|
794
|
-
: "full"
|
|
795
|
-
: "narrow";
|
|
753
|
+
const mode = kind === "agent" ? (body.mode === "narrow" ? "narrow" : "full") : undefined;
|
|
796
754
|
const payload = buildPackagePayload(live, {
|
|
797
755
|
kind,
|
|
798
756
|
id,
|
|
@@ -807,74 +765,51 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
807
765
|
const associations = kind === "agent" && mode
|
|
808
766
|
? releaseAssociations(live, mode, body.includeConnectorIds, body.includeServiceIds)
|
|
809
767
|
: undefined;
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
768
|
+
const saved = await magit.packPackage({
|
|
769
|
+
kind,
|
|
770
|
+
id,
|
|
771
|
+
message,
|
|
772
|
+
pack: payload,
|
|
773
|
+
mode,
|
|
774
|
+
associations,
|
|
775
|
+
});
|
|
776
|
+
// Full agent pack also snapshots selected connector/service packages.
|
|
777
|
+
// `commit` aliases `version` for the same backward-compat reason as the
|
|
778
|
+
// versions-list route above.
|
|
813
779
|
const bundled = [];
|
|
814
780
|
if (kind === "agent" && mode === "full" && associations) {
|
|
815
781
|
for (const connectorId of associations.connectorIds) {
|
|
816
|
-
const slice = buildPackagePayload(live, {
|
|
817
|
-
kind: "connector",
|
|
818
|
-
id: connectorId,
|
|
819
|
-
});
|
|
782
|
+
const slice = buildPackagePayload(live, { kind: "connector", id: connectorId });
|
|
820
783
|
const ok = validateMetadataPack(slice);
|
|
821
784
|
if (!ok.ok)
|
|
822
785
|
continue;
|
|
823
|
-
const b = await magit.
|
|
786
|
+
const b = await magit.packPackage({
|
|
824
787
|
kind: "connector",
|
|
825
788
|
id: connectorId,
|
|
826
|
-
message:
|
|
789
|
+
message: `bundled with agent ${id}: ${message}`,
|
|
827
790
|
pack: slice,
|
|
828
|
-
mode: "narrow",
|
|
829
|
-
actor,
|
|
830
|
-
});
|
|
831
|
-
pins.push({
|
|
832
|
-
kind: "connector",
|
|
833
|
-
id: connectorId,
|
|
834
|
-
version: b.version,
|
|
835
|
-
});
|
|
836
|
-
bundled.push({
|
|
837
|
-
kind: "connector",
|
|
838
|
-
id: connectorId,
|
|
839
|
-
commit: b.version,
|
|
840
|
-
version: b.version,
|
|
841
791
|
});
|
|
792
|
+
bundled.push({ kind: "connector", id: connectorId, version: b.version, commit: b.version });
|
|
842
793
|
}
|
|
843
794
|
for (const serviceId of associations.serviceIds) {
|
|
844
|
-
const slice = buildPackagePayload(live, {
|
|
845
|
-
kind: "service",
|
|
846
|
-
id: serviceId,
|
|
847
|
-
});
|
|
795
|
+
const slice = buildPackagePayload(live, { kind: "service", id: serviceId });
|
|
848
796
|
const ok = validateMetadataPack(slice);
|
|
849
797
|
if (!ok.ok)
|
|
850
798
|
continue;
|
|
851
|
-
const b = await magit.
|
|
799
|
+
const b = await magit.packPackage({
|
|
852
800
|
kind: "service",
|
|
853
801
|
id: serviceId,
|
|
854
|
-
message:
|
|
802
|
+
message: `bundled with agent ${id}: ${message}`,
|
|
855
803
|
pack: slice,
|
|
856
|
-
mode: "narrow",
|
|
857
|
-
actor,
|
|
858
|
-
});
|
|
859
|
-
pins.push({ kind: "service", id: serviceId, version: b.version });
|
|
860
|
-
bundled.push({
|
|
861
|
-
kind: "service",
|
|
862
|
-
id: serviceId,
|
|
863
|
-
commit: b.version,
|
|
864
|
-
version: b.version,
|
|
865
804
|
});
|
|
805
|
+
bundled.push({ kind: "service", id: serviceId, version: b.version, commit: b.version });
|
|
866
806
|
}
|
|
867
807
|
}
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
mode: kind === "agent" ? mode : "narrow",
|
|
874
|
-
associations,
|
|
875
|
-
pins: pins.length ? pins : undefined,
|
|
876
|
-
actor,
|
|
877
|
-
});
|
|
808
|
+
const note = saved.published === false
|
|
809
|
+
? "Published locally but not to the shared catalog (published=false)."
|
|
810
|
+
: saved.published === true
|
|
811
|
+
? "Published to the shared package catalog."
|
|
812
|
+
: "Published to the package catalog.";
|
|
878
813
|
return {
|
|
879
814
|
packed: true,
|
|
880
815
|
kind,
|
|
@@ -883,13 +818,13 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
883
818
|
mode: mode ?? null,
|
|
884
819
|
associations,
|
|
885
820
|
bundled,
|
|
886
|
-
commit: saved.version,
|
|
887
821
|
version: saved.version,
|
|
888
822
|
checksum: saved.checksum,
|
|
889
823
|
reused: saved.reused,
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
824
|
+
...(saved.published !== undefined ? { published: saved.published } : {}),
|
|
825
|
+
...(saved.storeId ? { storeId: saved.storeId } : {}),
|
|
826
|
+
...(saved.packageTypeId ? { packageTypeId: saved.packageTypeId } : {}),
|
|
827
|
+
note,
|
|
893
828
|
};
|
|
894
829
|
});
|
|
895
830
|
app.post("/api/metadata/packages/restore", async (req) => {
|
|
@@ -921,13 +856,14 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
921
856
|
throw new ServiceError("VALIDATION", "agentId required to restore connector/service into an agent pack", 400);
|
|
922
857
|
}
|
|
923
858
|
const sliceAgentId = catalogAgentId(kind, id, agentId);
|
|
924
|
-
const
|
|
925
|
-
|
|
859
|
+
const fetched = await magit.fetchPackage({ kind, id, version });
|
|
860
|
+
const loadedPack = packagePayloadToPack(sliceAgentId, fetched.payload);
|
|
861
|
+
let installPack = loadedPack;
|
|
926
862
|
// Connector/service restore merges slice into current live agent pack.
|
|
927
863
|
if (kind === "connector" || kind === "service") {
|
|
928
864
|
const live = packs[agentId] ?? emptyPackForRestore(agentId);
|
|
929
865
|
if (kind === "connector") {
|
|
930
|
-
const keepRefs = new Set((
|
|
866
|
+
const keepRefs = new Set((loadedPack.sources ?? [])
|
|
931
867
|
.map((s) => s && typeof s === "object" && "connectorRef" in s
|
|
932
868
|
? String(s.connectorRef ?? "")
|
|
933
869
|
: "")
|
|
@@ -941,19 +877,16 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
941
877
|
installPack = {
|
|
942
878
|
...live,
|
|
943
879
|
agentId,
|
|
944
|
-
sources: [...otherSources, ...(
|
|
880
|
+
sources: [...otherSources, ...(loadedPack.sources ?? [])],
|
|
945
881
|
};
|
|
946
882
|
}
|
|
947
883
|
else {
|
|
948
884
|
const svcId = id;
|
|
949
|
-
const otherServices = (live.services ?? []).filter((s) => !(s &&
|
|
950
|
-
typeof s === "object" &&
|
|
951
|
-
"id" in s &&
|
|
952
|
-
String(s.id) === svcId));
|
|
885
|
+
const otherServices = (live.services ?? []).filter((s) => !(s && typeof s === "object" && "id" in s && String(s.id) === svcId));
|
|
953
886
|
installPack = {
|
|
954
887
|
...live,
|
|
955
888
|
agentId,
|
|
956
|
-
services: [...otherServices, ...(
|
|
889
|
+
services: [...otherServices, ...(loadedPack.services ?? [])],
|
|
957
890
|
};
|
|
958
891
|
}
|
|
959
892
|
}
|
|
@@ -981,12 +914,10 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
981
914
|
id,
|
|
982
915
|
agentId,
|
|
983
916
|
version,
|
|
917
|
+
checksum: fetched.checksum,
|
|
984
918
|
targetOrgId: scope.orgId,
|
|
985
|
-
checksum: loaded.checksum,
|
|
986
919
|
result,
|
|
987
|
-
|
|
988
|
-
catalogOrg: PACKAGE_CATALOG_ORG,
|
|
989
|
-
note: "Restored from Magit package catalog into this environment. Org is restore target only.",
|
|
920
|
+
note: "Installed from the shared package catalog into this organization. Organization is the install target only.",
|
|
990
921
|
};
|
|
991
922
|
});
|
|
992
923
|
app.post("/api/metadata/packages/diff", async (req) => {
|
|
@@ -1010,12 +941,31 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
1010
941
|
agentId = kind === "agent" ? id : body.agentId?.trim() || id;
|
|
1011
942
|
}
|
|
1012
943
|
const sliceAgentId = catalogAgentId(kind, id, agentId);
|
|
1013
|
-
//
|
|
944
|
+
// "installed" sentinel → live / installed pack slice for typed-store compare.
|
|
945
|
+
// Flat Mode B still maps installed → "live" commit/mongo side via MagitPackService.
|
|
1014
946
|
const fromRef = from === "installed" ? "live" : from;
|
|
1015
947
|
const toRef = to === "installed" ? "live" : to;
|
|
1016
|
-
const
|
|
948
|
+
const needsInstalled = fromRef === "live" ||
|
|
949
|
+
toRef === "live" ||
|
|
950
|
+
from === "installed" ||
|
|
951
|
+
to === "installed";
|
|
952
|
+
let installedPack = null;
|
|
953
|
+
if (needsInstalled) {
|
|
954
|
+
const live = packs[agentId];
|
|
955
|
+
if (live) {
|
|
956
|
+
installedPack = buildPackagePayload(live, {
|
|
957
|
+
kind,
|
|
958
|
+
id,
|
|
959
|
+
mode: kind === "agent" ? "narrow" : undefined,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
const diff = await magit.diffPackages(sliceAgentId, {
|
|
1017
964
|
from: fromRef,
|
|
1018
965
|
to: toRef,
|
|
966
|
+
kind,
|
|
967
|
+
id,
|
|
968
|
+
installedPack,
|
|
1019
969
|
});
|
|
1020
970
|
return {
|
|
1021
971
|
...diff,
|
|
@@ -1023,10 +973,333 @@ export async function registerMetadataRoutes(app, platform) {
|
|
|
1023
973
|
id,
|
|
1024
974
|
agentId,
|
|
1025
975
|
catalogAgentId: sliceAgentId,
|
|
1026
|
-
remote: magit.hasRemoteCatalog(),
|
|
1027
976
|
note: "Compare package versions or version vs installed. Not env→env copy.",
|
|
1028
977
|
};
|
|
1029
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
|
+
});
|
|
1030
1303
|
}
|
|
1031
1304
|
function emptyPackForRestore(agentId) {
|
|
1032
1305
|
return {
|