@kungfu-tech/buildchain 4.1.3 → 4.1.4

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 (199) 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 +31 -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/publication/authority/control-plane.js +2 -2
  142. package/packages/core/publication/authority/seal.js +1 -1
  143. package/packages/core/publication/cli/dispatch.mjs +1 -12
  144. package/packages/core/publication/pipeline/actions.js +16 -9
  145. package/packages/core/publication/pipeline/apply.js +17 -9
  146. package/packages/core/publication/pipeline/context.js +29 -0
  147. package/packages/core/publication/pipeline/prepare.js +15 -6
  148. package/packages/core/publication/pipeline/recovery-materials.js +63 -1
  149. package/packages/core/publication/pipeline/recovery-prepare.js +21 -9
  150. package/packages/core/publication/pipeline/stable-entry.js +12 -7
  151. package/packages/core/publication/pipeline/stable-products.js +21 -19
  152. package/packages/core/publication/pipeline/version-actions.js +15 -6
  153. package/packages/core/publication/publication-rehearsal-projection.js +0 -9
  154. package/packages/core/release/cli/dispatch.mjs +1 -4
  155. package/packages/core/release/cli/inspection-handlers.mjs +1 -33
  156. package/packages/core/release/cli/release-handlers.mjs +2 -19
  157. package/packages/core/release/cli/verification-handlers.mjs +4 -25
  158. package/packages/core/release/next-development-projection.js +13 -13
  159. package/packages/core/release/passport/locations.js +13 -8
  160. package/packages/core/release/release-passport.js +70 -5
  161. package/packages/core/web/cli/badges.mjs +0 -4
  162. package/packages/core/web/publication-admission.js +1 -1
  163. package/packages/core/web/publication-capability.js +1 -1
  164. package/packages/core/web/readme-badges.js +1 -1
  165. package/packages/core/workflow/cli/main.mjs +1 -4
  166. package/packages/core/workflow/cli/process.mjs +1 -1
  167. package/packages/core/workflow/commands/buildchain-cli-help.mjs +2 -8
  168. package/packages/core/workflow/engine/conformance.js +1 -1
  169. package/packages/core/workflow/pipeline/build.js +21 -1
  170. package/packages/core/workflow/pipeline/delivery-observation.js +6 -2
  171. package/packages/core/workflow/workflow-taxonomy.mjs +61 -17
  172. package/scripts/check-code-layout.mjs +2 -0
  173. package/scripts/check-core-mechanism-inventory.mjs +29 -13
  174. package/scripts/check-floating-consumer-policy-contract.mjs +3 -3
  175. package/scripts/check-inventory.mjs +27 -27
  176. package/scripts/check-maintainability.mjs +28 -5
  177. package/scripts/check-public-dogfood-contract.mjs +3 -3
  178. package/scripts/check-release-topology.mjs +1 -1
  179. package/scripts/check-self-consumer-contract.mjs +7 -7
  180. package/scripts/check-universal-workflow-bootstrap.mjs +1 -1
  181. package/scripts/generate-channel-promotion-workflow.mjs +1 -1
  182. package/scripts/generate-minimal-consumer-contract.mjs +21 -1
  183. package/scripts/generate-site-bundle.mjs +2 -1
  184. package/scripts/generate-tail-reseal-fixture.mjs +3 -3
  185. package/scripts/inventory/promotion.mjs +1 -1
  186. package/scripts/next-development-self-dogfood-harness.mjs +1 -1
  187. package/scripts/next-development-self-dogfood.mjs +4 -4
  188. package/scripts/site-capability-metadata.mjs +2 -6
  189. package/scripts/source-metric-lineage.mjs +62 -7
  190. package/scripts/verify-golden-path.mjs +31 -35
  191. package/templates/native-dev-delivery.yml +1 -1
  192. package/templates/universal-buildchain-bootstrap.yml +1 -1
  193. package/packages/core/paper/operations/bootstrap.js +0 -75
  194. package/packages/core/paper/operations/plans.js +0 -213
  195. package/packages/core/paper/operations/provisioning.js +0 -113
  196. package/packages/core/paper/operations/scaffold-config.js +0 -77
  197. package/packages/core/paper/operations/scaffold-workflows.js +0 -116
  198. package/packages/core/paper/operations/workspace.js +0 -60
  199. package/packages/core/paper/paper-npm-bootstrap.js +0 -492
@@ -1,14 +1,12 @@
1
1
  import path from "node:path";
2
2
  import { collectPaperAgentEntry } from "./paper-agent-entry.js";
