@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/CHANGELOG.md +37 -0
- package/dist/index.cjs +725 -836
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +161 -161
- package/dist/index.d.ts +161 -161
- package/dist/index.js +708 -801
- package/dist/index.js.map +1 -1
- package/package.json +13 -12
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
+
return (version) => compareSemver(version, range) === 0;
|
|
74
74
|
}
|
|
75
75
|
function gteComparator(range) {
|
|
76
|
-
|
|
76
|
+
return (version) => compareSemver(version, range) >= 0;
|
|
77
77
|
}
|
|
78
78
|
function gtComparator(range) {
|
|
79
|
-
|
|
79
|
+
return (version) => compareSemver(version, range) > 0;
|
|
80
80
|
}
|
|
81
81
|
function lteComparator(range) {
|
|
82
|
-
|
|
82
|
+
return (version) => compareSemver(version, range) <= 0;
|
|
83
83
|
}
|
|
84
84
|
function ltComparator(range) {
|
|
85
|
-
|
|
85
|
+
return (version) => compareSemver(version, range) < 0;
|
|
86
86
|
}
|
|
87
87
|
function anyComparator() {
|
|
88
|
-
|
|
88
|
+
return () => true;
|
|
89
89
|
}
|
|
90
90
|
function parseComparator(raw) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
226
|
+
return JSON.stringify(sortJson(value));
|
|
190
227
|
}
|
|
191
228
|
function assemblyDigest(value) {
|
|
192
|
-
|
|
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
|
-
|
|
196
|
-
|
|
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
|
-
|
|
246
|
+
return assemblyDigest(lockfileDigestBody(lockfile));
|
|
200
247
|
}
|
|
201
248
|
function lockfileDigestBody(lockfile) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
260
|
+
return assemblyDigest({
|
|
261
|
+
...certification,
|
|
262
|
+
checks: [...certification.checks].sort()
|
|
263
|
+
});
|
|
215
264
|
}
|
|
216
265
|
function fail(path, message) {
|
|
217
|
-
|
|
266
|
+
throw new Error(`Assembly manifest ${path} ${message}.`);
|
|
218
267
|
}
|
|
219
268
|
function requireString(value, path) {
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|