@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,212 @@
1
+ export class V4PublicationRehearsalFault extends Error {
2
+ constructor(code, location, message) {
3
+ super(`${location}: ${message}`);
4
+ this.name = "V4PublicationRehearsalFault";
5
+ this.code = code;
6
+ this.location = location;
7
+ }
8
+ }
9
+
10
+ export function publicationRehearsalFault(code, location, message) {
11
+ throw new V4PublicationRehearsalFault(code, location, message);
12
+ }
13
+
14
+ export function publicationRehearsalExactKeys(value, expected, location) {
15
+ if (!value || typeof value !== "object" || Array.isArray(value))
16
+ publicationRehearsalFault(
17
+ "invalid-publication-rehearsal-shape",
18
+ location,
19
+ "object required",
20
+ );
21
+ const actual = Object.keys(value).sort();
22
+ const canonical = [...expected].sort();
23
+ if (
24
+ actual.length !== canonical.length ||
25
+ actual.some((key, index) => key !== canonical[index])
26
+ )
27
+ publicationRehearsalFault(
28
+ "invalid-publication-rehearsal-shape",
29
+ location,
30
+ `keys must be exactly ${canonical.join(", ")}`,
31
+ );
32
+ }
33
+
34
+ export function publicationRehearsalRoot(value, location) {
35
+ if (typeof value !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(value))
36
+ publicationRehearsalFault(
37
+ "invalid-publication-rehearsal-root",
38
+ location,
39
+ "SHA-256 root required",
40
+ );
41
+ return value;
42
+ }
43
+
44
+ export function publicationRehearsalText(value, location) {
45
+ if (typeof value !== "string" || value.trim() === "")
46
+ publicationRehearsalFault(
47
+ "invalid-publication-rehearsal-text",
48
+ location,
49
+ "non-empty text required",
50
+ );
51
+ return value.trim();
52
+ }
53
+
54
+ export function publicationRehearsalToken(value, location) {
55
+ const normalized = publicationRehearsalText(value, location);
56
+ if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/u.test(normalized))
57
+ publicationRehearsalFault(
58
+ "invalid-publication-rehearsal-token",
59
+ location,
60
+ "ASCII token required",
61
+ );
62
+ return normalized;
63
+ }
64
+
65
+ export function publicationRehearsalRelativePath(value, location) {
66
+ const normalized = publicationRehearsalText(value, location);
67
+ if (
68
+ normalized.startsWith("/") ||
69
+ normalized.includes("\\") ||
70
+ normalized.endsWith("/") ||
71
+ normalized.split("/").some((part) => !part || part === "." || part === "..")
72
+ )
73
+ publicationRehearsalFault(
74
+ "invalid-publication-rehearsal-path",
75
+ location,
76
+ "safe POSIX relative file path required",
77
+ );
78
+ return normalized;
79
+ }
80
+
81
+ export function publicationRehearsalByteSorted(
82
+ values,
83
+ location,
84
+ validate = publicationRehearsalToken,
85
+ allowEmpty = false,
86
+ ) {
87
+ if (!Array.isArray(values) || (!allowEmpty && values.length === 0))
88
+ publicationRehearsalFault(
89
+ "invalid-publication-rehearsal-shape",
90
+ location,
91
+ "array required",
92
+ );
93
+ const normalized = values.map((value, index) =>
94
+ validate(value, `${location}/${index}`),
95
+ );
96
+ const canonical = [...new Set(normalized)].sort((left, right) =>
97
+ Buffer.from(left).compare(Buffer.from(right)),
98
+ );
99
+ if (JSON.stringify(normalized) !== JSON.stringify(canonical))
100
+ publicationRehearsalFault(
101
+ "unordered-publication-rehearsal-values",
102
+ location,
103
+ "values must be unique and byte-sorted",
104
+ );
105
+ return normalized;
106
+ }
107
+
108
+ function canonicalObject(value, location, normalize) {
109
+ if (!value || typeof value !== "object" || Array.isArray(value))
110
+ publicationRehearsalFault(
111
+ "invalid-publication-rehearsal-shape",
112
+ location,
113
+ "object required",
114
+ );
115
+ const keys = Object.keys(value);
116
+ const sorted = [...keys].sort((left, right) =>
117
+ Buffer.from(left).compare(Buffer.from(right)),
118
+ );
119
+ if (JSON.stringify(keys) !== JSON.stringify(sorted))
120
+ publicationRehearsalFault(
121
+ "unordered-publication-rehearsal-values",
122
+ location,
123
+ "object keys must be byte-sorted",
124
+ );
125
+ return Object.fromEntries(
126
+ keys.map((key) => [
127
+ publicationRehearsalToken(key, `${location}/${key}`),
128
+ normalize(value[key], key),
129
+ ]),
130
+ );
131
+ }
132
+
133
+ export function validateV4PublicationRehearsalProviderBindings(value) {
134
+ publicationRehearsalExactKeys(
135
+ value,
136
+ ["schema", "artifacts", "documents", "evidence"],
137
+ "$/providerBindings",
138
+ );
139
+ if (value.schema !== "kungfu.buildchain.release-tail.provider-bindings/v1")
140
+ publicationRehearsalFault(
141
+ "invalid-publication-rehearsal-provider-bindings",
142
+ "$/providerBindings/schema",
143
+ "unsupported provider bindings schema",
144
+ );
145
+ const artifacts = canonicalObject(
146
+ value.artifacts,
147
+ "$/providerBindings/artifacts",
148
+ (binding, key) => {
149
+ publicationRehearsalExactKeys(
150
+ binding,
151
+ ["path", "name"],
152
+ `$/providerBindings/artifacts/${key}`,
153
+ );
154
+ return {
155
+ path: publicationRehearsalRelativePath(
156
+ binding.path,
157
+ `$/providerBindings/artifacts/${key}/path`,
158
+ ),
159
+ name: publicationRehearsalText(
160
+ binding.name,
161
+ `$/providerBindings/artifacts/${key}/name`,
162
+ ),
163
+ };
164
+ },
165
+ );
166
+ const documents = canonicalObject(
167
+ value.documents,
168
+ "$/providerBindings/documents",
169
+ (binding, key) => {
170
+ publicationRehearsalExactKeys(
171
+ binding,
172
+ ["path", "method"],
173
+ `$/providerBindings/documents/${key}`,
174
+ );
175
+ if (!new Set(["PUT", "POST"]).has(binding.method))
176
+ publicationRehearsalFault(
177
+ "invalid-publication-rehearsal-provider-bindings",
178
+ `$/providerBindings/documents/${key}/method`,
179
+ "PUT or POST required",
180
+ );
181
+ return {
182
+ path: publicationRehearsalRelativePath(
183
+ binding.path,
184
+ `$/providerBindings/documents/${key}/path`,
185
+ ),
186
+ method: binding.method,
187
+ };
188
+ },
189
+ );
190
+ publicationRehearsalExactKeys(
191
+ value.evidence,
192
+ ["inputs", "output"],
193
+ "$/providerBindings/evidence",
194
+ );
195
+ return {
196
+ schema: value.schema,
197
+ artifacts,
198
+ documents,
199
+ evidence: {
200
+ inputs: publicationRehearsalByteSorted(
201
+ value.evidence.inputs,
202
+ "$/providerBindings/evidence/inputs",
203
+ publicationRehearsalRelativePath,
204
+ true,
205
+ ),
206
+ output: publicationRehearsalRelativePath(
207
+ value.evidence.output,
208
+ "$/providerBindings/evidence/output",
209
+ ),
210
+ },
211
+ };
212
+ }
@@ -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
+ }