@fabricorg/assembly 0.4.6 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,894 +1,783 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- ASSEMBLY_LOCKFILE_FORMAT_VERSION: () => ASSEMBLY_LOCKFILE_FORMAT_VERSION,
24
- ASSEMBLY_MANIFEST_FORMAT_VERSION: () => ASSEMBLY_MANIFEST_FORMAT_VERSION,
25
- assertAssemblyLockfile: () => assertAssemblyLockfile,
26
- assertAssemblyManifest: () => assertAssemblyManifest,
27
- assertAssemblyResolved: () => assertAssemblyResolved,
28
- assertAssemblyRuntimeCompatible: () => assertAssemblyRuntimeCompatible,
29
- assertComponentPackManifest: () => assertComponentPackManifest,
30
- computeAdapterCertificationDigest: () => computeAdapterCertificationDigest,
31
- computeAssemblyLockfileDigest: () => computeAssemblyLockfileDigest,
32
- computeUsageContractDocumentDigest: () => computeUsageContractDocumentDigest,
33
- parseSemverRange: () => parseSemverRange,
34
- resolveAssembly: () => resolveAssembly,
35
- resolveVersionRange: () => resolveVersionRange,
36
- satisfiesRange: () => satisfiesRange,
37
- validateAssemblyBindings: () => validateAssemblyBindings
38
- });
39
- module.exports = __toCommonJS(index_exports);
40
- var import_node_crypto = require("crypto");
41
- var import_ports = require("@fabricorg/ports");
42
- var import_adoption_bindings = require("@fabricorg/adoption-bindings");
43
- var import_gen_capability = require("@fabricorg/gen-capability");
44
- var ASSEMBLY_MANIFEST_FORMAT_VERSION = 2;
45
- var ASSEMBLY_LOCKFILE_FORMAT_VERSION = 2;
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let crypto = require("crypto");
3
+ let _fabricorg_ports = require("@fabricorg/ports");
4
+ let _fabricorg_adoption_bindings = require("@fabricorg/adoption-bindings");
5
+ let _fabricorg_gen_capability = require("@fabricorg/gen-capability");
6
+ //#region index.ts
7
+ /**
8
+ * Version of the assembly manifest document. A vertical negotiates on this, not
9
+ * on this package's version, and it moves only when the serialized shape changes.
10
+ */
11
+ const ASSEMBLY_MANIFEST_FORMAT_VERSION = 2;
12
+ /**
13
+ * Version of the resolved lockfile document. Bump when the serialized shape
14
+ * changes in a way a consumer must detect.
15
+ */
16
+ const ASSEMBLY_LOCKFILE_FORMAT_VERSION = 2;
46
17
  function parseSemver(input) {
47
- const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(input.trim());
48
- if (!match) return void 0;
49
- return {
50
- major: Number(match[1]),
51
- minor: Number(match[2]),
52
- patch: Number(match[3]),
53
- prerelease: match[4] ? match[4].split(".") : []
54
- };
18
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(input.trim());
19
+ if (!match) return void 0;
20
+ return {
21
+ major: Number(match[1]),
22
+ minor: Number(match[2]),
23
+ patch: Number(match[3]),
24
+ prerelease: match[4] ? match[4].split(".") : []
25
+ };
55
26
  }
56
27
  function comparePrerelease(a, b) {
57
- if (a.length === 0 && b.length === 0) return 0;
58
- if (a.length === 0) return 1;
59
- if (b.length === 0) return -1;
60
- for (let i = 0; i < Math.max(a.length, b.length); i++) {
61
- const ai = a[i];
62
- const bi = b[i];
63
- if (ai === void 0) return -1;
64
- if (bi === void 0) return 1;
65
- const an = /^\d+$/.test(ai);
66
- const bn = /^\d+$/.test(bi);
67
- if (an && bn) {
68
- const diff = Number(ai) - Number(bi);
69
- if (diff !== 0) return diff;
70
- } else if (an) {
71
- return -1;
72
- } else if (bn) {
73
- return 1;
74
- } else {
75
- if (ai < bi) return -1;
76
- if (ai > bi) return 1;
77
- }
78
- }
79
- return 0;
28
+ if (a.length === 0 && b.length === 0) return 0;
29
+ if (a.length === 0) return 1;
30
+ if (b.length === 0) return -1;
31
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
32
+ const ai = a[i];
33
+ const bi = b[i];
34
+ if (ai === void 0) return -1;
35
+ if (bi === void 0) return 1;
36
+ const an = /^\d+$/.test(ai);
37
+ const bn = /^\d+$/.test(bi);
38
+ if (an && bn) {
39
+ const diff = Number(ai) - Number(bi);
40
+ if (diff !== 0) return diff;
41
+ } else if (an) return -1;
42
+ else if (bn) return 1;
43
+ else {
44
+ if (ai < bi) return -1;
45
+ if (ai > bi) return 1;
46
+ }
47
+ }
48
+ return 0;
80
49
  }
81
50
  function compareSemver(a, b) {
82
- if (a.major !== b.major) return a.major - b.major;
83
- if (a.minor !== b.minor) return a.minor - b.minor;
84
- if (a.patch !== b.patch) return a.patch - b.patch;
85
- return comparePrerelease(a.prerelease, b.prerelease);
51
+ if (a.major !== b.major) return a.major - b.major;
52
+ if (a.minor !== b.minor) return a.minor - b.minor;
53
+ if (a.patch !== b.patch) return a.patch - b.patch;
54
+ return comparePrerelease(a.prerelease, b.prerelease);
86
55
  }
87
56
  function caretComparator(range) {
88
- return (version) => {
89
- if (compareSemver(version, range) < 0) return false;
90
- if (version.major !== range.major) return false;
91
- if (version.major === 0) {
92
- if (version.minor !== range.minor) return false;
93
- if (range.minor === 0 && version.patch !== range.patch) return false;
94
- }
95
- return true;
96
- };
57
+ return (version) => {
58
+ if (compareSemver(version, range) < 0) return false;
59
+ if (version.major !== range.major) return false;
60
+ if (version.major === 0) {
61
+ if (version.minor !== range.minor) return false;
62
+ if (range.minor === 0 && version.patch !== range.patch) return false;
63
+ }
64
+ return true;
65
+ };
97
66
  }
98
67
  function tildeComparator(range) {
99
- return (version) => {
100
- if (compareSemver(version, range) < 0) return false;
101
- return version.major === range.major && version.minor === range.minor;
102
- };
68
+ return (version) => {
69
+ if (compareSemver(version, range) < 0) return false;
70
+ return version.major === range.major && version.minor === range.minor;
71
+ };
103
72
  }
104
73
  function exactComparator(range) {
105
- return (version) => compareSemver(version, range) === 0;
74
+ return (version) => compareSemver(version, range) === 0;
106
75
  }
107
76
  function gteComparator(range) {
108
- return (version) => compareSemver(version, range) >= 0;
77
+ return (version) => compareSemver(version, range) >= 0;
109
78
  }
110
79
  function gtComparator(range) {
111
- return (version) => compareSemver(version, range) > 0;
80
+ return (version) => compareSemver(version, range) > 0;
112
81
  }
113
82
  function lteComparator(range) {
114
- return (version) => compareSemver(version, range) <= 0;
83
+ return (version) => compareSemver(version, range) <= 0;
115
84
  }
116
85
  function ltComparator(range) {
117
- return (version) => compareSemver(version, range) < 0;
86
+ return (version) => compareSemver(version, range) < 0;
118
87
  }
119
88
  function anyComparator() {
120
- return () => true;
89
+ return () => true;
121
90
  }
