@kungfu-tech/buildchain 4.0.2-alpha.30 → 4.0.2-alpha.32
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/architecture/agent-change-map.md +4 -1
- package/architecture/maintainability-debt.json +90 -58
- package/architecture/maintainability-policy.json +22 -23
- package/architecture/v4-architecture-constitution.md +23 -20
- package/architecture/v4-partial-mutation-recovery-qualification.json +4 -1
- package/architecture/v4-provider-operation-journal-contract.json +5 -2
- package/architecture/v4-release-activation-shadow-domain.json +5 -2
- package/architecture/v4-release-topology.json +17 -2
- package/architecture/v4-rust-wasm-production-authority.json +57 -0
- package/architecture/v4-stable-publication-fence.json +5 -2
- package/dist/site/buildchain-contract.json +5 -5
- package/dist/site/buildchain-site.json +44 -8
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/kfd-claims.json +17 -5
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +27 -27
- package/dist/site/page-registry.json +39 -3
- package/dist/site/public-surface-audit.json +7 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +4 -4
- package/docs/node-api-reference.md +60 -60
- package/docs/v4-rust-wasm-production-authority.md +58 -0
- package/package.json +5 -3
- package/packages/core/buildchain-v4-domain.wasm +0 -0
- package/packages/core/dev-delivery-candidate-identity.js +48 -11
- package/packages/core/release-tail-provider-plane.js +68 -1048
- package/packages/core/v4-canonical-contracts.js +9 -75
- package/packages/core/v4-domain-wasm-artifact.js +7 -0
- package/packages/core/v4-domain-wasm.js +216 -0
- package/packages/core/v4-partial-mutation-recovery-qualification.js +9 -386
- package/packages/core/v4-product-publication.js +18 -384
- package/packages/core/v4-provider-operation-journal.js +18 -487
- package/packages/core/v4-provider-readback-idempotency.js +12 -97
- package/packages/core/v4-release-activation-shadow.js +13 -500
- package/packages/core/v4-release-invocation.js +28 -389
- package/packages/core/v4-stable-publication-fence.js +13 -342
- package/scripts/build-v4-domain-wasm.mjs +189 -0
- package/scripts/check-action-bundles.mjs +12 -2
- package/scripts/check-v4-release-topology.mjs +50 -25
- package/scripts/copy-v4-domain-wasm.mjs +14 -0
- package/scripts/dev-delivery-warrant.mjs +7 -3
- package/scripts/generate-v4-universal-workflow-facades.mjs +8 -3
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
v4ContentRoot,
|
|
4
|
-
validateV4Clock,
|
|
5
|
-
validateV4Root,
|
|
6
|
-
} from "./v4-canonical-contracts.js";
|
|
1
|
+
import { V4ContractFault } from "./v4-canonical-contracts.js";
|
|
2
|
+
import { V4DomainWasmFault, invokeV4DomainWasm } from "./v4-domain-wasm.js";
|
|
7
3
|
|
|
8
4
|
export const V4_STABLE_PUBLICATION_REQUEST_CONTRACT =
|
|
9
5
|
"buildchain-v4-stable-publication-request/v1";
|
|
@@ -12,346 +8,21 @@ export const V4_STABLE_PUBLICATION_PLAN_CONTRACT =
|
|
|
12
8
|
export const V4_STABLE_PUBLICATION_FENCE_CONTRACT =
|
|
13
9
|
"buildchain-v4-stable-publication-fence/v1";
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function fault(code, path, message) {
|
|
25
|
-
throw new V4ContractFault(code, path, message);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function exactKeys(value, expected, path) {
|
|
29
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
30
|
-
fault(
|
|
31
|
-
"invalid-stable-publication-shape",
|
|
32
|
-
path,
|
|
33
|
-
`${path} must be an object`,
|
|
34
|
-
);
|
|
35
|
-
const actual = Object.keys(value).sort();
|
|
36
|
-
const canonical = [...expected].sort();
|
|
37
|
-
if (
|
|
38
|
-
actual.length !== canonical.length ||
|
|
39
|
-
actual.some((key, index) => key !== canonical[index])
|
|
40
|
-
)
|
|
41
|
-
fault(
|
|
42
|
-
"invalid-stable-publication-shape",
|
|
43
|
-
path,
|
|
44
|
-
`${path} keys do not match the closed publication fence contract`,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function asciiCompare(left, right) {
|
|
49
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function validateToken(value, path) {
|
|
53
|
-
if (typeof value !== "string" || !TOKEN.test(value))
|
|
54
|
-
fault(
|
|
55
|
-
"invalid-stable-publication-token",
|
|
56
|
-
path,
|
|
57
|
-
`${path} must be an ASCII token`,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function validateGeneration(value, path) {
|
|
62
|
-
if (!Number.isSafeInteger(value) || value < 1)
|
|
63
|
-
fault(
|
|
64
|
-
"invalid-stable-publication-generation",
|
|
65
|
-
path,
|
|
66
|
-
`${path} must be a positive safe integer`,
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function normalizeRoots(values, path) {
|
|
71
|
-
if (!Array.isArray(values) || values.length === 0)
|
|
72
|
-
fault(
|
|
73
|
-
"stable-publication-provider-confirmation-mismatch",
|
|
74
|
-
path,
|
|
75
|
-
`${path} must contain rooted provider confirmations`,
|
|
76
|
-
);
|
|
77
|
-
for (const [index, value] of values.entries())
|
|
78
|
-
validateV4Root(value, `${path}/${index}`);
|
|
79
|
-
const sorted = [...values].sort(asciiCompare);
|
|
80
|
-
if (new Set(sorted).size !== sorted.length)
|
|
81
|
-
fault(
|
|
82
|
-
"stable-publication-provider-confirmation-mismatch",
|
|
83
|
-
path,
|
|
84
|
-
`${path} must not contain duplicate roots`,
|
|
85
|
-
);
|
|
86
|
-
return sorted;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function validateCandidate(candidate) {
|
|
90
|
-
exactKeys(
|
|
91
|
-
candidate,
|
|
92
|
-
[
|
|
93
|
-
"generation",
|
|
94
|
-
"commit",
|
|
95
|
-
"sourceRoot",
|
|
96
|
-
"metadataRoot",
|
|
97
|
-
"journalRoot",
|
|
98
|
-
"protectedAncestryRoot",
|
|
99
|
-
],
|
|
100
|
-
"$/candidate",
|
|
101
|
-
);
|
|
102
|
-
validateGeneration(candidate.generation, "$/candidate/generation");
|
|
103
|
-
if (typeof candidate.commit !== "string" || !COMMIT.test(candidate.commit))
|
|
104
|
-
fault(
|
|
105
|
-
"invalid-stable-publication-commit",
|
|
106
|
-
"$/candidate/commit",
|
|
107
|
-
"candidate commit must be a lowercase 40-byte Git object id",
|
|
108
|
-
);
|
|
109
|
-
for (const field of [
|
|
110
|
-
"sourceRoot",
|
|
111
|
-
"metadataRoot",
|
|
112
|
-
"journalRoot",
|
|
113
|
-
"protectedAncestryRoot",
|
|
114
|
-
])
|
|
115
|
-
validateV4Root(candidate[field], `$/candidate/${field}`);
|
|
116
|
-
return structuredClone(candidate);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function normalizeTargets(targets) {
|
|
120
|
-
if (!Array.isArray(targets) || targets.length === 0)
|
|
121
|
-
fault(
|
|
122
|
-
"invalid-stable-publication-target",
|
|
123
|
-
"$/targets",
|
|
124
|
-
"at least one shadow publication target is required",
|
|
125
|
-
);
|
|
126
|
-
const normalized = targets.map((target, index) => {
|
|
127
|
-
const path = `$/targets/${index}`;
|
|
128
|
-
exactKeys(
|
|
129
|
-
target,
|
|
130
|
-
["id", "kind", "desired", "providerConfirmationRoot"],
|
|
131
|
-
path,
|
|
132
|
-
);
|
|
133
|
-
validateToken(target.id, `${path}/id`);
|
|
134
|
-
if (!TARGET_KINDS.has(target.kind))
|
|
135
|
-
fault(
|
|
136
|
-
"invalid-stable-publication-target",
|
|
137
|
-
`${path}/kind`,
|
|
138
|
-
"publication target kind is unsupported",
|
|
139
|
-
);
|
|
140
|
-
if (
|
|
141
|
-
typeof target.desired !== "string" ||
|
|
142
|
-
target.desired.length === 0 ||
|
|
143
|
-
target.desired.length > 200 ||
|
|
144
|
-
[...target.desired].some((character) => {
|
|
145
|
-
const code = character.codePointAt(0);
|
|
146
|
-
return code < 0x21 || code > 0x7e;
|
|
147
|
-
})
|
|
148
|
-
)
|
|
149
|
-
fault(
|
|
150
|
-
"invalid-stable-publication-target",
|
|
151
|
-
`${path}/desired`,
|
|
152
|
-
"desired target must be non-empty printable ASCII without whitespace",
|
|
153
|
-
);
|
|
154
|
-
validateV4Root(
|
|
155
|
-
target.providerConfirmationRoot,
|
|
156
|
-
`${path}/providerConfirmationRoot`,
|
|
157
|
-
);
|
|
158
|
-
const payload = structuredClone(target);
|
|
159
|
-
return {
|
|
160
|
-
...payload,
|
|
161
|
-
targetRoot: v4ContentRoot("stable-publication-target", payload),
|
|
162
|
-
};
|
|
163
|
-
});
|
|
164
|
-
normalized.sort((left, right) => asciiCompare(left.id, right.id));
|
|
165
|
-
if (
|
|
166
|
-
new Set(normalized.map(({ id }) => id)).size !== normalized.length ||
|
|
167
|
-
new Set(normalized.map(({ kind }) => kind)).size !== normalized.length
|
|
168
|
-
)
|
|
169
|
-
fault(
|
|
170
|
-
"conflicting-stable-publication-target",
|
|
171
|
-
"$/targets",
|
|
172
|
-
"publication target ids and kinds must be unique",
|
|
173
|
-
);
|
|
174
|
-
return normalized;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function validateQualification(request, candidate, candidateRoot, targets) {
|
|
178
|
-
const qualification = request.qualification;
|
|
179
|
-
exactKeys(
|
|
180
|
-
qualification,
|
|
181
|
-
[
|
|
182
|
-
"mode",
|
|
183
|
-
"authorityGeneration",
|
|
184
|
-
"qualifiedCandidateRoot",
|
|
185
|
-
"qualifierAuthorityRoot",
|
|
186
|
-
"sealRoot",
|
|
187
|
-
"sourceRoot",
|
|
188
|
-
"metadataRoot",
|
|
189
|
-
"journalRoot",
|
|
190
|
-
"protectedAncestryRoot",
|
|
191
|
-
"providerConfirmationRoots",
|
|
192
|
-
],
|
|
193
|
-
"$/qualification",
|
|
194
|
-
);
|
|
195
|
-
if (!new Set(["n-minus-one", "independent-seal"]).has(qualification.mode))
|
|
196
|
-
fault(
|
|
197
|
-
"invalid-stable-publication-qualification",
|
|
198
|
-
"$/qualification/mode",
|
|
199
|
-
"qualification mode is unsupported",
|
|
200
|
-
);
|
|
201
|
-
validateGeneration(
|
|
202
|
-
qualification.authorityGeneration,
|
|
203
|
-
"$/qualification/authorityGeneration",
|
|
204
|
-
);
|
|
205
|
-
for (const field of [
|
|
206
|
-
"qualifiedCandidateRoot",
|
|
207
|
-
"qualifierAuthorityRoot",
|
|
208
|
-
"sealRoot",
|
|
209
|
-
"sourceRoot",
|
|
210
|
-
"metadataRoot",
|
|
211
|
-
"journalRoot",
|
|
212
|
-
"protectedAncestryRoot",
|
|
213
|
-
])
|
|
214
|
-
validateV4Root(qualification[field], `$/qualification/${field}`);
|
|
215
|
-
if (qualification.qualifiedCandidateRoot !== candidateRoot)
|
|
216
|
-
fault(
|
|
217
|
-
"stable-publication-candidate-root-mismatch",
|
|
218
|
-
"$/qualification/qualifiedCandidateRoot",
|
|
219
|
-
"qualification must bind the exact candidate root",
|
|
220
|
-
);
|
|
221
|
-
if (
|
|
222
|
-
qualification.mode === "n-minus-one" &&
|
|
223
|
-
qualification.authorityGeneration + 1 !== candidate.generation
|
|
224
|
-
)
|
|
225
|
-
fault(
|
|
226
|
-
"stable-publication-self-qualification",
|
|
227
|
-
"$/qualification/authorityGeneration",
|
|
228
|
-
"N-1 qualification must come from the immediately preceding generation",
|
|
229
|
-
);
|
|
230
|
-
if (
|
|
231
|
-
qualification.mode === "independent-seal" &&
|
|
232
|
-
qualification.authorityGeneration > candidate.generation
|
|
233
|
-
)
|
|
234
|
-
fault(
|
|
235
|
-
"stable-publication-self-qualification",
|
|
236
|
-
"$/qualification/authorityGeneration",
|
|
237
|
-
"independent sealed authority cannot come from a future generation",
|
|
238
|
-
);
|
|
239
|
-
if (qualification.qualifierAuthorityRoot === request.publisherAuthorityRoot)
|
|
240
|
-
fault(
|
|
241
|
-
"stable-publication-authority-mismatch",
|
|
242
|
-
"$/qualification/qualifierAuthorityRoot",
|
|
243
|
-
"qualification and publication authorities must be independent",
|
|
244
|
-
);
|
|
245
|
-
const coordinateFaults = [
|
|
246
|
-
["sourceRoot", "stable-publication-source-mismatch"],
|
|
247
|
-
["metadataRoot", "stable-publication-metadata-mismatch"],
|
|
248
|
-
["journalRoot", "stable-publication-journal-mismatch"],
|
|
249
|
-
["protectedAncestryRoot", "stable-publication-ancestry-mismatch"],
|
|
250
|
-
];
|
|
251
|
-
for (const [field, code] of coordinateFaults)
|
|
252
|
-
if (qualification[field] !== candidate[field])
|
|
253
|
-
fault(
|
|
254
|
-
code,
|
|
255
|
-
`$/qualification/${field}`,
|
|
256
|
-
`qualification ${field} must match the exact candidate coordinate`,
|
|
257
|
-
);
|
|
258
|
-
const qualifiedConfirmations = normalizeRoots(
|
|
259
|
-
qualification.providerConfirmationRoots,
|
|
260
|
-
"$/qualification/providerConfirmationRoots",
|
|
261
|
-
);
|
|
262
|
-
const targetConfirmations = targets
|
|
263
|
-
.map(({ providerConfirmationRoot }) => providerConfirmationRoot)
|
|
264
|
-
.sort(asciiCompare);
|
|
265
|
-
if (
|
|
266
|
-
qualifiedConfirmations.length !== targetConfirmations.length ||
|
|
267
|
-
qualifiedConfirmations.some(
|
|
268
|
-
(root, index) => root !== targetConfirmations[index],
|
|
269
|
-
)
|
|
270
|
-
)
|
|
271
|
-
fault(
|
|
272
|
-
"stable-publication-provider-confirmation-mismatch",
|
|
273
|
-
"$/qualification/providerConfirmationRoots",
|
|
274
|
-
"qualification must bind every exact provider confirmation and no others",
|
|
275
|
-
);
|
|
276
|
-
const payload = {
|
|
277
|
-
...structuredClone(qualification),
|
|
278
|
-
providerConfirmationRoots: qualifiedConfirmations,
|
|
279
|
-
};
|
|
280
|
-
return {
|
|
281
|
-
...payload,
|
|
282
|
-
qualificationRoot: v4ContentRoot(
|
|
283
|
-
"stable-publication-qualification",
|
|
284
|
-
payload,
|
|
285
|
-
),
|
|
286
|
-
};
|
|
11
|
+
function project(request) {
|
|
12
|
+
try {
|
|
13
|
+
return invokeV4DomainWasm("stable-publication", request);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error instanceof V4DomainWasmFault) {
|
|
16
|
+
throw new V4ContractFault(error.code, error.path, error.message);
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
287
20
|
}
|
|
288
21
|
|
|
289
22
|
export function planV4StablePublication(request) {
|
|
290
|
-
|
|
291
|
-
request,
|
|
292
|
-
[
|
|
293
|
-
"schema",
|
|
294
|
-
"declaredAt",
|
|
295
|
-
"candidate",
|
|
296
|
-
"qualification",
|
|
297
|
-
"publisherAuthorityRoot",
|
|
298
|
-
"targets",
|
|
299
|
-
],
|
|
300
|
-
"$",
|
|
301
|
-
);
|
|
302
|
-
if (request.schema !== V4_STABLE_PUBLICATION_REQUEST_CONTRACT)
|
|
303
|
-
fault(
|
|
304
|
-
"unsupported-stable-publication-version",
|
|
305
|
-
"$/schema",
|
|
306
|
-
"stable publication request version is unsupported",
|
|
307
|
-
);
|
|
308
|
-
validateV4Clock(request.declaredAt, "$/declaredAt");
|
|
309
|
-
validateV4Root(request.publisherAuthorityRoot, "$/publisherAuthorityRoot");
|
|
310
|
-
const candidate = validateCandidate(request.candidate);
|
|
311
|
-
const candidateRoot = v4ContentRoot(
|
|
312
|
-
"stable-publication-candidate",
|
|
313
|
-
candidate,
|
|
314
|
-
);
|
|
315
|
-
const targets = normalizeTargets(request.targets);
|
|
316
|
-
const qualification = validateQualification(
|
|
317
|
-
request,
|
|
318
|
-
candidate,
|
|
319
|
-
candidateRoot,
|
|
320
|
-
targets,
|
|
321
|
-
);
|
|
322
|
-
const payload = {
|
|
323
|
-
schema: V4_STABLE_PUBLICATION_PLAN_CONTRACT,
|
|
324
|
-
mode: "production",
|
|
325
|
-
productionAuthority: "v4",
|
|
326
|
-
declaredAt: request.declaredAt,
|
|
327
|
-
candidate,
|
|
328
|
-
candidateRoot,
|
|
329
|
-
qualification,
|
|
330
|
-
publisherAuthorityRoot: request.publisherAuthorityRoot,
|
|
331
|
-
targets,
|
|
332
|
-
};
|
|
333
|
-
return {
|
|
334
|
-
...payload,
|
|
335
|
-
planRoot: v4ContentRoot("stable-publication-plan", payload),
|
|
336
|
-
};
|
|
23
|
+
return project(request).plan;
|
|
337
24
|
}
|
|
338
25
|
|
|
339
26
|
export function projectV4StablePublication(request) {
|
|
340
|
-
|
|
341
|
-
const payload = {
|
|
342
|
-
schema: V4_STABLE_PUBLICATION_FENCE_CONTRACT,
|
|
343
|
-
decision: "allow-publication",
|
|
344
|
-
effectCount: plan.targets.length,
|
|
345
|
-
candidateRoot: plan.candidateRoot,
|
|
346
|
-
qualificationMode: plan.qualification.mode,
|
|
347
|
-
qualificationRoot: plan.qualification.qualificationRoot,
|
|
348
|
-
planRoot: plan.planRoot,
|
|
349
|
-
};
|
|
350
|
-
return {
|
|
351
|
-
plan,
|
|
352
|
-
fence: {
|
|
353
|
-
...payload,
|
|
354
|
-
fenceRoot: v4ContentRoot("stable-publication-fence", payload),
|
|
355
|
-
},
|
|
356
|
-
};
|
|
27
|
+
return project(request);
|
|
357
28
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { spawnSyncCommand } from "../packages/core/spawn-command.js";
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const crate = path.join(root, "crates", "buildchain-v4-contracts");
|
|
11
|
+
const artifact = path.join(
|
|
12
|
+
root,
|
|
13
|
+
"packages",
|
|
14
|
+
"core",
|
|
15
|
+
"buildchain-v4-domain.wasm",
|
|
16
|
+
);
|
|
17
|
+
const metadata = path.join(
|
|
18
|
+
root,
|
|
19
|
+
"packages",
|
|
20
|
+
"core",
|
|
21
|
+
"v4-domain-wasm-artifact.js",
|
|
22
|
+
);
|
|
23
|
+
const check = process.argv.includes("--check");
|
|
24
|
+
const toolchain = process.env.BUILDCHAIN_RUST_WASM_TOOLCHAIN || "1.96.0";
|
|
25
|
+
const expectedRustc = "rustc 1.96.0 (ac68faa20 2026-05-25)";
|
|
26
|
+
const canonicalBuildHost =
|
|
27
|
+
process.platform === "linux" && process.arch === "x64";
|
|
28
|
+
|
|
29
|
+
const sha256 = (bytes) =>
|
|
30
|
+
crypto.createHash("sha256").update(bytes).digest("hex");
|
|
31
|
+
const normalize = (value) => value.split(path.sep).join("/");
|
|
32
|
+
|
|
33
|
+
function rustBuildEnvironment() {
|
|
34
|
+
const environment = {
|
|
35
|
+
...process.env,
|
|
36
|
+
RUSTUP_TOOLCHAIN: toolchain,
|
|
37
|
+
CARGO_INCREMENTAL: "0",
|
|
38
|
+
SOURCE_DATE_EPOCH: "0",
|
|
39
|
+
};
|
|
40
|
+
delete environment.RUSTFLAGS;
|
|
41
|
+
delete environment.CARGO_ENCODED_RUSTFLAGS;
|
|
42
|
+
|
|
43
|
+
const metadata = spawnSyncCommand(
|
|
44
|
+
"cargo",
|
|
45
|
+
[
|
|
46
|
+
"metadata",
|
|
47
|
+
"--format-version",
|
|
48
|
+
"1",
|
|
49
|
+
"--locked",
|
|
50
|
+
"--manifest-path",
|
|
51
|
+
path.join(crate, "Cargo.toml"),
|
|
52
|
+
],
|
|
53
|
+
{ cwd: root, encoding: "utf8", env: environment },
|
|
54
|
+
);
|
|
55
|
+
if (metadata.error) throw metadata.error;
|
|
56
|
+
if (metadata.status !== 0) process.exit(metadata.status ?? 1);
|
|
57
|
+
const dependencyRemaps = JSON.parse(metadata.stdout)
|
|
58
|
+
.packages.filter((entry) => entry.source)
|
|
59
|
+
.map((entry) => [
|
|
60
|
+
path.dirname(entry.manifest_path),
|
|
61
|
+
`/buildchain/dependencies/${entry.name}-${entry.version}`,
|
|
62
|
+
])
|
|
63
|
+
.sort(([left], [right]) => right.length - left.length);
|
|
64
|
+
const remaps = [[root, "/buildchain/source"], ...dependencyRemaps].map(
|
|
65
|
+
([from, to]) => `--remap-path-prefix=${from}=${to}`,
|
|
66
|
+
);
|
|
67
|
+
environment.CARGO_ENCODED_RUSTFLAGS = remaps.join("\x1f");
|
|
68
|
+
return environment;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sourceFiles(directory) {
|
|
72
|
+
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
73
|
+
const absolute = path.join(directory, entry.name);
|
|
74
|
+
return entry.isDirectory() ? sourceFiles(absolute) : [absolute];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const rustc = spawnSyncCommand("rustc", ["--version"], {
|
|
79
|
+
cwd: root,
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
env: { ...process.env, RUSTUP_TOOLCHAIN: toolchain },
|
|
82
|
+
});
|
|
83
|
+
if (rustc.error) throw rustc.error;
|
|
84
|
+
if (rustc.status !== 0) process.exit(rustc.status ?? 1);
|
|
85
|
+
if (rustc.stdout.trim() !== expectedRustc) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Rust/WASM authority requires ${expectedRustc}; observed ${rustc.stdout.trim()}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const sourceInputs = [
|
|
92
|
+
path.join(crate, "Cargo.toml"),
|
|
93
|
+
path.join(crate, "Cargo.lock"),
|
|
94
|
+
path.join(crate, "rust-toolchain.toml"),
|
|
95
|
+
...sourceFiles(path.join(crate, "src")),
|
|
96
|
+
].sort();
|
|
97
|
+
const sourceHash = crypto.createHash("sha256");
|
|
98
|
+
for (const file of sourceInputs) {
|
|
99
|
+
sourceHash.update(normalize(path.relative(root, file)));
|
|
100
|
+
sourceHash.update("\0");
|
|
101
|
+
sourceHash.update(fs.readFileSync(file));
|
|
102
|
+
sourceHash.update("\0");
|
|
103
|
+
}
|
|
104
|
+
const sourceSha256 = sourceHash.digest("hex");
|
|
105
|
+
const generatedMetadata = (artifactSha256) =>
|
|
106
|
+
`// Generated by scripts/build-v4-domain-wasm.mjs.\nexport const V4_DOMAIN_WASM_ABI_VERSION = 1;\nexport const V4_DOMAIN_WASM_RUSTC = ${JSON.stringify(expectedRustc)};\nexport const V4_DOMAIN_WASM_SOURCE_SHA256 =\n ${JSON.stringify(sourceSha256)};\nexport const V4_DOMAIN_WASM_SHA256 =\n ${JSON.stringify(artifactSha256)};\n`;
|
|
107
|
+
|
|
108
|
+
if (!canonicalBuildHost && !check) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"Buildchain v4 Rust/WASM generation requires the canonical linux-x64 host",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!canonicalBuildHost) {
|
|
115
|
+
if (!fs.existsSync(artifact)) {
|
|
116
|
+
throw new Error("tracked Buildchain v4 Rust/WASM artifact is missing");
|
|
117
|
+
}
|
|
118
|
+
const artifactSha256 = sha256(fs.readFileSync(artifact));
|
|
119
|
+
if (
|
|
120
|
+
!fs.existsSync(metadata) ||
|
|
121
|
+
fs.readFileSync(metadata, "utf8") !== generatedMetadata(artifactSha256)
|
|
122
|
+
) {
|
|
123
|
+
throw new Error("tracked Buildchain v4 Rust/WASM metadata is stale");
|
|
124
|
+
}
|
|
125
|
+
console.log(
|
|
126
|
+
`Buildchain v4 Rust/WASM portable artifact verified: sha256:${artifactSha256}`,
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
const buildEnvironment = rustBuildEnvironment();
|
|
130
|
+
|
|
131
|
+
const temporary = fs.mkdtempSync(
|
|
132
|
+
path.join(os.tmpdir(), "buildchain-v4-wasm-"),
|
|
133
|
+
);
|
|
134
|
+
try {
|
|
135
|
+
const build = spawnSyncCommand(
|
|
136
|
+
"cargo",
|
|
137
|
+
[
|
|
138
|
+
"build",
|
|
139
|
+
"--locked",
|
|
140
|
+
"--release",
|
|
141
|
+
"--manifest-path",
|
|
142
|
+
path.join(crate, "Cargo.toml"),
|
|
143
|
+
"--target",
|
|
144
|
+
"wasm32-unknown-unknown",
|
|
145
|
+
"--target-dir",
|
|
146
|
+
temporary,
|
|
147
|
+
"--lib",
|
|
148
|
+
],
|
|
149
|
+
{
|
|
150
|
+
cwd: root,
|
|
151
|
+
stdio: "inherit",
|
|
152
|
+
env: buildEnvironment,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
if (build.error) throw build.error;
|
|
156
|
+
if (build.status !== 0) process.exit(build.status ?? 1);
|
|
157
|
+
const builtPath = path.join(
|
|
158
|
+
temporary,
|
|
159
|
+
"wasm32-unknown-unknown",
|
|
160
|
+
"release",
|
|
161
|
+
"buildchain_v4_contracts.wasm",
|
|
162
|
+
);
|
|
163
|
+
const built = fs.readFileSync(builtPath);
|
|
164
|
+
const artifactSha256 = sha256(built);
|
|
165
|
+
const generated = generatedMetadata(artifactSha256);
|
|
166
|
+
if (check) {
|
|
167
|
+
if (
|
|
168
|
+
!fs.existsSync(artifact) ||
|
|
169
|
+
!fs.readFileSync(artifact).equals(built)
|
|
170
|
+
) {
|
|
171
|
+
throw new Error("tracked Buildchain v4 Rust/WASM artifact is stale");
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
!fs.existsSync(metadata) ||
|
|
175
|
+
fs.readFileSync(metadata, "utf8") !== generated
|
|
176
|
+
) {
|
|
177
|
+
throw new Error("tracked Buildchain v4 Rust/WASM metadata is stale");
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
fs.copyFileSync(builtPath, artifact);
|
|
181
|
+
fs.writeFileSync(metadata, generated);
|
|
182
|
+
}
|
|
183
|
+
console.log(
|
|
184
|
+
`Buildchain v4 Rust/WASM ${check ? "verified" : "built"}: sha256:${artifactSha256}`,
|
|
185
|
+
);
|
|
186
|
+
} finally {
|
|
187
|
+
fs.rmSync(temporary, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -5,7 +5,7 @@ import { spawnSyncCommand } from "../packages/core/spawn-command.js";
|
|
|
5
5
|
|
|
6
6
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
7
|
const actionsRoot = path.join(root, "actions");
|
|
8
|
-
const
|
|
8
|
+
const javascriptBundlePaths = readdirSync(actionsRoot, { withFileTypes: true })
|
|
9
9
|
.filter((entry) => entry.isDirectory())
|
|
10
10
|
.map((entry) => path.join(actionsRoot, entry.name, "dist", "index.js"))
|
|
11
11
|
.filter((bundlePath) => {
|
|
@@ -17,6 +17,14 @@ const bundlePaths = readdirSync(actionsRoot, { withFileTypes: true })
|
|
|
17
17
|
}
|
|
18
18
|
})
|
|
19
19
|
.sort();
|
|
20
|
+
const wasmBundlePaths = [
|
|
21
|
+
"promote-buildchain-ref",
|
|
22
|
+
"release-tail",
|
|
23
|
+
"v4-release-candidate-promote",
|
|
24
|
+
].map((name) =>
|
|
25
|
+
path.join(actionsRoot, name, "dist", "buildchain-v4-domain.wasm"),
|
|
26
|
+
);
|
|
27
|
+
const bundlePaths = [...javascriptBundlePaths, ...wasmBundlePaths];
|
|
20
28
|
|
|
21
29
|
const before = new Map(
|
|
22
30
|
bundlePaths.map((bundlePath) => [bundlePath, readFileSync(bundlePath)]),
|
|
@@ -46,4 +54,6 @@ if (changed.length > 0) {
|
|
|
46
54
|
process.exit(1);
|
|
47
55
|
}
|
|
48
56
|
|
|
49
|
-
console.log(
|
|
57
|
+
console.log(
|
|
58
|
+
`action bundle integrity check passed (${bundlePaths.length} bundles)`,
|
|
59
|
+
);
|
|
@@ -83,6 +83,13 @@ function rootedPathSet(paths) {
|
|
|
83
83
|
.digest("hex")}`;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
function fileSha256(relative) {
|
|
87
|
+
return `sha256:${crypto
|
|
88
|
+
.createHash("sha256")
|
|
89
|
+
.update(fs.readFileSync(path.join(root, relative)))
|
|
90
|
+
.digest("hex")}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
86
93
|
function productionFiles(relative, extensions) {
|
|
87
94
|
const absolute = path.join(root, relative);
|
|
88
95
|
if (!fs.existsSync(absolute)) return [];
|
|
@@ -128,6 +135,12 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
128
135
|
/(?:createV4ReleaseReceipt|release-receipt\.json|V4_RELEASE_RECEIPT_CONTRACT)/u,
|
|
129
136
|
);
|
|
130
137
|
const privilegedModules = discoverStaticModuleClosure(PRIVILEGED_ENTRYPOINTS);
|
|
138
|
+
const rustWasmArtifact = "packages/core/buildchain-v4-domain.wasm";
|
|
139
|
+
const rustWasmDistributions = [
|
|
140
|
+
"actions/promote-buildchain-ref/dist/buildchain-v4-domain.wasm",
|
|
141
|
+
"actions/release-tail/dist/buildchain-v4-domain.wasm",
|
|
142
|
+
"actions/v4-release-candidate-promote/dist/buildchain-v4-domain.wasm",
|
|
143
|
+
];
|
|
131
144
|
const legacyEngineModules = productionFiles(
|
|
132
145
|
"actions/promote-buildchain-ref",
|
|
133
146
|
[".js", ".mjs", ".cjs"],
|
|
@@ -153,6 +166,17 @@ export function discoverV4ReleaseAuthorityClosure() {
|
|
|
153
166
|
modules: privilegedModules,
|
|
154
167
|
root: rootedPathSet(privilegedModules),
|
|
155
168
|
},
|
|
169
|
+
rustWasmAuthority: {
|
|
170
|
+
loader: "packages/core/v4-domain-wasm.js",
|
|
171
|
+
metadata: "packages/core/v4-domain-wasm-artifact.js",
|
|
172
|
+
artifact: rustWasmArtifact,
|
|
173
|
+
artifactRoot: fileSha256(rustWasmArtifact),
|
|
174
|
+
distributedArtifacts: rustWasmDistributions,
|
|
175
|
+
byteIdenticalDistributions: rustWasmDistributions.every(
|
|
176
|
+
(relative) => fileSha256(relative) === fileSha256(rustWasmArtifact),
|
|
177
|
+
),
|
|
178
|
+
fallbackWriterCount: 0,
|
|
179
|
+
},
|
|
156
180
|
legacyEngineModules,
|
|
157
181
|
};
|
|
158
182
|
}
|
|
@@ -183,31 +207,32 @@ function workflowSnapshot(relative) {
|
|
|
183
207
|
const jobs = document.jobs
|
|
184
208
|
.filter((job) => job.id !== "universal-bootstrap")
|
|
185
209
|
.map((job) => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
const block = jobBlock(source, job.id);
|
|
211
|
+
const uses = job.uses || null;
|
|
212
|
+
return {
|
|
213
|
+
id: job.id,
|
|
214
|
+
kind: uses ? "reusable-call" : "runner",
|
|
215
|
+
uses,
|
|
216
|
+
permissions: {
|
|
217
|
+
contents: permission(block, "contents"),
|
|
218
|
+
idToken: permission(block, "id-token"),
|
|
219
|
+
},
|
|
220
|
+
carriers: {
|
|
221
|
+
artifactDownload: /uses:\s+actions\/download-artifact@/u.test(block),
|
|
222
|
+
artifactUpload: /uses:\s+actions\/upload-artifact@/u.test(block),
|
|
223
|
+
jobOutput: /GITHUB_OUTPUT/u.test(block),
|
|
224
|
+
},
|
|
225
|
+
mutationSignals: [
|
|
226
|
+
/contents:\s*write/u.test(block) && "contents-write",
|
|
227
|
+
/id-token:\s*write/u.test(block) && "oidc-write",
|
|
228
|
+
/(?:promote-buildchain-ref|v4-release-candidate-promote)/u.test(
|
|
229
|
+
block,
|
|
230
|
+
) && "promotion-runtime",
|
|
231
|
+
/(?:git push|npm publish|gh release (?:create|upload))/u.test(
|
|
232
|
+
block,
|
|
233
|
+
) && "direct-publication-command",
|
|
234
|
+
].filter(Boolean),
|
|
235
|
+
};
|
|
211
236
|
});
|
|
212
237
|
return {
|
|
213
238
|
path: relative,
|