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

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 (87) hide show
  1. package/architecture/decisions/0002-next-development-transition.md +101 -0
  2. package/architecture/internal-capabilities.json +52 -3
  3. package/architecture/maintainability-policy.json +22 -10
  4. package/architecture/v3-core-mechanism-inventory.json +89 -0
  5. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  6. package/architecture/v4-compatibility-facts-parity.json +77 -0
  7. package/architecture/v4-next-development-parity.json +192 -0
  8. package/architecture/v4-release-train-parity.json +1 -0
  9. package/architecture/v4-tail-reseal-parity.json +150 -0
  10. package/bin/buildchain.mjs +16 -1
  11. package/bin/internal/command-registry.mjs +2 -0
  12. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  13. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  14. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  15. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  16. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  17. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  18. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  19. package/contracts/next-development-request-v1.schema.json +66 -0
  20. package/contracts/next-development-transition-v1.schema.json +292 -0
  21. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  22. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  23. package/dist/site/agent-index.json +1 -0
  24. package/dist/site/artifact-schemas.json +2 -0
  25. package/dist/site/buildchain-contract.json +1388 -23
  26. package/dist/site/buildchain-site.json +257 -19
  27. package/dist/site/capability-registry.json +10 -8
  28. package/dist/site/cli-registry.json +106 -0
  29. package/dist/site/kfd-claims.json +187 -12
  30. package/dist/site/kfd-upstream-aggregate.json +1 -1
  31. package/dist/site/manual-registry.json +35 -4
  32. package/dist/site/node-api-registry.json +6579 -4493
  33. package/dist/site/page-registry.json +233 -11
  34. package/dist/site/public-surface-audit.json +241 -7
  35. package/dist/site/publication-authority-registry.json +6 -1
  36. package/dist/site/publication-registry.json +4 -4
  37. package/dist/site/release-provenance.json +7 -0
  38. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  39. package/dist/site/site-manifest.json +24 -8
  40. package/dist/site/workflow-registry.json +36 -4
  41. package/docs/MAP.md +4 -1
  42. package/docs/cli-reference.md +125 -0
  43. package/docs/dev-alpha-candidate-patrol.md +8 -0
  44. package/docs/next-development-transition.md +118 -0
  45. package/docs/node-api-reference.md +250 -117
  46. package/docs/v4-compatibility-facts.md +91 -0
  47. package/docs/v4-tail-reseal.md +73 -0
  48. package/docs/versioning.md +4 -3
  49. package/package.json +14 -5
  50. package/packages/core/buildchain-agent-manuals.js +2 -0
  51. package/packages/core/buildchain-compatibility-authority.js +336 -0
  52. package/packages/core/buildchain-compatibility-fact.js +394 -0
  53. package/packages/core/buildchain-compatibility-facts.json +217 -0
  54. package/packages/core/buildchain-compatibility-proof.js +566 -0
  55. package/packages/core/buildchain-contract.js +93 -141
  56. package/packages/core/channel-candidate.js +8 -0
  57. package/packages/core/channel-promotion-baseline.js +55 -0
  58. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  59. package/packages/core/dev-delivery-execution-transfer.js +21 -1
  60. package/packages/core/dev-delivery-process-boundary.js +19 -6
  61. package/packages/core/dev-delivery-provider-heartbeat.js +18 -6
  62. package/packages/core/kungfu-temporal-fact.js +557 -0
  63. package/packages/core/next-development-candidate-reservation.js +186 -0
  64. package/packages/core/next-development-controller.js +728 -0
  65. package/packages/core/next-development-projection.js +287 -0
  66. package/packages/core/next-development-transition.js +738 -0
  67. package/packages/core/paper-agent-entry.js +11 -5
  68. package/packages/core/paper.js +16 -5
  69. package/packages/core/v4-canonical-contracts.js +5 -0
  70. package/packages/core/v4-floating-consumer-policy.js +29 -9
  71. package/packages/core/v4-tail-reseal-contract.js +26 -0
  72. package/packages/core/v4-tail-reseal-github.js +178 -0
  73. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  74. package/packages/core/v4-tail-reseal.js +594 -0
  75. package/scripts/buildchain-cli-help.mjs +20 -0
  76. package/scripts/check-inventory.mjs +11 -0
  77. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  78. package/scripts/generate-next-development-guidance.mjs +49 -0
  79. package/scripts/generate-site-bundle.mjs +16 -3
  80. package/scripts/init-repo.mjs +22 -4
  81. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  82. package/scripts/next-development-self-dogfood.mjs +597 -0
  83. package/scripts/next-development-transition.mjs +47 -0
  84. package/scripts/site-capability-metadata.mjs +12 -0
  85. package/scripts/v4-consumer-policy.mjs +1 -5
  86. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  87. package/scripts/v4-tail-reseal.mjs +416 -0
