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

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 (108) hide show
  1. package/actions/release-tail/README.md +8 -0
  2. package/architecture/ci-lane-change-budget.json +187 -0
  3. package/architecture/internal-capabilities.json +65 -5
  4. package/architecture/maintainability-policy.json +27 -21
  5. package/architecture/release-tail-contract-inventory.json +5 -2
  6. package/architecture/v4-adopter-delivery-parity.json +32 -5
  7. package/architecture/v4-floating-consumer-policy.json +6 -0
  8. package/architecture/v4-publication-rehearsal-parity.json +84 -0
  9. package/bin/buildchain.mjs +7 -1
  10. package/bin/internal/adopter-delivery-cli.mjs +70 -0
  11. package/bin/internal/command-registry.mjs +1 -0
  12. package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
  13. package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
  14. package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
  15. package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
  16. package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
  17. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
  18. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
  19. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
  20. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
  21. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
  22. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
  23. package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
  24. package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
  25. package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
  26. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  27. package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
  28. package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
  29. package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  30. package/dist/site/agent-index.json +3 -0
  31. package/dist/site/artifact-schemas.json +6 -0
  32. package/dist/site/buildchain-contract.json +14 -14
  33. package/dist/site/buildchain-site.json +231 -24
  34. package/dist/site/capability-registry.json +11 -10
  35. package/dist/site/cli-registry.json +53 -1
  36. package/dist/site/kfd-claims.json +279 -14
  37. package/dist/site/kfd-upstream-aggregate.json +1 -1
  38. package/dist/site/manual-registry.json +20 -5
  39. package/dist/site/node-api-registry.json +11569 -9299
  40. package/dist/site/page-registry.json +214 -15
  41. package/dist/site/public-surface-audit.json +206 -11
  42. package/dist/site/publication-authority-registry.json +39 -8
  43. package/dist/site/publication-registry.json +4 -4
  44. package/dist/site/release-provenance.json +15 -0
  45. package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
  46. package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
  47. package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
  48. package/dist/site/site-manifest.json +17 -9
  49. package/dist/site/workflow-registry.json +110 -8
  50. package/docs/MAP.md +3 -1
  51. package/docs/cli-reference.md +68 -0
  52. package/docs/cli.md +8 -0
  53. package/docs/dev-delivery-warrant.md +22 -1
  54. package/docs/node-api-reference.md +332 -172
  55. package/docs/release-tail-provider-plane.md +30 -0
  56. package/docs/v4-adopter-delivery.md +133 -0
  57. package/docs/v4-publication-rehearsal.md +130 -0
  58. package/package.json +18 -3
  59. package/packages/core/adopter-delivery-passport.js +259 -0
  60. package/packages/core/adopter-delivery-vectors.js +158 -0
  61. package/packages/core/adopter-delivery-vectors.json +77 -0
  62. package/packages/core/buildchain-agent-manuals.js +1 -0
  63. package/packages/core/buildchain-config.js +84 -0
  64. package/packages/core/buildchain-delivery-bootstrap.js +240 -0
  65. package/packages/core/buildchain-delivery-infrastructure.js +164 -0
  66. package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
  67. package/packages/core/buildchain-publication-authority.js +1 -0
  68. package/packages/core/ci-lane-change-budget.js +247 -0
  69. package/packages/core/dev-delivery-native-proof.js +43 -27
  70. package/packages/core/dev-delivery-warrant-state.js +31 -8
  71. package/packages/core/dev-delivery-warrant.js +8 -3
  72. package/packages/core/github-governance-authority.js +1 -0
  73. package/packages/core/index.js +5 -0
  74. package/packages/core/legacy-kfd-adopter-driver.js +167 -0
  75. package/packages/core/published-delivery-authority.js +266 -0
  76. package/packages/core/release-candidate-recovery.js +66 -11
  77. package/packages/core/release-candidate.js +2 -2
  78. package/packages/core/release-passport.js +36 -9
  79. package/packages/core/v4-adopter-delivery-parity.js +3 -3
  80. package/packages/core/v4-adopter-delivery.js +359 -0
  81. package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
  82. package/packages/core/v4-floating-consumer-evidence.js +18 -4
  83. package/packages/core/v4-floating-consumer-policy.js +138 -35
  84. package/packages/core/v4-floating-consumer-release-passport.js +30 -3
  85. package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
  86. package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
  87. package/packages/core/v4-publication-rehearsal.js +481 -0
  88. package/packages/core/workflow-yaml-contract.js +24 -2
  89. package/scripts/buildchain-cli-help.mjs +10 -0
  90. package/scripts/check-ci-lane-change-budget.mjs +100 -0
  91. package/scripts/check-inventory.mjs +3 -3
  92. package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
  93. package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
  94. package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
  95. package/scripts/dev-delivery-warrant-store.mjs +10 -6
  96. package/scripts/dev-delivery-warrant.mjs +5 -1
  97. package/scripts/dev-pr-delivery-warrant.mjs +1 -0
  98. package/scripts/dev-qualification-patrol.mjs +9 -9
  99. package/scripts/generate-channel-promotion-workflow.mjs +2 -2
  100. package/scripts/generate-release-candidate-passport.mjs +152 -0
  101. package/scripts/generate-site-bundle.mjs +30 -0
  102. package/scripts/promotion-channel-router.mjs +4 -4
  103. package/scripts/release-candidate-anchor-provenance.mjs +99 -0
  104. package/scripts/release-tail.mjs +51 -3
  105. package/scripts/resume-from-candidate-run.mjs +556 -29
  106. package/scripts/site-capability-metadata.mjs +17 -0
  107. package/scripts/v4-consumer-policy.mjs +4 -0
  108. package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
