@kungfu-tech/buildchain 4.0.0 → 4.0.1-alpha.10

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 (228) hide show
  1. package/AGENTS.md +13 -5
  2. package/actions/promote-buildchain-ref/README.md +11 -6
  3. package/actions/release-tail/README.md +8 -0
  4. package/architecture/ci-lane-change-budget.json +187 -0
  5. package/architecture/decisions/0002-next-development-transition.md +101 -0
  6. package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
  7. package/architecture/internal-capabilities.json +219 -7
  8. package/architecture/maintainability-policy.json +246 -29
  9. package/architecture/release-tail-contract-inventory.json +5 -2
  10. package/architecture/v3-core-mechanism-inventory.json +91 -0
  11. package/architecture/v4-adopter-delivery-parity.json +32 -5
  12. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  13. package/architecture/v4-compatibility-facts-parity.json +77 -0
  14. package/architecture/v4-delivery-authority-parity.json +250 -0
  15. package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
  16. package/architecture/v4-floating-consumer-policy.json +81 -0
  17. package/architecture/v4-floating-consumer-policy.md +32 -0
  18. package/architecture/v4-next-development-parity.json +192 -0
  19. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  20. package/architecture/v4-release-train-parity.json +1 -0
  21. package/architecture/v4-runtime-ref-resume-authority.json +64 -0
  22. package/architecture/v4-stage-capsule-qualification.json +2 -2
  23. package/architecture/v4-tail-reseal-parity.json +150 -0
  24. package/bin/buildchain.mjs +32 -3
  25. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  26. package/bin/internal/command-registry.mjs +3 -0
  27. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  28. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  29. package/contracts/dev-delivery-authority-v2.schema.json +662 -0
  30. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  31. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  32. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  33. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  34. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  35. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  36. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  37. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  38. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  39. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +59 -0
  40. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  41. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  42. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  43. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  44. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  45. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  46. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  47. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  48. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  49. package/contracts/fixtures/v4-runtime-ref-resume-authority-v1/scenario.json +15 -0
  50. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  51. package/contracts/next-development-request-v1.schema.json +66 -0
  52. package/contracts/next-development-transition-v1.schema.json +292 -0
  53. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  54. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  55. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +107 -0
  56. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  57. package/contracts/v4-runtime-ref-resume-authority-v1.schema.json +235 -0
  58. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  59. package/dist/site/agent-index.json +5 -0
  60. package/dist/site/artifact-schemas.json +10 -0
  61. package/dist/site/buildchain-contract.json +1562 -44
  62. package/dist/site/buildchain-site.json +684 -42
  63. package/dist/site/capability-registry.json +17 -14
  64. package/dist/site/cli-registry.json +205 -3
  65. package/dist/site/controller-registry.json +62 -6
  66. package/dist/site/kfd-claims.json +551 -22
  67. package/dist/site/kfd-upstream-aggregate.json +1 -1
  68. package/dist/site/manual-registry.json +55 -9
  69. package/dist/site/node-api-registry.json +15247 -8690
  70. package/dist/site/page-registry.json +647 -29
  71. package/dist/site/public-surface-audit.json +876 -55
  72. package/dist/site/publication-authority-registry.json +44 -8
  73. package/dist/site/publication-registry.json +4 -4
  74. package/dist/site/release-provenance.json +26 -0
  75. package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
  76. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  77. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  78. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  79. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  80. package/dist/site/site-manifest.json +37 -13
  81. package/dist/site/workflow-registry.json +200 -22
  82. package/docs/MAP.md +10 -2
  83. package/docs/cli-reference.md +404 -13
  84. package/docs/cli.md +8 -0
  85. package/docs/dev-alpha-candidate-patrol.md +8 -0
  86. package/docs/dev-delivery-qualification-landing-adr.md +251 -0
  87. package/docs/dev-delivery-warrant.md +361 -30
  88. package/docs/lifecycle-protocol.md +41 -0
  89. package/docs/next-development-transition.md +118 -0
  90. package/docs/node-api-reference.md +748 -322
  91. package/docs/release-tail-provider-plane.md +30 -0
  92. package/docs/reusable-build-surface.md +41 -0
  93. package/docs/runtime-train-validation.md +10 -0
  94. package/docs/v4-adopter-delivery.md +133 -0
  95. package/docs/v4-compatibility-facts.md +91 -0
  96. package/docs/v4-publication-rehearsal.md +130 -0
  97. package/docs/v4-runtime-ref-resume-authority.md +69 -0
  98. package/docs/v4-tail-reseal.md +73 -0
  99. package/docs/versioning.md +4 -2
  100. package/package.json +36 -6
  101. package/packages/core/adopter-delivery-passport.js +259 -0
  102. package/packages/core/adopter-delivery-vectors.js +158 -0
  103. package/packages/core/adopter-delivery-vectors.json +77 -0
  104. package/packages/core/artifact-signing.js +61 -0
  105. package/packages/core/buildchain-agent-manuals.js +3 -0
  106. package/packages/core/buildchain-compatibility-authority.js +336 -0
  107. package/packages/core/buildchain-compatibility-fact.js +394 -0
  108. package/packages/core/buildchain-compatibility-facts.json +217 -0
  109. package/packages/core/buildchain-compatibility-proof.js +566 -0
  110. package/packages/core/buildchain-config.js +150 -1
  111. package/packages/core/buildchain-contract.js +96 -143
  112. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  113. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  114. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  115. package/packages/core/buildchain-publication-authority.js +1 -0
  116. package/packages/core/channel-candidate.js +8 -0
  117. package/packages/core/channel-promotion-baseline.js +55 -0
  118. package/packages/core/ci-lane-change-budget.js +247 -0
  119. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  120. package/packages/core/dev-delivery-authority-candidate.js +270 -0
  121. package/packages/core/dev-delivery-authority-evidence.js +146 -0
  122. package/packages/core/dev-delivery-authority-landing.js +461 -0
  123. package/packages/core/dev-delivery-authority-observation.js +48 -0
  124. package/packages/core/dev-delivery-authority-qualification.js +591 -0
  125. package/packages/core/dev-delivery-authority-settlement.js +213 -0
  126. package/packages/core/dev-delivery-authority-state.js +583 -0
  127. package/packages/core/dev-delivery-candidate-identity.js +13 -0
  128. package/packages/core/dev-delivery-contract-surface.js +76 -0
  129. package/packages/core/dev-delivery-execution-failure.js +133 -0
  130. package/packages/core/dev-delivery-execution-transfer.js +592 -0
  131. package/packages/core/dev-delivery-landing-admission-core.js +119 -0
  132. package/packages/core/dev-delivery-landing-readback.js +598 -0
  133. package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
  134. package/packages/core/dev-delivery-landing-testing-port.js +6 -0
  135. package/packages/core/dev-delivery-native-execution.js +110 -0
  136. package/packages/core/dev-delivery-native-proof.js +562 -0
  137. package/packages/core/dev-delivery-process-boundary.js +563 -0
  138. package/packages/core/dev-delivery-proof.js +37 -3
  139. package/packages/core/dev-delivery-provider-attempt.js +127 -0
  140. package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
  141. package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
  142. package/packages/core/dev-delivery-warrant-qualification.js +145 -0
  143. package/packages/core/dev-delivery-warrant-settlement.js +237 -36
  144. package/packages/core/dev-delivery-warrant-state.js +587 -0
  145. package/packages/core/dev-delivery-warrant.js +328 -370
  146. package/packages/core/github-governance-authority.js +1 -0
  147. package/packages/core/index.js +7 -0
  148. package/packages/core/kungfu-temporal-fact.js +557 -0
  149. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  150. package/packages/core/next-development-candidate-reservation.js +186 -0
  151. package/packages/core/next-development-controller.js +728 -0
  152. package/packages/core/next-development-projection.js +287 -0
  153. package/packages/core/next-development-transition.js +738 -0
  154. package/packages/core/paper-agent-entry.js +11 -5
  155. package/packages/core/paper.js +16 -5
  156. package/packages/core/publication-authority.js +5 -5
  157. package/packages/core/published-delivery-authority.js +266 -0
  158. package/packages/core/release-candidate-recovery.js +66 -11
  159. package/packages/core/release-candidate.js +79 -19
  160. package/packages/core/release-passport.js +271 -38
  161. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  162. package/packages/core/v4-adopter-delivery.js +359 -0
  163. package/packages/core/v4-canonical-contracts.js +5 -0
  164. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  165. package/packages/core/v4-floating-consumer-evidence.js +338 -0
  166. package/packages/core/v4-floating-consumer-policy.js +569 -0
  167. package/packages/core/v4-floating-consumer-release-passport.js +153 -0
  168. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  169. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  170. package/packages/core/v4-publication-rehearsal.js +481 -0
  171. package/packages/core/v4-runtime-ref-resume-authority.js +625 -0
  172. package/packages/core/v4-runtime-selector-persistence.js +228 -0
  173. package/packages/core/v4-tail-reseal-contract.js +26 -0
  174. package/packages/core/v4-tail-reseal-github.js +178 -0
  175. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  176. package/packages/core/v4-tail-reseal.js +594 -0
  177. package/packages/core/workflow-yaml-contract.js +72 -2
  178. package/scripts/audit-publication-control-plane.mjs +31 -31
  179. package/scripts/buildchain-cli-help.mjs +41 -2
  180. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  181. package/scripts/check-inventory.mjs +14 -3
  182. package/scripts/check-v4-floating-consumer-policy-contract.mjs +217 -0
  183. package/scripts/check-v4-public-dogfood-contract.mjs +6 -11
  184. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  185. package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
  186. package/scripts/dev-delivery-authority-provider.mjs +28 -0
  187. package/scripts/dev-delivery-authority.mjs +490 -0
  188. package/scripts/dev-delivery-native-run.mjs +177 -0
  189. package/scripts/dev-delivery-process-boundary.mjs +260 -0
  190. package/scripts/dev-delivery-proof.mjs +67 -2
  191. package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
  192. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  193. package/scripts/dev-delivery-source-proof-reuse.mjs +547 -0
  194. package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
  195. package/scripts/dev-delivery-two-phase.mjs +573 -0
  196. package/scripts/dev-delivery-warrant-options.mjs +266 -0
  197. package/scripts/dev-delivery-warrant-store.mjs +231 -0
  198. package/scripts/dev-delivery-warrant.mjs +232 -194
  199. package/scripts/dev-pr-auto-merge.mjs +37 -48
  200. package/scripts/dev-pr-delivery-warrant.mjs +216 -2
  201. package/scripts/dev-pr-prequeue-guard.mjs +399 -0
  202. package/scripts/dev-qualification-patrol.mjs +9 -9
  203. package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
  204. package/scripts/ensure-github-release.mjs +3 -3
  205. package/scripts/generate-channel-build-workflow.mjs +3 -0
  206. package/scripts/generate-channel-promotion-workflow.mjs +114 -14
  207. package/scripts/generate-next-development-guidance.mjs +49 -0
  208. package/scripts/generate-release-candidate-passport.mjs +192 -32
  209. package/scripts/generate-site-bundle.mjs +69 -4
  210. package/scripts/init-repo.mjs +26 -4
  211. package/scripts/inspect-artifact-signing-requests.mjs +6 -0
  212. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  213. package/scripts/next-development-self-dogfood.mjs +597 -0
  214. package/scripts/next-development-transition.mjs +47 -0
  215. package/scripts/npm-publish-transaction.mjs +103 -91
  216. package/scripts/promotion-channel-router.mjs +4 -4
  217. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  218. package/scripts/release-tail.mjs +51 -3
  219. package/scripts/release-train-self-dogfood.mjs +94 -92
  220. package/scripts/resume-from-candidate-run.mjs +565 -41
  221. package/scripts/seal-artifact-signing-requests.mjs +6 -0
  222. package/scripts/site-capability-metadata.mjs +43 -0
  223. package/scripts/stable-candidate-qualification.mjs +9 -10
  224. package/scripts/v4-consumer-policy.mjs +231 -0
  225. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
  226. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  227. package/scripts/v4-tail-reseal.mjs +416 -0
  228. package/templates/native-dev-delivery.yml +143 -0