122
91
  function parseComparator(raw) {
123
- const trimmed = raw.trim();
124
- if (trimmed === "*" || trimmed === "") return { operator: "*", comparator: anyComparator(), raw: trimmed, rangeVersion: void 0 };
125
- const caret = /^(\^)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
126
- if (caret) {
127
- const version = parseSemver(caret[2]);
128
- if (!version) return void 0;
129
- return { operator: "^", comparator: caretComparator(version), raw: trimmed, rangeVersion: version };
130
- }
131
- const tilde = /^(~)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
132
- if (tilde) {
133
- const version = parseSemver(tilde[2]);
134
- if (!version) return void 0;
135
- return { operator: "~", comparator: tildeComparator(version), raw: trimmed, rangeVersion: version };
136
- }
137
- const op = /^(>=|<=|>|<|=)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
138
- if (op) {
139
- const version = parseSemver(op[2]);
140
- if (!version) return void 0;
141
- const operator = op[1];
142
- let comparator;
143
- switch (operator) {
144
- case ">=":
145
- comparator = gteComparator(version);
146
- break;
147
- case "<=":
148
- comparator = lteComparator(version);
149
- break;
150
- case ">":
151
- comparator = gtComparator(version);
152
- break;
153
- case "<":
154
- comparator = ltComparator(version);
155
- break;
156
- default:
157
- comparator = exactComparator(version);
158
- break;
159
- }
160
- return { operator, comparator, raw: trimmed, rangeVersion: version };
161
- }
162
- const bare = /^(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
163
- if (bare) {
164
- const version = parseSemver(bare[1]);
165
- if (!version) return void 0;
166
- return { operator: "=", comparator: exactComparator(version), raw: trimmed, rangeVersion: version };
167
- }
168
- return void 0;
92
+ const trimmed = raw.trim();
93
+ if (trimmed === "*" || trimmed === "") return {
94
+ operator: "*",
95
+ comparator: anyComparator(),
96
+ raw: trimmed,
97
+ rangeVersion: void 0
98
+ };
99
+ const caret = /^(\^)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
100
+ if (caret) {
101
+ const version = parseSemver(caret[2]);
102
+ if (!version) return void 0;
103
+ return {
104
+ operator: "^",
105
+ comparator: caretComparator(version),
106
+ raw: trimmed,
107
+ rangeVersion: version
108
+ };
109
+ }
110
+ const tilde = /^(~)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
111
+ if (tilde) {
112
+ const version = parseSemver(tilde[2]);
113
+ if (!version) return void 0;
114
+ return {
115
+ operator: "~",
116
+ comparator: tildeComparator(version),
117
+ raw: trimmed,
118
+ rangeVersion: version
119
+ };
120
+ }
121
+ const op = /^(>=|<=|>|<|=)(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
122
+ if (op) {
123
+ const version = parseSemver(op[2]);
124
+ if (!version) return void 0;
125
+ const operator = op[1];
126
+ let comparator;
127
+ switch (operator) {
128
+ case ">=":
129
+ comparator = gteComparator(version);
130
+ break;
131
+ case "<=":
132
+ comparator = lteComparator(version);
133
+ break;
134
+ case ">":
135
+ comparator = gtComparator(version);
136
+ break;
137
+ case "<":
138
+ comparator = ltComparator(version);
139
+ break;
140
+ default: comparator = exactComparator(version);
141
+ }
142
+ return {
143
+ operator,
144
+ comparator,
145
+ raw: trimmed,
146
+ rangeVersion: version
147
+ };
148
+ }
149
+ const bare = /^(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.exec(trimmed);
150
+ if (bare) {
151
+ const version = parseSemver(bare[1]);
152
+ if (!version) return void 0;
153
+ return {
154
+ operator: "=",
155
+ comparator: exactComparator(version),
156
+ raw: trimmed,
157
+ rangeVersion: version
158
+ };
159
+ }
169
160
  }
170
161
  function comparatorRangeVersion(comparator) {
171
- return comparator.rangeVersion;
162
+ return comparator.rangeVersion;
172
163
  }
164
+ /**
165
+ * Parse a semver range string into a {@link SemverRange}. Returns `undefined`
166
+ * for an unparseable range rather than throwing, so callers can distinguish
167
+ * "bad range" from "no satisfying version".
168
+ */
173
169
  function parseSemverRange(raw) {
174
- const trimmed = raw.trim();
175
- if (!trimmed) return void 0;
176
- const parts = trimmed.split(/\s+/);
177
- const comparators = [];
178
- for (const part of parts) {
179
- const parsed = parseComparator(part);
180
- if (!parsed) return void 0;
181
- comparators.push(parsed);
182
- }
183
- if (comparators.length === 0) return void 0;
184
- return { comparators, raw: trimmed };
185
- }
170
+ const trimmed = raw.trim();
171
+ if (!trimmed) return void 0;
172
+ const parts = trimmed.split(/\s+/);
173
+ const comparators = [];
174
+ for (const part of parts) {
175
+ const parsed = parseComparator(part);
176
+ if (!parsed) return void 0;
177
+ comparators.push(parsed);
178
+ }
179
+ if (comparators.length === 0) return void 0;
180
+ return {
181
+ comparators,
182
+ raw: trimmed
183
+ };
184
+ }
185
+ /**
186
+ * Test whether a version string satisfies a parsed range.
187
+ *
188
+ * Prerelease versions only satisfy a range when a comparator in the range
189
+ * declares a prerelease at the same `[major, minor, patch]` tuple. This
190
+ * follows npm semantics: `^1.0.0` does not match `1.1.0-alpha`, but
191
+ * `^1.0.0-alpha` matches `1.0.0-alpha.2`.
192
+ */
186
193
  function satisfiesRange(version, range) {
187
- const parsed = parseSemver(version);
188
- if (!parsed) return false;
189
- if (!range.comparators.every((comparator) => comparator.comparator(parsed))) return false;
190
- if (parsed.prerelease.length > 0) {
191
- return range.comparators.some((comparator) => {
192
- const rangeVersion = comparatorRangeVersion(comparator);
193
- return rangeVersion !== void 0 && rangeVersion.prerelease.length > 0 && rangeVersion.major === parsed.major && rangeVersion.minor === parsed.minor && rangeVersion.patch === parsed.patch;
194
- });
195
- }
196
- return true;
197
- }
194
+ const parsed = parseSemver(version);
195
+ if (!parsed) return false;
196
+ if (!range.comparators.every((comparator) => comparator.comparator(parsed))) return false;
197
+ if (parsed.prerelease.length > 0) return range.comparators.some((comparator) => {
198
+ const rangeVersion = comparatorRangeVersion(comparator);
199
+ return rangeVersion !== void 0 && rangeVersion.prerelease.length > 0 && rangeVersion.major === parsed.major && rangeVersion.minor === parsed.minor && rangeVersion.patch === parsed.patch;
200
+ });
201
+ return true;
202
+ }
203
+ /**
204
+ * Resolve a range against a list of available versions, returning the highest
205
+ * satisfying version. Returns `undefined` when no available version satisfies
206
+ * the range. Deterministic: the same inputs always produce the same result.
207
+ */
198
208
  function resolveVersionRange(range, available) {
199
- const parsed = available.map((version) => ({ version, parsed: parseSemver(version) })).filter((entry) => entry.parsed !== void 0).filter((entry) => {
200
- if (!range.comparators.every((comparator) => comparator.comparator(entry.parsed))) return false;
201
- if (entry.parsed.prerelease.length > 0) {
202
- return range.comparators.some((comparator) => {
203
- const rangeVersion = comparatorRangeVersion(comparator);
204
- return rangeVersion !== void 0 && rangeVersion.prerelease.length > 0 && rangeVersion.major === entry.parsed.major && rangeVersion.minor === entry.parsed.minor && rangeVersion.patch === entry.parsed.patch;
205
- });
206
- }
207
- return true;
208
- }).sort((a, b) => compareSemver(b.parsed, a.parsed));
209
- return parsed[0]?.version;
209
+ return available.map((version) => ({
210
+ version,
211
+ parsed: parseSemver(version)
212
+ })).filter((entry) => entry.parsed !== void 0).filter((entry) => {
213
+ if (!range.comparators.every((comparator) => comparator.comparator(entry.parsed))) return false;
214
+ if (entry.parsed.prerelease.length > 0) return range.comparators.some((comparator) => {
215
+ const rangeVersion = comparatorRangeVersion(comparator);
216
+ return rangeVersion !== void 0 && rangeVersion.prerelease.length > 0 && rangeVersion.major === entry.parsed.major && rangeVersion.minor === entry.parsed.minor && rangeVersion.patch === entry.parsed.patch;
217
+ });
218
+ return true;
219
+ }).sort((a, b) => compareSemver(b.parsed, a.parsed))[0]?.version;
210
220
  }
211
221
  function sortJson(value) {
212
- if (Array.isArray(value)) return value.map(sortJson);
213
- if (value && typeof value === "object") {
214
- return Object.fromEntries(
215
- Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortJson(child)])
216
- );
217
- }
218
- return value;
222
+ if (Array.isArray(value)) return value.map(sortJson);
223
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sortJson(child)]));
224
+ return value;
219
225
  }
220
226
  function stableStringify(value) {
221
- return JSON.stringify(sortJson(value));
227
+ return JSON.stringify(sortJson(value));
222
228
  }
223
229
  function assemblyDigest(value) {
224
- return (0, import_node_crypto.createHash)("sha256").update(stableStringify(value)).digest("hex");
230
+ return (0, crypto.createHash)("sha256").update(stableStringify(value)).digest("hex");
225
231
  }
232
+ /** Compute the digest stored for an exact generated usage-contract document. */
226
233
  function computeUsageContractDocumentDigest(document) {
227
- (0, import_gen_capability.assertUsageContractDocument)(document);
228
- return assemblyDigest(document);
229
- }
234
+ (0, _fabricorg_gen_capability.assertUsageContractDocument)(document);
235
+ return assemblyDigest(document);
236
+ }
237
+ /**
238
+ * The canonical digest of a lockfile's resolved content.
239
+ *
240
+ * `assertAssemblyLockfile` recomputes this on every load, so publishing it
241
+ * grants nothing an attacker did not already have; it lets tooling and tests
242
+ * construct a self-consistent lockfile instead of hand-fitting a hash. A
243
+ * forged lockfile still fails downstream, because a release binds the assembly
244
+ * digest it was validated against.
245
+ */
230
246
  function computeAssemblyLockfileDigest(lockfile) {
231
- return assemblyDigest(lockfileDigestBody(lockfile));
247
+ return assemblyDigest(lockfileDigestBody(lockfile));
232
248
  }
233
249
  function lockfileDigestBody(lockfile) {
234
- return {
235
- formatVersion: lockfile.formatVersion,
236
- application: lockfile.application,
237
- capabilities: lockfile.capabilities,
238
- componentPacks: lockfile.componentPacks,
239
- bindings: lockfile.bindings,
240
- // Omitted when absent, so a lockfile from before the adapter plane
241
- // keeps the digest it was approved with.
242
- ...lockfile.adapters ? { adapters: lockfile.adapters } : {}
243
- };
244
- }
250
+ return {
251
+ formatVersion: lockfile.formatVersion,
252
+ application: lockfile.application,
253
+ capabilities: lockfile.capabilities,
254
+ componentPacks: lockfile.componentPacks,
255
+ bindings: lockfile.bindings,
256
+ ...lockfile.adapters ? { adapters: lockfile.adapters } : {}
257
+ };
258
+ }
259
+ /** The digest a certification record locks under: the whole record, keys sorted, checks sorted. */
245
260
  function computeAdapterCertificationDigest(certification) {
246
- return assemblyDigest({ ...certification, checks: [...certification.checks].sort() });
261
+ return assemblyDigest({
262
+ ...certification,
263
+ checks: [...certification.checks].sort()
264
+ });
247
265
  }
248
266
  function fail(path, message) {
249
- throw new Error(`Assembly manifest ${path} ${message}.`);
267
+ throw new Error(`Assembly manifest ${path} ${message}.`);
250
268
  }
251
269
  function requireString(value, path) {
252
- if (typeof value !== "string" || !value.trim()) fail(path, "must be a non-empty string");
253
- return value;
270
+ if (typeof value !== "string" || !value.trim()) fail(path, "must be a non-empty string");
271
+ return value;
254
272
  }
255
273
  function requireArray(value, path) {
256
- if (!Array.isArray(value)) fail(path, "must be an array");
257
- return value;
274
+ if (!Array.isArray(value)) fail(path, "must be an array");
275
+ return value;
258
276
  }
