@kungfu-tech/buildchain 4.1.3-alpha.9 → 4.1.4-alpha.0

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 (205) hide show
  1. package/AGENTS.md +11 -13
  2. package/CONTRIBUTING.md +32 -9
  3. package/README.md +56 -140
  4. package/actions/release/promotion/ref/README.md +1 -1
  5. package/architecture/action-taxonomy.json +1 -1
  6. package/architecture/agent-change-map.md +32 -45
  7. package/architecture/build-orchestration.json +1 -1
  8. package/architecture/capability-state-machine-manifest.json +5 -5
  9. package/architecture/ci-lane-change-budget.json +92 -92
  10. package/architecture/code-layout.json +2 -12
  11. package/architecture/core-mechanisms.json +38 -39
  12. package/architecture/cross-platform-adopter-qualification.json +1 -1
  13. package/architecture/dev-delivery-orchestration.json +2 -2
  14. package/architecture/floating-consumer-policy.json +3 -3
  15. package/architecture/internal-capabilities.json +27 -28
  16. package/architecture/maintainability-debt.json +60 -94
  17. package/architecture/maintainability-policy.json +7 -13
  18. package/architecture/minimal-consumer-migration.json +43 -5014
  19. package/architecture/platform-stage-checkpoints.json +1 -1
  20. package/architecture/release-tail-contract.json +2 -2
  21. package/architecture/release-topology.json +33 -25
  22. package/architecture/stage-capsule-qualification.json +1 -1
  23. package/architecture/universal-workflow-bootstrap.json +60 -60
  24. package/architecture/universal-workflow-capability-policy.json +3 -3
  25. package/architecture/universal-workflow-fault-campaign.json +7 -7
  26. package/architecture/workflow-taxonomy.json +59 -60
  27. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
  28. package/dist/site/buildchain-contract.json +138 -66
  29. package/dist/site/buildchain-site.json +257 -331
  30. package/dist/site/capability-registry.json +4 -4
  31. package/dist/site/cli-registry.json +6 -142
  32. package/dist/site/controller-registry.json +19 -19
  33. package/dist/site/kfd-claims.json +481 -529
  34. package/dist/site/kfd-upstream-aggregate.json +1 -1
  35. package/dist/site/manual-registry.json +22 -22
  36. package/dist/site/node-api-registry.json +82 -339
  37. package/dist/site/page-registry.json +229 -304
  38. package/dist/site/public-surface-audit.json +544 -754
  39. package/dist/site/publication-authority-registry.json +316 -316
  40. package/dist/site/publication-registry.json +1 -1
  41. package/dist/site/site-manifest.json +23 -23
  42. package/dist/site/workflow-registry.json +1071 -1027
  43. package/docs/MAP.md +28 -20
  44. package/docs/auditable-demo.md +17 -49
  45. package/docs/bootstrap-recovery.md +46 -37
  46. package/docs/cli-reference.md +2 -46
  47. package/docs/cli.md +97 -186
  48. package/docs/code-organization.md +7 -5
  49. package/docs/dev-alpha-candidate-patrol.md +19 -42
  50. package/docs/dev-delivery-orchestration.md +15 -11
  51. package/docs/dev-delivery-qualification-landing-adr.md +10 -9
  52. package/docs/dev-delivery-warrant.md +17 -16
  53. package/docs/dev-qualification-patrol.md +25 -20
  54. package/docs/getting-started.md +80 -139
  55. package/docs/github-artifact-attestation.md +31 -81
  56. package/docs/install.md +66 -30
  57. package/docs/lifecycle-protocol.md +19 -57
  58. package/docs/migration-inventory.md +17 -45
  59. package/docs/next-development-transition.md +13 -13
  60. package/docs/node-api-reference.md +122 -138
  61. package/docs/oci-publication.md +13 -10
  62. package/docs/publication-artifacts.md +128 -486
  63. package/docs/publication-authority.md +15 -45
  64. package/docs/publish-transaction.md +13 -11
  65. package/docs/release-candidate.md +126 -355
  66. package/docs/release-discussions.md +36 -67
  67. package/docs/release-flow.md +80 -339
  68. package/docs/release-governance.md +114 -962
  69. package/docs/release-promotion-request.md +45 -43
  70. package/docs/release-propagation.md +13 -27
  71. package/docs/release-tail-provider-plane.md +38 -65
  72. package/docs/reusable-build-surface.md +78 -196
  73. package/docs/runtime-train-validation.md +8 -6
  74. package/docs/stable-candidate-patrol.md +13 -66
  75. package/docs/v4-adopter-delivery.md +15 -15
  76. package/docs/v4-declarative-release-tail-migration.md +19 -17
  77. package/docs/v4-production-release.md +11 -6
  78. package/docs/v4-publication-rehearsal.md +12 -27
  79. package/docs/v4-stage-capsule.md +14 -20
  80. package/docs/v4-tail-reseal.md +34 -20
  81. package/docs/web-surface-deployments.md +26 -264
  82. package/docs/workflow-catalog.md +31 -31
  83. package/docs/workflow-path-migration.md +10 -4
  84. package/package.json +5 -4
  85. package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
  86. package/packages/core/adoption/cli/output.mjs +1 -9
  87. package/packages/core/adoption/cli/product-gate.mjs +1 -15
  88. package/packages/core/adoption/cli/project.mjs +9 -10
  89. package/packages/core/adoption/cli/standards.mjs +1 -14
  90. package/packages/core/adoption/cli/support.mjs +0 -2
  91. package/packages/core/adoption/cli/surface-register.mjs +0 -2
  92. package/packages/core/adoption/commands/init-repo.mjs +77 -485
  93. package/packages/core/adoption/consumer-init.js +279 -0
  94. package/packages/core/adoption/qualification/admission.js +1 -1
  95. package/packages/core/build/cli/cache.mjs +0 -2
  96. package/packages/core/build/cli/facts.mjs +0 -3
  97. package/packages/core/build/cli/homebrew.mjs +0 -4
  98. package/packages/core/build/cli/lifecycle.mjs +0 -3
  99. package/packages/core/build/github-artifact-attestation.js +2 -2
  100. package/packages/core/build/platform-stage-checkpoints.js +1 -1
  101. package/packages/core/build/signing/dispatch.js +1 -1
  102. package/packages/core/consumer/buildchain-config.js +9 -0
  103. package/packages/core/consumer/contract/inspection.js +351 -9
  104. package/packages/core/consumer/contract/javascript-syntax.js +439 -0
  105. package/packages/core/consumer/contract/javascript-values.js +61 -0
  106. package/packages/core/consumer/contract/javascript.js +555 -0
  107. package/packages/core/consumer/contract/local-validation.js +84 -0
  108. package/packages/core/contracts/buildchain-contract.js +39 -8
  109. package/packages/core/contracts/cli/introspection.mjs +1 -9
  110. package/packages/core/contracts/public-surface-cli.js +0 -1
  111. package/packages/core/dev-delivery/dev-delivery-contract-surface.js +2 -2
  112. package/packages/core/governance/buildchain-publication-authority.js +24 -24
  113. package/packages/core/governance/cli/doctor.mjs +58 -42
  114. package/packages/core/governance/commands/architecture.mjs +6 -1
  115. package/packages/core/governance/commands/audit-publication-control-plane.mjs +1 -1
  116. package/packages/core/governance/commands/workflow-friction-report.mjs +1 -1
  117. package/packages/core/governance/github-governance-authority.js +2 -2
  118. package/packages/core/governance/publication/collection.js +1 -1
  119. package/packages/core/index.js +1 -3
  120. package/packages/core/observability/cli/candidate.mjs +0 -2
  121. package/packages/core/observability/cli/context.mjs +0 -5
  122. package/packages/core/observability/cli/diagnostics.mjs +0 -7
  123. package/packages/core/observability/cli/events.mjs +2 -10
  124. package/packages/core/paper/commands/paper-work-fleet-cli.mjs +1 -1
  125. package/packages/core/paper/commands/paper.mjs +33 -422
  126. package/packages/core/paper/consumer-status.js +72 -0
  127. package/packages/core/paper/operations/identity.js +0 -3
  128. package/packages/core/paper/operations/migration.js +460 -0
  129. package/packages/core/paper/operations/preflight-checks.js +8 -68
  130. package/packages/core/paper/operations/scaffold.js +16 -326
  131. package/packages/core/paper/operations/status.js +27 -53
  132. package/packages/core/paper/paper-agent-entry.js +36 -147
  133. package/packages/core/paper/paper-fleet.js +24 -39
  134. package/packages/core/paper/paper-repository.js +16 -0
  135. package/packages/core/paper/paper-runtime-channels.js +1 -1
  136. package/packages/core/paper/paper-scaffold-content.js +153 -162
  137. package/packages/core/paper/paper-work.js +3 -10
  138. package/packages/core/paper/paper.js +43 -5
  139. package/packages/core/providers/github/artifact-attestation.js +1 -1
  140. package/packages/core/providers/github/pipeline-context-artifacts.js +172 -0
  141. package/packages/core/providers/github/pipeline-release-evidence.js +17 -98
  142. package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
  143. package/packages/core/providers/github/pipeline-stable-source.js +22 -4
  144. package/packages/core/providers/github/release-evidence-assets.js +97 -0
  145. package/packages/core/publication/authority/control-plane.js +2 -2
  146. package/packages/core/publication/authority/seal.js +1 -1
  147. package/packages/core/publication/cli/dispatch.mjs +1 -12
  148. package/packages/core/publication/pipeline/actions.js +16 -9
  149. package/packages/core/publication/pipeline/apply.js +17 -9
  150. package/packages/core/publication/pipeline/context.js +29 -0
  151. package/packages/core/publication/pipeline/journal.js +13 -1
  152. package/packages/core/publication/pipeline/prepare.js +15 -6
  153. package/packages/core/publication/pipeline/recovery-import.js +1 -1
  154. package/packages/core/publication/pipeline/recovery-materials.js +63 -1
  155. package/packages/core/publication/pipeline/recovery-prepare.js +21 -9
  156. package/packages/core/publication/pipeline/stable-entry.js +12 -3
  157. package/packages/core/publication/pipeline/stable-products.js +21 -19
  158. package/packages/core/publication/pipeline/version-actions.js +15 -6
  159. package/packages/core/publication/publication-rehearsal-projection.js +0 -9
  160. package/packages/core/release/cli/dispatch.mjs +1 -4
  161. package/packages/core/release/cli/inspection-handlers.mjs +1 -33
  162. package/packages/core/release/cli/release-handlers.mjs +2 -19
  163. package/packages/core/release/cli/verification-handlers.mjs +4 -25
  164. package/packages/core/release/next-development-projection.js +13 -13
  165. package/packages/core/release/passport/locations.js +13 -8
  166. package/packages/core/release/release-passport.js +70 -5
  167. package/packages/core/web/cli/badges.mjs +0 -4
  168. package/packages/core/web/publication-admission.js +1 -1
  169. package/packages/core/web/publication-capability.js +1 -1
  170. package/packages/core/web/readme-badges.js +1 -1
  171. package/packages/core/workflow/cli/main.mjs +1 -4
  172. package/packages/core/workflow/cli/process.mjs +1 -1
  173. package/packages/core/workflow/commands/buildchain-cli-help.mjs +2 -8
  174. package/packages/core/workflow/engine/conformance.js +1 -1
  175. package/packages/core/workflow/pipeline/build.js +21 -1
  176. package/packages/core/workflow/pipeline/delivery-observation.js +6 -2
  177. package/packages/core/workflow/workflow-taxonomy.mjs +61 -17
  178. package/scripts/check-code-layout.mjs +2 -0
  179. package/scripts/check-core-mechanism-inventory.mjs +29 -13
  180. package/scripts/check-floating-consumer-policy-contract.mjs +3 -3
  181. package/scripts/check-inventory.mjs +27 -27
  182. package/scripts/check-maintainability.mjs +28 -5
  183. package/scripts/check-public-dogfood-contract.mjs +3 -3
  184. package/scripts/check-release-topology.mjs +1 -1
  185. package/scripts/check-self-consumer-contract.mjs +7 -7
  186. package/scripts/check-universal-workflow-bootstrap.mjs +1 -1
  187. package/scripts/generate-channel-promotion-workflow.mjs +1 -1
  188. package/scripts/generate-minimal-consumer-contract.mjs +21 -1
  189. package/scripts/generate-site-bundle.mjs +2 -1
  190. package/scripts/generate-tail-reseal-fixture.mjs +3 -3
  191. package/scripts/inventory/promotion.mjs +1 -1
  192. package/scripts/next-development-self-dogfood-harness.mjs +1 -1
  193. package/scripts/next-development-self-dogfood.mjs +4 -4
  194. package/scripts/site-capability-metadata.mjs +2 -6
  195. package/scripts/source-metric-lineage.mjs +62 -7
  196. package/scripts/verify-golden-path.mjs +31 -35
  197. package/templates/native-dev-delivery.yml +1 -1
  198. package/templates/universal-buildchain-bootstrap.yml +1 -1
  199. package/packages/core/paper/operations/bootstrap.js +0 -75
  200. package/packages/core/paper/operations/plans.js +0 -213
  201. package/packages/core/paper/operations/provisioning.js +0 -113
  202. package/packages/core/paper/operations/scaffold-config.js +0 -77
  203. package/packages/core/paper/operations/scaffold-workflows.js +0 -116
  204. package/packages/core/paper/operations/workspace.js +0 -60
  205. package/packages/core/paper/paper-npm-bootstrap.js +0 -492