@@ -12,6 +12,7 @@ import {
12
12
  stableJson,
13
13
  workCheck,
14
14
  } from "./paper-repository.js";
15
+ import { mergeNextDevelopmentAgentInstructions } from "./next-development-projection.js";
15
16
 
16
17
  export const PAPER_AGENT_ENTRY_CONTRACT = "kungfu-buildchain-paper-agent-entry";
17
18
  export const PAPER_AGENT_ENTRY_SCHEMA_VERSION = 1;
@@ -167,9 +168,11 @@ export function paperAgentEntryFiles({
167
168
  [PAPER_PATHS.agentEntry, jsonText(entry)],
168
169
  [
169
170
  PAPER_PATHS.agentInstructions,
170
- mergePaperAgentEntryInstructions(currentAgents, {
171
- developmentRef: resolvedDevelopmentRef,
172
- }),
171
+ mergeNextDevelopmentAgentInstructions(
172
+ mergePaperAgentEntryInstructions(currentAgents, {
173
+ developmentRef: resolvedDevelopmentRef,
174
+ }),
175
+ ),
173
176
  ],
174
177
  ]);
175
178
  }
@@ -205,8 +208,11 @@ function ciContext({ env, developmentRef }) {
205
208
  sourceBranch.startsWith(versionStatePrefix) &&
206
209
  /^[0-9a-f]{12}$/i.test(sourceBranch.slice(versionStatePrefix.length));
207
210
  const branchOk = pullRequest
208
- ? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) && targetBranch === developmentRef) ||
209
- (channelIndex > 0 && sourceBranch === channelBranches[channelIndex - 1]) || generatedVersionState
211
+ ? (PAPER_WORK_BRANCH_PATTERN.test(sourceBranch) &&
212
+ targetBranch === developmentRef) ||
213
+ (channelIndex > 0 &&
214
+ sourceBranch === channelBranches[channelIndex - 1]) ||
215
+ generatedVersionState
210
216
  : channelBranches.includes(refName);
