@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21

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 (203) hide show
  1. package/actions/release-tail/README.md +8 -0
  2. package/architecture/ci-lane-change-budget.json +187 -0
  3. package/architecture/decisions/0002-next-development-transition.md +101 -0
  4. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  5. package/architecture/internal-capabilities.json +211 -8
  6. package/architecture/maintainability-policy.json +113 -28
  7. package/architecture/release-tail-contract-inventory.json +5 -2
  8. package/architecture/v3-core-mechanism-inventory.json +91 -0
  9. package/architecture/v4-adopter-delivery-parity.json +32 -5
  10. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  11. package/architecture/v4-compatibility-facts-parity.json +77 -0
  12. package/architecture/v4-delivery-authority-parity.json +250 -0
  13. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  14. package/architecture/v4-floating-consumer-policy.json +6 -0
  15. package/architecture/v4-next-development-parity.json +192 -0
  16. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  17. package/architecture/v4-release-train-parity.json +1 -0
  18. package/architecture/v4-tail-reseal-parity.json +150 -0
  19. package/bin/buildchain.mjs +32 -3
  20. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  21. package/bin/internal/command-registry.mjs +3 -0
  22. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  23. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  24. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  25. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  26. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  27. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  28. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  29. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  30. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  31. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  32. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  33. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  34. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  35. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  36. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  37. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  38. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  39. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  40. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  41. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  42. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  43. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  44. package/contracts/next-development-request-v1.schema.json +66 -0
  45. package/contracts/next-development-transition-v1.schema.json +292 -0
  46. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  47. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  48. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  49. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  50. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  51. package/dist/site/agent-index.json +5 -0
  52. package/dist/site/artifact-schemas.json +10 -0
  53. package/dist/site/buildchain-contract.json +1475 -27
  54. package/dist/site/buildchain-site.json +627 -31
  55. package/dist/site/capability-registry.json +17 -14
  56. package/dist/site/cli-registry.json +205 -3
  57. package/dist/site/kfd-claims.json +490 -16
  58. package/dist/site/kfd-upstream-aggregate.json +1 -1
  59. package/dist/site/manual-registry.json +52 -6
  60. package/dist/site/node-api-registry.json +14736 -8851
  61. package/dist/site/page-registry.json +593 -21
  62. package/dist/site/public-surface-audit.json +837 -48
  63. package/dist/site/publication-authority-registry.json +44 -8
  64. package/dist/site/publication-registry.json +4 -4
  65. package/dist/site/release-provenance.json +24 -0
  66. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  67. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  68. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  69. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  70. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  71. package/dist/site/site-manifest.json +34 -10
  72. package/dist/site/workflow-registry.json +160 -10
  73. package/docs/MAP.md +8 -2
  74. package/docs/cli-reference.md +404 -13
  75. package/docs/cli.md +8 -0
  76. package/docs/dev-alpha-candidate-patrol.md +8 -0
  77. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  78. package/docs/dev-delivery-warrant.md +351 -38
  79. package/docs/next-development-transition.md +118 -0
  80. package/docs/node-api-reference.md +646 -293
  81. package/docs/release-tail-provider-plane.md +30 -0
  82. package/docs/v4-adopter-delivery.md +133 -0
  83. package/docs/v4-compatibility-facts.md +91 -0
  84. package/docs/v4-publication-rehearsal.md +130 -0
  85. package/docs/v4-tail-reseal.md +73 -0
  86. package/docs/versioning.md +4 -3
  87. package/package.json +34 -6
  88. package/packages/core/adopter-delivery-passport.js +259 -0
  89. package/packages/core/adopter-delivery-vectors.js +158 -0
  90. package/packages/core/adopter-delivery-vectors.json +77 -0
  91. package/packages/core/buildchain-agent-manuals.js +3 -0
  92. package/packages/core/buildchain-compatibility-authority.js +336 -0
  93. package/packages/core/buildchain-compatibility-fact.js +394 -0
  94. package/packages/core/buildchain-compatibility-facts.json +217 -0
  95. package/packages/core/buildchain-compatibility-proof.js +566 -0
  96. package/packages/core/buildchain-config.js +84 -0
  97. package/packages/core/buildchain-contract.js +96 -143
  98. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  99. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  100. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  101. package/packages/core/buildchain-publication-authority.js +1 -0
  102. package/packages/core/channel-candidate.js +8 -0
  103. package/packages/core/channel-promotion-baseline.js +55 -0
  104. package/packages/core/ci-lane-change-budget.js +247 -0
  105. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  106. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  107. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  108. package/packages/core/dev-delivery-authority-landing.js +461 -0
  109. package/packages/core/dev-delivery-authority-observation.js +48 -0
  110. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  111. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  112. package/packages/core/dev-delivery-authority-state.js +583 -0
  113. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  114. package/packages/core/dev-delivery-contract-surface.js +76 -0
  115. package/packages/core/dev-delivery-execution-failure.js +133 -0
  116. package/packages/core/dev-delivery-execution-transfer.js +592 -0
  117. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  118. package/packages/core/dev-delivery-landing-readback.js +598 -0
  119. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  120. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  121. package/packages/core/dev-delivery-native-execution.js +110 -0
  122. package/packages/core/dev-delivery-native-proof.js +562 -0
  123. package/packages/core/dev-delivery-process-boundary.js +563 -0
  124. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  125. package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
  126. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  127. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  128. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  129. package/packages/core/dev-delivery-warrant-state.js +587 -0
  130. package/packages/core/dev-delivery-warrant.js +328 -370
  131. package/packages/core/github-governance-authority.js +1 -0
  132. package/packages/core/index.js +5 -0
  133. package/packages/core/kungfu-temporal-fact.js +557 -0
  134. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  135. package/packages/core/next-development-candidate-reservation.js +186 -0
  136. package/packages/core/next-development-controller.js +728 -0
  137. package/packages/core/next-development-projection.js +287 -0
  138. package/packages/core/next-development-transition.js +738 -0
  139. package/packages/core/paper-agent-entry.js +11 -5
  140. package/packages/core/paper.js +16 -5
  141. package/packages/core/published-delivery-authority.js +266 -0
  142. package/packages/core/release-candidate-recovery.js +70 -15
  143. package/packages/core/release-candidate.js +2 -2
  144. package/packages/core/release-passport.js +36 -9
  145. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  146. package/packages/core/v4-adopter-delivery.js +359 -0
  147. package/packages/core/v4-canonical-contracts.js +5 -0
  148. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  149. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  150. package/packages/core/v4-floating-consumer-policy.js +143 -20
  151. package/packages/core/v4-floating-consumer-release-passport.js +32 -5
  152. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  153. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  154. package/packages/core/v4-publication-rehearsal.js +481 -0
  155. package/packages/core/v4-tail-reseal-contract.js +26 -0
  156. package/packages/core/v4-tail-reseal-github.js +178 -0
  157. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  158. package/packages/core/v4-tail-reseal.js +594 -0
  159. package/packages/core/workflow-yaml-contract.js +24 -2
  160. package/scripts/buildchain-cli-help.mjs +41 -2
  161. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  162. package/scripts/check-inventory.mjs +14 -3
  163. package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
  164. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  165. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  166. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  167. package/scripts/dev-delivery-authority.mjs +490 -0
  168. package/scripts/dev-delivery-native-run.mjs +177 -0
  169. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  170. package/scripts/dev-delivery-proof.mjs +67 -2
  171. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  172. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  173. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  174. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  175. package/scripts/dev-delivery-two-phase.mjs +573 -0
  176. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  177. package/scripts/dev-delivery-warrant-store.mjs +231 -0
  178. package/scripts/dev-delivery-warrant.mjs +232 -194
  179. package/scripts/dev-pr-auto-merge.mjs +1 -1
  180. package/scripts/dev-pr-delivery-warrant.mjs +11 -0
  181. package/scripts/dev-qualification-patrol.mjs +9 -9
  182. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  183. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  184. package/scripts/generate-next-development-guidance.mjs +49 -0
  185. package/scripts/generate-release-candidate-passport.mjs +152 -0
  186. package/scripts/generate-site-bundle.mjs +69 -4
  187. package/scripts/init-repo.mjs +22 -4
  188. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  189. package/scripts/next-development-self-dogfood.mjs +597 -0
  190. package/scripts/next-development-transition.mjs +47 -0
  191. package/scripts/npm-publish-transaction.mjs +2 -2
  192. package/scripts/promotion-channel-router.mjs +4 -4
  193. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  194. package/scripts/release-tail.mjs +51 -3
  195. package/scripts/release-train-self-dogfood.mjs +94 -92
  196. package/scripts/resume-from-candidate-run.mjs +557 -30
  197. package/scripts/site-capability-metadata.mjs +41 -0
  198. package/scripts/stable-candidate-qualification.mjs +9 -10
  199. package/scripts/v4-consumer-policy.mjs +5 -5
  200. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  201. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  202. package/scripts/v4-tail-reseal.mjs +416 -0
  203. package/templates/native-dev-delivery.yml +143 -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