@@ -77,10 +77,31 @@ function normalizeCertification(value, expected) {
77
77
  return { certificationRoot: verification.certificationRoot, certification };
78
78
  }
79
79
 
80
+ export function resolveV4ConsumerPolicyCertificationIdentity({
81
+ release = {},
82
+ routing = {},
83
+ runtimeResume = undefined,
84
+ sourceSha = "",
85
+ } = {}) {
86
+ const builtSourceSha =
87
+ release?.builtSourceSha || release?.built_source_sha || "";
88
+ return {
89
+ sourceSha:
90
+ release?.treeEquivalent === true && builtSourceSha
91
+ ? builtSourceSha
92
+ : sourceSha,
93
+ runtimeSha:
94
+ runtimeResume?.lineage?.attempts?.build?.runtimeSha ||
95
+ routing?.runtime?.resolvedSha ||
96
+ "",
97
+ };
98
+ }
99
+
80
100
  export function requireV4ConsumerPolicyCertification({
81
101
  value,
82
102
  repository,
83
103
  sourceSha,
104
+ runtimeSha = "",
84
105
  routing,
85
106
  cwd,
86
107
  certificationRoot,
@@ -100,7 +121,7 @@ export function requireV4ConsumerPolicyCertification({
100
121
  return normalizeCertification(value, {
101
122
  repository,
102
123
  sourceSha,
103
- runtimeSha: routing?.runtime?.resolvedSha || "",
124
+ runtimeSha: runtimeSha || routing?.runtime?.resolvedSha || "",
104
125
  certificationRoot,
105
126
  ...(v4Routing ? resolveV4FloatingConsumerCallerLocks(value, cwd) : {}),
106
127
  });
@@ -112,12 +133,18 @@ export function releasePassportCertificationVerificationOptions({
112
133
  routing,
113
134
  }) {
114
135
  const authority = evidence?.certification?.authority || {};
136
+ const identity = resolveV4ConsumerPolicyCertificationIdentity({
137
+ release: passport?.release,
138
+ routing,
139
+ runtimeResume: passport?.v4RuntimeResume,
140
+ sourceSha: passport?.release?.sourceSha || "",
141
+ });
115
142
  return {
116
143
  certification: evidence?.certification,
117
144
  certificationRoot: evidence?.certificationRoot,
118
145
  repository: passport?.product?.repository || "",
119
- sourceSha: passport?.release?.sourceSha || "",
120
- resolvedRuntimeSha: routing?.runtime?.resolvedSha || "",
146
+ sourceSha: identity.sourceSha,
147
+ resolvedRuntimeSha: identity.runtimeSha,
121
148
  stableLockRoot: authority.contractLocks?.stable?.root || "",
122
149
  alphaLockRoot: authority.contractLocks?.alpha?.root || "",
123
150
  stableLockPath: authority.contractLocks?.stable?.path || "",
@@ -0,0 +1,468 @@
1
+ import {
2
+ RELEASE_TAIL_TRANSACTION_POLICY,
3
+ compileReleaseTailDeclaration,
4
+ createReleaseTailTransaction,
5
+ parseReleaseTailDeclaration,
6
+ releaseTailRoot,
7
+ validateReleaseTailTransaction,
8
+ } from "./release-tail-provider-plane.js";
9
+ import {
10
+ V4PublicationRehearsalFault,
11
+ publicationRehearsalByteSorted as byteSorted,
12
+ publicationRehearsalExactKeys as exactKeys,
13
+ publicationRehearsalFault as fault,
14
+ publicationRehearsalRelativePath as relativePath,
15
+ publicationRehearsalRoot as root,
16
+ publicationRehearsalText as text,
17
+ publicationRehearsalToken as token,
18
+ validateV4PublicationRehearsalProviderBindings,
19
+ } from "./v4-publication-rehearsal-provider-bindings.js";
20
+
21
+ export {
22
+ V4PublicationRehearsalFault,
23
+ exactKeys as assertPublicationRehearsalExactKeys,
24
+ fault as publicationRehearsalFault,
25
+ root as publicationRehearsalRoot,
26
+ validateV4PublicationRehearsalProviderBindings,
27
+ };
28
+
29
+ export const V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT =
30
+ "buildchain-v4-publication-rehearsal-capsule/v1";
31
+ export const V4_PUBLICATION_REHEARSAL_EVIDENCE_CONTRACT =
32
+ "buildchain-v4-publication-rehearsal-evidence/v1";
33
+ export const V4_PUBLICATION_REHEARSAL_AUTHORITY_CONTRACT =
34
+ "buildchain-v4-publication-rehearsal-authority/v1";
35
+ export const V4_PUBLICATION_REHEARSAL_PROVIDER_POLICY_CONTRACT =
36
+ "buildchain-v4-publication-rehearsal-provider-policy/v1";
37
+ export const V4_PUBLICATION_REHEARSAL_CORE_VERSION =
38
+ RELEASE_TAIL_TRANSACTION_POLICY;
39
+
40
+ const ROOT = /^sha256:[0-9a-f]{64}$/u;
41
+ const SHA = /^[0-9a-f]{40}$/u;
42
+ const TOKEN = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/u;
43
+ const MODES = new Set(["simulate", "replay", "provider"]);
44
+
45
+ function sourceRoot(source) {
46
+ return releaseTailRoot({
47
+ repository: source.repository,
48
+ revision: source.revision,
49
+ });
50
+ }
51
+
52
+ function candidateRoot(files) {
53
+ return releaseTailRoot(
54
+ files.map(({ role, path: filePath, size, root: fileRoot }) => ({
55
+ role,
56
+ path: filePath,
57
+ size,
58
+ root: fileRoot,
59
+ })),
60
+ );
61
+ }
62
+
63
+ function policyPayload(policy) {
64
+ return {
65
+ schema: policy.schema,
66
+ effectDefault: policy.effectDefault,
67
+ allowedCapabilityIds: policy.allowedCapabilityIds,
68
+ allowedAdapters: policy.allowedAdapters,
69
+ };
70
+ }
71
+
72
+ function capsulePayload(capsule) {
73
+ const payload = structuredClone(capsule);
74
+ delete payload.capsuleRoot;
75
+ return payload;
76
+ }
77
+
78
+ function normalizeFile(value, index) {
79
+ const location = `$/files/${index}`;
80
+ exactKeys(value, ["role", "path", "size", "root"], location);
81
+ if (!Number.isSafeInteger(value.size) || value.size < 0)
82
+ fault(
83
+ "invalid-publication-rehearsal-file",
84
+ `${location}/size`,
85
+ "non-negative safe integer required",
86
+ );
87
+ return {
88
+ role: token(value.role, `${location}/role`),
89
+ path: relativePath(value.path, `${location}/path`),
90
+ size: value.size,
91
+ root: root(value.root, `${location}/root`),
92
+ };
93
+ }
94
+
95
+ function normalizeBinding(value, location) {
96
+ exactKeys(value, ["path", "root"], location);
97
+ return {
98
+ path: relativePath(value.path, `${location}/path`),
99
+ root: root(value.root, `${location}/root`),
100
+ };
101
+ }
102
+
103
+ function normalizeCandidateInputs(value) {
104
+ exactKeys(value.candidate, ["root"], "$/candidate");
105
+ root(value.candidate.root, "$/candidate/root");
106
+ if (!Array.isArray(value.files) || value.files.length === 0)
107
+ fault(
108
+ "invalid-publication-rehearsal-files",
109
+ "$/files",
110
+ "non-empty file inventory required",
111
+ );
112
+ const files = value.files.map(normalizeFile);
113
+ const paths = files.map((entry) => entry.path);
114
+ if (
115
+ JSON.stringify(paths) !==
116
+ JSON.stringify(
117
+ [...new Set(paths)].sort((left, right) =>
118
+ Buffer.from(left).compare(Buffer.from(right)),
119
+ ),
120
+ ) ||
121
+ value.candidate.root !== candidateRoot(files)
122
+ )
123
+ fault(
124
+ "publication-rehearsal-candidate-root-mismatch",
125
+ "$/candidate/root",
126
+ "candidate inventory is unordered, duplicated, or drifted",
127
+ );
128
+ const manifest = normalizeBinding(value.manifest, "$/manifest");
129
+ const config = normalizeBinding(value.config, "$/config");
130
+ for (const [name, binding] of Object.entries({ manifest, config }))
131
+ if (
132
+ !files.some(
133
+ (entry) => entry.path === binding.path && entry.root === binding.root,
134
+ )
135
+ )
136
+ fault(
137
+ `publication-rehearsal-${name}-root-mismatch`,
138
+ `$/${name}`,
139
+ `${name} is not bound by the candidate inventory`,
140
+ );
141
+ const providerBindings = validateV4PublicationRehearsalProviderBindings(
142
+ value.providerBindings,
143
+ );
144
+ root(value.providerBindingsRoot, "$/providerBindingsRoot");
145
+ if (value.providerBindingsRoot !== releaseTailRoot(providerBindings))
146
+ fault(
147
+ "publication-rehearsal-provider-bindings-root-mismatch",
148
+ "$/providerBindingsRoot",
149
+ "provider bindings payload drifted",
150
+ );
151
+ const inventoryPaths = new Set(paths);
152
+ const requiredInputs = [
153
+ ...Object.values(providerBindings.artifacts).map(({ path }) => path),
154
+ ...Object.values(providerBindings.documents).map(({ path }) => path),
155
+ ...providerBindings.evidence.inputs,
156
+ ];
157
+ for (const inputPath of requiredInputs)
158
+ if (!inventoryPaths.has(inputPath))
159
+ fault(
160
+ "publication-rehearsal-provider-binding-file-missing",
161
+ "$/providerBindings",
162
+ `provider input is absent from files: ${inputPath}`,
163
+ );
164
+ return { files, manifest, config, providerBindings };
165
+ }
166
+
167
+ function normalizeProviderPolicy(value, declaration) {
168
+ exactKeys(
169
+ value,
170
+ [
171
+ "schema",
172
+ "effectDefault",
173
+ "allowedCapabilityIds",
174
+ "allowedAdapters",
175
+ "root",
176
+ ],
177
+ "$/providerPolicy",
178
+ );
179
+ if (
180
+ value.schema !== V4_PUBLICATION_REHEARSAL_PROVIDER_POLICY_CONTRACT ||
181
+ value.effectDefault !== "disabled"
182
+ )
183
+ fault(
184
+ "invalid-publication-rehearsal-provider-policy",
185
+ "$/providerPolicy",
186
+ "provider rehearsal effects must default to disabled",
187
+ );
188
+ const capabilityIds = byteSorted(
189
+ value.allowedCapabilityIds,
190
+ "$/providerPolicy/allowedCapabilityIds",
191
+ text,
192
+ );
193
+ const adapters = byteSorted(
194
+ value.allowedAdapters,
195
+ "$/providerPolicy/allowedAdapters",
196
+ token,
197
+ );
198
+ const expectedCapabilityIds = declaration.capabilities
199
+ .map(({ id }) => id)
200
+ .sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
201
+ const expectedAdapters = [
202
+ ...new Set(declaration.capabilities.map(({ adapter }) => adapter)),
203
+ ].sort((left, right) => Buffer.from(left).compare(Buffer.from(right)));
204
+ if (
205
+ JSON.stringify(capabilityIds) !== JSON.stringify(expectedCapabilityIds) ||
206
+ JSON.stringify(adapters) !== JSON.stringify(expectedAdapters)
207
+ )
208
+ fault(
209
+ "publication-rehearsal-provider-policy-mismatch",
210
+ "$/providerPolicy",
211
+ "policy must enumerate the exact shared-core capabilities and adapters",
212
+ );
213
+ const normalized = {
214
+ schema: value.schema,
215
+ effectDefault: value.effectDefault,
216
+ allowedCapabilityIds: capabilityIds,
217
+ allowedAdapters: adapters,
218
+ root: root(value.root, "$/providerPolicy/root"),
219
+ };
220
+ if (normalized.root !== releaseTailRoot(policyPayload(normalized)))
221
+ fault(
222
+ "publication-rehearsal-provider-policy-root-mismatch",
223
+ "$/providerPolicy/root",
224
+ "provider policy root drifted",
225
+ );
226
+ return normalized;
227
+ }
228
+
229
+ function normalizeObservation(value, index, operationIds) {
230
+ const location = `$/expectedObservations/entries/${index}`;
231
+ exactKeys(value, ["operationId", "readbacks", "apply"], location);
232
+ const operationId = root(value.operationId, `${location}/operationId`);
233
+ if (!operationIds.has(operationId))
234
+ fault(
235
+ "publication-rehearsal-observation-operation-mismatch",
236
+ `${location}/operationId`,
237
+ "unknown operation",
238
+ );
239
+ if (!Array.isArray(value.readbacks) || value.readbacks.length === 0)
240
+ fault(
241
+ "invalid-publication-rehearsal-observation",
242
+ `${location}/readbacks`,
243
+ "non-empty readback sequence required",
244
+ );
245
+ for (const [readbackIndex, readback] of value.readbacks.entries()) {
246
+ exactKeys(
247
+ readback,
248
+ ["outcome", "subjectRoot", "targetRoot", "evidenceRoots", "providerCode"],
249
+ `${location}/readbacks/${readbackIndex}`,
250
+ );
251
+ if (
252
+ !new Set(["observed", "absent", "transient", "conflict"]).has(
253
+ readback.outcome,
254
+ )
255
+ )
256
+ fault(
257
+ "invalid-publication-rehearsal-observation",
258
+ `${location}/readbacks/${readbackIndex}/outcome`,
259
+ "unsupported outcome",
260
+ );
261
+ if (readback.subjectRoot)
262
+ root(
263
+ readback.subjectRoot,
264
+ `${location}/readbacks/${readbackIndex}/subjectRoot`,
265
+ );
266
+ if (readback.targetRoot)
267
+ root(
268
+ readback.targetRoot,
269
+ `${location}/readbacks/${readbackIndex}/targetRoot`,
270
+ );
271
+ byteSorted(
272
+ readback.evidenceRoots,
273
+ `${location}/readbacks/${readbackIndex}/evidenceRoots`,
274
+ root,
275
+ true,
276
+ );
277
+ text(
278
+ readback.providerCode,
279
+ `${location}/readbacks/${readbackIndex}/providerCode`,
280
+ );
281
+ }
282
+ exactKeys(
283
+ value.apply,
284
+ ["outcome", "code", "classification"],
285
+ `${location}/apply`,
286
+ );
287
+ if (!new Set(["applied", "transient", "conflict"]).has(value.apply.outcome))
288
+ fault(
289
+ "invalid-publication-rehearsal-observation",
290
+ `${location}/apply/outcome`,
291
+ "unsupported apply outcome",
292
+ );
293
+ text(value.apply.code, `${location}/apply/code`);
294
+ text(value.apply.classification, `${location}/apply/classification`);
295
+ return structuredClone(value);
296
+ }
297
+
298
+ export function createV4PublicationRehearsalProviderPolicy(declarationInput) {
299
+ const declaration = parseReleaseTailDeclaration(declarationInput);
300
+ const body = {
301
+ schema: V4_PUBLICATION_REHEARSAL_PROVIDER_POLICY_CONTRACT,
302
+ effectDefault: "disabled",
303
+ allowedCapabilityIds: declaration.capabilities
304
+ .map(({ id }) => id)
305
+ .sort((left, right) => Buffer.from(left).compare(Buffer.from(right))),
306
+ allowedAdapters: [
307
+ ...new Set(declaration.capabilities.map(({ adapter }) => adapter)),
308
+ ].sort((left, right) => Buffer.from(left).compare(Buffer.from(right))),
309
+ };
310
+ return { ...body, root: releaseTailRoot(body) };
311
+ }
312
+
313
+ export function createV4PublicationRehearsalCapsule(input) {
314
+ const declaration = parseReleaseTailDeclaration(input.declaration);
315
+ const transaction =
316
+ input.transaction || createReleaseTailTransaction(declaration);
317
+ const files = structuredClone(input.files || []).sort((left, right) =>
318
+ Buffer.from(left.path).compare(Buffer.from(right.path)),
319
+ );
320
+ const source = {
321
+ repository: text(input.source?.repository, "$/source/repository"),
322
+ revision: text(input.source?.revision, "$/source/revision"),
323
+ };
324
+ const policy =
325
+ input.providerPolicy ||
326
+ createV4PublicationRehearsalProviderPolicy(declaration);
327
+ const observations = structuredClone(input.expectedObservations || []);
328
+ const providerBindings = validateV4PublicationRehearsalProviderBindings(
329
+ input.providerBindings,
330
+ );
331
+ const body = {
332
+ schema: V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT,
333
+ coreVersion: V4_PUBLICATION_REHEARSAL_CORE_VERSION,
334
+ source: { ...source, root: sourceRoot(source) },
335
+ candidate: { root: candidateRoot(files) },
336
+ manifest: structuredClone(input.manifest),
337
+ config: structuredClone(input.config),
338
+ providerBindings,
339
+ providerBindingsRoot: releaseTailRoot(providerBindings),
340
+ providerPolicy: structuredClone(policy),
341
+ expectedObservations: {
342
+ entries: observations,
343
+ root: releaseTailRoot(observations),
344
+ },
345
+ declaration,
346
+ transaction: structuredClone(transaction),
347
+ files,
348
+ };
349
+ return validateV4PublicationRehearsalCapsule({
350
+ ...body,
351
+ capsuleRoot: releaseTailRoot(body),
352
+ });
353
+ }
354
+
355
+ export function validateV4PublicationRehearsalCapsule(value) {
356
+ exactKeys(
357
+ value,
358
+ [
359
+ "schema",
360
+ "coreVersion",
361
+ "source",
362
+ "candidate",
363
+ "manifest",
364
+ "config",
365
+ "providerBindings",
366
+ "providerBindingsRoot",
367
+ "providerPolicy",
368
+ "expectedObservations",
369
+ "declaration",
370
+ "transaction",
371
+ "files",
372
+ "capsuleRoot",
373
+ ],
374
+ "$",
375
+ );
376
+ if (
377
+ value.schema !== V4_PUBLICATION_REHEARSAL_CAPSULE_CONTRACT ||
378
+ value.coreVersion !== V4_PUBLICATION_REHEARSAL_CORE_VERSION
379
+ )
380
+ fault(
381
+ "unsupported-publication-rehearsal-core-version",
382
+ "$/coreVersion",
383
+ "capsule must bind the active production release-tail core",
384
+ );
385
+ exactKeys(value.source, ["repository", "revision", "root"], "$/source");
386
+ text(value.source.repository, "$/source/repository");
387
+ if (!SHA.test(value.source.revision))
388
+ fault(
389
+ "invalid-publication-rehearsal-source",
390
+ "$/source/revision",
391
+ "exact lowercase Git SHA required",
392
+ );
393
+ root(value.source.root, "$/source/root");
394
+ if (value.source.root !== sourceRoot(value.source))
395
+ fault(
396
+ "publication-rehearsal-source-root-mismatch",
397
+ "$/source/root",
398
+ "source root drifted",
399
+ );
400
+ const { files, manifest, config, providerBindings } =
401
+ normalizeCandidateInputs(value);
402
+ const declaration = parseReleaseTailDeclaration(value.declaration);
403
+ const plan = compileReleaseTailDeclaration(declaration);
404
+ const transactionValidation = validateReleaseTailTransaction(
405
+ value.transaction,
406
+ );
407
+ const initial = createReleaseTailTransaction(plan);
408
+ if (
409
+ !transactionValidation.valid ||
410
+ value.transaction.declarationRoot !== plan.declarationRoot ||
411
+ value.transaction.planRoot !== plan.planRoot ||
412
+ value.transaction.transactionRoot !== initial.transactionRoot
413
+ )
414
+ fault(
415
+ "publication-rehearsal-core-binding-mismatch",
416
+ "$/transaction",
417
+ "transaction does not belong to the shared production planner",
418
+ );
419
+ const providerPolicy = normalizeProviderPolicy(
420
+ value.providerPolicy,
421
+ declaration,
422
+ );
423
+ exactKeys(
424
+ value.expectedObservations,
425
+ ["entries", "root"],
426
+ "$/expectedObservations",
427
+ );
428
+ if (!Array.isArray(value.expectedObservations.entries))
429
+ fault(
430
+ "invalid-publication-rehearsal-observations",
431
+ "$/expectedObservations/entries",
432
+ "array required",
433
+ );
434
+ const operationIds = new Set(value.transaction.operationOrder);
435
+ const expectedObservations = value.expectedObservations.entries.map(
436
+ (entry, index) => normalizeObservation(entry, index, operationIds),
437
+ );
438
+ if (
439
+ new Set(expectedObservations.map(({ operationId }) => operationId)).size !==
440
+ expectedObservations.length ||
441
+ value.expectedObservations.root !== releaseTailRoot(expectedObservations)
442
+ )
443
+ fault(
444
+ "publication-rehearsal-expected-observation-root-mismatch",
445
+ "$/expectedObservations/root",
446
+ "expected observations are duplicated or drifted",
447
+ );
448
+ root(value.capsuleRoot, "$/capsuleRoot");
449
+ if (value.capsuleRoot !== releaseTailRoot(capsulePayload(value)))
450
+ fault(
451
+ "publication-rehearsal-capsule-root-mismatch",
452
+ "$/capsuleRoot",
453
+ "capsule payload drifted",
454
+ );
455
+ return {
456
+ ...structuredClone(value),
457
+ manifest,
458
+ config,
459
+ providerBindings,
460
+ providerPolicy,
461
+ expectedObservations: {
462
+ entries: expectedObservations,
463
+ root: value.expectedObservations.root,
464
+ },
465
+ declaration,
466
+ files,
467
+ };
468
+ }