211
217
  return {
212
218
  mode: "ci",
@@ -46,6 +46,11 @@ import {
46
46
  scaffoldReadme,
47
47
  } from "./paper-scaffold-content.js";
48
48
  import { executePaperNpmBootstrap as executePaperNpmBootstrapOperation } from "./paper-npm-bootstrap.js";
49
+ import {
50
+ appendNextDevelopmentToml,
51
+ nextDevelopmentWorkflowHeader,
52
+ projectNextDevelopmentToml,
53
+ } from "./next-development-projection.js";
49
54
 
50
55
  export { PAPER_PATHS, resolvePaperRepository } from "./paper-repository.js";
51
56
  export {
@@ -350,7 +355,7 @@ immutable_base_url = ${tomlString(joinUrl(siteBaseUrl, "archive").replace(/\/$/,
350
355
  registry_path = ".buildchain/publication/publication-registry.json"
351
356
  `
352
357
  : "";
353
- return `schema = 1
358
+ return appendNextDevelopmentToml(`schema = 1
354
359
 
355
360
  [project]
356
361
  type = "publication-artifact"
@@ -384,14 +389,14 @@ command = "make pdf"
384
389
 
385
390
  [lifecycle.verify]
386
391
  command = "make check"
387
- `;
392
+ `);
388
393
  }
389
394
 
390
395
  function scaffoldBuildWorkflow(
391
396
  buildchainSha,
392
397
  { artifactName = "paper-publication" } = {},
393
398
  ) {
394
- return `name: Build
399
+ return `${nextDevelopmentWorkflowHeader()}name: Build
395
400
 
396
401
  on:
397
402
  workflow_dispatch:
@@ -419,7 +424,7 @@ jobs:
419
424
  }
420
425
 
421
426
  function scaffoldVerifyWorkflow(buildchainSha) {
422
- return `name: Verify
427
+ return `${nextDevelopmentWorkflowHeader()}name: Verify
423
428
 
424
429
  on:
425
430
  pull_request:
@@ -450,7 +455,7 @@ function scaffoldReleaseWorkflow(
450
455
  const passportInput = releasePassportProductName
451
456
  ? ` release-passport-product-name: ${JSON.stringify(releasePassportProductName)}\n`
452
457
  : "";
453
- return `name: Paper Release
458
+ return `${nextDevelopmentWorkflowHeader()}name: Paper Release
454
459
 
455
460
  on:
456
461
  workflow_dispatch:
@@ -997,6 +1002,12 @@ function migrationFiles({
997
1002
  runtimeIdentity.version,
998
1003
  );
999
1004
  const files = new Map([
1005
+ [
1006
+ PAPER_PATHS.config,
1007
+ projectNextDevelopmentToml(
1008
+ fs.readFileSync(path.resolve(cwd, PAPER_PATHS.config), "utf8"),
1009
+ ),
1010
+ ],
1000
1011
  [PAPER_PATHS.contractLock, contractLockText],
1001
1012
  [PAPER_PATHS.versionPin, `${runtimeIdentity.version}\n`],
1002
1013
  [PAPER_PATHS.buildWorkflow, buildWorkflow],
@@ -56,6 +56,11 @@ const ROOT_DOMAINS = new Set([
56
56
  "stable-publication-fence",
57
57
  "partial-mutation-recovery-checkpoint",
58
58
  "partial-mutation-recovery-plan",
59
+ "tail-reseal-plan",
60
+ "tail-reseal-admission",
61
+ "tail-reseal-artifact-files",
62
+ "tail-reseal-receipt",
63
+ "release-candidate-passport",
59
64
  ]);
60
65
  const FAULT_CLASSES = new Set([
61
66
  "validation",
@@ -1,6 +1,10 @@
1
1
  import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import {
5
+ createBuildchainContractWorld,
6
+ evaluateBuildchainContractLock,
7
+ } from "./buildchain-contract.js";
4
8
  import { parseYamlUses } from "./workflow-yaml-contract.js";
5
9
  import {
6
10
  V4_FLOATING_CONSUMER_POLICY,
@@ -29,6 +33,7 @@ const TRANSIENT_ACTION =
29
33
  const SCANNER_PATHS = Object.freeze([
30
34
  "architecture/v4-floating-consumer-policy.json",
31
35
  "contracts/v4-floating-consumer-policy-receipt-v1.schema.json",
36
+ "packages/core/buildchain-contract.js",
32
37
  "packages/core/v4-floating-consumer-policy.js",
33
38
  "packages/core/workflow-yaml-contract.js",
34
39
  "scripts/v4-consumer-policy.mjs",
@@ -381,15 +386,30 @@ export function scanV4FloatingConsumerPolicy({
381
386
  expectedInvocationChannel,
382
387
  );
383
388
  const selectedLock = selected?.channel === "alpha" ? alpha : stable;
384
- if (
385
- selected?.channel &&
386
- selectedLock?.buildchain?.resolvedSha?.toLowerCase() !==
387
- normalizedWorkflowSha
388
- ) {
389
- failures.push({
390
- code: "selected-lock-runtime-mismatch",
391
- message: `${selected.channel} contract lock does not bind resolved workflow shell ${normalizedWorkflowSha}`,
392
- });
389
+ if (selected?.channel && selectedLock) {
390
+ try {
391
+ const compatibility = evaluateBuildchainContractLock({
392
+ lock: selectedLock,
393
+ current: createBuildchainContractWorld({ root: defaultRuntimeRoot() }),
394
+ runtimeRef: selected.selector,
395
+ runtimeSha: normalizedWorkflowSha,
396
+ runtimeClass: selected.channel,
397
+ workflowShellRef: selected.selector,
398
+ expectedChannel: selected.channel,
399
+ expectedMajor: "v4",
400
+ });
401
+ if (!compatibility.ok) {
402
+ failures.push({
403
+ code: "selected-lock-runtime-incompatible",
404
+ message: `${selected.channel} contract lock rejects resolved workflow shell ${normalizedWorkflowSha}: ${compatibility.reasons?.join("; ") || compatibility.status}`,
405
+ });
406
+ }
407
+ } catch (error) {
408
+ failures.push({
409
+ code: "selected-lock-contract-world-invalid",
410
+ message: `cannot evaluate ${selected.channel} contract lock against the resolved workflow shell: ${error.message}`,
411
+ });
412
+ }
393
413
  }
394
414
 
395
415
  const policyRoot = sha256(stableJson(policy));
@@ -0,0 +1,26 @@
1
+ export const V4_TAIL_RESEAL_REQUEST_CONTRACT =
2
+ "kungfu-buildchain-v4-tail-reseal-request/v1";
3
+ export const V4_TAIL_RESEAL_PLAN_CONTRACT =
4
+ "kungfu-buildchain-v4-tail-reseal-plan/v1";
5
+
6
+ export const V4_TAIL_RESEAL_PLATFORMS = Object.freeze([
7
+ "linux-arm64",
8
+ "linux-x64",
9
+ "macos-arm64",
10
+ "windows-x64",
11
+ ]);
12
+
13
+ export const V4_TAIL_RESEAL_PLATFORM_RUNNERS = Object.freeze({
14
+ "linux-arm64": '["ubuntu-24.04-arm"]',
15
+ "linux-x64": '["ubuntu-24.04"]',
16
+ "macos-arm64": '["macos-15"]',
17
+ "windows-x64": '["windows-2025"]',
18
+ });
19
+
20
+ export const V4_TAIL_RESEAL_REUSED_STAGE_KEYS = Object.freeze(
21
+ V4_TAIL_RESEAL_PLATFORMS.flatMap((platformId) =>
22
+ ["build", "install", "package", "verify"].map(
23
+ (stage) => `${platformId}:${stage}`,
24
+ ),
25
+ ).sort(),
26
+ );
@@ -0,0 +1,178 @@
1
+ import { v4ContentRoot } from "./v4-canonical-contracts.js";
2
+ import {
3
+ V4TailResealFault,
4
+ normalizeV4TailResealRequest,
5
+ } from "./v4-tail-reseal.js";
6
+
7
+ export const V4_TAIL_RESEAL_ADMISSION_CONTRACT =
8
+ "kungfu-buildchain-v4-tail-reseal-admission/v1";
9
+
10
+ const REQUIRED_SUCCESS_JOBS = Object.freeze([
11
+ "build / Linux ARM64",
12
+ "build / Linux x64",
13
+ "build / macOS ARM64",
14
+ "build / Windows x64",
15
+ "build / Control detached signing Linux ARM64",
16
+ "build / Control detached signing Linux x64",
17
+ "build / Control detached signing macOS ARM64",
18
+ "build / Control detached signing Windows x64",
19
+ "build / Finalize signed artifact Linux ARM64",
20
+ "build / Finalize signed artifact Linux x64",
21
+ "build / Finalize signed artifact Windows x64",
22
+ ]);
23
+
24
+ function fault(code, path, message) {
25
+ throw new V4TailResealFault(code, path, message);
26
+ }
27
+
28
+ function exactArtifact(artifacts, name, digest, path) {
29
+ const matches = artifacts.filter((artifact) => artifact.name === name);
30
+ if (matches.length !== 1)
31
+ fault(
32
+ "tail-reseal-artifact-ambiguous",
33
+ path,
34
+ "retained artifact must resolve exactly once",
35
+ );
36
+ const artifact = matches[0];
37
+ if (artifact.expired === true)
38
+ fault("tail-reseal-retention-expired", path, "retained artifact expired");
39
+ if (artifact.digest !== digest)
40
+ fault(
41
+ "tail-reseal-artifact-archive-mismatch",
42
+ path,
43
+ "GitHub artifact archive digest drifted",
44
+ );
45
+ return artifact;
46
+ }
47
+
48
+ function validateSourceIdentity(normalized, sourceCommit, sourceRun) {
49
+ if (
50
+ sourceCommit?.sha !== normalized.source.sha ||
51
+ sourceCommit?.tree?.sha !== normalized.source.treeSha
52
+ )
53
+ fault(
54
+ "tail-reseal-source-mismatch",
55
+ "$/sourceCommit",
56
+ "source commit or tree readback drifted",
57
+ );
58
+ const workflowFile = String(sourceRun?.path || "")
59
+ .split("@")[0]
60
+ .replace(/^\.github\/workflows\//u, "");
61
+ if (
62
+ Number(sourceRun?.id) !== normalized.source.runId ||
63
+ Number(sourceRun?.run_attempt) !== normalized.source.runAttempt ||
64
+ sourceRun?.event !== "workflow_dispatch" ||
65
+ sourceRun?.status !== "completed" ||
66
+ sourceRun?.conclusion !== "failure" ||
67
+ sourceRun?.head_sha !== normalized.source.sha ||
68
+ workflowFile !== normalized.source.workflowFile ||
69
+ sourceRun?.name !== normalized.source.workflowName
70
+ )
71
+ fault(
72
+ "tail-reseal-source-run-mismatch",
73
+ "$/sourceRun",
74
+ "source run is not the exact retained failed Build run",
75
+ );
76
+ }
77
+
78
+ function validateSourceJobs(normalized, sourceJobs) {
79
+ const jobs = Array.isArray(sourceJobs) ? sourceJobs : [];
80
+ const byName = new Map(jobs.map((job) => [job.name, job]));
81
+ for (const name of REQUIRED_SUCCESS_JOBS)
82
+ if (byName.get(name)?.conclusion !== "success")
83
+ fault(
84
+ "tail-reseal-prior-stage-mismatch",
85
+ "$/sourceJobs",
86
+ `required source job did not succeed: ${name}`,
87
+ );
88
+ const failedJob = jobs.find(
89
+ (job) => Number(job.id) === normalized.failure.jobId,
90
+ );
91
+ const failedSteps = (failedJob?.steps || [])
92
+ .filter((step) => step.conclusion === "failure")
93
+ .map((step) => step.name);
94
+ if (
95
+ failedJob?.name !== normalized.failure.jobName ||
96
+ failedJob?.conclusion !== "failure" ||
97
+ JSON.stringify(failedSteps) !==
98
+ JSON.stringify([normalized.failure.stepName])
99
+ )
100
+ fault(
101
+ "tail-reseal-failure-mismatch",
102
+ "$/sourceJobs",
103
+ "observed failure is not the single authorized macOS tail failure",
104
+ );
105
+ }
106
+
107
+ function validateRetainedArtifacts(normalized, sourceArtifacts) {
108
+ for (const [index, platform] of normalized.platforms.entries()) {
109
+ exactArtifact(
110
+ sourceArtifacts || [],
111
+ platform.artifactName,
112
+ platform.artifactArchiveRoot,
113
+ `$/sourceArtifacts/${index}/payload`,
114
+ );
115
+ exactArtifact(
116
+ sourceArtifacts || [],
117
+ platform.manifestArtifactName,
118
+ platform.manifestArchiveRoot,
119
+ `$/sourceArtifacts/${index}/manifest`,
120
+ );
121
+ }
122
+ }
123
+
124
+ function validateSigningAuthority(normalized, signingRun, signingArtifacts) {
125
+ if (
126
+ Number(signingRun?.id) !== normalized.signing.authorityRunId ||
127
+ signingRun?.status !== "completed" ||
128
+ signingRun?.conclusion !== "success" ||
129
+ signingRun?.head_sha !== normalized.signing.runtimeSha
130
+ )
131
+ fault(
132
+ "tail-reseal-signer-authority-mismatch",
133
+ "$/signingRun",
134
+ "signing authority run readback drifted",
135
+ );
136
+ exactArtifact(
137
+ signingArtifacts || [],
138
+ normalized.signing.resultArtifact,
139
+ normalized.signing.resultArtifactRoot,
140
+ "$/signingArtifacts/result",
141
+ );
142
+ }
143
+
144
+ export function validateV4TailResealGitHubEvidence({
145
+ request,
146
+ sourceCommit,
147
+ sourceRun,
148
+ sourceJobs,
149
+ sourceArtifacts,
150
+ signingRun,
151
+ signingArtifacts,
152
+ } = {}) {
153
+ const normalized = normalizeV4TailResealRequest(request);
154
+ validateSourceIdentity(normalized, sourceCommit, sourceRun);
155
+ validateSourceJobs(normalized, sourceJobs);
156
+ validateRetainedArtifacts(normalized, sourceArtifacts);
157
+ validateSigningAuthority(normalized, signingRun, signingArtifacts);
158
+ const payload = {
159
+ schema: V4_TAIL_RESEAL_ADMISSION_CONTRACT,
160
+ repository: normalized.repository,
161
+ sourceRoot: normalized.source.sourceRoot,
162
+ sourceRunId: normalized.source.runId,
163
+ signingAuthorityRunId: normalized.signing.authorityRunId,
164
+ retainedArchiveRoots: normalized.platforms.flatMap((platform) => [
165
+ platform.artifactArchiveRoot,
166
+ platform.manifestArchiveRoot,
167
+ ]),
168
+ priorFailureRoot: normalized.failure.evidenceRoot,
169
+ retentionReadbackRoot: normalized.retention.readbackRoot,
170
+ warrantReadbackRoot: normalized.warrant.stateReadbackRoot,
171
+ };
172
+ return {
173
+ ...payload,
174
+ admissionRoot: v4ContentRoot("tail-reseal-admission", payload),
175
+ };
176
+ }
177
+
178
+ export { REQUIRED_SUCCESS_JOBS as V4_TAIL_RESEAL_REQUIRED_SUCCESS_JOBS };
@@ -0,0 +1,225 @@
1
+ import { v4ContentRoot, validateV4Root } from "./v4-canonical-contracts.js";
2
+ import { validateReleaseCandidatePassport } from "./release-candidate.js";
3
+ import {
4
+ V4_TAIL_RESEAL_PLAN_CONTRACT,
5
+ V4_TAIL_RESEAL_PLATFORMS,
6
+ V4TailResealFault,
7
+ normalizeV4TailResealRequest,
8
+ planV4TailReseal,
9
+ } from "./v4-tail-reseal.js";
10
+
11
+ export const V4_TAIL_RESEAL_RECEIPT_CONTRACT =
12
+ "kungfu-buildchain-v4-tail-reseal-receipt/v1";
13
+
14
+ function fault(code, path, message) {
15
+ throw new V4TailResealFault(code, path, message);
16
+ }
17
+
18
+ function root(value, path) {
19
+ try {
20
+ validateV4Root(value, path);
21
+ } catch (error) {
22
+ fault("invalid-tail-reseal-root", path, error.message);
23
+ }
24
+ return value;
25
+ }
26
+
27
+ function exactKeys(value, expected, path) {
28
+ if (!value || typeof value !== "object" || Array.isArray(value))
29
+ fault("invalid-tail-reseal-shape", path, "object required");
30
+ const actual = Object.keys(value).sort();
31
+ const canonical = [...expected].sort();
32
+ if (
33
+ actual.length !== canonical.length ||
34
+ actual.some((key, index) => key !== canonical[index])
35
+ )
36
+ fault(
37
+ "invalid-tail-reseal-shape",
38
+ path,
39
+ "keys do not match the closed tail-reseal contract",
40
+ );
41
+ }
42
+
43
+ function positiveInteger(value, path) {
44
+ if (!Number.isSafeInteger(value) || value < 1)
45
+ fault(
46
+ "invalid-tail-reseal-integer",
47
+ path,
48
+ "positive safe integer required",
49
+ );
50
+ }
51
+
52
+ function validateReadbacks(readbacks, request) {
53
+ if (
54
+ !Array.isArray(readbacks) ||
55
+ readbacks.length !== V4_TAIL_RESEAL_PLATFORMS.length
56
+ )
57
+ fault(
58
+ "tail-reseal-readback-mismatch",
59
+ "$/readbacks",
60
+ "one readback per platform required",
61
+ );
62
+ const normalized = [...readbacks].sort((left, right) =>
63
+ Buffer.from(left.platformId || "").compare(
64
+ Buffer.from(right.platformId || ""),
65
+ ),
66
+ );
67
+ for (const [index, readback] of normalized.entries()) {
68
+ const path = `$/readbacks/${index}`;
69
+ exactKeys(
70
+ readback,
71
+ [
72
+ "platformId",
73
+ "artifactRoot",
74
+ "manifestRoot",
75
+ "capsuleRoot",
76
+ "byteIdentical",
77
+ "providerReadbackRoot",
78
+ ],
79
+ path,
80
+ );
81
+ const expected = request.platforms[index];
82
+ if (readback.platformId !== expected.id)
83
+ fault(
84
+ "tail-reseal-platform-mismatch",
85
+ `${path}/platformId`,
86
+ "unexpected platform readback",
87
+ );
88
+ for (const field of ["artifactRoot", "manifestRoot", "capsuleRoot"])
89
+ root(readback[field], `${path}/${field}`);
90
+ if (readback.capsuleRoot !== expected.capsuleRoot)
91
+ fault(
92
+ "tail-reseal-capsule-mismatch",
93
+ `${path}/capsuleRoot`,
94
+ "Stage Capsule identity changed during tail execution",
95
+ );
96
+ if (readback.platformId === "macos-arm64") {
97
+ if (readback.byteIdentical !== false)
98
+ fault(
99
+ "tail-reseal-macos-effect-missing",
100
+ `${path}/byteIdentical`,
101
+ "macOS signed bytes must be independently resealed",
102
+ );
103
+ root(readback.providerReadbackRoot, `${path}/providerReadbackRoot`);
104
+ } else if (
105
+ readback.byteIdentical !== true ||
106
+ readback.artifactRoot !== expected.artifactRoot ||
107
+ readback.manifestRoot !== expected.manifestRoot ||
108
+ readback.providerReadbackRoot !== null
109
+ )
110
+ fault(
111
+ "tail-reseal-nontail-byte-drift",
112
+ path,
113
+ "non-macOS retained artifacts must remain byte-identical and effect-free",
114
+ );
115
+ }
116
+ return normalized;
117
+ }
118
+
119
+ export function createV4TailResealReceipt({
120
+ request,
121
+ plan,
122
+ readbacks,
123
+ passport,
124
+ protectedReadbackRoot,
125
+ currentRun,
126
+ } = {}) {
127
+ const normalized = normalizeV4TailResealRequest(request);
128
+ const expectedPlan = planV4TailReseal(normalized);
129
+ const { planRoot: _planRoot, ...planBody } = plan || {};
130
+ if (
131
+ plan?.schema !== V4_TAIL_RESEAL_PLAN_CONTRACT ||
132
+ plan.planRoot !== expectedPlan.planRoot ||
133
+ v4ContentRoot("tail-reseal-plan", planBody) !== expectedPlan.planRoot
134
+ )
135
+ fault(
136
+ "tail-reseal-plan-drift",
137
+ "$/plan",
138
+ "plan does not match the exact request",
139
+ );
140
+ const platformReadbacks = validateReadbacks(readbacks, normalized);
141
+ const passportValidation = validateReleaseCandidatePassport({
142
+ passport,
143
+ repository: normalized.repository,
144
+ targetChannel: normalized.target.channel,
145
+ version: normalized.target.version,
146
+ sourceHeadSha: normalized.source.sha,
147
+ requirePlatforms: true,
148
+ });
149
+ if (!passportValidation.ok)
150
+ fault(
151
+ "tail-reseal-passport-invalid",
152
+ "$/passport",
153
+ passportValidation.errors.join("; "),
154
+ );
155
+ if (
156
+ passport.contract !== "kungfu-buildchain-release-candidate-passport" ||
157
+ passport.buildchain?.sha !== normalized.runtime.sha ||
158
+ passport.consumerPolicy?.receiptRoot !==
159
+ normalized.runtime.consumerPolicyReceiptRoot
160
+ )
161
+ fault(
162
+ "tail-reseal-passport-binding-mismatch",
163
+ "$/passport",
164
+ "standard v4 candidate Passport bindings drifted",
165
+ );
166
+ root(protectedReadbackRoot, "$/protectedReadbackRoot");
167
+ exactKeys(currentRun, ["id", "attempt"], "$/currentRun");
168
+ positiveInteger(currentRun.id, "$/currentRun/id");
169
+ positiveInteger(currentRun.attempt, "$/currentRun/attempt");
170
+ const payload = {
171
+ schema: V4_TAIL_RESEAL_RECEIPT_CONTRACT,
172
+ action: "reused-retained-tail",
173
+ repository: normalized.repository,
174
+ source: normalized.source,
175
+ target: normalized.target,
176
+ runtime: normalized.runtime,
177
+ planRoot: expectedPlan.planRoot,
178
+ warrant: normalized.warrant,
179
+ retention: normalized.retention,
180
+ originalFailure: normalized.failure,
181
+ signingAuthority: normalized.signing,
182
+ releaseTail: normalized.releaseTail,
183
+ platformReadbacks,
184
+ skippedStages: expectedPlan.skippedStages,
185
+ rerunStages: expectedPlan.rerunStages,
186
+ passport: {
187
+ contract: passport.contract,
188
+ candidateHash: passport.candidateHash,
189
+ passportRoot: v4ContentRoot("release-candidate-passport", passport),
190
+ },
191
+ protectedReadbackRoot,
192
+ currentRun,
193
+ };
194
+ return {
195
+ ...payload,
196
+ receiptRoot: v4ContentRoot("tail-reseal-receipt", payload),
197
+ };
198
+ }
199
+
200
+ export function verifyV4TailResealReceipt({ receipt, request, passport } = {}) {
201
+ try {
202
+ const expected = createV4TailResealReceipt({
203
+ request,
204
+ plan: planV4TailReseal(request),
205
+ readbacks: receipt.platformReadbacks,
206
+ passport,
207
+ protectedReadbackRoot: receipt.protectedReadbackRoot,
208
+ currentRun: receipt.currentRun,
209
+ });
210
+ return {
211
+ ok: expected.receiptRoot === receipt.receiptRoot,
212
+ receiptRoot: expected.receiptRoot,
213
+ failures:
214
+ expected.receiptRoot === receipt.receiptRoot
215
+ ? []
216
+ : [{ code: "tail-reseal-receipt-root-mismatch" }],
217
+ };
218
+ } catch (error) {
219
+ return {
220
+ ok: false,
221
+ receiptRoot: "",
222
+ failures: [{ code: error.code || "tail-reseal-receipt-invalid" }],
223
+ };
224
+ }
225
+ }