277
+ /**
278
+ * Structural validation of an assembly manifest. Checks the document's own
279
+ * shape; does not resolve ranges or validate contracts/bindings.
280
+ */
259
281
  function assertAssemblyManifest(value, path = "assembly") {
260
- if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
261
- const manifest = value;
262
- if (manifest.formatVersion !== ASSEMBLY_MANIFEST_FORMAT_VERSION) {
263
- fail(`${path}.formatVersion`, `must be ${ASSEMBLY_MANIFEST_FORMAT_VERSION}, received ${JSON.stringify(manifest.formatVersion)}`);
264
- }
265
- if (!/^[a-z][a-z0-9-]*$/.test(requireString(manifest.application, `${path}.application`))) {
266
- fail(`${path}.application`, "must be lowercase letters, numbers and hyphens");
267
- }
268
- requireArray(manifest.capabilities, `${path}.capabilities`).forEach((entry, index) => {
269
- const capability = entry;
270
- requireString(capability?.namespace, `${path}.capabilities[${index}].namespace`);
271
- requireString(capability?.range, `${path}.capabilities[${index}].range`);
272
- });
273
- requireArray(manifest.componentPacks, `${path}.componentPacks`).forEach((entry, index) => {
274
- const pack = entry;
275
- requireString(pack?.pack, `${path}.componentPacks[${index}].pack`);
276
- requireString(pack?.namespace, `${path}.componentPacks[${index}].namespace`);
277
- requireString(pack?.range, `${path}.componentPacks[${index}].range`);
278
- });
279
- requireArray(manifest.bindings, `${path}.bindings`).forEach((entry, index) => {
280
- const binding = entry;
281
- requireString(binding?.capability, `${path}.bindings[${index}].capability`);
282
- (0, import_adoption_bindings.assertAdoptionBinding)(binding?.binding, `${path}.bindings[${index}].binding`);
283
- });
284
- requireArray(manifest.contracts, `${path}.contracts`).forEach((entry, index) => {
285
- const contract = entry;
286
- requireString(contract?.namespace, `${path}.contracts[${index}].namespace`);
287
- try {
288
- (0, import_gen_capability.assertUsageContractDocument)(contract?.document);
289
- } catch (error) {
290
- fail(`${path}.contracts[${index}].document`, error instanceof Error ? error.message : String(error));
291
- }
292
- });
293
- if (manifest.adapters !== void 0) {
294
- requireArray(manifest.adapters, `${path}.adapters`).forEach((entry, index) => {
295
- assertAdapterEntry(entry, `${path}.adapters[${index}]`);
296
- });
297
- }
282
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
283
+ const manifest = value;
284
+ if (manifest.formatVersion !== 2) fail(`${path}.formatVersion`, `must be 2, received ${JSON.stringify(manifest.formatVersion)}`);
285
+ if (!/^[a-z][a-z0-9-]*$/.test(requireString(manifest.application, `${path}.application`))) fail(`${path}.application`, "must be lowercase letters, numbers and hyphens");
286
+ requireArray(manifest.capabilities, `${path}.capabilities`).forEach((entry, index) => {
287
+ const capability = entry;
288
+ requireString(capability?.namespace, `${path}.capabilities[${index}].namespace`);
289
+ requireString(capability?.range, `${path}.capabilities[${index}].range`);
290
+ });
291
+ requireArray(manifest.componentPacks, `${path}.componentPacks`).forEach((entry, index) => {
292
+ const pack = entry;
293
+ requireString(pack?.pack, `${path}.componentPacks[${index}].pack`);
294
+ requireString(pack?.namespace, `${path}.componentPacks[${index}].namespace`);
295
+ requireString(pack?.range, `${path}.componentPacks[${index}].range`);
296
+ });
297
+ requireArray(manifest.bindings, `${path}.bindings`).forEach((entry, index) => {
298
+ const binding = entry;
299
+ requireString(binding?.capability, `${path}.bindings[${index}].capability`);
300
+ (0, _fabricorg_adoption_bindings.assertAdoptionBinding)(binding?.binding, `${path}.bindings[${index}].binding`);
301
+ });
302
+ requireArray(manifest.contracts, `${path}.contracts`).forEach((entry, index) => {
303
+ const contract = entry;
304
+ requireString(contract?.namespace, `${path}.contracts[${index}].namespace`);
305
+ try {
306
+ (0, _fabricorg_gen_capability.assertUsageContractDocument)(contract?.document);
307
+ } catch (error) {
308
+ fail(`${path}.contracts[${index}].document`, error instanceof Error ? error.message : String(error));
309
+ }
310
+ });
311
+ if (manifest.adapters !== void 0) requireArray(manifest.adapters, `${path}.adapters`).forEach((entry, index) => {
312
+ assertAdapterEntry(entry, `${path}.adapters[${index}]`);
313
+ });
298
314
  }