3
3
  import {
4
- PAPER_PATHS,
5
4
  PAPER_WORK_BRANCH_PATTERN,
6
5
  gitResult,
7
6
  gitValue,
8
7
  normalizedWorkBranch,
9
8
  paperDevelopmentRef,
10
9
  paperWorkSource,
11
- readJson,
12
10
  remoteBranchObservation,
13
11
  rootedPlan,
14
12
  workCheck,
@@ -30,12 +28,7 @@ function failedActions(checks) {
30
28
  }
31
29
 
32
30
  function agentEntryForWork(cwd, buildchainSha) {
33
- const policy = readJson(path.resolve(cwd, PAPER_PATHS.agentEntry)).value;
34
- return collectPaperAgentEntry({
35
- cwd,
36
- buildchainSha: buildchainSha || policy?.runtime?.sourceSha || "",
37
- mode: "contract",
38
- });
31
+ return collectPaperAgentEntry({ cwd, buildchainSha, mode: "contract" });
39
32
  }
40
33
 
41
34
  export function createPaperWorkStartPlan({
@@ -74,7 +67,7 @@ export function createPaperWorkStartPlan({
74
67
  workCheck(
75
68
  "agent-entry.current",
76
69
  agentEntry.ok,
77
- "The mandatory Buildchain Paper agent-entry contract is current.",
70
+ "The Paper product configuration and shared consumer wiring are current.",
78
71
  "buildchain paper migrate --write --json",
79
72
  ),
80
73
  workCheck(
@@ -252,7 +245,7 @@ export function createPaperWorkSubmitPlan({
252
245
  workCheck(
253
246
  "agent-entry.current",
254
247
  agentEntry.ok,
255
- "The mandatory Buildchain Paper agent-entry contract is current.",
248
+ "The Paper product configuration and shared consumer wiring are current.",
256
249
  "buildchain paper migrate --write --json",
257
250
  ),
258
251
  workCheck(
@@ -1,3 +1,4 @@
1
+ import { paperConsumerStatus } from "./consumer-status.js";
1
2
  import path from "node:path";
2
3
  import {
3
4
  paperConfig,
@@ -44,15 +45,52 @@ import {
44
45
  paperPreflightNextActions,
45
46
  } from "./operations/preflight-checks.js";
46
47
  export function collectPaperSourcePolicy({ cwd = process.cwd() } = {}) {
48
+ const consumer = paperConsumerStatus(cwd);
49
+ if (consumer) return consumer;
47
50
  const agentEntry = collectPaperAgentEntry({ cwd, mode: "ci" });
48
51
  const provisioning = validatePaperProvisioningAuthority(cwd);
49
- const checks = [...agentEntry.checks, { id: "provisioning.authority", status: provisioning.valid ? "pass" : "fail", message: provisioning.errors.join("; ") }];
50
- try { validateBuildchainConfig(cwd, { requireLifecycleStages: ["verify"] }); }
51
- catch (error) { checks.push({ id: "config.publication", status: "fail", message: error.message }); }
52
- return { ok: checks.every(check => check.status === "pass" || check.status === "skip"), checks };
52
+ const checks = [
53
+ ...agentEntry.checks,
54
+ {
55
+ id: "provisioning.authority",
56
+ status: provisioning.valid ? "pass" : "fail",
57
+ message: provisioning.errors.join("; "),
58
+ },
59
+ ];
60
+ try {
61
+ validateBuildchainConfig(cwd, { requireLifecycleStages: ["verify"] });
62
+ } catch (error) {
63
+ checks.push({
64
+ id: "config.publication",
65
+ status: "fail",
66
+ message: error.message,
67
+ });
68
+ }
69
+ return {
70
+ ok: checks.every(
71
+ (check) => check.status === "pass" || check.status === "skip",
72
+ ),
73
+ checks,
74
+ };
75
+ }
76
+
77
+ export function collectPaperPreflight(options = {}) {
78
+ const consumer = paperConsumerStatus(
79
+ path.resolve(options.cwd || process.cwd()),
80
+ );
81
+ if (!consumer) return collectLegacyPaperPreflight(options);
82
+ const result =
83
+ options.agentEntryMode && options.agentEntryMode !== "contract"
84
+ ? collectPaperAgentEntry({
85
+ cwd: consumer.cwd,
86
+ mode: options.agentEntryMode,
87
+ env: options.env,
88
+ })
89
+ : consumer;
90
+ return { ...result, contract: PAPER_PREFLIGHT_CONTRACT };
53
91
  }
54
92
 
55
- export function collectPaperPreflight({
93
+ function collectLegacyPaperPreflight({
56
94
  cwd = process.cwd(),
57
95
  buildchainRoot = process.cwd(),
58
96
  buildchainVersion = "",
@@ -10,7 +10,7 @@ export function verifyArtifactAttestationSigner(request, execute = command) {
10
10
  "--repo",
11
11
  request.repository,
12
12
  "--signer-workflow",
13
- "kungfu-systems/buildchain/.github/workflows/public-release-artifact-attestation.yml",
13
+ "kungfu-systems/buildchain/.github/workflows/.release-artifact-attestation.yml",
14
14
  "--signer-digest",
15
15
  request.signerDigest,
16
16
  "--source-digest",
@@ -0,0 +1,172 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import artifact from "@actions/artifact";
4
+ import {
5
+ canonicalJson,
6
+ recordDigest,
7
+ } from "../../release/discussion/envelope.js";
8
+ import { publicationPath } from "../../publication/pipeline/files.js";
9
+
10
+ const INLINE_LIMIT = 32 * 1024;
11
+ const MATERIAL_LIMIT = 16 * 1024 * 1024;
12
+ const REFERENCE = "buildchain.pipeline-context-reference/v1";
13
+ const SCHEMAS = [
14
+ "buildchain.pipeline-publication-context/v1",
15
+ "buildchain.pipeline-version-context/v1",
16
+ ];
17
+ const DIGEST = /^sha256:[0-9a-f]{64}$/u;
18
+
19
+ function execution(env) {
20
+ const result = {
21
+ repository: env.GITHUB_REPOSITORY,
22
+ runId: Number(env.GITHUB_RUN_ID),
23
+ runAttempt: Number(env.GITHUB_RUN_ATTEMPT),
24
+ };
25
+ if (
26
+ !/^[\w.-]+\/[\w.-]+$/u.test(result.repository || "") ||
27
+ !Number.isSafeInteger(result.runId) ||
28
+ result.runId < 1 ||
29
+ !Number.isSafeInteger(result.runAttempt) ||
30
+ result.runAttempt < 1
31
+ )
32
+ throw new Error("Context transfer requires its exact provider execution");
33
+ return result;
34
+ }
35
+
36
+ function projection(value, env) {
37
+ const current = execution(env);
38
+ if (
39
+ !SCHEMAS.includes(value.schema) ||
40
+ value.runId !== current.runId ||
41
+ value.runAttempt !== current.runAttempt
42
+ )
43
+ throw new Error("Context transfer belongs to another provider execution");
44
+ const source =
45
+ value.schema === SCHEMAS[0]
46
+ ? value.materialization?.source
47
+ : value.preparation?.source;
48
+ if (
49
+ source?.repository !== current.repository ||
50
+ !/^[0-9a-f]{40}$/u.test(source?.commit || "")
51
+ )
52
+ throw new Error("Context transfer requires an exact repository source");
53
+ return {
54
+ schema: REFERENCE,
55
+ contextSchema: value.schema,
56
+ ...current,
57
+ contextRoot: recordDigest(value),
58
+ ...(value.schema === SCHEMAS[0]
59
+ ? { materialization: { source } }
60
+ : { preparation: { source } }),
61
+ };
62
+ }
63
+
64
+ function workspace(env) {
65
+ const directory = path.join(
66
+ env.GITHUB_WORKSPACE,
67
+ ".buildchain/context-material",
68
+ );
69
+ fs.mkdirSync(directory, { recursive: true });
70
+ return fs.mkdtempSync(path.join(directory, "transfer-"));
71
+ }
72
+
73
+ // Only an immutable reference and the checkout projection cross job outputs.
74
+ // The original context remains unchanged in the authoritative attempt journal.
75
+ export async function writePipelineContext(value, env, client = artifact) {
76
+ const serialized = canonicalJson(value);
77
+ const bytes = Buffer.byteLength(serialized);
78
+ if (bytes <= INLINE_LIMIT) return serialized;
79
+ if (bytes > MATERIAL_LIMIT)
80
+ throw new Error("Pipeline context exceeds its retained material bound");
81
+ const selected = projection(value, env);
82
+ const directory = workspace(env);
83
+ try {
84
+ const file = path.join(directory, "context.json");
85
+ fs.writeFileSync(file, serialized, { flag: "wx" });
86
+ const uploaded = await client.uploadArtifact(
87
+ `buildchain-context-${selected.runAttempt}-${selected.contextRoot.slice(7)}`,
88
+ [file],
89
+ directory,
90
+ { retentionDays: 30, compressionLevel: 0 },
91
+ );
92
+ const digest = `sha256:${String(uploaded.digest || "").replace(/^sha256:/u, "")}`;
93
+ if (
94
+ !Number.isSafeInteger(uploaded.id) ||
95
+ uploaded.id < 1 ||
96
+ !DIGEST.test(digest)
97
+ )
98
+ throw new Error("Context upload omitted immutable provider coordinates");
99
+ const encoded = canonicalJson({
100
+ ...selected,
101
+ bytes,
102
+ artifact: { id: uploaded.id, digest },
103
+ });
104
+ if (Buffer.byteLength(encoded) > INLINE_LIMIT)
105
+ throw new Error(
106
+ "Context checkout projection exceeds the job output bound",
107
+ );
108
+ return encoded;
109
+ } finally {
110
+ fs.rmSync(directory, { recursive: true, force: true });
111
+ }
112
+ }
113
+
114
+ function validateReference(value, env) {
115
+ const current = execution(env);
116
+ if (
117
+ !SCHEMAS.includes(value.contextSchema) ||
118
+ value.repository !== current.repository ||
119
+ value.runId !== current.runId ||
120
+ value.runAttempt !== current.runAttempt ||
121
+ !DIGEST.test(value.contextRoot || "") ||
122
+ !Number.isSafeInteger(value.bytes) ||
123
+ value.bytes <= INLINE_LIMIT ||
124
+ value.bytes > MATERIAL_LIMIT ||
125
+ !Number.isSafeInteger(value.artifact?.id) ||
126
+ value.artifact.id < 1 ||
127
+ !DIGEST.test(value.artifact?.digest || "")
128
+ )
129
+ throw new Error(
130
+ "Context reference changed its execution or material boundary",
131
+ );
132
+ }
133
+
134
+ export async function readPipelineContext(encoded, env, client = artifact) {
135
+ if (Buffer.byteLength(encoded) > INLINE_LIMIT)
136
+ throw new Error("Pipeline context must use bounded job output transport");
137
+ const reference = JSON.parse(encoded);
138
+ if (reference.schema !== REFERENCE) return reference;
139
+ validateReference(reference, env);
140
+ const directory = workspace(env);
141
+ try {
142
+ // No findBy or repository token: the artifact SDK scopes this read to the
143
+ // current workflow execution, including credentialless product build jobs.
144
+ const downloaded = await client.downloadArtifact(reference.artifact.id, {
145
+ path: directory,
146
+ expectedHash: reference.artifact.digest,
147
+ });
148
+ if (
149
+ downloaded.digestMismatch ||
150
+ canonicalJson(fs.readdirSync(directory)) !== '["context.json"]'
151
+ )
152
+ throw new Error("Context artifact changed its archive or file inventory");
153
+ const file = publicationPath(directory, "context.json");
154
+ if (fs.statSync(file).size !== reference.bytes)
155
+ throw new Error("Context artifact changed its retained byte length");
156
+ const value = JSON.parse(
157
+ new TextDecoder("utf-8", { fatal: true }).decode(fs.readFileSync(file)),
158
+ );
159
+ const expected = {
160
+ ...projection(value, env),
161
+ bytes: reference.bytes,
162
+ artifact: reference.artifact,
163
+ };
164
+ if (recordDigest(expected) !== recordDigest(reference))
165
+ throw new Error(
166
+ "Context artifact differs from its exact checkout projection or root",
167
+ );
168
+ return value;
169
+ } finally {
170
+ fs.rmSync(directory, { recursive: true, force: true });
171
+ }
172
+ }
@@ -1,8 +1,8 @@
1
1
  export function publicationControlPlaneRequest(request) {
2
2
  const kind = request.autoAdmissionKind;
3
3
  const defaults = {
4
- "release-candidate": ".github/workflows/public-release-promote.yml",
5
- "publication-artifact": ".github/workflows/public-release-paper.yml",
4
+ "release-candidate": ".github/workflows/.release-candidate-promote.yml",
5
+ "publication-artifact": ".github/workflows/.release-paper.yml",
6
6
  "binary-release-assets": ".github/workflows/.release-binary-assets.yml",
7
7
  };
8
8
  if (!defaults[kind])
@@ -26,7 +26,7 @@ export function sealManagedPublicationAdmission({
26
26
  );
27
27
  const suffix = binary ? ":binary-release-assets" : paper ? ":paper" : "";
28
28
  const defaults = {
29
- "release-candidate": ".github/workflows/public-release-promote.yml",
29
+ "release-candidate": ".github/workflows/.release-candidate-promote.yml",
30
30
  "publication-artifact": ".github/workflows/public-release-paper-sealed.yml",
31
31
  "binary-release-assets": ".github/workflows/.release-binary-assets.yml",
32
32
  };
@@ -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
+ }
@@ -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 ||
@@ -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
  }