+ }
@@ -0,0 +1,26 @@
1
+ export const V4_TAIL_RESEAL_REQUEST_CONTRACT =
2
+ "kungfu-buildchain-v4-tail-reseal-request/v1";
3
+ export const V4_TAIL_RESEAL_PLAN_CONTRACT =
4
+ "kungfu-buildchain-v4-tail-reseal-plan/v1";
5
+
6
+ export const V4_TAIL_RESEAL_PLATFORMS = Object.freeze([
7
+ "linux-arm64",
8
+ "linux-x64",
9
+ "macos-arm64",
10
+ "windows-x64",
11
+ ]);
12
+
13
+ export const V4_TAIL_RESEAL_PLATFORM_RUNNERS = Object.freeze({
14
+ "linux-arm64": '["ubuntu-24.04-arm"]',
15
+ "linux-x64": '["ubuntu-24.04"]',
16
+ "macos-arm64": '["macos-15"]',
17
+ "windows-x64": '["windows-2025"]',
18
+ });
19
+
20
+ export const V4_TAIL_RESEAL_REUSED_STAGE_KEYS = Object.freeze(
21
+ V4_TAIL_RESEAL_PLATFORMS.flatMap((platformId) =>
22
+ ["build", "install", "package", "verify"].map(
23
+ (stage) => `${platformId}:${stage}`,
24
+ ),
25
+ ).sort(),
26
+ );
@@ -0,0 +1,178 @@
1
+ import { v4ContentRoot } from "./v4-canonical-contracts.js";
2
+ import {
3
+ V4TailResealFault,
4
+ normalizeV4TailResealRequest,
5
+ } from "./v4-tail-reseal.js";
6
+
7
+ export const V4_TAIL_RESEAL_ADMISSION_CONTRACT =
8
+ "kungfu-buildchain-v4-tail-reseal-admission/v1";
9
+
10
+ const REQUIRED_SUCCESS_JOBS = Object.freeze([
11
+ "build / Linux ARM64",
12
+ "build / Linux x64",
13
+ "build / macOS ARM64",
14
+ "build / Windows x64",
15
+ "build / Control detached signing Linux ARM64",
16
+ "build / Control detached signing Linux x64",
17
+ "build / Control detached signing macOS ARM64",
18
+ "build / Control detached signing Windows x64",
19
+ "build / Finalize signed artifact Linux ARM64",
20
+ "build / Finalize signed artifact Linux x64",
21
+ "build / Finalize signed artifact Windows x64",
22
+ ]);
23
+
24
+ function fault(code, path, message) {
25
+ throw new V4TailResealFault(code, path, message);
26
+ }
27
+
28
+ function exactArtifact(artifacts, name, digest, path) {
29
+ const matches = artifacts.filter((artifact) => artifact.name === name);
30
+ if (matches.length !== 1)
31
+ fault(
32
+ "tail-reseal-artifact-ambiguous",
33
+ path,
34
+ "retained artifact must resolve exactly once",
35
+ );
36
+ const artifact = matches[0];
37
+ if (artifact.expired === true)
38
+ fault("tail-reseal-retention-expired", path, "retained artifact expired");
39
+ if (artifact.digest !== digest)
40
+ fault(
41
+ "tail-reseal-artifact-archive-mismatch",
42
+ path,
43
+ "GitHub artifact archive digest drifted",
44
+ );
45
+ return artifact;
46
+ }
47
+
48
+ function validateSourceIdentity(normalized, sourceCommit, sourceRun) {
49
+ if (
50
+ sourceCommit?.sha !== normalized.source.sha ||
51
+ sourceCommit?.tree?.sha !== normalized.source.treeSha
52
+ )
53
+ fault(
54
+ "tail-reseal-source-mismatch",
55
+ "$/sourceCommit",
56
+ "source commit or tree readback drifted",
57
+ );
58
+ const workflowFile = String(sourceRun?.path || "")
59
+ .split("@")[0]
60
+ .replace(/^\.github\/workflows\//u, "");
61
+ if (
62
+ Number(sourceRun?.id) !== normalized.source.runId ||
63
+ Number(sourceRun?.run_attempt) !== normalized.source.runAttempt ||
64
+ sourceRun?.event !== "workflow_dispatch" ||
65
+ sourceRun?.status !== "completed" ||
66
+ sourceRun?.conclusion !== "failure" ||
67
+ sourceRun?.head_sha !== normalized.source.sha ||
68
+ workflowFile !== normalized.source.workflowFile ||
69
+ sourceRun?.name !== normalized.source.workflowName
70
+ )
71
+ fault(
72
+ "tail-reseal-source-run-mismatch",
73
+ "$/sourceRun",
74
+ "source run is not the exact retained failed Build run",
75
+ );
76
+ }
77
+
78
+ function validateSourceJobs(normalized, sourceJobs) {
79
+ const jobs = Array.isArray(sourceJobs) ? sourceJobs : [];
80
+ const byName = new Map(jobs.map((job) => [job.name, job]));
81
+ for (const name of REQUIRED_SUCCESS_JOBS)
82
+ if (byName.get(name)?.conclusion !== "success")
83
+ fault(
84
+ "tail-reseal-prior-stage-mismatch",
85
+ "$/sourceJobs",
86
+ `required source job did not succeed: ${name}`,
87
+ );
88
+ const failedJob = jobs.find(
89
+ (job) => Number(job.id) === normalized.failure.jobId,
90
+ );
91
+ const failedSteps = (failedJob?.steps || [])
92
+ .filter((step) => step.conclusion === "failure")
93
+ .map((step) => step.name);
94
+ if (
95
+ failedJob?.name !== normalized.failure.jobName ||
96
+ failedJob?.conclusion !== "failure" ||
97
+ JSON.stringify(failedSteps) !==
98
+ JSON.stringify([normalized.failure.stepName])
99
+ )
100
+ fault(
101
+ "tail-reseal-failure-mismatch",
102
+ "$/sourceJobs",
103
+ "observed failure is not the single authorized macOS tail failure",
104
+ );
105
+ }
106
+
107
+ function validateRetainedArtifacts(normalized, sourceArtifacts) {
108
+ for (const [index, platform] of normalized.platforms.entries()) {
109
+ exactArtifact(
110
+ sourceArtifacts || [],
111
+ platform.artifactName,
112
+ platform.artifactArchiveRoot,
113
+ `$/sourceArtifacts/${index}/payload`,
114
+ );
115
+ exactArtifact(
116
+ sourceArtifacts || [],
117
+ platform.manifestArtifactName,
118
+ platform.manifestArchiveRoot,
119
+ `$/sourceArtifacts/${index}/manifest`,
120
+ );
121
+ }
122
+ }
123
+
124
+ function validateSigningAuthority(normalized, signingRun, signingArtifacts) {
125
+ if (
126
+ Number(signingRun?.id) !== normalized.signing.authorityRunId ||
127
+ signingRun?.status !== "completed" ||
128
+ signingRun?.conclusion !== "success" ||
129
+ signingRun?.head_sha !== normalized.signing.runtimeSha
130
+ )
131
+ fault(
132
+ "tail-reseal-signer-authority-mismatch",
133
+ "$/signingRun",
134
+ "signing authority run readback drifted",
135
+ );
136
+ exactArtifact(
137
+ signingArtifacts || [],
138
+ normalized.signing.resultArtifact,
139
+ normalized.signing.resultArtifactRoot,
140
+ "$/signingArtifacts/result",
141
+ );
142
+ }
143
+
144
+ export function validateV4TailResealGitHubEvidence({
145
+ request,
146
+ sourceCommit,
147
+ sourceRun,
148
+ sourceJobs,
149
+ sourceArtifacts,
150
+ signingRun,
151
+ signingArtifacts,
152
+ } = {}) {
153
+ const normalized = normalizeV4TailResealRequest(request);
154
+ validateSourceIdentity(normalized, sourceCommit, sourceRun);
155
+ validateSourceJobs(normalized, sourceJobs);
156
+ validateRetainedArtifacts(normalized, sourceArtifacts);
157
+ validateSigningAuthority(normalized, signingRun, signingArtifacts);
158
+ const payload = {
159
+ schema: V4_TAIL_RESEAL_ADMISSION_CONTRACT,
160
+ repository: normalized.repository,
161
+ sourceRoot: normalized.source.sourceRoot,
162
+ sourceRunId: normalized.source.runId,
163
+ signingAuthorityRunId: normalized.signing.authorityRunId,
164
+ retainedArchiveRoots: normalized.platforms.flatMap((platform) => [
165
+ platform.artifactArchiveRoot,
166
+ platform.manifestArchiveRoot,
167
+ ]),
168
+ priorFailureRoot: normalized.failure.evidenceRoot,
169
+ retentionReadbackRoot: normalized.retention.readbackRoot,
170
+ warrantReadbackRoot: normalized.warrant.stateReadbackRoot,
171
+ };
172
+ return {
173
+ ...payload,
174
+ admissionRoot: v4ContentRoot("tail-reseal-admission", payload),
175
+ };
176
+ }
177
+
178
+ export { REQUIRED_SUCCESS_JOBS as V4_TAIL_RESEAL_REQUIRED_SUCCESS_JOBS };