@@ -2,22 +2,11 @@ import { npmPublishDryRun } from "../npm/preview.js";
2
2
  import { runPublicationArtifactCli } from "../commands/publication-artifact.mjs";
3
3
  import { runPublicationPackageCli } from "../commands/publication-package.mjs";
4
4
  import { runPublicationReproducibilityCli } from "../commands/publication-reproducibility.mjs";
5
- import {
6
- BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT,
7
- formatDiagnosticsSummaryTable,
8
- startProcessSampler,
9
- summarizeDiagnosticsArtifacts,
10
- summarizeProcessSamples,
11
- validateAnchoredPackageRelease,
12
- } from "../../observability/diagnostics.js";
5
+ import { validateAnchoredPackageRelease } from "../../observability/diagnostics.js";
13
6
  import {
14
7
  printJson,
15
8
  readBooleanFlag,
16
9
  readFlag,
17
- readJsonInput,
18
- readRepeatedFlag,
19
- readRepeatedJsonInputs,
20
- writeJsonFile,
21
10
  } from "../../contracts/cli/options.mjs";
22
11
  import { runScript } from "../../workflow/cli/process.mjs";
23
12
 
@@ -11,9 +11,13 @@ import {
11
11
  import { applyPipelinePublication } from "./apply.js";
12
12
  import { settlePipelinePublication } from "./settle.js";
13
13
  import { githubJsonClient } from "../../providers/github/json-client.js";
14
+ import {
15
+ readPipelineContext,
16
+ writePipelineContext,
17
+ } from "../../providers/github/pipeline-context-artifacts.js";
14
18
 
15
- const contextInput = (core) =>
16
- JSON.parse(core.getInput("context", { required: true }));
19
+ const contextInput = (core, env) =>
20
+ readPipelineContext(core.getInput("context", { required: true }), env);
17
21
  const directory = (env) =>
18
22
  path.join(env.GITHUB_WORKSPACE, ".buildchain/publication");
19
23
 
@@ -26,7 +30,10 @@ export async function preparePipelinePublicationAction(core, env) {
26
30
  );
27
31
  core.setOutput("operation", result.operation);
28
32
  core.setOutput("stable-wait", result.wait ? JSON.stringify(result.wait) : "");
29
- core.setOutput("context", JSON.stringify(result.context || {}));
33
+ core.setOutput(
34
+ "context",
35
+ await writePipelineContext(result.context || {}, env),
36
+ );
30
37
  core.setOutput(
31
38
  "matrix",
32
39
  JSON.stringify({ include: result.context?.platforms || [] }),
@@ -34,7 +41,7 @@ export async function preparePipelinePublicationAction(core, env) {
34
41
  }
35
42
 
36
43
  export async function buildPipelinePublicationAction(core, env) {
37
- const context = contextInput(core);
44
+ const context = await contextInput(core, env);
38
45
  const platform = core.getInput("platform", { required: true });
39
46
  const cwd = path.join(env.GITHUB_WORKSPACE, ".buildchain/product");
40
47
  await buildPipelineSource({
@@ -59,7 +66,7 @@ export async function buildPipelinePublicationAction(core, env) {
59
66
  export async function preparePipelineQualificationAction(core, env) {
60
67
  const host = await pipelineHost(core, env, "Qualify and sign products");
61
68
  const prepared = await preparePipelineQualification(
62
- contextInput(core),
69
+ await contextInput(core, env),
63
70
  host,
64
71
  directory(env),
65
72
  );
@@ -71,7 +78,7 @@ export async function preparePipelineQualificationAction(core, env) {
71
78
  export async function sealPipelineQualificationAction(core, env) {
72
79
  const host = await pipelineHost(core, env, "Qualify and sign products");
73
80
  await sealPipelineQualification(
74
- contextInput(core),
81
+ await contextInput(core, env),
75
82
  host,
76
83
  directory(env),
77
84
  core.getInput("bundle-path", { required: true }),
@@ -81,7 +88,7 @@ export async function sealPipelineQualificationAction(core, env) {
81
88
  export async function applyPipelinePublicationAction(core, env) {
82
89
  const host = await pipelineHost(core, env, "Apply qualified publication");
83
90
  const result = await applyPipelinePublication(
84
- contextInput(core),
91
+ await contextInput(core, env),
85
92
  host,
86
93
  directory(env),
87
94
  env,
@@ -98,7 +105,7 @@ export async function settlePipelinePublicationAction(core, env) {
98
105
  userAgent: "buildchain-next-development",
99
106
  });
100
107
  const result = await settlePipelinePublication(
101
- contextInput(core),
108
+ await contextInput(core, env),
102
109
  host,
103
110
  directory(env),
104
111
  env,
@@ -106,7 +113,7 @@ export async function settlePipelinePublicationAction(core, env) {
106
113
  core.info(result.reason);
107
114
  core.setOutput(
108
115
  "version-context",
109
- JSON.stringify(result.versionContext || {}),
116
+ await writePipelineContext(result.versionContext || {}, env),
110
117
  );
111
118
  core.setOutput("operation", result.versionContext ? "regenerate" : "wait");
112
119
  core.setOutput(
@@ -8,7 +8,11 @@ import {
8
8
  } from "../../release/release-invocation.js";
9
9
  import { githubPipelineProductRelease } from "../../providers/github/pipeline-product-release.js";
10
10
  import { pipelineNpmProvider } from "../npm/pipeline-provider.js";
11
- import { publicationContext, uniquePublicationMaterial } from "./context.js";
11
+ import {
12
+ publicationContext,
13
+ uniquePublicationMaterial,
14
+ completedPublicationRecovery,
15
+ } from "./context.js";
12
16
  import { restorePipelineProducts } from "./sealed-products.js";
13
17
  import {
14
18
  pipelineReleaseDocuments,
@@ -34,16 +38,14 @@ export async function applyPipelinePublication(
34
38
  } = {},
35
39
  ) {
36
40
  const { journal, archive } = await publicationContext(context, host);
37
- const wait = await preparePipelineStableQualification(
38
- context.plan,
39
- host,
40
- journal,
41
- {
41
+ const completed = await completedPublicationRecovery(context, journal);
42
+ const wait =
43
+ !completed &&
44
+ (await preparePipelineStableQualification(context.plan, host, journal, {
42
45
  attempt: context.attempt,
43
46
  generation: context.generation,
44
47
  phase: "publish",
45
- },
46
- );
48
+ }));
47
49
  if (wait) return { operation: "wait", wait };
48
50
  const retained = await uniquePublicationMaterial(
49
51
  journal,
@@ -113,7 +115,13 @@ export async function applyPipelinePublication(
113
115
  const provider = {
114
116
  observe: (effect) => providerFor(effect).observe(effect),
115
117
  matches: (effect, value) => providerFor(effect).matches(effect, value),
116
- apply: (effect) => providerFor(effect).apply(effect),
118
+ apply: (effect) => {
119
+ if (completed)
120
+ throw new Error(
121
+ "Completed publication recovery cannot perform new provider effects",
122
+ );
123
+ return providerFor(effect).apply(effect);
124
+ },
117
125
  };
118
126
  const receipts = await journal.materials("publication/effect/");
119
127
  const effects = pipelinePublicationEffects({
@@ -1,6 +1,7 @@
1
1
  import { recordDigest } from "../../release/discussion/envelope.js";
2
2
  import { resumePipelineSession } from "../../workflow/pipeline/session.js";
3
3
  import { pipelinePublicationJournal } from "./journal.js";
4
+ import { createReleaseReceipt } from "../../release/release-invocation.js";
4
5
 
5
6
  export async function publicationContext(context, host) {
6
7
  if (
@@ -37,3 +38,31 @@ export async function uniquePublicationMaterial(journal, prefix) {
37
38
  );
38
39
  return values[0];
39
40
  }
41
+
42
+ export async function completedPublicationRecovery(context, journal) {
43
+ if (!context.recovery?.preserveTransaction) return null;
44
+ const values = await journal.materials("publication/predecessor-complete/");
45
+ if (!values.length) return null;
46
+ const complete = values[0];
47
+ const retained = await uniquePublicationMaterial(
48
+ journal,
49
+ "publication/qualified/",
50
+ );
51
+ const receipt = complete.release?.receipt;
52
+ if (
53
+ values.length !== 1 ||
54
+ complete.schema !== "buildchain.pipeline-publication-complete/v1" ||
55
+ !Number.isFinite(Date.parse(complete.completedAt)) ||
56
+ receipt?.outcome !== "complete" ||
57
+ retained.qualified.planRoot !== context.plan.root ||
58
+ receipt.transactionRoot !==
59
+ retained.documents.transaction.transactionRoot ||
60
+ receipt.releasePassportRoot !== retained.documents.passport.passportRoot ||
61
+ recordDigest(createReleaseReceipt(receipt)) !==
62
+ recordDigest(complete.release)
63
+ )
64
+ throw new Error(
65
+ "Completed publication recovery changed its retained signed transaction",
66
+ );
67
+ return complete;
68
+ }
@@ -106,5 +106,17 @@ export function pipelinePublicationJournal(session, host) {
106
106
  });
107
107
  return value;
108
108
  }
109
- return { fence, materials, record };
109
+ async function recordImport(value, phase) {
110
+ publicationImportedValues(value);
111
+ const observed = await fence();
112
+ const id = "publication/recovery-import";
113
+ const eventKey = `publication:${id}:${recordDigest(value)}`;
114
+ const prior = observed.history
115
+ .at(-1)
116
+ .events.find((event) => event.payload.eventKey === eventKey);
117
+ // Import is one atomic append, not a new result for each resumed phase.
118
+ // Replay still verifies the original event, state and retained bytes.
119
+ return record(id, value, { phase: prior?.node || phase });
120
+ }
121
+ return { fence, materials, record, recordImport };
110
122
  }
@@ -5,6 +5,7 @@ import { githubPipelineVersion } from "../../providers/github/pipeline-version.j
5
5
  import { createPipelinePublicationPlan } from "./source-plan.js";
6
6
  import { preparePipelineStableQualification } from "./stable-wait.js";
7
7
  import { prepareRecoveredPublication } from "./recovery-prepare.js";
8
+ import { completedPublicationRecovery } from "./context.js";
8
9
  import {
9
10
  preparePipelineVersionContext,
10
11
  retainedPipelineVersionRegeneration,
@@ -48,12 +49,20 @@ export async function preparePipelinePublication(attempt, publisherSha, host) {
48
49
  );
49
50
  await journal.record("publication/plan", plan);
50
51
  }
51
- const wait = await preparePipelineStableQualification(plan, host, journal, {
52
- attempt,
53
- generation: observed.generation,
54
- phase,
55
- });
56
- if (wait) return { operation: "wait", wait };
52
+ // Native phase order admits follow-ups only after publication succeeded.
53
+ // Later PR events must not reopen the completed publication's Alpha gate.
54
+ const completed = await completedPublicationRecovery(
55
+ { plan, recovery },
56
+ journal,
57
+ );
58
+ if (phase === "publish" && !completed) {
59
+ const wait = await preparePipelineStableQualification(plan, host, journal, {
60
+ attempt,
61
+ generation: observed.generation,
62
+ phase,
63
+ });
64
+ if (wait) return { operation: "wait", wait };
65
+ }
57
66
  if (
58
67
  !recovery &&
59
68
  (plan.publisher.workflowSha !== publisherSha ||
@@ -76,5 +76,5 @@ export async function importRecoveredPublication(
76
76
  publicationImportedValues(bundle);
77
77
  // One journal append publishes the complete import and derived source/plan.
78
78
  // An interruption cannot expose a new plan beside an old materialization.
79
- await journal.record("publication/recovery-import", bundle, { phase });
79
+ await journal.recordImport(bundle, phase);
80
80
  }
@@ -68,7 +68,69 @@ export async function readRecoveryPublicationMaterials(session, host) {
68
68
  throw new Error(
69
69
  "Expanded publication recovery inventory exceeds its bound",
70
70
  );
71
- return result;
71
+ return signedRecoveryMaterials(result);
72
+ }
73
+
74
+ function signedRecoveryMaterials(materials) {
75
+ const plans = materials.filter((item) =>
76
+ item.id.startsWith("publication/plan/"),
77
+ );
78
+ if (new Set(plans.map((item) => item.value.root)).size < 2) return materials;
79
+ const retained = recoveryPublicationMaterial(
80
+ materials,
81
+ "publication/qualified/",
82
+ );
83
+ if (!retained?.signing) return materials;
84
+ const plan = publicationLineageMaterial(
85
+ materials,
86
+ "buildchain.pipeline-publication-plan/v1",
87
+ (value) => value.root === retained.qualified.planRoot,
88
+ );
89
+ const materialization = publicationLineageMaterial(
90
+ materials,
91
+ "buildchain.pipeline-version-materialization/v1",
92
+ (value) => value.root === retained.signing.materializationRoot,
93
+ );
94
+ const planIdentity = ({
95
+ root,
96
+ publisher: { workflowSha, ...publisher },
97
+ ...body
98
+ }) => recordDigest({ ...body, publisher });
99
+ const sourceIdentity = ({ root, planRoot, ...body }) => recordDigest(body);
100
+ for (const item of plans)
101
+ if (planIdentity(item.value) !== planIdentity(plan))
102
+ throw new Error(
103
+ "Conflicting recovery plans changed more than the publisher entry",
104
+ );
105
+ // Preserve every original reference and member ID. Only a signed lineage
106
+ // can classify the otherwise identical, unsigned rederivation as history.
107
+ return materials.map((item) => {
108
+ let name;
109
+ if (
110
+ item.id.startsWith("publication/plan/") &&
111
+ item.value.root !== plan.root
112
+ )
113
+ name = "plan";
114
+ if (
115
+ item.id.startsWith("publication/materialization/") &&
116
+ item.value.root !== materialization.root
117
+ ) {
118
+ if (sourceIdentity(item.value) !== sourceIdentity(materialization))
119
+ throw new Error(
120
+ "Conflicting recovery materializations changed retained source",
121
+ );
122
+ name = "materialization";
123
+ }
124
+ return name
125
+ ? {
126
+ ...item,
127
+ id: item.id.replace(
128
+ `publication/${name}/`,
129
+ `publication/predecessor-${name}/`,
130
+ ),
131
+ }
132
+ : item;
133
+ });
72
134
  }
73
135
 
74
136
  export function recoveryPublicationMaterial(
@@ -54,20 +54,32 @@ export async function prepareRecoveredPublication(
54
54
  tree: runtime.tree.sha,
55
55
  },
56
56
  };
57
- await importRecoveredPublication(
58
- publication,
59
- execution,
60
- recoveryPlan.root,
61
- journal,
62
- phase,
63
- );
57
+ const qualified = await journal.materials("publication/qualified/");
58
+ if (qualified.length > 1)
59
+ throw new Error("Recovered publication has conflicting qualifications");
60
+ if (qualified.length) {
61
+ const plans = await journal.materials("publication/plan/");
62
+ if (plans.length !== 1 || plans[0].root !== qualified[0].qualified.planRoot)
63
+ throw new Error("Recovered qualification lacks its one retained plan");
64
+ }
65
+ // A prepared recovery may already have signed and published its derived
66
+ // plan. Later workers retain that plan even when the floating entry moves.
67
+ if (!qualified.length)
68
+ await importRecoveredPublication(
69
+ publication,
70
+ execution,
71
+ recoveryPlan.root,
72
+ journal,
73
+ phase,
74
+ );
75
+ const mode = qualified.length ? "qualified" : publication.mode;
64
76
  return {
65
77
  planRoot: recoveryPlan.root,
66
78
  predecessor: recoveryPlan.predecessor,
67
79
  originalPlanRoot: plan.root,
68
- mode: publication.mode,
80
+ mode,
69
81
  build: publication.build || null,
70
- preserveTransaction: publication.mode === "qualified",
82
+ preserveTransaction: mode === "qualified",
71
83
  execution,
72
84
  };
73
85
  }
@@ -113,12 +113,21 @@ async function inspectEntryRun(listed, plan, source, host) {
113
113
  const { run, jobs } = await host.runs.read(listed.id, listed.run_attempt);
114
114
  const consumer = await pipelineEntryRun(run, plan, source, host);
115
115
  if (!consumer) return null;
116
+ const productJobs = jobs.filter((job) =>
117
+ /(?:^| \/ )Build product \([^)]+\)$/u.test(job.name),
118
+ );
119
+ const check = await pipelineEntryCheck(run, host);
120
+ // Controller-only PR notifications are not new product qualifications. A
121
+ // failed controller must not displace an earlier actual build. Conversely,
122
+ // any admitted native receipt or product job (even cancelled/skipped setup)
123
+ // keeps the execution in scope and must qualify its original full evidence.
116
124
  if (
117
- !jobs.some((job) => /(?:^| \/ )Build product \([^)]+\)$/u.test(job.name)) &&
118
- run.conclusion === "success"
125
+ !check &&
126
+ (!productJobs.length ||
127
+ (run.conclusion === "success" &&
128
+ productJobs.every((job) => job.conclusion === "skipped")))
119
129
  )
120
130
  return null;
121
- const check = await pipelineEntryCheck(run, host);
122
131
  if (!check)
123
132
  return {
124
133
  status: "missing",
@@ -17,7 +17,6 @@ function assertPublishedPlan(stablePlan, source, values) {
17
17
  const { plan, qualification: qualified, release } = values;
18
18
  verifyPipelinePublicationPlan(plan);
19
19
  if (
20
- plan.evidenceVersion !== 1 ||
21
20
  plan.channel !== "alpha" ||
22
21
  plan.version !== stablePlan.candidateVersion ||
23
22
  plan.tag !== source.candidate.tag ||
@@ -32,17 +31,6 @@ function assertPublishedPlan(stablePlan, source, values) {
32
31
  throw new Error(
33
32
  "Published product evidence differs from the exact Alpha contract or source",
34
33
  );
35
- if (
36
- plan.publisher.repository !== "kungfu-systems/buildchain" ||
37
- plan.publisher.workflow !==
38
- ".github/workflows/.release-pipeline-products.yml" ||
39
- plan.publisher.job !== "apply" ||
40
- !/^[0-9a-f]{40}$/u.test(plan.publisher.workflowSha || "") ||
41
- plan.runtime.repository !== "kungfu-systems/buildchain"
42
- )
43
- throw new Error(
44
- "Published product evidence requires the central publisher",
45
- );
46
34
  const issued = Date.parse(qualified.qualification?.issuedAt);
47
35
  if (
48
36
  !Number.isFinite(issued) ||
@@ -200,7 +188,11 @@ function completionTime(jobs, qualified, candidate) {
200
188
  return new Date(Math.max(...values)).toISOString();
201
189
  }
202
190
 
203
- function verifyPublishedDocuments(values, bundle, directory, host, execute) {
191
+ export function verifyPublishedDocuments(
192
+ values,
193
+ bundle,
194
+ { directory, token, execute },
195
+ ) {
204
196
  const {
205
197
  plan,
206
198
  qualification: qualified,
@@ -208,6 +200,18 @@ function verifyPublishedDocuments(values, bundle, directory, host, execute) {
208
200
  release,
209
201
  invocation,
210
202
  } = values;
203
+ if (
204
+ plan.evidenceVersion !== 1 ||
205
+ plan.publisher.repository !== "kungfu-systems/buildchain" ||
206
+ plan.publisher.workflow !==
207
+ ".github/workflows/.release-pipeline-products.yml" ||
208
+ plan.publisher.job !== "apply" ||
209
+ !/^[0-9a-f]{40}$/u.test(plan.publisher.workflowSha || "") ||
210
+ plan.runtime.repository !== "kungfu-systems/buildchain"
211
+ )
212
+ throw new Error(
213
+ "Published product evidence requires the central publisher",
214
+ );
211
215
  const materialization = release.versionMaterialization;
212
216
  const signing = verifyPipelineSigning({
213
217
  plan,
@@ -218,7 +222,7 @@ function verifyPublishedDocuments(values, bundle, directory, host, execute) {
218
222
  bundle,
219
223
  ),
220
224
  directory: path.join(directory, "signature"),
221
- token: host.token,
225
+ token,
222
226
  // Historical evidence is checked at issue time. It never supplies fresh
223
227
  // Stable publisher admission or extends the original receipt's lifetime.
224
228
  evaluatedAt: qualified.qualification.issuedAt,
@@ -274,13 +278,11 @@ export async function readPipelineStableProducts(
274
278
  path.join(fs.realpathSync(os.tmpdir()), "buildchain-stable-products-"),
275
279
  );
276
280
  try {
277
- const signing = verifyPublishedDocuments(
278
- evidence.values,
279
- evidence.bundle,
281
+ const signing = verifyPublishedDocuments(evidence.values, evidence.bundle, {
280
282
  directory,
281
- host,
283
+ token: host.token,
282
284
  execute,
283
- );
285
+ });
284
286
  await reobservePublicationBuild(qualified.build, qualified.source, host);
285
287
  const signer = await signingRun(plan, qualified, host);
286
288
  const completedAt = completionTime(
@@ -6,14 +6,18 @@ import { buildPipelineVersionMaterial } from "./version-build.js";
6
6
  import { qualifyPipelineVersionContext } from "./version-context.js";
7
7
  import { preparePipelinePublication } from "./prepare.js";
8
8
  import { settlePipelinePublication } from "./settle.js";
9
+ import {
10
+ readPipelineContext,
11
+ writePipelineContext,
12
+ } from "../../providers/github/pipeline-context-artifacts.js";
9
13
 
10
- const contextInput = (core) =>
11
- JSON.parse(core.getInput("context", { required: true }));
14
+ const contextInput = (core, env) =>
15
+ readPipelineContext(core.getInput("context", { required: true }), env);
12
16
  const directory = (env) =>
13
17
  path.join(env.GITHUB_WORKSPACE, ".buildchain/version-material");
14
18
 
15
19
  export async function buildPipelineVersionAction(core, env) {
16
- const context = contextInput(core);
20
+ const context = await contextInput(core, env);
17
21
  if (
18
22
  context.schema !== "buildchain.pipeline-version-context/v1" ||
19
23
  context.runId !== Number(env.GITHUB_RUN_ID) ||
@@ -36,7 +40,7 @@ export async function buildPipelineVersionAction(core, env) {
36
40
  }
37
41
 
38
42
  export async function materializePipelinePublicationAction(core, env) {
39
- const context = contextInput(core);
43
+ const context = await contextInput(core, env);
40
44
  const operation = core.getInput("operation", { required: true });
41
45
  let result = { operation, context };
42
46
  if (operation === "regenerate") {
@@ -58,7 +62,12 @@ export async function materializePipelinePublicationAction(core, env) {
58
62
  }
59
63
  core.setOutput("operation", result.operation);
60
64
  core.setOutput("stable-wait", result.wait ? JSON.stringify(result.wait) : "");
61
- core.setOutput("context", JSON.stringify(result.context || {}));
65
+ core.setOutput(
66
+ "context",
67
+ operation === "regenerate"
68
+ ? await writePipelineContext(result.context || {}, env)
69
+ : core.getInput("context", { required: true }),
70
+ );
62
71
  core.setOutput(
63
72
  "matrix",
64
73
  JSON.stringify({ include: result.context?.platforms || [] }),
@@ -66,7 +75,7 @@ export async function materializePipelinePublicationAction(core, env) {
66
75
  }
67
76
 
68
77
  export async function materializePipelineDevelopmentAction(core, env) {
69
- const context = contextInput(core);
78
+ const context = await contextInput(core, env);
70
79
  if (context.preparation.purpose !== "development")
71
80
  throw new Error(
72
81
  "Development materialization received a different version purpose",
@@ -9,8 +9,6 @@ export const PUBLICATION_REHEARSAL_AGENT_SECTION_START =
9
9
  "<!-- buildchain:publication-rehearsal:start -->";
10
10
  export const PUBLICATION_REHEARSAL_AGENT_SECTION_END =
11
11
  "<!-- buildchain:publication-rehearsal:end -->";
12
- export const PUBLICATION_REHEARSAL_WORKFLOW_PATH =
13
- ".github/workflows/publication-rehearsal.yml";
14
12
 
15
13
  export function publicationRehearsalToml() {
16
14
  return `[publication_rehearsal]\ncontract = ${JSON.stringify(PUBLICATION_REHEARSAL_CAPSULE_CONTRACT)}\nadr = ${JSON.stringify(RELEASE_LOCAL_CONSTRUCTIBILITY_ADR)}\ninvariant = ${JSON.stringify(RELEASE_LOCAL_CONSTRUCTIBILITY_INVARIANT)}\ncommand = ${JSON.stringify(PUBLICATION_REHEARSAL_COMMAND)}\ncapsule = ".buildchain/publication-rehearsal/capsule.json"\ncandidate_root = ".buildchain/publication-rehearsal/candidate"\nstate = ".buildchain/publication-rehearsal/state.json"\nevidence = ".buildchain/publication-rehearsal/evidence.json"\n`;
@@ -95,10 +93,3 @@ export function mergePublicationRehearsalAgentInstructions(current = "") {
95
93
  end + PUBLICATION_REHEARSAL_AGENT_SECTION_END.length,
96
94
  )}`;
97
95
  }
98
-
99
- export function publicationRehearsalWorkflow(buildchainRef) {
100
- const ref = String(buildchainRef || "").trim();
101
- if (!/^v4(?:-alpha)?$/u.test(ref))
102
- throw new Error("publication rehearsal workflow requires a floating public entry");
103
- return `name: Publication Rehearsal\n\non:\n workflow_dispatch:\n inputs:\n capsule-path:\n description: "Source-bound v4 publication rehearsal capsule"\n required: true\n default: ".buildchain/publication-rehearsal/capsule.json"\n candidate-root:\n description: "Repository-relative candidate root"\n required: true\n default: ".buildchain/publication-rehearsal/candidate"\n\npermissions:\n contents: read\n\njobs:\n rehearsal:\n uses: kungfu-systems/buildchain/.github/workflows/public-release-tail.yml@${ref}\n with:\n rehearsal-capsule-path: \${{ inputs.capsule-path }}\n candidate-root: \${{ inputs.candidate-root }}\n rehearsal-mode: simulate\n state-path: ".buildchain/publication-rehearsal/state.json"\n rehearsal-evidence-path: ".buildchain/publication-rehearsal/evidence.json"\n`;
104
- }
@@ -1,10 +1,7 @@
1
1
  import { runReleasePropagationCli } from "../commands/release-propagation.mjs";
2
2
  import { runReleaseTailCli } from "../commands/release-tail.mjs";
3
3
  import { runTailResealCli } from "../commands/tail-reseal.mjs";
4
- import {
5
- TRUST_RELEASE_COMMANDS,
6
- dispatchTrustReleaseCommand,
7
- } from "./trust-release.mjs";
4
+ import { dispatchTrustReleaseCommand } from "./trust-release.mjs";
8
5
  import { packageVersion } from "../../contracts/cli/context.mjs";
9
6
  import { runScript } from "../../workflow/cli/process.mjs";
10
7
 
@@ -1,44 +1,12 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { spawnSync } from "node:child_process";
4
- import { verifyInfraContractEvidenceBundle } from "../../providers/commands/infra-contract-core.mjs";
5
- import { verifyBuildchainLogEvents } from "../../observability/logging.js";
6
- import {
7
- explainReleaseLineDryRun,
8
- formatReleaseLineDryRun,
9
- } from "../release-line-dry-run.js";
10
- import {
11
- planReleaseLineBootstrap,
12
- writeReleaseLineBootstrapVersionState,
13
- } from "../release-line-bootstrap.js";
14
- import { collectGitHubReleasePassport } from "../passport/collection.js";
15
- import { explainReleasePassport, verifyReleasePassport } from "../release-passport.js";
16
- import {
17
- createPublicationAdmission,
18
- createRunnerProvenance,
19
- verifyPublicationAdmission,
20
- } from "../../publication/publication-authority.js";
21
- import {
22
- createGitHubArtifactAttestationPolicy,
23
- createGitHubArtifactAttestationVerificationPlan,
24
- verifyGitHubArtifactAttestationEvidence,
25
- } from "../../build/github-artifact-attestation.js";
1
+ import { explainReleasePassport } from "../release-passport.js";
26
2
  import {
27
3
  explainArtifactPassport,
28
4
  verifyArtifactPassport,
29
5
  } from "../../build/artifact-passport.js";
30
6
  import {
31
- projectArtifactVerificationEnvelopeToKfx,
32
- verifyArtifactVerificationEnvelope,
33
- } from "../../build/artifact-verification-envelope.js";
34
- import {
35
- artifactEnvelopeOptions,
36
7
  printJson,
37
8
  readBooleanFlag,
38
9
  readFlag,
39
- readJsonInput,
40
- readRepeatedFlag,
41
- writeJsonFile,
42
10
  } from "../../contracts/cli/options.mjs";
43
11
 
44
12
  async function handleAuditCommand({ args, runScript, packageVersion }) {
@@ -1,8 +1,4 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
- import { spawnSync } from "node:child_process";
4
- import { verifyInfraContractEvidenceBundle } from "../../providers/commands/infra-contract-core.mjs";
5
- import { verifyBuildchainLogEvents } from "../../observability/logging.js";
6
2
  import {
7
3
  explainReleaseLineDryRun,
8
4
  formatReleaseLineDryRun,
@@ -12,25 +8,12 @@ import {
12
8
  writeReleaseLineBootstrapVersionState,
13
9
  } from "../release-line-bootstrap.js";
14
10
  import { collectGitHubReleasePassport } from "../passport/collection.js";
15
- import { explainReleasePassport, verifyReleasePassport } from "../release-passport.js";
16
11
  import {
17
12
  createPublicationAdmission,
18
13
  createRunnerProvenance,
19
- verifyPublicationAdmission,
20
14
  } from "../../publication/publication-authority.js";
21
- import {
22
- createGitHubArtifactAttestationPolicy,
23
- createGitHubArtifactAttestationVerificationPlan,
24
- verifyGitHubArtifactAttestationEvidence,
25
- } from "../../build/github-artifact-attestation.js";
26
- import {
27
- explainArtifactPassport,
28
- verifyArtifactPassport,
29
- } from "../../build/artifact-passport.js";
30
- import {
31
- projectArtifactVerificationEnvelopeToKfx,
32
- verifyArtifactVerificationEnvelope,
33
- } from "../../build/artifact-verification-envelope.js";
15
+ import { createGitHubArtifactAttestationPolicy } from "../../build/github-artifact-attestation.js";
16
+ import { projectArtifactVerificationEnvelopeToKfx } from "../../build/artifact-verification-envelope.js";
34
17
  import {
35
18
  artifactEnvelopeOptions,
36
19
  printJson,