@kungfu-tech/buildchain 4.0.1-alpha.5 → 4.0.1-alpha.55

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.
Files changed (159) hide show
  1. package/README.md +9 -6
  2. package/actions/release-tail/README.md +8 -0
  3. package/actions/v4-release-candidate-promote/README.md +3 -0
  4. package/architecture/agent-change-map.md +1015 -0
  5. package/architecture/ci-lane-change-budget.json +256 -0
  6. package/architecture/decisions/0004-v4-declarative-release-tail.md +47 -0
  7. package/architecture/internal-capabilities.json +220 -15
  8. package/architecture/maintainability-debt.json +3229 -0
  9. package/architecture/maintainability-policy.json +489 -132
  10. package/architecture/release-tail-contract-inventory.json +5 -2
  11. package/architecture/v3-core-mechanism-inventory.json +4 -3
  12. package/architecture/v3-v4-capability-closure.md +59 -0
  13. package/architecture/v3-v4-live-capability-inventory.json +102404 -0
  14. package/architecture/v4-adopter-delivery-parity.json +32 -5
  15. package/architecture/v4-cross-platform-adopter-qualification.json +52 -0
  16. package/architecture/v4-delivery-warrant-shadow-fixtures.json +3 -3
  17. package/architecture/v4-floating-consumer-policy.json +6 -0
  18. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  19. package/architecture/v4-runtime-semantic-closure.json +710 -0
  20. package/bin/buildchain.mjs +7 -1
  21. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  22. package/bin/internal/command-registry.mjs +1 -0
  23. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  24. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  25. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  26. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  28. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  29. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  30. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  31. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  32. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  33. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  38. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  39. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  40. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  41. package/contracts/v4-publication-qualification-v1.schema.json +51 -0
  42. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  43. package/dist/site/agent-index.json +4 -0
  44. package/dist/site/artifact-schemas.json +8 -0
  45. package/dist/site/buildchain-contract.json +125 -34
  46. package/dist/site/buildchain-site.json +308 -33
  47. package/dist/site/capability-registry.json +13 -12
  48. package/dist/site/cli-registry.json +57 -2
  49. package/dist/site/controller-registry.json +41 -5
  50. package/dist/site/kfd-claims.json +438 -24
  51. package/dist/site/kfd-upstream-aggregate.json +1 -1
  52. package/dist/site/manual-registry.json +22 -7
  53. package/dist/site/node-api-registry.json +12843 -9314
  54. package/dist/site/page-registry.json +288 -21
  55. package/dist/site/public-surface-audit.json +281 -21
  56. package/dist/site/publication-authority-registry.json +66 -8
  57. package/dist/site/publication-registry.json +4 -4
  58. package/dist/site/release-provenance.json +20 -0
  59. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  60. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  61. package/dist/site/schemas/v4-publication-qualification-v1.schema.json +51 -0
  62. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  63. package/dist/site/site-manifest.json +19 -11
  64. package/dist/site/workflow-registry.json +195 -17
  65. package/docs/MAP.md +4 -2
  66. package/docs/cli-reference.md +70 -2
  67. package/docs/cli.md +8 -0
  68. package/docs/dev-delivery-warrant.md +34 -10
  69. package/docs/node-api-reference.md +524 -279
  70. package/docs/publish-transaction.md +11 -4
  71. package/docs/release-candidate.md +7 -0
  72. package/docs/release-tail-provider-plane.md +30 -0
  73. package/docs/v4-adopter-delivery.md +152 -0
  74. package/docs/v4-declarative-release-tail-migration.md +43 -0
  75. package/docs/v4-publication-rehearsal.md +130 -0
  76. package/package.json +24 -4
  77. package/packages/core/adopter-delivery-passport.js +295 -0
  78. package/packages/core/adopter-delivery-vectors.js +158 -0
  79. package/packages/core/adopter-delivery-vectors.json +77 -0
  80. package/packages/core/buildchain-agent-manuals.js +1 -0
  81. package/packages/core/buildchain-config.js +84 -0
  82. package/packages/core/buildchain-contract.js +31 -1
  83. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  84. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  85. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  86. package/packages/core/buildchain-publication-authority.js +2 -0
  87. package/packages/core/ci-lane-change-budget.js +247 -0
  88. package/packages/core/dev-delivery-native-proof.js +43 -27
  89. package/packages/core/dev-delivery-warrant-settlement.js +239 -3
  90. package/packages/core/dev-delivery-warrant-state.js +46 -10
  91. package/packages/core/dev-delivery-warrant.js +29 -7
  92. package/packages/core/github-governance-authority.js +5 -4
  93. package/packages/core/index.js +6 -0
  94. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  95. package/packages/core/paper-scaffold-content.js +401 -0
  96. package/packages/core/paper.js +12 -378
  97. package/packages/core/publication-control-plane-audit.js +135 -111
  98. package/packages/core/publication-rehearsal-projection.js +104 -0
  99. package/packages/core/publication-rehearsal-runtime.js +190 -0
  100. package/packages/core/published-delivery-authority.js +266 -0
  101. package/packages/core/release-candidate-recovery.js +71 -15
  102. package/packages/core/release-candidate.js +2 -2
  103. package/packages/core/release-passport.js +95 -170
  104. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  105. package/packages/core/v4-adopter-delivery.js +359 -0
  106. package/packages/core/v4-cross-platform-adopter-qualification.js +343 -0
  107. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  108. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  109. package/packages/core/v4-floating-consumer-policy.js +139 -35
  110. package/packages/core/v4-floating-consumer-release-passport.js +154 -8
  111. package/packages/core/v4-publication-qualification.js +222 -0
  112. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  113. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  114. package/packages/core/v4-publication-rehearsal.js +481 -0
  115. package/packages/core/workflow-yaml-contract.js +24 -2
  116. package/scripts/artifact-signing-controller-core.mjs +1 -1
  117. package/scripts/audit-publication-control-plane.mjs +17 -17
  118. package/scripts/buildchain-cli-help.mjs +13 -0
  119. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  120. package/scripts/check-internal-architecture.mjs +23 -3
  121. package/scripts/check-inventory.mjs +24 -6
  122. package/scripts/check-maintainability.mjs +145 -4
  123. package/scripts/check-project-cut-merge-queue-admission.mjs +87 -0
  124. package/scripts/check-v3-v4-capability-inventory.mjs +574 -0
  125. package/scripts/check-v4-floating-consumer-policy-contract.mjs +30 -4
  126. package/scripts/check-v4-runtime-semantic-closure.mjs +266 -0
  127. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  128. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  129. package/scripts/dev-delivery-warrant-options.mjs +27 -11
  130. package/scripts/dev-delivery-warrant-store.mjs +10 -6
  131. package/scripts/dev-delivery-warrant.mjs +50 -6
  132. package/scripts/dev-pr-auto-merge.mjs +25 -11
  133. package/scripts/dev-pr-delivery-warrant.mjs +45 -0
  134. package/scripts/dev-pr-prequeue-guard.mjs +0 -18
  135. package/scripts/dev-qualification-patrol.mjs +9 -9
  136. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  137. package/scripts/generate-agent-change-map.mjs +118 -0
  138. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  139. package/scripts/generate-release-candidate-passport.mjs +177 -0
  140. package/scripts/generate-site-bundle.mjs +36 -0
  141. package/scripts/maintainability-domain-metrics.mjs +237 -0
  142. package/scripts/maintainability-governance.mjs +473 -0
  143. package/scripts/maintainability-metrics.mjs +28 -6
  144. package/scripts/project-cut-merge-queue-admission.mjs +394 -0
  145. package/scripts/promotion-channel-router.mjs +4 -4
  146. package/scripts/promotion-routing-evidence.mjs +35 -6
  147. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  148. package/scripts/release-candidate-resolver.mjs +44 -0
  149. package/scripts/release-line-policy.mjs +25 -15
  150. package/scripts/release-tail.mjs +68 -3
  151. package/scripts/resume-from-candidate-run.mjs +595 -65
  152. package/scripts/run-lifecycle-core.mjs +341 -376
  153. package/scripts/site-capability-metadata.mjs +21 -0
  154. package/scripts/stable-candidate-qualification.mjs +9 -10
  155. package/scripts/v3-v4-capability-catalog.mjs +439 -0
  156. package/scripts/v4-consumer-policy.mjs +4 -0
  157. package/scripts/v4-cross-platform-adopter-qualification.mjs +228 -0
  158. package/scripts/v4-declarative-promotion-admission.mjs +42 -0
  159. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