299
315
  function assertAdapterEntry(value, path) {
300
- if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
301
- const adapter = value;
302
- requireString(adapter.port, `${path}.port`);
303
- requireString(adapter.vendor, `${path}.vendor`);
304
- if (adapter.version !== void 0) requireString(adapter.version, `${path}.version`);
305
- if (adapter.standard !== void 0) {
306
- const standard = adapter.standard;
307
- if (!standard || typeof standard !== "object" || Array.isArray(standard)) fail(`${path}.standard`, "must be an object");
308
- requireString(standard.name, `${path}.standard.name`);
309
- if (standard.version !== void 0) requireString(standard.version, `${path}.standard.version`);
310
- }
311
- if (adapter.certification !== void 0) {
312
- const certification = adapter.certification;
313
- if (!certification || typeof certification !== "object" || Array.isArray(certification)) fail(`${path}.certification`, "must be an object");
314
- requireString(certification.portId, `${path}.certification.portId`);
315
- if (!/^\d+\.\d+\.\d+$/.test(requireString(certification.portVersion, `${path}.certification.portVersion`))) {
316
- fail(`${path}.certification.portVersion`, "must be a semver version");
317
- }
318
- const checks = requireArray(certification.checks, `${path}.certification.checks`);
319
- if (checks.length === 0 || !checks.every((check) => typeof check === "string" && check.trim())) {
320
- fail(`${path}.certification.checks`, "must name at least one check");
321
- }
322
- if (Number.isNaN(Date.parse(requireString(certification.certifiedAt, `${path}.certification.certifiedAt`)))) {
323
- fail(`${path}.certification.certifiedAt`, "must be an instant");
324
- }
325
- if (certification.portId !== adapter.port) {
326
- fail(`${path}.certification.portId`, `must match the adapter's port "${String(adapter.port)}"`);
327
- }
328
- }
329
- }
316
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
317
+ const adapter = value;
318
+ requireString(adapter.port, `${path}.port`);
319
+ requireString(adapter.vendor, `${path}.vendor`);
320
+ if (adapter.version !== void 0) requireString(adapter.version, `${path}.version`);
321
+ if (adapter.standard !== void 0) {
322
+ const standard = adapter.standard;
323
+ if (!standard || typeof standard !== "object" || Array.isArray(standard)) fail(`${path}.standard`, "must be an object");
324
+ requireString(standard.name, `${path}.standard.name`);
325
+ if (standard.version !== void 0) requireString(standard.version, `${path}.standard.version`);
326
+ }
327
+ if (adapter.certification !== void 0) {
328
+ const certification = adapter.certification;
329
+ if (!certification || typeof certification !== "object" || Array.isArray(certification)) fail(`${path}.certification`, "must be an object");
330
+ requireString(certification.portId, `${path}.certification.portId`);
331
+ if (!/^\d+\.\d+\.\d+$/.test(requireString(certification.portVersion, `${path}.certification.portVersion`))) fail(`${path}.certification.portVersion`, "must be a semver version");
332
+ const checks = requireArray(certification.checks, `${path}.certification.checks`);
333
+ if (checks.length === 0 || !checks.every((check) => typeof check === "string" && check.trim())) fail(`${path}.certification.checks`, "must name at least one check");
334
+ if (Number.isNaN(Date.parse(requireString(certification.certifiedAt, `${path}.certification.certifiedAt`)))) fail(`${path}.certification.certifiedAt`, "must be an instant");
335
+ if (certification.portId !== adapter.port) fail(`${path}.certification.portId`, `must match the adapter's port "${String(adapter.port)}"`);
336
+ }
337
+ }
338
+ /**
339
+ * Structural validation of a component pack manifest.
340
+ */
330
341
  function assertComponentPackManifest(value, path = "pack") {
331
- if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
332
- const pack = value;
333
- requireString(pack.pack, `${path}.pack`);
334
- requireString(pack.namespace, `${path}.namespace`);
335
- requireString(pack.version, `${path}.version`);
336
- if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(pack.version)) {
337
- fail(`${path}.version`, "must be a semver version");
338
- }
339
- if (!pack.components || typeof pack.components !== "object" || Array.isArray(pack.components)) {
340
- fail(`${path}.components`, "must be an object");
341
- }
342
- if (!/^[0-9a-f]{64}$/.test(requireString(pack.artifactDigest, `${path}.artifactDigest`))) {
343
- fail(`${path}.artifactDigest`, "must be a lowercase SHA-256 digest");
344
- }
345
- }
342
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
343
+ const pack = value;
344
+ requireString(pack.pack, `${path}.pack`);
345
+ requireString(pack.namespace, `${path}.namespace`);
346
+ requireString(pack.version, `${path}.version`);
347
+ if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(pack.version)) fail(`${path}.version`, "must be a semver version");
348
+ if (!pack.components || typeof pack.components !== "object" || Array.isArray(pack.components)) fail(`${path}.components`, "must be an object");
349
+ if (!/^[0-9a-f]{64}$/.test(requireString(pack.artifactDigest, `${path}.artifactDigest`))) fail(`${path}.artifactDigest`, "must be a lowercase SHA-256 digest");
350
+ }
351
+ /**
352
+ * Validate a resolved lockfile and verify that its digest still identifies its
353
+ * exact application, capability, pack, contract, and binding content.
354
+ */
346
355
  function assertAssemblyLockfile(value, path = "lockfile") {
347
- if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
348
- const lockfile = value;
349
- if (lockfile.formatVersion !== ASSEMBLY_LOCKFILE_FORMAT_VERSION) {
350
- fail(`${path}.formatVersion`, `must be ${ASSEMBLY_LOCKFILE_FORMAT_VERSION}, received ${JSON.stringify(lockfile.formatVersion)}`);
351
- }
352
- if (!/^[a-z][a-z0-9-]*$/.test(requireString(lockfile.application, `${path}.application`))) {
353
- fail(`${path}.application`, "must be lowercase letters, numbers and hyphens");
354
- }
355
- if (!/^[0-9a-f]{64}$/.test(requireString(lockfile.assemblyDigest, `${path}.assemblyDigest`))) {
356
- fail(`${path}.assemblyDigest`, "must be a lowercase SHA-256 digest");
357
- }
358
- const capabilities = requireArray(lockfile.capabilities, `${path}.capabilities`);
359
- const componentPacks = requireArray(lockfile.componentPacks, `${path}.componentPacks`);
360
- const bindings = requireArray(lockfile.bindings, `${path}.bindings`);
361
- const seenCapabilities = /* @__PURE__ */ new Set();
362
- for (const [index, entry] of capabilities.entries()) {
363
- const capability = entry;
364
- const namespace = String(capability?.namespace);
365
- if (seenCapabilities.has(namespace)) {
366
- fail(`${path}.capabilities[${index}]`, `declares capability "${namespace}" a second time`);
367
- }
368
- seenCapabilities.add(namespace);
369
- requireString(capability?.namespace, `${path}.capabilities[${index}].namespace`);
370
- requireString(capability?.version, `${path}.capabilities[${index}].version`);
371
- requireString(capability?.range, `${path}.capabilities[${index}].range`);
372
- if (!/^[0-9a-f]{64}$/.test(requireString(capability?.manifestDigest, `${path}.capabilities[${index}].manifestDigest`))) {
373
- fail(`${path}.capabilities[${index}].manifestDigest`, "must be a lowercase SHA-256 digest");
374
- }
375
- if (capability?.usageContractDigest !== void 0 && !/^[0-9a-f]{64}$/.test(requireString(capability.usageContractDigest, `${path}.capabilities[${index}].usageContractDigest`))) {
376
- fail(`${path}.capabilities[${index}].usageContractDigest`, "must be a lowercase SHA-256 digest");
377
- }
378
- }
379
- const seenPackIdentities = /* @__PURE__ */ new Set();
380
- for (const [index, entry] of componentPacks.entries()) {
381
- const pack = entry;
382
- requireString(pack?.pack, `${path}.componentPacks[${index}].pack`);
383
- const identity = `${String(pack?.namespace)}/${String(pack?.pack)}`;
384
- if (seenPackIdentities.has(identity)) {
385
- fail(`${path}.componentPacks[${index}]`, `declares component pack "${identity}" a second time; first-match resolution would make the choice arbitrary`);
386
- }
387
- seenPackIdentities.add(identity);
388
- requireString(pack?.namespace, `${path}.componentPacks[${index}].namespace`);
389
- requireString(pack?.version, `${path}.componentPacks[${index}].version`);
390
- requireString(pack?.range, `${path}.componentPacks[${index}].range`);
391
- if (!/^[0-9a-f]{64}$/.test(requireString(pack?.artifactDigest, `${path}.componentPacks[${index}].artifactDigest`))) {
392
- fail(`${path}.componentPacks[${index}].artifactDigest`, "must be a lowercase SHA-256 digest");
393
- }
394
- if (pack?.remote !== void 0) {
395
- const remotePath = `${path}.componentPacks[${index}].remote`;
396
- const remote = pack.remote;
397
- if (remote === null || typeof remote !== "object" || Array.isArray(remote)) fail(remotePath, "must be an object");
398
- requireString(remote.entry, `${remotePath}.entry`);
399
- if (!/^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$/.test(requireString(remote.integrity, `${remotePath}.integrity`))) {
400
- fail(`${remotePath}.integrity`, 'must be a subresource-integrity value, e.g. "sha384-\u2026"');
401
- }
402
- const exposes = remote.exposes;
403
- if (exposes === null || typeof exposes !== "object" || Array.isArray(exposes)) {
404
- fail(`${remotePath}.exposes`, "must be an object");
405
- }
406
- for (const [component, modulePath] of Object.entries(exposes)) {
407
- requireString(modulePath, `${remotePath}.exposes.${component}`);
408
- }
409
- }
410
- }
411
- for (const [index, entry] of bindings.entries()) {
412
- const binding = entry;
413
- requireString(binding?.capability, `${path}.bindings[${index}].capability`);
414
- if (!/^[0-9a-f]{64}$/.test(requireString(binding?.bindingDigest, `${path}.bindings[${index}].bindingDigest`))) {
415
- fail(`${path}.bindings[${index}].bindingDigest`, "must be a lowercase SHA-256 digest");
416
- }
417
- if (binding?.valid !== true) fail(`${path}.bindings[${index}].valid`, "must be true");
418
- requireArray(binding?.findings, `${path}.bindings[${index}].findings`);
419
- }
420
- let adapters;
421
- if (lockfile.adapters !== void 0) {
422
- adapters = requireArray(lockfile.adapters, `${path}.adapters`);
423
- const seen = /* @__PURE__ */ new Set();
424
- for (const [index, entry] of adapters.entries()) {
425
- const adapter = entry;
426
- const port = requireString(adapter?.port, `${path}.adapters[${index}].port`);
427
- const vendor = requireString(adapter?.vendor, `${path}.adapters[${index}].vendor`);
428
- const key = `${port}/${vendor}`;
429
- if (seen.has(key)) fail(`${path}.adapters[${index}]`, `declares adapter "${key}" a second time`);
430
- seen.add(key);
431
- if (adapter?.version !== void 0) requireString(adapter.version, `${path}.adapters[${index}].version`);
432
- if (adapter?.standard !== void 0) {
433
- const standard = adapter.standard;
434
- if (!standard || typeof standard !== "object" || Array.isArray(standard)) fail(`${path}.adapters[${index}].standard`, "must be an object");
435
- requireString(standard.name, `${path}.adapters[${index}].standard.name`);
436
- if (standard.version !== void 0) requireString(standard.version, `${path}.adapters[${index}].standard.version`);
437
- }
438
- if (typeof adapter?.certified !== "boolean") fail(`${path}.adapters[${index}].certified`, "must be a boolean");
439
- if (adapter.certified) {
440
- if (!/^\d+\.\d+\.\d+$/.test(requireString(adapter?.portVersion, `${path}.adapters[${index}].portVersion`))) {
441
- fail(`${path}.adapters[${index}].portVersion`, "must be a semver version");
442
- }
443
- if (Number.isNaN(Date.parse(requireString(adapter?.certifiedAt, `${path}.adapters[${index}].certifiedAt`)))) {
444
- fail(`${path}.adapters[${index}].certifiedAt`, "must be an instant");
445
- }
446
- if (!/^[0-9a-f]{64}$/.test(requireString(adapter?.certificationDigest, `${path}.adapters[${index}].certificationDigest`))) {
447
- fail(`${path}.adapters[${index}].certificationDigest`, "must be a lowercase SHA-256 digest");
448
- }
449
- } else if (adapter.portVersion !== void 0 || adapter.certifiedAt !== void 0 || adapter.certificationDigest !== void 0) {
450
- fail(`${path}.adapters[${index}]`, "carries certification fields but is marked uncertified");
451
- }
452
- }
453
- }
454
- const expected = assemblyDigest(lockfileDigestBody({
455
- formatVersion: ASSEMBLY_LOCKFILE_FORMAT_VERSION,
456
- application: lockfile.application,
457
- capabilities,
458
- componentPacks,
459
- bindings,
460
- ...adapters ? { adapters } : {}
461
- }));
462
- if (lockfile.assemblyDigest !== expected) {
463
- fail(`${path}.assemblyDigest`, `does not match resolved lockfile content; expected "${expected}"`);
464
- }
465
- }
356
+ if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
357
+ const lockfile = value;
358
+ if (lockfile.formatVersion !== 2) fail(`${path}.formatVersion`, `must be 2, received ${JSON.stringify(lockfile.formatVersion)}`);
359
+ if (!/^[a-z][a-z0-9-]*$/.test(requireString(lockfile.application, `${path}.application`))) fail(`${path}.application`, "must be lowercase letters, numbers and hyphens");
360
+ if (!/^[0-9a-f]{64}$/.test(requireString(lockfile.assemblyDigest, `${path}.assemblyDigest`))) fail(`${path}.assemblyDigest`, "must be a lowercase SHA-256 digest");
361
+ const capabilities = requireArray(lockfile.capabilities, `${path}.capabilities`);
362
+ const componentPacks = requireArray(lockfile.componentPacks, `${path}.componentPacks`);
363
+ const bindings = requireArray(lockfile.bindings, `${path}.bindings`);
364
+ const seenCapabilities = /* @__PURE__ */ new Set();
365
+ for (const [index, entry] of capabilities.entries()) {
366
+ const capability = entry;
367
+ const namespace = String(capability?.namespace);
368
+ if (seenCapabilities.has(namespace)) fail(`${path}.capabilities[${index}]`, `declares capability "${namespace}" a second time`);
369
+ seenCapabilities.add(namespace);
370
+ requireString(capability?.namespace, `${path}.capabilities[${index}].namespace`);
371
+ requireString(capability?.version, `${path}.capabilities[${index}].version`);
372
+ requireString(capability?.range, `${path}.capabilities[${index}].range`);
373
+ if (!/^[0-9a-f]{64}$/.test(requireString(capability?.manifestDigest, `${path}.capabilities[${index}].manifestDigest`))) fail(`${path}.capabilities[${index}].manifestDigest`, "must be a lowercase SHA-256 digest");
374
+ if (capability?.usageContractDigest !== void 0 && !/^[0-9a-f]{64}$/.test(requireString(capability.usageContractDigest, `${path}.capabilities[${index}].usageContractDigest`))) fail(`${path}.capabilities[${index}].usageContractDigest`, "must be a lowercase SHA-256 digest");
375
+ }
376
+ const seenPackIdentities = /* @__PURE__ */ new Set();
377
+ for (const [index, entry] of componentPacks.entries()) {
378
+ const pack = entry;
379
+ requireString(pack?.pack, `${path}.componentPacks[${index}].pack`);
380
+ const identity = `${String(pack?.namespace)}/${String(pack?.pack)}`;
381
+ if (seenPackIdentities.has(identity)) fail(`${path}.componentPacks[${index}]`, `declares component pack "${identity}" a second time; first-match resolution would make the choice arbitrary`);
382
+ seenPackIdentities.add(identity);
383
+ requireString(pack?.namespace, `${path}.componentPacks[${index}].namespace`);
384
+ requireString(pack?.version, `${path}.componentPacks[${index}].version`);
385
+ requireString(pack?.range, `${path}.componentPacks[${index}].range`);
386
+ if (!/^[0-9a-f]{64}$/.test(requireString(pack?.artifactDigest, `${path}.componentPacks[${index}].artifactDigest`))) fail(`${path}.componentPacks[${index}].artifactDigest`, "must be a lowercase SHA-256 digest");
387
+ if (pack?.remote !== void 0) {
388
+ const remotePath = `${path}.componentPacks[${index}].remote`;
389
+ const remote = pack.remote;
390
+ if (remote === null || typeof remote !== "object" || Array.isArray(remote)) fail(remotePath, "must be an object");
391
+ requireString(remote.entry, `${remotePath}.entry`);
392
+ if (!/^sha(256|384|512)-[A-Za-z0-9+/]+={0,2}$/.test(requireString(remote.integrity, `${remotePath}.integrity`))) fail(`${remotePath}.integrity`, "must be a subresource-integrity value, e.g. \"sha384-…\"");
393
+ const exposes = remote.exposes;
394
+ if (exposes === null || typeof exposes !== "object" || Array.isArray(exposes)) fail(`${remotePath}.exposes`, "must be an object");
395
+ for (const [component, modulePath] of Object.entries(exposes)) requireString(modulePath, `${remotePath}.exposes.${component}`);
396
+ }
397
+ }
398
+ for (const [index, entry] of bindings.entries()) {
399
+ const binding = entry;
400
+ requireString(binding?.capability, `${path}.bindings[${index}].capability`);
401
+ if (!/^[0-9a-f]{64}$/.test(requireString(binding?.bindingDigest, `${path}.bindings[${index}].bindingDigest`))) fail(`${path}.bindings[${index}].bindingDigest`, "must be a lowercase SHA-256 digest");
402
+ if (binding?.valid !== true) fail(`${path}.bindings[${index}].valid`, "must be true");
403
+ requireArray(binding?.findings, `${path}.bindings[${index}].findings`);
404
+ }
405
+ let adapters;
406
+ if (lockfile.adapters !== void 0) {
407
+ adapters = requireArray(lockfile.adapters, `${path}.adapters`);
408
+ const seen = /* @__PURE__ */ new Set();
409
+ for (const [index, entry] of adapters.entries()) {
410
+ const adapter = entry;
411
+ const key = `${requireString(adapter?.port, `${path}.adapters[${index}].port`)}/${requireString(adapter?.vendor, `${path}.adapters[${index}].vendor`)}`;
412
+ if (seen.has(key)) fail(`${path}.adapters[${index}]`, `declares adapter "${key}" a second time`);
413
+ seen.add(key);
414
+ if (adapter?.version !== void 0) requireString(adapter.version, `${path}.adapters[${index}].version`);
415
+ if (adapter?.standard !== void 0) {
416
+ const standard = adapter.standard;
417
+ if (!standard || typeof standard !== "object" || Array.isArray(standard)) fail(`${path}.adapters[${index}].standard`, "must be an object");
418
+ requireString(standard.name, `${path}.adapters[${index}].standard.name`);
419
+ if (standard.version !== void 0) requireString(standard.version, `${path}.adapters[${index}].standard.version`);
420
+ }
421
+ if (typeof adapter?.certified !== "boolean") fail(`${path}.adapters[${index}].certified`, "must be a boolean");
422
+ if (adapter.certified) {
423
+ if (!/^\d+\.\d+\.\d+$/.test(requireString(adapter?.portVersion, `${path}.adapters[${index}].portVersion`))) fail(`${path}.adapters[${index}].portVersion`, "must be a semver version");
424
+ if (Number.isNaN(Date.parse(requireString(adapter?.certifiedAt, `${path}.adapters[${index}].certifiedAt`)))) fail(`${path}.adapters[${index}].certifiedAt`, "must be an instant");
425
+ if (!/^[0-9a-f]{64}$/.test(requireString(adapter?.certificationDigest, `${path}.adapters[${index}].certificationDigest`))) fail(`${path}.adapters[${index}].certificationDigest`, "must be a lowercase SHA-256 digest");
426
+ } else if (adapter.portVersion !== void 0 || adapter.certifiedAt !== void 0 || adapter.certificationDigest !== void 0) fail(`${path}.adapters[${index}]`, "carries certification fields but is marked uncertified");
427
+ }
428
+ }
429
+ const expected = assemblyDigest(lockfileDigestBody({
430
+ formatVersion: 2,
431
+ application: lockfile.application,
432
+ capabilities,
433
+ componentPacks,
434
+ bindings,
435
+ ...adapters ? { adapters } : {}
436
+ }));
437
+ if (lockfile.assemblyDigest !== expected) fail(`${path}.assemblyDigest`, `does not match resolved lockfile content; expected "${expected}"`);
438
+ }
439
+ /**
440
+ * Verify that the capability artifacts loaded by a runtime are exactly those
441
+ * approved by an integrity-checked assembly lockfile.
442
+ */
466
443
  function assertAssemblyRuntimeCompatible(lockfile, loadedCapabilities, registeredAdapters) {
467
- assertAssemblyLockfile(lockfile);
468
- if (lockfile.adapters !== void 0 && registeredAdapters !== void 0) {
469
- const registeredByKey = new Map(registeredAdapters.map((adapter) => [`${adapter.port}/${adapter.vendor}`, adapter]));
470
- for (const approved of lockfile.adapters) {
471
- const key = `${approved.port}/${approved.vendor}`;
472
- const registered = registeredByKey.get(key);
473
- if (!registered) throw new Error(`Approved adapter "${key}" is not registered by this runtime.`);
474
- if (approved.version !== void 0 && registered.version !== approved.version) {
475
- throw new Error(`Registered adapter "${key}" is version "${registered.version ?? "unversioned"}"; the assembly approved "${approved.version}".`);
476
- }
477
- if (approved.certified) {
478
- const digest = registered.certification ? computeAdapterCertificationDigest(registered.certification) : void 0;
479
- if (digest !== approved.certificationDigest) {
480
- throw new Error(`Registered adapter "${key}" does not carry the certification the assembly approved (digest "${approved.certificationDigest}").`);
481
- }
482
- }
483
- }
484
- for (const registered of registeredAdapters) {
485
- const key = `${registered.port}/${registered.vendor}`;
486
- if (!lockfile.adapters.some((approved) => approved.port === registered.port && approved.vendor === registered.vendor)) {
487
- throw new Error(`Registered adapter "${key}" is not approved by assembly "${lockfile.assemblyDigest}".`);
488
- }
489
- }
490
- }
491
- const loadedByNamespace = /* @__PURE__ */ new Map();
492
- for (const loaded of loadedCapabilities) {
493
- if (loadedByNamespace.has(loaded.namespace)) {
494
- throw new Error(`Loaded capability "${loaded.namespace}" is duplicated.`);
495
- }
496
- loadedByNamespace.set(loaded.namespace, loaded);
497
- }
498
- for (const approved of lockfile.capabilities) {
499
- const loaded = loadedByNamespace.get(approved.namespace);
500
- if (!loaded || loaded.version !== approved.version || loaded.manifestDigest !== approved.manifestDigest) {
501
- throw new Error(
502
- `Loaded capability "${approved.namespace}" does not match approved capability "${approved.namespace}@${approved.version}" with manifest digest "${approved.manifestDigest}".`
503
- );
504
- }
505
- }
506
- for (const loaded of loadedCapabilities) {
507
- if (!lockfile.capabilities.some((approved) => approved.namespace === loaded.namespace)) {
508
- throw new Error(`Loaded capability "${loaded.namespace}" is not approved by assembly "${lockfile.assemblyDigest}".`);
509
- }
510
- }
511
- }
444
+ assertAssemblyLockfile(lockfile);
445
+ if (lockfile.adapters !== void 0 && registeredAdapters !== void 0) {
446
+ const registeredByKey = new Map(registeredAdapters.map((adapter) => [`${adapter.port}/${adapter.vendor}`, adapter]));
447
+ for (const approved of lockfile.adapters) {
448
+ const key = `${approved.port}/${approved.vendor}`;
449
+ const registered = registeredByKey.get(key);
450
+ if (!registered) throw new Error(`Approved adapter "${key}" is not registered by this runtime.`);
451
+ if (approved.version !== void 0 && registered.version !== approved.version) throw new Error(`Registered adapter "${key}" is version "${registered.version ?? "unversioned"}"; the assembly approved "${approved.version}".`);
452
+ if (approved.certified) {
453
+ if ((registered.certification ? computeAdapterCertificationDigest(registered.certification) : void 0) !== approved.certificationDigest) throw new Error(`Registered adapter "${key}" does not carry the certification the assembly approved (digest "${approved.certificationDigest}").`);
454
+ }
455
+ }
456
+ for (const registered of registeredAdapters) {
457
+ const key = `${registered.port}/${registered.vendor}`;
458
+ if (!lockfile.adapters.some((approved) => approved.port === registered.port && approved.vendor === registered.vendor)) throw new Error(`Registered adapter "${key}" is not approved by assembly "${lockfile.assemblyDigest}".`);
459
+ }
460
+ }
461
+ const loadedByNamespace = /* @__PURE__ */ new Map();
462
+ for (const loaded of loadedCapabilities) {
463
+ if (loadedByNamespace.has(loaded.namespace)) throw new Error(`Loaded capability "${loaded.namespace}" is duplicated.`);
464
+ loadedByNamespace.set(loaded.namespace, loaded);
465
+ }
466
+ for (const approved of lockfile.capabilities) {
467
+ const loaded = loadedByNamespace.get(approved.namespace);
468
+ if (!loaded || loaded.version !== approved.version || loaded.manifestDigest !== approved.manifestDigest) throw new Error(`Loaded capability "${approved.namespace}" does not match approved capability "${approved.namespace}@${approved.version}" with manifest digest "${approved.manifestDigest}".`);
469
+ }
470
+ for (const loaded of loadedCapabilities) if (!lockfile.capabilities.some((approved) => approved.namespace === loaded.namespace)) throw new Error(`Loaded capability "${loaded.namespace}" is not approved by assembly "${lockfile.assemblyDigest}".`);
471
+ }
472
+ /**
473
+ * Resolve an assembly manifest into a deterministic lockfile. This is the
474
+ * single build gate: it resolves version ranges, rejects unresolved or
475
+ * duplicate capability identities, validates usage contracts, component
476
+ * packs, and adoption bindings, and produces a lockfile with a canonical
477
+ * digest.
478
+ *
479
+ * Returns every finding rather than throwing, so one CI run tells a vertical
480
+ * everything it has to change. Use {@link assertAssemblyResolved} to throw
481
+ * on the first invalid result.
482
+ */
512
483
  function resolveAssembly(input) {
513
- assertAssemblyManifest(input.manifest);
514
- const findings = [];
515
- const add = (code, path, message) => {
516
- findings.push({ code, path, message });
517
- };
518
- const capabilityNamespaces = /* @__PURE__ */ new Set();
519
- for (const capability of input.manifest.capabilities) {
520
- if (capabilityNamespaces.has(capability.namespace)) {
521
- add(
522
- "duplicate_capability",
523
- `assembly.capabilities.${capability.namespace}`,
524
- `capability "${capability.namespace}" is declared more than once`
525
- );
526
- }
527
- capabilityNamespaces.add(capability.namespace);
528
- }
529
- const packKeys = /* @__PURE__ */ new Set();
530
- for (const pack of input.manifest.componentPacks) {
531
- const key = `${pack.namespace}/${pack.pack}`;
532
- if (packKeys.has(key)) {
533
- add(
534
- "duplicate_component_pack",
535
- `assembly.componentPacks.${key}`,
536
- `component pack "${key}" is declared more than once`
537
- );
538
- }
539
- packKeys.add(key);
540
- }
541
- const contractNamespaces = /* @__PURE__ */ new Set();
542
- for (const contract of input.manifest.contracts) {
543
- if (contractNamespaces.has(contract.namespace)) {
544
- add(
545
- "duplicate_contract",
546
- `assembly.contracts.${contract.namespace}`,
547
- `contract for "${contract.namespace}" is declared more than once`
548
- );
549
- }
550
- contractNamespaces.add(contract.namespace);
551
- }
552
- const resolvedCapabilities = [];
553
- const capabilitiesByNamespace = /* @__PURE__ */ new Map();
554
- for (const available of input.capabilities) {
555
- const list = capabilitiesByNamespace.get(available.namespace) ?? [];
556
- list.push(available);
557
- capabilitiesByNamespace.set(available.namespace, list);
558
- }
559
- for (const ref of input.manifest.capabilities) {
560
- const range = parseSemverRange(ref.range);
561
- if (!range) {
562
- add(
563
- "invalid_range",
564
- `assembly.capabilities.${ref.namespace}.range`,
565
- `range "${ref.range}" is not a valid semver range`
566
- );
567
- continue;
568
- }
569
- const available = capabilitiesByNamespace.get(ref.namespace) ?? [];
570
- const versions = available.map((entry) => entry.version);
571
- const resolved = resolveVersionRange(range, versions);
572
- if (!resolved) {
573
- add(
574
- "unresolved_capability",
575
- `assembly.capabilities.${ref.namespace}`,
576
- `no available version of "${ref.namespace}" satisfies range "${ref.range}"`
577
- );
578
- continue;
579
- }
580
- const matched = available.find((entry) => entry.version === resolved);
581
- resolvedCapabilities.push({
582
- namespace: ref.namespace,
583
- version: resolved,
584
- range: ref.range,
585
- manifestDigest: matched.manifestDigest
586
- });
587
- }
588
- const resolvedPacks = [];
589
- const packsByKey = /* @__PURE__ */ new Map();
590
- for (const available of input.componentPacks) {
591
- const key = `${available.namespace}/${available.pack}`;
592
- const list = packsByKey.get(key) ?? [];
593
- list.push(available);
594
- packsByKey.set(key, list);
595
- }
596
- for (const ref of input.manifest.componentPacks) {
597
- const range = parseSemverRange(ref.range);
598
- if (!range) {
599
- add(
600
- "invalid_range",
601
- `assembly.componentPacks.${ref.namespace}/${ref.pack}.range`,
602
- `range "${ref.range}" is not a valid semver range`
603
- );
604
- continue;
605
- }
606
- const key = `${ref.namespace}/${ref.pack}`;
607
- const available = packsByKey.get(key) ?? [];
608
- const versions = available.map((entry) => entry.version);
609
- const resolved = resolveVersionRange(range, versions);
610
- if (!resolved) {
611
- add(
612
- "unresolved_component_pack",
613
- `assembly.componentPacks.${key}`,
614
- `no available version of pack "${key}" satisfies range "${ref.range}"`
615
- );
616
- continue;
617
- }
618
- const matched = available.find((entry) => entry.version === resolved);
619
- resolvedPacks.push({
620
- pack: ref.pack,
621
- namespace: ref.namespace,
622
- version: resolved,
623
- range: ref.range,
624
- artifactDigest: matched.artifactDigest,
625
- ...matched.remote ? { remote: matched.remote } : {}
626
- });
627
- }
628
- const packManifests = input.packManifests ?? [];
629
- for (const pack of packManifests) {
630
- try {
631
- assertComponentPackManifest(pack);
632
- } catch (error) {
633
- add(
634
- "invalid_component_pack",
635
- `packManifests.${pack.namespace}/${pack.pack}`,
636
- error instanceof Error ? error.message : String(error)
637
- );
638
- }
639
- }
640
- const resolvedPackManifests = [];
641
- for (const resolved of resolvedPacks) {
642
- const key = `${resolved.namespace}/${resolved.pack}`;
643
- const manifest = packManifests.find(
644
- (candidate) => candidate.namespace === resolved.namespace && candidate.pack === resolved.pack && candidate.version === resolved.version
645
- );
646
- if (!manifest) {
647
- add(
648
- "missing_component_pack_manifest",
649
- `assembly.componentPacks.${key}`,
650
- `resolved pack "${key}@${resolved.version}" has no matching component-pack manifest`
651
- );
652
- continue;
653
- }
654
- if (manifest.artifactDigest !== resolved.artifactDigest) {
655
- add(
656
- "component_pack_artifact_digest_mismatch",
657
- `assembly.componentPacks.${key}.artifactDigest`,
658
- `component-pack manifest digest "${manifest.artifactDigest}" does not match resolved artifact digest "${resolved.artifactDigest}"`
659
- );
660
- continue;
661
- }
662
- resolvedPackManifests.push(manifest);
663
- }
664
- const contractsByNamespace = /* @__PURE__ */ new Map();
665
- for (const entry of input.manifest.contracts) {
666
- try {
667
- (0, import_gen_capability.assertUsageContractDocument)(entry.document);
668
- } catch (error) {
669
- add(
670
- "invalid_contract",
671
- `assembly.contracts.${entry.namespace}`,
672
- error instanceof Error ? error.message : String(error)
673
- );
674
- continue;
675
- }
676
- if (entry.document.capability.namespace !== entry.namespace) {
677
- add(
678
- "contract_namespace_mismatch",
679
- `assembly.contracts.${entry.namespace}`,
680
- `contract namespace "${entry.document.capability.namespace}" does not match assembly namespace "${entry.namespace}"`
681
- );
682
- }
683
- const resolved = resolvedCapabilities.find((capability) => capability.namespace === entry.namespace);
684
- if (resolved && entry.document.manifestDigest !== resolved.manifestDigest) {
685
- add(
686
- "contract_manifest_digest_mismatch",
687
- `assembly.contracts.${entry.namespace}.manifestDigest`,
688
- `contract manifest digest "${entry.document.manifestDigest}" does not match resolved capability digest "${resolved.manifestDigest}"`
689
- );
690
- } else if (resolved) {
691
- resolved.usageContractDigest = computeUsageContractDocumentDigest(entry.document);
692
- }
693
- contractsByNamespace.set(entry.namespace, entry.document.capability);
694
- }
695
- const resolvedAdapters = [];
696
- const declaredAdapters = input.manifest.adapters ?? [];
697
- const adapterKeys = /* @__PURE__ */ new Set();
698
- for (const adapter of declaredAdapters) {
699
- const key = `${adapter.port}/${adapter.vendor}`;
700
- if (adapterKeys.has(key)) {
701
- add("duplicate_adapter", `assembly.adapters.${key}`, `adapter "${key}" is declared a second time`);
702
- continue;
703
- }
704
- adapterKeys.add(key);
705
- resolvedAdapters.push({
706
- port: adapter.port,
707
- vendor: adapter.vendor,
708
- ...adapter.version === void 0 ? {} : { version: adapter.version },
709
- ...adapter.standard === void 0 ? {} : { standard: adapter.standard },
710
- certified: adapter.certification !== void 0,
711
- ...adapter.certification === void 0 ? {} : {
712
- portVersion: adapter.certification.portVersion,
713
- certifiedAt: adapter.certification.certifiedAt,
714
- certificationDigest: computeAdapterCertificationDigest(adapter.certification)
715
- }
716
- });
717
- }
718
- const portDefinitions = (input.portDefinitions ?? []).map((definition, index) => {
719
- if (typeof definition.id !== "string" || !import_ports.PORT_ID.test(definition.id)) {
720
- fail(`portDefinitions[${index}].id`, `must be a namespaced lowercase port id, received ${JSON.stringify(definition.id)}`);
721
- }
722
- if (typeof definition.version !== "string" || !/^\d+\.\d+\.\d+$/.test(definition.version)) {
723
- fail(`portDefinitions[${index}].version`, `must be a semver version, received ${JSON.stringify(definition.version)}`);
724
- }
725
- return { id: definition.id, version: definition.version, description: "", checks: () => [] };
726
- });
727
- for (const [namespace, capability] of contractsByNamespace) {
728
- if (input.manifest.adapters === void 0) break;
729
- if (!capability.requirements?.ports?.length) continue;
730
- const result = (0, import_ports.validatePortRequirements)({
731
- capability: { namespace, requirements: { ports: capability.requirements.ports } },
732
- adapters: declaredAdapters,
733
- definitions: portDefinitions,
734
- requireCertification: true
735
- });
736
- for (const finding of result.findings) {
737
- add(finding.code, `assembly.contracts.${namespace}.requirements.ports.${finding.port}`, finding.message);
738
- }
739
- }
740
- const resolvedBindings = [];
741
- for (const entry of input.manifest.bindings) {
742
- const contract = contractsByNamespace.get(entry.capability);
743
- if (!contract) {
744
- add(
745
- "unknown_contract_for_binding",
746
- `assembly.bindings.${entry.capability}`,
747
- `binding targets capability "${entry.capability}" but no contract for it is declared`
748
- );
749
- resolvedBindings.push({
750
- capability: entry.capability,
751
- bindingDigest: assemblyDigest(entry.binding),
752
- valid: false,
753
- findings: []
754
- });
755
- continue;
756
- }
757
- const result = (0, import_adoption_bindings.validateBinding)({
758
- contract,
759
- binding: entry.binding,
760
- packs: resolvedPackManifests,
761
- coreComponents: input.coreComponents ?? [],
762
- availableContracts: input.availableContracts ?? []
763
- });
764
- resolvedBindings.push({
765
- capability: entry.capability,
766
- bindingDigest: assemblyDigest(entry.binding),
767
- valid: result.valid,
768
- findings: result.findings
769
- });
770
- if (!result.valid) {
771
- for (const finding of result.findings) {
772
- add(
773
- "binding_validation_failed",
774
- `assembly.bindings.${entry.capability}.${finding.path}`,
775
- finding.message
776
- );
777
- }
778
- }
779
- }
780
- const capabilities = [...resolvedCapabilities].sort(
781
- (a, b) => a.namespace < b.namespace ? -1 : a.namespace > b.namespace ? 1 : 0
782
- );
783
- const componentPacks = [...resolvedPacks].sort((a, b) => {
784
- const key = (e) => `${e.namespace}/${e.pack}`;
785
- return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0;
786
- });
787
- const bindings = [...resolvedBindings].sort(
788
- (a, b) => a.capability < b.capability ? -1 : a.capability > b.capability ? 1 : 0
789
- );
790
- const adapters = input.manifest.adapters === void 0 ? void 0 : [...resolvedAdapters].sort((a, b) => {
791
- const key = (e) => `${e.port}/${e.vendor}`;
792
- return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0;
793
- });
794
- const lockfileBody = {
795
- formatVersion: ASSEMBLY_LOCKFILE_FORMAT_VERSION,
796
- application: input.manifest.application,
797
- capabilities,
798
- componentPacks,
799
- bindings,
800
- ...adapters ? { adapters } : {}
801
- };
802
- const lockfile = {
803
- formatVersion: ASSEMBLY_LOCKFILE_FORMAT_VERSION,
804
- application: input.manifest.application,
805
- assemblyDigest: assemblyDigest(lockfileDigestBody(lockfileBody)),
806
- capabilities,
807
- componentPacks,
808
- bindings,
809
- ...adapters ? { adapters } : {}
810
- };
811
- return {
812
- lockfile: findings.length === 0 ? lockfile : void 0,
813
- findings
814
- };
815
- }
484
+ assertAssemblyManifest(input.manifest);
485
+ const findings = [];
486
+ const add = (code, path, message) => {
487
+ findings.push({
488
+ code,
489
+ path,
490
+ message
491
+ });
492
+ };
493
+ const capabilityNamespaces = /* @__PURE__ */ new Set();
494
+ for (const capability of input.manifest.capabilities) {
495
+ if (capabilityNamespaces.has(capability.namespace)) add("duplicate_capability", `assembly.capabilities.${capability.namespace}`, `capability "${capability.namespace}" is declared more than once`);
496
+ capabilityNamespaces.add(capability.namespace);
497
+ }
498
+ const packKeys = /* @__PURE__ */ new Set();
499
+ for (const pack of input.manifest.componentPacks) {
500
+ const key = `${pack.namespace}/${pack.pack}`;
501
+ if (packKeys.has(key)) add("duplicate_component_pack", `assembly.componentPacks.${key}`, `component pack "${key}" is declared more than once`);
502
+ packKeys.add(key);
503
+ }
504
+ const contractNamespaces = /* @__PURE__ */ new Set();
505
+ for (const contract of input.manifest.contracts) {
506
+ if (contractNamespaces.has(contract.namespace)) add("duplicate_contract", `assembly.contracts.${contract.namespace}`, `contract for "${contract.namespace}" is declared more than once`);
507
+ contractNamespaces.add(contract.namespace);
508
+ }
509
+ const resolvedCapabilities = [];
510
+ const capabilitiesByNamespace = /* @__PURE__ */ new Map();
511
+ for (const available of input.capabilities) {
512
+ const list = capabilitiesByNamespace.get(available.namespace) ?? [];
513
+ list.push(available);
514
+ capabilitiesByNamespace.set(available.namespace, list);
515
+ }
516
+ for (const ref of input.manifest.capabilities) {
517
+ const range = parseSemverRange(ref.range);
518
+ if (!range) {
519
+ add("invalid_range", `assembly.capabilities.${ref.namespace}.range`, `range "${ref.range}" is not a valid semver range`);
520
+ continue;
521
+ }
522
+ const available = capabilitiesByNamespace.get(ref.namespace) ?? [];
523
+ const resolved = resolveVersionRange(range, available.map((entry) => entry.version));
524
+ if (!resolved) {
525
+ add("unresolved_capability", `assembly.capabilities.${ref.namespace}`, `no available version of "${ref.namespace}" satisfies range "${ref.range}"`);
526
+ continue;
527
+ }
528
+ const matched = available.find((entry) => entry.version === resolved);
529
+ resolvedCapabilities.push({
530
+ namespace: ref.namespace,
531
+ version: resolved,
532
+ range: ref.range,
533
+ manifestDigest: matched.manifestDigest
534
+ });
535
+ }
536
+ const resolvedPacks = [];
537
+ const packsByKey = /* @__PURE__ */ new Map();
538
+ for (const available of input.componentPacks) {
539
+ const key = `${available.namespace}/${available.pack}`;
540
+ const list = packsByKey.get(key) ?? [];
541
+ list.push(available);
542
+ packsByKey.set(key, list);
543
+ }
544
+ for (const ref of input.manifest.componentPacks) {
545
+ const range = parseSemverRange(ref.range);
546
+ if (!range) {
547
+ add("invalid_range", `assembly.componentPacks.${ref.namespace}/${ref.pack}.range`, `range "${ref.range}" is not a valid semver range`);
548
+ continue;
549
+ }
550
+ const key = `${ref.namespace}/${ref.pack}`;
551
+ const available = packsByKey.get(key) ?? [];
552
+ const resolved = resolveVersionRange(range, available.map((entry) => entry.version));
553
+ if (!resolved) {
554
+ add("unresolved_component_pack", `assembly.componentPacks.${key}`, `no available version of pack "${key}" satisfies range "${ref.range}"`);
555
+ continue;
556
+ }
557
+ const matched = available.find((entry) => entry.version === resolved);
558
+ resolvedPacks.push({
559
+ pack: ref.pack,
560
+ namespace: ref.namespace,
561
+ version: resolved,
562
+ range: ref.range,
563
+ artifactDigest: matched.artifactDigest,
564
+ ...matched.remote ? { remote: matched.remote } : {}
565
+ });
566
+ }
567
+ const packManifests = input.packManifests ?? [];
568
+ for (const pack of packManifests) try {
569
+ assertComponentPackManifest(pack);
570
+ } catch (error) {
571
+ add("invalid_component_pack", `packManifests.${pack.namespace}/${pack.pack}`, error instanceof Error ? error.message : String(error));
572
+ }
573
+ const resolvedPackManifests = [];
574
+ for (const resolved of resolvedPacks) {
575
+ const key = `${resolved.namespace}/${resolved.pack}`;
576
+ const manifest = packManifests.find((candidate) => candidate.namespace === resolved.namespace && candidate.pack === resolved.pack && candidate.version === resolved.version);
577
+ if (!manifest) {
578
+ add("missing_component_pack_manifest", `assembly.componentPacks.${key}`, `resolved pack "${key}@${resolved.version}" has no matching component-pack manifest`);
579
+ continue;
580
+ }
581
+ if (manifest.artifactDigest !== resolved.artifactDigest) {
582
+ add("component_pack_artifact_digest_mismatch", `assembly.componentPacks.${key}.artifactDigest`, `component-pack manifest digest "${manifest.artifactDigest}" does not match resolved artifact digest "${resolved.artifactDigest}"`);
583
+ continue;
584
+ }
585
+ resolvedPackManifests.push(manifest);
586
+ }
587
+ const contractsByNamespace = /* @__PURE__ */ new Map();
588
+ for (const entry of input.manifest.contracts) {
589
+ try {
590
+ (0, _fabricorg_gen_capability.assertUsageContractDocument)(entry.document);
591
+ } catch (error) {
592
+ add("invalid_contract", `assembly.contracts.${entry.namespace}`, error instanceof Error ? error.message : String(error));
593
+ continue;
594
+ }
595
+ if (entry.document.capability.namespace !== entry.namespace) add("contract_namespace_mismatch", `assembly.contracts.${entry.namespace}`, `contract namespace "${entry.document.capability.namespace}" does not match assembly namespace "${entry.namespace}"`);
596
+ const resolved = resolvedCapabilities.find((capability) => capability.namespace === entry.namespace);
597
+ if (resolved && entry.document.manifestDigest !== resolved.manifestDigest) add("contract_manifest_digest_mismatch", `assembly.contracts.${entry.namespace}.manifestDigest`, `contract manifest digest "${entry.document.manifestDigest}" does not match resolved capability digest "${resolved.manifestDigest}"`);
598
+ else if (resolved) resolved.usageContractDigest = computeUsageContractDocumentDigest(entry.document);
599
+ contractsByNamespace.set(entry.namespace, entry.document.capability);
600
+ }
601
+ const resolvedAdapters = [];
602
+ const declaredAdapters = input.manifest.adapters ?? [];
603
+ const adapterKeys = /* @__PURE__ */ new Set();
604
+ for (const adapter of declaredAdapters) {
605
+ const key = `${adapter.port}/${adapter.vendor}`;
606
+ if (adapterKeys.has(key)) {
607
+ add("duplicate_adapter", `assembly.adapters.${key}`, `adapter "${key}" is declared a second time`);
608
+ continue;
609
+ }
610
+ adapterKeys.add(key);
611
+ resolvedAdapters.push({
612
+ port: adapter.port,
613
+ vendor: adapter.vendor,
614
+ ...adapter.version === void 0 ? {} : { version: adapter.version },
615
+ ...adapter.standard === void 0 ? {} : { standard: adapter.standard },
616
+ certified: adapter.certification !== void 0,
617
+ ...adapter.certification === void 0 ? {} : {
618
+ portVersion: adapter.certification.portVersion,
619
+ certifiedAt: adapter.certification.certifiedAt,
620
+ certificationDigest: computeAdapterCertificationDigest(adapter.certification)
621
+ }
622
+ });
623
+ }
624
+ const portDefinitions = (input.portDefinitions ?? []).map((definition, index) => {
625
+ if (typeof definition.id !== "string" || !_fabricorg_ports.PORT_ID.test(definition.id)) fail(`portDefinitions[${index}].id`, `must be a namespaced lowercase port id, received ${JSON.stringify(definition.id)}`);
626
+ if (typeof definition.version !== "string" || !/^\d+\.\d+\.\d+$/.test(definition.version)) fail(`portDefinitions[${index}].version`, `must be a semver version, received ${JSON.stringify(definition.version)}`);
627
+ return {
628
+ id: definition.id,
629
+ version: definition.version,
630
+ description: "",
631
+ checks: () => []
632
+ };
633
+ });
634
+ for (const [namespace, capability] of contractsByNamespace) {
635
+ if (input.manifest.adapters === void 0) break;
636
+ if (!capability.requirements?.ports?.length) continue;
637
+ const result = (0, _fabricorg_ports.validatePortRequirements)({
638
+ capability: {
639
+ namespace,
640
+ requirements: { ports: capability.requirements.ports }
641
+ },
642
+ adapters: declaredAdapters,
643
+ definitions: portDefinitions,
644
+ requireCertification: true
645
+ });
646
+ for (const finding of result.findings) add(finding.code, `assembly.contracts.${namespace}.requirements.ports.${finding.port}`, finding.message);
647
+ }
648
+ const resolvedBindings = [];
649
+ for (const entry of input.manifest.bindings) {
650
+ const contract = contractsByNamespace.get(entry.capability);
651
+ if (!contract) {
652
+ add("unknown_contract_for_binding", `assembly.bindings.${entry.capability}`, `binding targets capability "${entry.capability}" but no contract for it is declared`);
653
+ resolvedBindings.push({
654
+ capability: entry.capability,
655
+ bindingDigest: assemblyDigest(entry.binding),
656
+ valid: false,
657
+ findings: []
658
+ });
659
+ continue;
660
+ }
661
+ const result = (0, _fabricorg_adoption_bindings.validateBinding)({
662
+ contract,
663
+ binding: entry.binding,
664
+ packs: resolvedPackManifests,
665
+ coreComponents: input.coreComponents ?? [],
666
+ availableContracts: input.availableContracts ?? []
667
+ });
668
+ resolvedBindings.push({
669
+ capability: entry.capability,
670
+ bindingDigest: assemblyDigest(entry.binding),
671
+ valid: result.valid,
672
+ findings: result.findings
673
+ });
674
+ if (!result.valid) for (const finding of result.findings) add("binding_validation_failed", `assembly.bindings.${entry.capability}.${finding.path}`, finding.message);
675
+ }
676
+ const capabilities = [...resolvedCapabilities].sort((a, b) => a.namespace < b.namespace ? -1 : a.namespace > b.namespace ? 1 : 0);
677
+ const componentPacks = [...resolvedPacks].sort((a, b) => {
678
+ const key = (e) => `${e.namespace}/${e.pack}`;
679
+ return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0;
680
+ });
681
+ const bindings = [...resolvedBindings].sort((a, b) => a.capability < b.capability ? -1 : a.capability > b.capability ? 1 : 0);
682
+ const adapters = input.manifest.adapters === void 0 ? void 0 : [...resolvedAdapters].sort((a, b) => {
683
+ const key = (e) => `${e.port}/${e.vendor}`;
684
+ return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0;
685
+ });
686
+ const lockfileBody = {
687
+ formatVersion: 2,
688
+ application: input.manifest.application,
689
+ capabilities,
690
+ componentPacks,
691
+ bindings,
692
+ ...adapters ? { adapters } : {}
693
+ };
694
+ const lockfile = {
695
+ formatVersion: 2,
696
+ application: input.manifest.application,
697
+ assemblyDigest: assemblyDigest(lockfileDigestBody(lockfileBody)),
698
+ capabilities,
699
+ componentPacks,
700
+ bindings,
701
+ ...adapters ? { adapters } : {}
702
+ };
703
+ return {
704
+ lockfile: findings.length === 0 ? lockfile : void 0,
705
+ findings
706
+ };
707
+ }
708
+ /**
709
+ * Throw with every finding listed, for use as a build step. Returns the
710
+ * lockfile when valid.
711
+ */
816
712
  function assertAssemblyResolved(input) {
817
- const result = resolveAssembly(input);
818
- if (result.findings.length === 0) return result.lockfile;
819
- throw new Error([
820
- `Assembly "${input.manifest.application}" could not be resolved:`,
821
- ...result.findings.map((finding) => ` - [${finding.code}] ${finding.path}: ${finding.message}`)
822
- ].join("\n"));
823
- }
713
+ const result = resolveAssembly(input);
714
+ if (result.findings.length === 0) return result.lockfile;
715
+ throw new Error([`Assembly "${input.manifest.application}" could not be resolved:`, ...result.findings.map((finding) => ` - [${finding.code}] ${finding.path}: ${finding.message}`)].join("\n"));
716
+ }
717
+ /**
718
+ * Validate an assembly manifest's bindings as a standalone build gate,
719
+ * without resolving versions. Useful when versions are already locked and
720
+ * only the binding contracts need checking.
721
+ */
824
722
  function validateAssemblyBindings(input) {
825
- assertAssemblyManifest(input.manifest);
826
- const findings = [];
827
- const add = (code, path, message) => {
828
- findings.push({ code, path, message });
829
- };
830
- const contractsByNamespace = /* @__PURE__ */ new Map();
831
- for (const entry of input.manifest.contracts) {
832
- try {
833
- (0, import_gen_capability.assertUsageContractDocument)(entry.document);
834
- } catch (error) {
835
- add(
836
- "invalid_contract",
837
- `assembly.contracts.${entry.namespace}`,
838
- error instanceof Error ? error.message : String(error)
839
- );
840
- continue;
841
- }
842
- contractsByNamespace.set(entry.namespace, entry.document.capability);
843
- }
844
- const packManifests = input.packManifests ?? [];
845
- for (const entry of input.manifest.bindings) {
846
- const contract = contractsByNamespace.get(entry.capability);
847
- if (!contract) {
848
- add(
849
- "unknown_contract_for_binding",
850
- `assembly.bindings.${entry.capability}`,
851
- `binding targets capability "${entry.capability}" but no contract for it is declared`
852
- );
853
- continue;
854
- }
855
- try {
856
- (0, import_adoption_bindings.assertBindingValid)({
857
- contract,
858
- binding: entry.binding,
859
- packs: packManifests,
860
- coreComponents: input.coreComponents ?? [],
861
- availableContracts: input.availableContracts ?? []
862
- });
863
- } catch (error) {
864
- add(
865
- "binding_validation_failed",
866
- `assembly.bindings.${entry.capability}`,
867
- error instanceof Error ? error.message : String(error)
868
- );
869
- }
870
- }
871
- return {
872
- lockfile: void 0,
873
- findings
874
- };
875
- }
876
- // Annotate the CommonJS export names for ESM import in node:
877
- 0 && (module.exports = {
878
- ASSEMBLY_LOCKFILE_FORMAT_VERSION,
879
- ASSEMBLY_MANIFEST_FORMAT_VERSION,
880
- assertAssemblyLockfile,
881
- assertAssemblyManifest,
882
- assertAssemblyResolved,
883
- assertAssemblyRuntimeCompatible,
884
- assertComponentPackManifest,
885
- computeAdapterCertificationDigest,
886
- computeAssemblyLockfileDigest,
887
- computeUsageContractDocumentDigest,
888
- parseSemverRange,
889
- resolveAssembly,
890
- resolveVersionRange,
891
- satisfiesRange,
892
- validateAssemblyBindings
893
- });
723
+ assertAssemblyManifest(input.manifest);
724
+ const findings = [];
725
+ const add = (code, path, message) => {
726
+ findings.push({
727
+ code,
728
+ path,
729
+ message
730
+ });
731
+ };
732
+ const contractsByNamespace = /* @__PURE__ */ new Map();
733
+ for (const entry of input.manifest.contracts) {
734
+ try {
735
+ (0, _fabricorg_gen_capability.assertUsageContractDocument)(entry.document);
736
+ } catch (error) {
737
+ add("invalid_contract", `assembly.contracts.${entry.namespace}`, error instanceof Error ? error.message : String(error));
738
+ continue;
739
+ }
740
+ contractsByNamespace.set(entry.namespace, entry.document.capability);
741
+ }
742
+ const packManifests = input.packManifests ?? [];
743
+ for (const entry of input.manifest.bindings) {
744
+ const contract = contractsByNamespace.get(entry.capability);
745
+ if (!contract) {
746
+ add("unknown_contract_for_binding", `assembly.bindings.${entry.capability}`, `binding targets capability "${entry.capability}" but no contract for it is declared`);
747
+ continue;
748
+ }
749
+ try {
750
+ (0, _fabricorg_adoption_bindings.assertBindingValid)({
751
+ contract,
752
+ binding: entry.binding,
753
+ packs: packManifests,
754
+ coreComponents: input.coreComponents ?? [],
755
+ availableContracts: input.availableContracts ?? []
756
+ });
757
+ } catch (error) {
758
+ add("binding_validation_failed", `assembly.bindings.${entry.capability}`, error instanceof Error ? error.message : String(error));
759
+ }
760
+ }
761
+ return {
762
+ lockfile: void 0,
763
+ findings
764
+ };
765
+ }
766
+ //#endregion
767
+ exports.ASSEMBLY_LOCKFILE_FORMAT_VERSION = ASSEMBLY_LOCKFILE_FORMAT_VERSION;
768
+ exports.ASSEMBLY_MANIFEST_FORMAT_VERSION = ASSEMBLY_MANIFEST_FORMAT_VERSION;
769
+ exports.assertAssemblyLockfile = assertAssemblyLockfile;
770
+ exports.assertAssemblyManifest = assertAssemblyManifest;
771
+ exports.assertAssemblyResolved = assertAssemblyResolved;
772
+ exports.assertAssemblyRuntimeCompatible = assertAssemblyRuntimeCompatible;
773
+ exports.assertComponentPackManifest = assertComponentPackManifest;
774
+ exports.computeAdapterCertificationDigest = computeAdapterCertificationDigest;
775
+ exports.computeAssemblyLockfileDigest = computeAssemblyLockfileDigest;
776
+ exports.computeUsageContractDocumentDigest = computeUsageContractDocumentDigest;
777
+ exports.parseSemverRange = parseSemverRange;
778
+ exports.resolveAssembly = resolveAssembly;
779
+ exports.resolveVersionRange = resolveVersionRange;
780
+ exports.satisfiesRange = satisfiesRange;
781
+ exports.validateAssemblyBindings = validateAssemblyBindings;
782
+
894
783
  //# sourceMappingURL=index.cjs.map