@@ -0,0 +1,625 @@
1
+ import crypto from "node:crypto";
2
+ import {
3
+ V4_RUNTIME_PERSISTENCE_SCAN_CONTRACT,
4
+ scanV4RuntimeSelectorPersistence,
5
+ } from "./v4-runtime-selector-persistence.js";
6
+
7
+ export {
8
+ V4_RUNTIME_PERSISTENCE_SCAN_CONTRACT,
9
+ scanV4RuntimeSelectorPersistence,
10
+ };
11
+
12
+ export const V4_RUNTIME_AUTHORIZATION_CONTRACT =
13
+ "kungfu-buildchain-v4-runtime-authorization/v1";
14
+ export const V4_RUNTIME_RESUME_LINEAGE_CONTRACT =
15
+ "kungfu-buildchain-v4-runtime-resume-lineage/v1";
16
+ const EXACT_SHA = /^[0-9a-f]{40}$/u;
17
+ const SHA256_ROOT = /^sha256:[0-9a-f]{64}$/u;
18
+ const OFFICIAL_V4_REF = /^v4(?:-alpha)?$/u;
19
+ const TRAIN_V4_REF = /^train\/v4\/v4\.\d+\/[A-Za-z0-9._/-]+$/u;
20
+ const AUTHORITY_V4_REF = /^authority\/v4\/v4\.\d+\/[A-Za-z0-9._/-]+$/u;
21
+ const TOKEN = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/u;
22
+
23
+ function stableJson(value) {
24
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
25
+ if (value && typeof value === "object") {
26
+ return `{${Object.keys(value)
27
+ .sort()
28
+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
29
+ .join(",")}}`;
30
+ }
31
+ return JSON.stringify(value);
32
+ }
33
+
34
+ export function v4RuntimeResumeDocumentRoot(value) {
35
+ return `sha256:${crypto.createHash("sha256").update(stableJson(value)).digest("hex")}`;
36
+ }
37
+
38
+ function exactSha(value, label) {
39
+ const normalized = String(value || "")
40
+ .trim()
41
+ .toLowerCase();
42
+ if (!EXACT_SHA.test(normalized)) {
43
+ throw new Error(`${label} must be an exact 40-character Git SHA`);
44
+ }
45
+ return normalized;
46
+ }
47
+
48
+ function root(value, label) {
49
+ const normalized = String(value || "")
50
+ .trim()
51
+ .toLowerCase();
52
+ if (!SHA256_ROOT.test(normalized)) {
53
+ throw new Error(`${label} must be a sha256 content root`);
54
+ }
55
+ return normalized;
56
+ }
57
+
58
+ function nonEmpty(value, label) {
59
+ const normalized = String(value || "").trim();
60
+ if (!normalized) throw new Error(`${label} must be non-empty`);
61
+ return normalized;
62
+ }
63
+
64
+ function token(value, label) {
65
+ const normalized = nonEmpty(value, label);
66
+ if (!TOKEN.test(normalized)) {
67
+ throw new Error(`${label} must be an ASCII identity token`);
68
+ }
69
+ return normalized;
70
+ }
71
+
72
+ function exactKeys(value, expected, label) {
73
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
74
+ throw new Error(`${label} must be an object`);
75
+ }
76
+ const actual = Object.keys(value).sort();
77
+ const canonical = [...expected].sort();
78
+ if (
79
+ actual.length !== canonical.length ||
80
+ actual.some((key, index) => key !== canonical[index])
81
+ ) {
82
+ throw new Error(`${label} has an invalid field set`);
83
+ }
84
+ }
85
+
86
+ function sortedUnique(values, label) {
87
+ if (!Array.isArray(values) || values.length === 0) {
88
+ throw new Error(`${label} must be a non-empty array`);
89
+ }
90
+ const normalized = values.map((value, index) =>
91
+ token(value, `${label}[${index}]`),
92
+ );
93
+ const expected = [...new Set(normalized)].sort((left, right) =>
94
+ Buffer.from(left).compare(Buffer.from(right)),
95
+ );
96
+ if (
97
+ normalized.length !== expected.length ||
98
+ normalized.some((value, index) => value !== expected[index])
99
+ ) {
100
+ throw new Error(`${label} must be byte-sorted and duplicate-free`);
101
+ }
102
+ return normalized;
103
+ }
104
+
105
+ function normalizeRef(value) {
106
+ return String(value || "")
107
+ .trim()
108
+ .replace(/^refs\/(?:heads|tags)\//u, "");
109
+ }
110
+
111
+ function approvedV4Ref(value) {
112
+ const ref = normalizeRef(value);
113
+ return (
114
+ OFFICIAL_V4_REF.test(ref) ||
115
+ TRAIN_V4_REF.test(ref) ||
116
+ AUTHORITY_V4_REF.test(ref)
117
+ );
118
+ }
119
+
120
+ function runtimeClass(value) {
121
+ const ref = normalizeRef(value);
122
+ if (EXACT_SHA.test(ref.toLowerCase())) return "exact-sha";
123
+ if (OFFICIAL_V4_REF.test(ref)) return "floating";
124
+ if (TRAIN_V4_REF.test(ref)) return "train";
125
+ if (AUTHORITY_V4_REF.test(ref)) return "authority";
126
+ return "rejected";
127
+ }
128
+
129
+ function normalizeReadbacks(values, runtimeSha) {
130
+ if (!Array.isArray(values) || values.length === 0) {
131
+ throw new Error(
132
+ "approvedRefReadbacks must contain provider readback evidence",
133
+ );
134
+ }
135
+ const normalized = values.map((value, index) => {
136
+ exactKeys(
137
+ value,
138
+ ["ref", "sha", "containsRuntimeSha", "readbackRoot"],
139
+ `approvedRefReadbacks[${index}]`,
140
+ );
141
+ const ref = normalizeRef(value.ref);
142
+ if (!approvedV4Ref(ref)) {
143
+ throw new Error(
144
+ `approvedRefReadbacks[${index}].ref is not an approved v4 ref`,
145
+ );
146
+ }
147
+ return {
148
+ ref,
149
+ sha: exactSha(value.sha, `approvedRefReadbacks[${index}].sha`),
150
+ containsRuntimeSha: value.containsRuntimeSha === true,
151
+ readbackRoot: root(
152
+ value.readbackRoot,
153
+ `approvedRefReadbacks[${index}].readbackRoot`,
154
+ ),
155
+ };
156
+ });
157
+ normalized.sort((left, right) =>
158
+ Buffer.from(left.ref).compare(Buffer.from(right.ref)),
159
+ );
160
+ if (
161
+ new Set(normalized.map((entry) => entry.ref)).size !== normalized.length
162
+ ) {
163
+ throw new Error("approvedRefReadbacks refs must be unique");
164
+ }
165
+ if (!normalized.some((entry) => entry.containsRuntimeSha)) {
166
+ throw new Error(
167
+ `runtime ${runtimeSha} is not reachable from an approved v4 ref`,
168
+ );
169
+ }
170
+ return normalized;
171
+ }
172
+
173
+ export function authorizeV4RuntimeSelection({
174
+ repository,
175
+ eventName,
176
+ mode = "dispatch",
177
+ actor,
178
+ actorPermission,
179
+ reason,
180
+ authorizedAt,
181
+ sourceSha,
182
+ sourceTreeSha,
183
+ requestedRef,
184
+ resolvedRuntimeSha,
185
+ approvedRefReadbacks,
186
+ stableContractLockRoot,
187
+ alphaContractLockRoot,
188
+ consumerPolicyReceiptRoot,
189
+ persistenceScan,
190
+ } = {}) {
191
+ const runtimeSha = exactSha(resolvedRuntimeSha, "resolvedRuntimeSha");
192
+ const ref = normalizeRef(requestedRef);
193
+ const refClass = runtimeClass(ref);
194
+ if (refClass === "rejected")
195
+ throw new Error(
196
+ "requestedRef is outside the v4 runtime authority boundary",
197
+ );
198
+ if (!new Set(["dispatch", "resume", "rescue"]).has(mode)) {
199
+ throw new Error("mode must be dispatch, resume, or rescue");
200
+ }
201
+ if (eventName !== "workflow_dispatch") {
202
+ throw new Error(
203
+ "v4 runtime escape is only allowed for trusted workflow_dispatch runs",
204
+ );
205
+ }
206
+ if (!new Set(["write", "maintain", "admin"]).has(actorPermission)) {
207
+ throw new Error(
208
+ "v4 runtime escape requires write, maintain, or admin permission",
209
+ );
210
+ }
211
+ if (
212
+ !persistenceScan ||
213
+ persistenceScan.contract !== V4_RUNTIME_PERSISTENCE_SCAN_CONTRACT
214
+ ) {
215
+ throw new Error("a v4 runtime persistence scan is required");
216
+ }
217
+ if (persistenceScan.status !== "passed" || persistenceScan.failures?.length) {
218
+ throw new Error(
219
+ "runtime selector persistence scan must pass before authorization",
220
+ );
221
+ }
222
+ const scanPayload = { ...persistenceScan };
223
+ delete scanPayload.root;
224
+ if (persistenceScan.root !== v4RuntimeResumeDocumentRoot(scanPayload)) {
225
+ throw new Error("runtime persistence scan root mismatch");
226
+ }
227
+ const readbacks = normalizeReadbacks(approvedRefReadbacks, runtimeSha);
228
+ if (refClass !== "exact-sha") {
229
+ const direct = readbacks.find((entry) => entry.ref === ref);
230
+ if (!direct || direct.sha !== runtimeSha) {
231
+ throw new Error(
232
+ "named runtime ref readback does not match resolvedRuntimeSha",
233
+ );
234
+ }
235
+ }
236
+ const timestamp = nonEmpty(authorizedAt, "authorizedAt");
237
+ if (Number.isNaN(Date.parse(timestamp)))
238
+ throw new Error("authorizedAt must be an ISO-8601 timestamp");
239
+ const receipt = {
240
+ schemaVersion: 1,
241
+ contract: V4_RUNTIME_AUTHORIZATION_CONTRACT,
242
+ status: "authorized",
243
+ mode,
244
+ eventName,
245
+ repository: nonEmpty(repository, "repository"),
246
+ actor: {
247
+ login: nonEmpty(actor, "actor"),
248
+ permission: actorPermission,
249
+ },
250
+ reason: nonEmpty(reason, "reason"),
251
+ authorizedAt: timestamp,
252
+ source: {
253
+ sha: exactSha(sourceSha, "sourceSha"),
254
+ treeSha: exactSha(sourceTreeSha, "sourceTreeSha"),
255
+ },
256
+ request: { ref, class: refClass },
257
+ runtime: { sha: runtimeSha, reachableFrom: readbacks },
258
+ contractLocks: {
259
+ stableRoot: root(stableContractLockRoot, "stableContractLockRoot"),
260
+ alphaRoot: root(alphaContractLockRoot, "alphaContractLockRoot"),
261
+ },
262
+ consumerPolicyReceiptRoot: root(
263
+ consumerPolicyReceiptRoot,
264
+ "consumerPolicyReceiptRoot",
265
+ ),
266
+ persistenceScanRoot: root(persistenceScan.root, "persistenceScan.root"),
267
+ };
268
+ return { receipt, receiptRoot: v4RuntimeResumeDocumentRoot(receipt) };
269
+ }
270
+
271
+ export function verifyV4RuntimeAuthorizationReceipt({
272
+ receipt,
273
+ receiptRoot,
274
+ repository = "",
275
+ sourceSha = "",
276
+ runtimeSha = "",
277
+ consumerPolicyReceiptRoot = "",
278
+ } = {}) {
279
+ const failures = [];
280
+ const check = (condition, code) => {
281
+ if (!condition) failures.push(code);
282
+ };
283
+ check(
284
+ receipt?.contract === V4_RUNTIME_AUTHORIZATION_CONTRACT,
285
+ "contract-invalid",
286
+ );
287
+ check(receipt?.status === "authorized", "status-not-authorized");
288
+ check(
289
+ SHA256_ROOT.test(String(receiptRoot || "")) &&
290
+ receiptRoot === v4RuntimeResumeDocumentRoot(receipt),
291
+ "receipt-root-mismatch",
292
+ );
293
+ check(receipt?.eventName === "workflow_dispatch", "event-not-trusted");
294
+ check(
295
+ new Set(["write", "maintain", "admin"]).has(receipt?.actor?.permission),
296
+ "permission-insufficient",
297
+ );
298
+ check(
299
+ EXACT_SHA.test(String(receipt?.runtime?.sha || "")),
300
+ "runtime-sha-invalid",
301
+ );
302
+ check(
303
+ Array.isArray(receipt?.runtime?.reachableFrom) &&
304
+ receipt.runtime.reachableFrom.some(
305
+ (entry) =>
306
+ approvedV4Ref(entry.ref) && entry.containsRuntimeSha === true,
307
+ ),
308
+ "runtime-unreachable",
309
+ );
310
+ if (repository)
311
+ check(receipt?.repository === repository, "repository-mismatch");
312
+ if (sourceSha)
313
+ check(receipt?.source?.sha === sourceSha, "source-sha-mismatch");
314
+ if (runtimeSha)
315
+ check(receipt?.runtime?.sha === runtimeSha, "runtime-sha-mismatch");
316
+ if (consumerPolicyReceiptRoot) {
317
+ check(
318
+ receipt?.consumerPolicyReceiptRoot === consumerPolicyReceiptRoot,
319
+ "consumer-policy-root-mismatch",
320
+ );
321
+ }
322
+ return { ok: failures.length === 0, failures };
323
+ }
324
+
325
+ export function createV4RuntimeResumeLineage({
326
+ authorization,
327
+ authorizationRoot,
328
+ buildAttempt,
329
+ resumeAttempt,
330
+ source,
331
+ consumerPolicyReceiptRoot,
332
+ requiredPlatforms,
333
+ stageCapsules,
334
+ resumePlanRoot,
335
+ finalPublicReadbackRoot,
336
+ floatingRefBefore,
337
+ floatingRefAfter,
338
+ } = {}) {
339
+ const verification = verifyV4RuntimeAuthorizationReceipt({
340
+ receipt: authorization,
341
+ receiptRoot: authorizationRoot,
342
+ sourceSha: source?.sha,
343
+ runtimeSha: resumeAttempt?.runtimeSha,
344
+ consumerPolicyReceiptRoot,
345
+ });
346
+ if (!verification.ok) {
347
+ throw new Error(
348
+ `runtime authorization invalid: ${verification.failures.join(", ")}`,
349
+ );
350
+ }
351
+ const buildId = token(buildAttempt?.id, "buildAttempt.id");
352
+ const resumeId = token(resumeAttempt?.id, "resumeAttempt.id");
353
+ if (buildId === resumeId) {
354
+ throw new Error("resume must create a new governed attempt");
355
+ }
356
+ const buildRuntimeSha = exactSha(
357
+ buildAttempt?.runtimeSha,
358
+ "buildAttempt.runtimeSha",
359
+ );
360
+ const resumeRuntimeSha = exactSha(
361
+ resumeAttempt?.runtimeSha,
362
+ "resumeAttempt.runtimeSha",
363
+ );
364
+ if (buildRuntimeSha === resumeRuntimeSha) {
365
+ throw new Error(
366
+ "resume runtime must differ from the original build runtime",
367
+ );
368
+ }
369
+ const sourceIdentity = {
370
+ sha: exactSha(source?.sha, "source.sha"),
371
+ treeSha: exactSha(source?.treeSha, "source.treeSha"),
372
+ };
373
+ const policyRoot = root(
374
+ consumerPolicyReceiptRoot,
375
+ "consumerPolicyReceiptRoot",
376
+ );
377
+ const platforms = sortedUnique(requiredPlatforms, "requiredPlatforms");
378
+ if (!Array.isArray(stageCapsules))
379
+ throw new Error("stageCapsules must be an array");
380
+ const capsuleByPlatform = new Map();
381
+ for (const [index, capsule] of stageCapsules.entries()) {
382
+ exactKeys(
383
+ capsule,
384
+ [
385
+ "platform",
386
+ "capsuleRoot",
387
+ "identityRoot",
388
+ "artifactDigest",
389
+ "sourceSha",
390
+ "sourceTreeSha",
391
+ "policyRoot",
392
+ "buildRuntimeSha",
393
+ "sealed",
394
+ ],
395
+ `stageCapsules[${index}]`,
396
+ );
397
+ const platform = token(
398
+ capsule.platform,
399
+ `stageCapsules[${index}].platform`,
400
+ );
401
+ if (!platforms.includes(platform)) {
402
+ throw new Error(`stageCapsules[${index}] names an undeclared platform`);
403
+ }
404
+ if (capsuleByPlatform.has(platform)) {
405
+ throw new Error(`duplicate Stage Capsule for ${platform}`);
406
+ }
407
+ if (capsule.sealed !== true)
408
+ throw new Error(`Stage Capsule ${platform} is not sealed`);
409
+ if (
410
+ exactSha(capsule.sourceSha, `${platform}.sourceSha`) !==
411
+ sourceIdentity.sha ||
412
+ exactSha(capsule.sourceTreeSha, `${platform}.sourceTreeSha`) !==
413
+ sourceIdentity.treeSha ||
414
+ root(capsule.policyRoot, `${platform}.policyRoot`) !== policyRoot ||
415
+ exactSha(capsule.buildRuntimeSha, `${platform}.buildRuntimeSha`) !==
416
+ buildRuntimeSha
417
+ ) {
418
+ throw new Error(
419
+ `Stage Capsule ${platform} identity is stale or ambiguous`,
420
+ );
421
+ }
422
+ capsuleByPlatform.set(platform, {
423
+ platform,
424
+ capsuleRoot: root(capsule.capsuleRoot, `${platform}.capsuleRoot`),
425
+ identityRoot: root(capsule.identityRoot, `${platform}.identityRoot`),
426
+ artifactDigest: root(
427
+ capsule.artifactDigest,
428
+ `${platform}.artifactDigest`,
429
+ ),
430
+ });
431
+ }
432
+ const before = {
433
+ ref: normalizeRef(floatingRefBefore?.ref),
434
+ sha: exactSha(floatingRefBefore?.sha, "floatingRefBefore.sha"),
435
+ };
436
+ const after = {
437
+ ref: normalizeRef(floatingRefAfter?.ref),
438
+ sha: exactSha(floatingRefAfter?.sha, "floatingRefAfter.sha"),
439
+ };
440
+ if (!OFFICIAL_V4_REF.test(before.ref) || before.ref !== after.ref) {
441
+ throw new Error(
442
+ "floating ref movement must remain on one official v4 channel",
443
+ );
444
+ }
445
+ if (before.sha === after.sha) {
446
+ throw new Error(
447
+ "floating ref movement must bind distinct before and after SHAs",
448
+ );
449
+ }
450
+ const reusedCapsules = platforms
451
+ .filter((platform) => capsuleByPlatform.has(platform))
452
+ .map((platform) => capsuleByPlatform.get(platform));
453
+ const rebuildPlatforms = platforms.filter(
454
+ (platform) => !capsuleByPlatform.has(platform),
455
+ );
456
+ const lineage = {
457
+ schemaVersion: 1,
458
+ contract: V4_RUNTIME_RESUME_LINEAGE_CONTRACT,
459
+ status: "qualified",
460
+ authorizationRoot: root(authorizationRoot, "authorizationRoot"),
461
+ source: sourceIdentity,
462
+ consumerPolicyReceiptRoot: policyRoot,
463
+ attempts: {
464
+ build: { id: buildId, runtimeSha: buildRuntimeSha },
465
+ resume: { id: resumeId, runtimeSha: resumeRuntimeSha },
466
+ },
467
+ floatingRefMovement: { before, after },
468
+ continuation: {
469
+ mechanism: "new-governed-attempt",
470
+ rerunFailedJobs: false,
471
+ resumePlanRoot: root(resumePlanRoot, "resumePlanRoot"),
472
+ },
473
+ stageCapsules: {
474
+ requiredPlatforms: platforms,
475
+ reused: reusedCapsules,
476
+ rebuildPlatforms,
477
+ },
478
+ finalPublicReadbackRoot: root(
479
+ finalPublicReadbackRoot,
480
+ "finalPublicReadbackRoot",
481
+ ),
482
+ };
483
+ return { lineage, lineageRoot: v4RuntimeResumeDocumentRoot(lineage) };
484
+ }
485
+
486
+ function validSourceIdentity(source) {
487
+ return (
488
+ EXACT_SHA.test(String(source?.sha || "")) &&
489
+ EXACT_SHA.test(String(source?.treeSha || ""))
490
+ );
491
+ }
492
+
493
+ function validRuntimeTransition(attempts) {
494
+ return (
495
+ EXACT_SHA.test(String(attempts?.build?.runtimeSha || "")) &&
496
+ EXACT_SHA.test(String(attempts?.resume?.runtimeSha || "")) &&
497
+ attempts.build.runtimeSha !== attempts.resume.runtimeSha
498
+ );
499
+ }
500
+
501
+ function validFloatingRefMovement(movement) {
502
+ return (
503
+ OFFICIAL_V4_REF.test(String(movement?.before?.ref || "")) &&
504
+ movement.before.ref === movement?.after?.ref &&
505
+ EXACT_SHA.test(String(movement.before.sha || "")) &&
506
+ EXACT_SHA.test(String(movement.after.sha || "")) &&
507
+ movement.before.sha !== movement.after.sha
508
+ );
509
+ }
510
+
511
+ function platformLineageFailures(stageCapsules) {
512
+ const failures = [];
513
+ const required = stageCapsules?.requiredPlatforms || [];
514
+ const reused = stageCapsules?.reused || [];
515
+ const rebuilt = stageCapsules?.rebuildPlatforms || [];
516
+ const requiredCanonical = [...new Set(required)].sort();
517
+ const reusedPlatforms = reused.map((entry) => entry.platform);
518
+ const rebuiltCanonical = [...new Set(rebuilt)].sort();
519
+ if (
520
+ required.length === 0 ||
521
+ required.length !== requiredCanonical.length ||
522
+ !required.every((platform, index) => platform === requiredCanonical[index])
523
+ ) {
524
+ failures.push("required-platforms-invalid");
525
+ }
526
+ if (
527
+ reusedPlatforms.length !== new Set(reusedPlatforms).size ||
528
+ rebuilt.length !== rebuiltCanonical.length ||
529
+ !reusedPlatforms.every((platform) => !rebuilt.includes(platform))
530
+ ) {
531
+ failures.push("platform-lineage-ambiguous");
532
+ }
533
+ if (
534
+ [...reusedPlatforms, ...rebuilt].sort().join("\0") !==
535
+ [...required].sort().join("\0")
536
+ ) {
537
+ failures.push("platform-lineage-incomplete");
538
+ }
539
+ if (
540
+ !reused.every((entry) =>
541
+ [entry.capsuleRoot, entry.identityRoot, entry.artifactDigest].every(
542
+ (value) => SHA256_ROOT.test(String(value || "")),
543
+ ),
544
+ )
545
+ ) {
546
+ failures.push("capsule-root-invalid");
547
+ }
548
+ return failures;
549
+ }
550
+
551
+ export function verifyV4RuntimeResumeLineage({
552
+ lineage,
553
+ lineageRoot,
554
+ repository = "",
555
+ sourceSha = "",
556
+ buildRuntimeSha = "",
557
+ resumeRuntimeSha = "",
558
+ consumerPolicyReceiptRoot = "",
559
+ } = {}) {
560
+ const failures = [];
561
+ const check = (condition, code) => {
562
+ if (!condition) failures.push(code);
563
+ };
564
+ check(
565
+ lineage?.contract === V4_RUNTIME_RESUME_LINEAGE_CONTRACT,
566
+ "contract-invalid",
567
+ );
568
+ check(lineage?.status === "qualified", "status-not-qualified");
569
+ check(
570
+ SHA256_ROOT.test(String(lineageRoot || "")) &&
571
+ lineageRoot === v4RuntimeResumeDocumentRoot(lineage),
572
+ "lineage-root-mismatch",
573
+ );
574
+ check(
575
+ lineage?.attempts?.build?.id &&
576
+ lineage?.attempts?.resume?.id &&
577
+ lineage.attempts.build.id !== lineage.attempts.resume.id,
578
+ "attempt-not-fresh",
579
+ );
580
+ check(validSourceIdentity(lineage?.source), "source-identity-invalid");
581
+ check(validRuntimeTransition(lineage?.attempts), "runtime-lineage-invalid");
582
+ check(
583
+ validFloatingRefMovement(lineage?.floatingRefMovement),
584
+ "floating-ref-movement-invalid",
585
+ );
586
+ check(
587
+ lineage?.continuation?.mechanism === "new-governed-attempt" &&
588
+ lineage?.continuation?.rerunFailedJobs === false,
589
+ "continuation-mechanism-invalid",
590
+ );
591
+ failures.push(...platformLineageFailures(lineage?.stageCapsules));
592
+ check(
593
+ [
594
+ lineage?.authorizationRoot,
595
+ lineage?.consumerPolicyReceiptRoot,
596
+ lineage?.continuation?.resumePlanRoot,
597
+ lineage?.finalPublicReadbackRoot,
598
+ ].every((value) => SHA256_ROOT.test(String(value || ""))),
599
+ "evidence-root-invalid",
600
+ );
601
+ if (sourceSha)
602
+ check(lineage?.source?.sha === sourceSha, "source-sha-mismatch");
603
+ if (buildRuntimeSha) {
604
+ check(
605
+ lineage?.attempts?.build?.runtimeSha === buildRuntimeSha,
606
+ "build-runtime-mismatch",
607
+ );
608
+ }
609
+ if (resumeRuntimeSha) {
610
+ check(
611
+ lineage?.attempts?.resume?.runtimeSha === resumeRuntimeSha,
612
+ "resume-runtime-mismatch",
613
+ );
614
+ }
615
+ if (consumerPolicyReceiptRoot) {
616
+ check(
617
+ lineage?.consumerPolicyReceiptRoot === consumerPolicyReceiptRoot,
618
+ "consumer-policy-root-mismatch",
619
+ );
620
+ }
621
+ if (repository) {
622
+ check(Boolean(lineage?.authorizationRoot), "authorization-root-missing");
623
+ }
624
+ return { ok: failures.length === 0, failures };
625
+ }