@@ -0,0 +1,481 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import {
6
+ executeReleaseTailTransaction,
7
+ releaseTailRoot,
8
+ validateReleaseTailTransaction,
9
+ } from "./release-tail-provider-plane.js";
10
+ import {
11
+ V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT,
12
+ V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
13
+ V4_PUBLICATION_REHEARSAL_CORE_VERSION,
14
+ V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
15
+ V4_PUBLICATION_REHEARSAL_PROVIDER_POLICY_CONTRACT,
16
+ V4PublicationRehearsalFault,
17
+ assertPublicationRehearsalExactKeys as exactKeys,
18
+ createV4PublicationRehearsalCapsule,
19
+ createV4PublicationRehearsalProviderPolicy,
20
+ publicationRehearsalFault as fault,
21
+ publicationRehearsalRoot,
22
+ validateV4PublicationRehearsalCapsule,
23
+ validateV4PublicationRehearsalProviderBindings,
24
+ } from "./v4-publication-rehearsal-capsule.js";
25
+
26
+ export {
27
+ V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT,
28
+ V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
29
+ V4_PUBLICATION_REHEARSAL_CORE_VERSION,
30
+ V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
31
+ V4_PUBLICATION_REHEARSAL_PROVIDER_POLICY_CONTRACT,
32
+ V4PublicationRehearsalFault,
33
+ createV4PublicationRehearsalCapsule,
34
+ createV4PublicationRehearsalProviderPolicy,
35
+ validateV4PublicationRehearsalCapsule,
36
+ validateV4PublicationRehearsalProviderBindings,
37
+ };
38
+
39
+ const ROOT = /^sha256:[0-9a-f]{64}$/u;
40
+ const MODES = new Set(["simulate", "replay", "provider"]);
41
+
42
+ function fileRoot(filePath) {
43
+ const hash = crypto.createHash("sha256");
44
+ const descriptor = fs.openSync(filePath, "r");
45
+ const buffer = Buffer.allocUnsafe(8 * 1024 * 1024);
46
+ try {
47
+ for (;;) {
48
+ const count = fs.readSync(descriptor, buffer, 0, buffer.length, null);
49
+ if (count === 0) break;
50
+ hash.update(buffer.subarray(0, count));
51
+ }
52
+ } finally {
53
+ fs.closeSync(descriptor);
54
+ }
55
+ return `sha256:${hash.digest("hex")}`;
56
+ }
57
+
58
+ function resolveCandidateFile(candidateRootPath, relative, location) {
59
+ const base = fs.realpathSync(candidateRootPath);
60
+ const resolved = path.resolve(base, relative);
61
+ if (!resolved.startsWith(`${base}${path.sep}`))
62
+ fault(
63
+ "publication-rehearsal-path-escape",
64
+ location,
65
+ "candidate path escapes the explicit root",
66
+ );
67
+ if (
68
+ !fs.existsSync(resolved) ||
69
+ !fs.statSync(resolved).isFile() ||
70
+ fs.lstatSync(resolved).isSymbolicLink() ||
71
+ !fs.realpathSync(resolved).startsWith(`${base}${path.sep}`)
72
+ )
73
+ fault(
74
+ "publication-rehearsal-file-missing",
75
+ location,
76
+ "regular in-root file required",
77
+ );
78
+ return resolved;
79
+ }
80
+
81
+ function resolveCandidateOutput(candidateRootPath, relative, location) {
82
+ const base = fs.realpathSync(candidateRootPath);
83
+ const resolved = path.resolve(base, relative);
84
+ if (!resolved.startsWith(`${base}${path.sep}`))
85
+ fault(
86
+ "publication-rehearsal-path-escape",
87
+ location,
88
+ "candidate output escapes the explicit root",
89
+ );
90
+ for (
91
+ let current = path.dirname(resolved);
92
+ current !== base;
93
+ current = path.dirname(current)
94
+ ) {
95
+ if (!fs.existsSync(current)) continue;
96
+ if (
97
+ fs.lstatSync(current).isSymbolicLink() ||
98
+ !fs.statSync(current).isDirectory() ||
99
+ !fs.realpathSync(current).startsWith(`${base}${path.sep}`)
100
+ )
101
+ fault(
102
+ "publication-rehearsal-path-escape",
103
+ location,
104
+ "candidate output ancestor is not a regular in-root directory",
105
+ );
106
+ }
107
+ if (
108
+ fs.existsSync(resolved) &&
109
+ (fs.lstatSync(resolved).isSymbolicLink() ||
110
+ !fs.statSync(resolved).isFile() ||
111
+ !fs.realpathSync(resolved).startsWith(`${base}${path.sep}`))
112
+ )
113
+ fault(
114
+ "publication-rehearsal-path-escape",
115
+ location,
116
+ "candidate output is not a regular in-root file",
117
+ );
118
+ return resolved;
119
+ }
120
+
121
+ export function verifyV4PublicationRehearsalCapsule({
122
+ capsule,
123
+ candidateRoot: candidateRootPath,
124
+ }) {
125
+ const normalized = validateV4PublicationRehearsalCapsule(capsule);
126
+ if (!path.isAbsolute(candidateRootPath || ""))
127
+ fault(
128
+ "publication-rehearsal-implicit-root-forbidden",
129
+ "$/candidateRoot",
130
+ "explicit absolute candidate root required",
131
+ );
132
+ if (
133
+ !fs.existsSync(candidateRootPath) ||
134
+ !fs.statSync(candidateRootPath).isDirectory()
135
+ )
136
+ fault(
137
+ "publication-rehearsal-candidate-missing",
138
+ "$/candidateRoot",
139
+ "candidate directory required",
140
+ );
141
+ for (const entry of normalized.files) {
142
+ const resolved = resolveCandidateFile(
143
+ candidateRootPath,
144
+ entry.path,
145
+ `$/files/${entry.path}`,
146
+ );
147
+ if (
148
+ fs.statSync(resolved).size !== entry.size ||
149
+ fileRoot(resolved) !== entry.root
150
+ )
151
+ fault(
152
+ "publication-rehearsal-file-tampered",
153
+ `$/files/${entry.path}`,
154
+ "candidate bytes do not match the capsule",
155
+ );
156
+ }
157
+ return normalized;
158
+ }
159
+
160
+ export function resolveV4PublicationRehearsalProviderBindings({
161
+ capsule,
162
+ candidateRoot: candidateRootPath,
163
+ providerBindings = capsule?.providerBindings,
164
+ }) {
165
+ const normalized = verifyV4PublicationRehearsalCapsule({
166
+ capsule,
167
+ candidateRoot: candidateRootPath,
168
+ });
169
+ const supplied =
170
+ validateV4PublicationRehearsalProviderBindings(providerBindings);
171
+ if (
172
+ releaseTailRoot(supplied) !== normalized.providerBindingsRoot ||
173
+ JSON.stringify(supplied) !== JSON.stringify(normalized.providerBindings)
174
+ )
175
+ fault(
176
+ "publication-rehearsal-provider-bindings-root-mismatch",
177
+ "$/providerBindings",
178
+ "external provider bindings do not match the capsule",
179
+ );
180
+ return {
181
+ schema: supplied.schema,
182
+ artifacts: Object.fromEntries(
183
+ Object.entries(supplied.artifacts).map(([role, binding]) => [
184
+ role,
185
+ {
186
+ ...binding,
187
+ path: resolveCandidateFile(
188
+ candidateRootPath,
189
+ binding.path,
190
+ `$/providerBindings/artifacts/${role}/path`,
191
+ ),
192
+ },
193
+ ]),
194
+ ),
195
+ documents: Object.fromEntries(
196
+ Object.entries(supplied.documents).map(([capabilityId, binding]) => [
197
+ capabilityId,
198
+ {
199
+ ...binding,
200
+ path: resolveCandidateFile(
201
+ candidateRootPath,
202
+ binding.path,
203
+ `$/providerBindings/documents/${capabilityId}/path`,
204
+ ),
205
+ },
206
+ ]),
207
+ ),
208
+ evidence: {
209
+ inputs: supplied.evidence.inputs.map((inputPath, index) =>
210
+ resolveCandidateFile(
211
+ candidateRootPath,
212
+ inputPath,
213
+ `$/providerBindings/evidence/inputs/${index}`,
214
+ ),
215
+ ),
216
+ output: resolveCandidateOutput(
217
+ candidateRootPath,
218
+ supplied.evidence.output,
219
+ "$/providerBindings/evidence/output",
220
+ ),
221
+ },
222
+ };
223
+ }
224
+
225
+ function simulatedAdapters(declaration, transcript) {
226
+ const applied = new Set();
227
+ return Object.fromEntries(
228
+ declaration.capabilities.map((capability) => [
229
+ capability.adapter,
230
+ {
231
+ async readback(effect) {
232
+ const response = applied.has(effect.operationId)
233
+ ? {
234
+ outcome: "observed",
235
+ subjectRoot: effect.subjectRoot,
236
+ targetRoot: effect.targetRoot,
237
+ evidenceRoots: [effect.targetRoot],
238
+ providerCode: "simulation-observed",
239
+ }
240
+ : {
241
+ outcome: "absent",
242
+ subjectRoot: "",
243
+ targetRoot: "",
244
+ evidenceRoots: [],
245
+ providerCode: "simulation-absent",
246
+ };
247
+ transcript.push({
248
+ operationId: effect.operationId,
249
+ method: "readback",
250
+ requestRoot: effect.effectRoot,
251
+ response,
252
+ });
253
+ return response;
254
+ },
255
+ async apply(effect) {
256
+ applied.add(effect.operationId);
257
+ transcript.push({
258
+ operationId: effect.operationId,
259
+ method: "apply",
260
+ requestRoot: effect.effectRoot,
261
+ response: { outcome: "simulated", code: "effect-disabled" },
262
+ });
263
+ },
264
+ },
265
+ ]),
266
+ );
267
+ }
268
+
269
+ function observationAdapters(capsule, transcript, adapters, mode) {
270
+ const records = new Map(
271
+ capsule.expectedObservations.entries.map((entry) => [
272
+ entry.operationId,
273
+ { ...entry, index: 0 },
274
+ ]),
275
+ );
276
+ return Object.fromEntries(
277
+ capsule.declaration.capabilities.map((capability) => {
278
+ const provided = adapters?.[capability.adapter];
279
+ if (
280
+ mode === "provider" &&
281
+ (!provided ||
282
+ typeof provided.readback !== "function" ||
283
+ typeof provided.apply !== "function")
284
+ )
285
+ fault(
286
+ "publication-rehearsal-provider-adapter-missing",
287
+ "$/adapters",
288
+ `adapter missing: ${capability.adapter}`,
289
+ );
290
+ return [
291
+ capability.adapter,
292
+ {
293
+ async readback(effect) {
294
+ const record = records.get(effect.operationId);
295
+ const expected = record?.readbacks[record.index++];
296
+ if (!expected)
297
+ fault(
298
+ "publication-rehearsal-observation-exhausted",
299
+ "$/expectedObservations",
300
+ `readback missing for ${effect.operationId}`,
301
+ );
302
+ const response =
303
+ mode === "replay"
304
+ ? structuredClone(expected)
305
+ : await provided.readback(effect);
306
+ if (JSON.stringify(response) !== JSON.stringify(expected))
307
+ fault(
308
+ "publication-rehearsal-provider-readback-mismatch",
309
+ "$/expectedObservations",
310
+ `provider readback drifted for ${effect.operationId}`,
311
+ );
312
+ transcript.push({
313
+ operationId: effect.operationId,
314
+ method: "readback",
315
+ requestRoot: effect.effectRoot,
316
+ response: structuredClone(response),
317
+ });
318
+ return response;
319
+ },
320
+ async apply(effect) {
321
+ const record = records.get(effect.operationId);
322
+ if (!record)
323
+ fault(
324
+ "publication-rehearsal-observation-exhausted",
325
+ "$/expectedObservations",
326
+ `apply missing for ${effect.operationId}`,
327
+ );
328
+ if (mode === "provider") await provided.apply(effect);
329
+ transcript.push({
330
+ operationId: effect.operationId,
331
+ method: "apply",
332
+ requestRoot: effect.effectRoot,
333
+ response: structuredClone(record.apply),
334
+ });
335
+ if (record.apply.outcome !== "applied") {
336
+ const error = new Error(
337
+ "expected provider apply did not succeed",
338
+ );
339
+ error.releaseTailCode = record.apply.code;
340
+ error.releaseTailClass = record.apply.classification;
341
+ throw error;
342
+ }
343
+ },
344
+ },
345
+ ];
346
+ }),
347
+ );
348
+ }
349
+
350
+ export function createV4PublicationRehearsalAuthority(
351
+ capsule,
352
+ { authorizationRoot } = {},
353
+ ) {
354
+ const normalized = validateV4PublicationRehearsalCapsule(capsule);
355
+ const body = {
356
+ schema: V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT,
357
+ mode: "provider-rehearsal",
358
+ capsuleRoot: normalized.capsuleRoot,
359
+ providerPolicyRoot: normalized.providerPolicy.root,
360
+ providerBindingsRoot: normalized.providerBindingsRoot,
361
+ authorizationRoot: publicationRehearsalRoot(
362
+ authorizationRoot,
363
+ "$/authorizationRoot",
364
+ ),
365
+ productionAuthority: false,
366
+ };
367
+ return { ...body, authorityRoot: releaseTailRoot(body) };
368
+ }
369
+
370
+ function validateAuthority(value, capsule) {
371
+ exactKeys(
372
+ value,
373
+ [
374
+ "schema",
375
+ "mode",
376
+ "capsuleRoot",
377
+ "providerPolicyRoot",
378
+ "providerBindingsRoot",
379
+ "authorizationRoot",
380
+ "productionAuthority",
381
+ "authorityRoot",
382
+ ],
383
+ "$/authority",
384
+ );
385
+ const body = { ...value };
386
+ delete body.authorityRoot;
387
+ if (
388
+ value.schema !== V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT ||
389
+ value.mode !== "provider-rehearsal" ||
390
+ value.productionAuthority !== false ||
391
+ value.capsuleRoot !== capsule.capsuleRoot ||
392
+ value.providerPolicyRoot !== capsule.providerPolicy.root ||
393
+ value.providerBindingsRoot !== capsule.providerBindingsRoot ||
394
+ !ROOT.test(value.authorizationRoot) ||
395
+ value.authorityRoot !== releaseTailRoot(body)
396
+ )
397
+ fault(
398
+ "publication-rehearsal-authority-mismatch",
399
+ "$/authority",
400
+ "exact rehearsal-only authority required",
401
+ );
402
+ return structuredClone(value);
403
+ }
404
+
405
+ export async function executeV4PublicationRehearsal({
406
+ capsule,
407
+ candidateRoot: candidateRootPath,
408
+ mode = "simulate",
409
+ adapters = {},
410
+ authority = null,
411
+ transaction = null,
412
+ checkpoint,
413
+ } = {}) {
414
+ if (!MODES.has(mode))
415
+ fault(
416
+ "invalid-publication-rehearsal-mode",
417
+ "$/mode",
418
+ "simulate, replay, or provider required",
419
+ );
420
+ const normalized = verifyV4PublicationRehearsalCapsule({
421
+ capsule,
422
+ candidateRoot: candidateRootPath,
423
+ });
424
+ const providerAuthority =
425
+ mode === "provider" ? validateAuthority(authority, normalized) : null;
426
+ const current = transaction
427
+ ? structuredClone(transaction)
428
+ : structuredClone(normalized.transaction);
429
+ const validation = validateReleaseTailTransaction(current);
430
+ if (
431
+ !validation.valid ||
432
+ current.transactionRoot !== normalized.transaction.transactionRoot ||
433
+ current.declarationRoot !== normalized.transaction.declarationRoot ||
434
+ current.planRoot !== normalized.transaction.planRoot
435
+ )
436
+ fault(
437
+ "publication-rehearsal-transaction-mismatch",
438
+ "$/transaction",
439
+ "transaction is not bound to this capsule",
440
+ );
441
+ const transcript = [];
442
+ const selectedAdapters =
443
+ mode === "simulate"
444
+ ? simulatedAdapters(normalized.declaration, transcript)
445
+ : observationAdapters(normalized, transcript, adapters, mode);
446
+ const result = await executeReleaseTailTransaction(current, {
447
+ adapters: selectedAdapters,
448
+ checkpoint,
449
+ });
450
+ const body = {
451
+ schema: V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT,
452
+ mode,
453
+ truth:
454
+ mode === "provider"
455
+ ? "provider-observed"
456
+ : mode === "replay"
457
+ ? "recorded-replay"
458
+ : "simulation-only",
459
+ capsuleRoot: normalized.capsuleRoot,
460
+ sourceRoot: normalized.source.root,
461
+ candidateRoot: normalized.candidate.root,
462
+ manifestRoot: normalized.manifest.root,
463
+ configRoot: normalized.config.root,
464
+ providerBindingsRoot: normalized.providerBindingsRoot,
465
+ providerPolicyRoot: normalized.providerPolicy.root,
466
+ expectedObservationRoot: normalized.expectedObservations.root,
467
+ coreVersion: normalized.coreVersion,
468
+ transactionRoot: result.transactionRoot,
469
+ stateRoot: result.stateRoot,
470
+ receiptRoots: result.receipts.map(({ receiptRoot }) => receiptRoot),
471
+ transcript,
472
+ transcriptRoot: releaseTailRoot(transcript),
473
+ authorityRoot: providerAuthority?.authorityRoot || null,
474
+ productionAuthority: false,
475
+ releasePassport: null,
476
+ };
477
+ return {
478
+ transaction: result,
479
+ evidence: { ...body, evidenceRoot: releaseTailRoot(body) },
480
+ };
481
+ }
@@ -67,8 +67,7 @@ function directEntries(lines, parentLine) {
67
67
  const trimmed = line.trim();
68
68
  const indent = indentation(line);
69
69
  if (trimmed && indent <= parentIndent) break;
70
- if (!trimmed || trimmed.startsWith("#") || indent !== childIndent)
71
- continue;
70
+ if (!trimmed || trimmed.startsWith("#") || indent !== childIndent) continue;
72
71
  const match = line.match(/^\s*([A-Za-z0-9_.-]+):(?:\s*(.*))?$/);
73
72
  if (match) {
74
73
  entries.push({
@@ -263,6 +262,28 @@ export function parseWorkflowCallJobs(text) {
263
262
  .filter((job) => job.uses);
264
263
  }
265
264
 
265
+ export function parseWorkflowJobs(text) {
266
+ const lines = String(text || "").split(/\r?\n/);
267
+ const jobs = topLevelEntry(lines, "jobs");
268
+ if (!jobs) return [];
269
+ return directEntries(lines, jobs.line)
270
+ .map((job) => {
271
+ const properties = directEntries(lines, job.line);
272
+ const property = (name) =>
273
+ properties.find((entry) => entry.key === name) || null;
274
+ return {
275
+ id: job.key,
276
+ name: unquote(property("name")?.value || job.key),
277
+ uses: unquote(property("uses")?.value || ""),
278
+ runsOn: unquote(property("runs-on")?.value || ""),
279
+ timeoutMinutes: property("timeout-minutes")
280
+ ? scalar(property("timeout-minutes").value)
281
+ : null,
282
+ };
283
+ })
284
+ .sort((left, right) => compareCodeUnits(left.id, right.id));
285
+ }
286
+
266
287
  function blockScalarIndent(lines, lineIndex, parentIndent) {
267
288
  for (let index = lineIndex + 1; index < lines.length; index += 1) {
268
289
  if (!lines[index].trim()) continue;
@@ -315,6 +336,7 @@ export function parseWorkflowDocument(text) {
315
336
  return {
316
337
  triggers: parseTriggers(String(text || "").split(/\r?\n/)),
317
338
  interface: parseReusableWorkflowInterface(text),
339
+ jobs: parseWorkflowJobs(text),
318
340
  callJobs: parseWorkflowCallJobs(text),
319
341
  };
320
342
  }
@@ -374,6 +374,7 @@ export function assertArtifactSigningControlRequestContext(
374
374
  export function artifactSigningControlRequestOutputs(request) {
375
375
  const value = validateArtifactSigningControlRequest(request);
376
376
  return {
377
+ "source-run-attempt": String(value.source.runAttempt),
377
378
  "request-count": String(value.request.count),
378
379
  "request-artifact": value.request.artifact,
379
380
  "request-root": value.request.root,
@@ -384,7 +385,6 @@ export function artifactSigningControlRequestOutputs(request) {
384
385
  "runtime-sha": value.runtime.sha,
385
386
  };
386
387
  }
387
-
388
388
  export function normalizeControllerStatus(value) {
389
389
  const normalized = required(value, "authority status");
390
390
  if (
@@ -11,10 +11,9 @@ import {
11
11
 
12
12
  const GITHUB_JSON_MAX_BUFFER = 16 * 1024 * 1024;
13
13
 
14
- function flag(name, fallback = "") {
15
- const index = process.argv.indexOf(`--${name}`);
16
- return index === -1 ? fallback : String(process.argv[index + 1] || "");
17
- }
14
+ const flag = (name, fallback = "") => process.argv.includes(`--${name}`)
15
+ ? String(process.argv[process.argv.indexOf(`--${name}`) + 1] || "")
16
+ : fallback;
18
17
 
19
18
  function commandJson(command, args, label, { publicReadFallback = false } = {}) {
20
19
  const options = {
@@ -42,13 +41,9 @@ function commandJson(command, args, label, { publicReadFallback = false } = {})
42
41
  }
43
42
  }
44
43
 
45
- function githubJson(apiPath, label, { publicReadFallback = false } = {}) {
46
- return commandJson("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], label, { publicReadFallback });
47
- }
44
+ const githubJson = (apiPath, label, { publicReadFallback = false } = {}) => commandJson("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], label, { publicReadFallback });
48
45
 
49
- function githubPublicJson(apiPath, label) {
50
- return githubJson(apiPath, label, { publicReadFallback: true });
51
- }
46
+ const githubPublicJson = (apiPath, label) => githubJson(apiPath, label, { publicReadFallback: true });
52
47
 
53
48
  function githubJsonOptional(apiPath, label, fallback, fallbackPattern = /404|not found/i) {
54
49
  const result = spawnSyncCommand("gh", ["api", apiPath, "-H", "Accept: application/vnd.github+json"], {
@@ -68,9 +63,7 @@ function githubJsonOptional(apiPath, label, fallback, fallbackPattern = /404|not
68
63
  }
69
64
  }
70
65
 
71
- function githubJsonReadLimited(apiPath, label, fallback) {
72
- return githubJsonOptional(apiPath, label, fallback, /401|403|404|unauthorized|forbidden|not found/i);
73
- }
66
+ const githubJsonReadLimited = (apiPath, label, fallback) => githubJsonOptional(apiPath, label, fallback, /401|403|404|unauthorized|forbidden|not found/i);
74
67
 
75
68
  function rulesetIncludesBranch(ruleset, branch, defaultBranch) {
76
69
  const includes = ruleset.conditions?.ref_name?.include || [];
@@ -102,7 +95,7 @@ function normalizeRulesetBranchPolicy(rulesets, branch, defaultBranch) {
102
95
  };
103
96
  }
104
97
 
105
- function jobBlock(workflowText, jobId) {
98
+ function jobBlock(workflowText, jobId, delegated = false) {
106
99
  const lines = String(workflowText).split(/\r?\n/);
107
100
  const jobsIndex = lines.findIndex((line) => /^jobs:\s*$/.test(line));
108
101
  if (jobsIndex === -1) return "";
@@ -115,14 +108,21 @@ function jobBlock(workflowText, jobId) {
115
108
  break;
116
109
  }
117
110
  }
118
- return lines.slice(start, end).join("\n");
111
+ const block = lines.slice(start, end).join("\n");
112
+ if (delegated) return block;
113
+ const declarations = [...block.matchAll(/^ {4}# buildchain-publication-authority-job:\s*([A-Za-z0-9_-]+)\s*$/gm)];
114
+ if (declarations.length > 1 || declarations[0]?.[1] === jobId) throw new Error(`publication authority job delegation is invalid: ${jobId}`);
115
+ if (!declarations.length) return block;
116
+ const delegatedJobId = declarations[0][1];
117
+ const delegatedBlock = jobBlock(workflowText, delegatedJobId, true);
118
+ if (!delegatedBlock) throw new Error(`delegated publication workflow job is missing: ${delegatedJobId}`);
119
+ return delegatedBlock;
119
120
  }
120
121
 
121
122
  function first(value, keys) {
122
123
  for (const key of keys) {
123
- const parts = key.split(".");
124
124
  let current = value;
125
- for (const part of parts) current = current && typeof current === "object" ? current[part] : undefined;
125
+ for (const part of key.split(".")) current = current && typeof current === "object" ? current[part] : undefined;
126
126
  if (current !== undefined && current !== null && current !== "") return current;
127
127
  }
128
128
  return undefined;
@@ -8,6 +8,11 @@ export const BUILDCHAIN_USAGE = `Usage:
8
8
  buildchain architecture qualify --authority-revision <git-revision>
9
9
  [--candidate-revision <git-revision>]
10
10
  [--cwd <dir>] [--json]
11
+ buildchain adopter-delivery run --input <json-or-path> [--output <path>]
12
+ buildchain adopter-delivery verify --input <json-or-path>
13
+ --readback <json-or-path> [--output <path>]
14
+ buildchain adopter-delivery bootstrap --input <json-or-path> [--output <path>]
15
+ buildchain adopter-delivery archive --input <json-or-path> [--output <path>]
11
16
  buildchain portable-cache plan --manifest <file-or-json> [--output <file>]
12
17
  [--github-output <file>] [--json]
13
18
  buildchain portable-cache receipt --plan <file-or-json> [--matched-key <key>]
@@ -44,6 +49,13 @@ export const BUILDCHAIN_USAGE = `Usage:
44
49
  buildchain release-tail init --declaration <json-or-path> [--state <path>]
45
50
  buildchain release-tail <status|verify> [--state <path>] [--output <path>]
46
51
  buildchain release-tail compat --hooks-json <json-or-path> [--output <path>]
52
+ buildchain release-tail rehearse --capsule <absolute-path>
53
+ --candidate-root <absolute-path>
54
+ [--capsule-root <absolute-path>]
55
+ [--environment-json <empty-json-or-path>]
56
+ --mode <simulate|replay>
57
+ --state <absolute-path>
58
+ --evidence <absolute-path>
47
59
  buildchain tail-reseal plan --request <request.json> [--output <plan.json>]
48
60
  buildchain tail-reseal admit --request <request.json> [--output <admission.json>]
49
61
  buildchain tail-reseal verify-platform --request <request.json> --platform <id>
@@ -60,6 +72,7 @@ export const BUILDCHAIN_USAGE = `Usage:
60
72
  buildchain release <inspect|recover|finalize|abort> ...
61
73
  buildchain transaction inspect ...
62
74
  buildchain collect github-release --tag <tag> [--repository <owner/repo>]
75
+ [--adopter-delivery-json <legacy-json-or-path>]
63
76
  [--assets-dir <dir>] [--assets-json <json-or-path>]
64
77
  [--release-json <json-or-path>] [--package-set-json <json-or-path>]
65
78
  [--product-name <name>]