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