@kungfu-tech/buildchain 4.1.3-alpha.1 → 4.1.3-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/architecture/action-taxonomy.json +24 -0
  2. package/architecture/agent-change-map.md +256 -2
  3. package/architecture/ci-lane-change-budget.json +374 -0
  4. package/architecture/decisions/0005-minimal-consumer-contract.md +122 -0
  5. package/architecture/decisions/0006-business-attempt-journal.md +89 -0
  6. package/architecture/decisions/0007-hosted-pipeline-controller.md +106 -0
  7. package/architecture/decisions/0008-pipeline-product-publication.md +99 -0
  8. package/architecture/decisions/0009-pipeline-version-preparation.md +186 -0
  9. package/architecture/internal-capabilities.json +361 -2
  10. package/architecture/maintainability-debt.json +6 -4
  11. package/architecture/maintainability-policy.json +8 -8
  12. package/architecture/minimal-consumer-migration.json +7567 -0
  13. package/architecture/product-upstreams.json +20 -0
  14. package/architecture/release-topology.json +282 -11
  15. package/architecture/universal-workflow-bootstrap.json +12 -0
  16. package/architecture/universal-workflow-capability-policy.json +1 -1
  17. package/architecture/workflow-taxonomy.json +66 -0
  18. package/dist/readers/business-attempt.cjs +809 -0
  19. package/dist/site/buildchain-contract.json +7 -5
  20. package/dist/site/buildchain-site.json +18 -13
  21. package/dist/site/capability-registry.json +3 -3
  22. package/dist/site/kfd-claims.json +385 -10
  23. package/dist/site/kfd-upstream-aggregate.json +1 -1
  24. package/dist/site/manual-registry.json +1 -1
  25. package/dist/site/node-api-registry.json +21 -21
  26. package/dist/site/page-registry.json +13 -8
  27. package/dist/site/public-surface-audit.json +258 -7
  28. package/dist/site/publication-authority-registry.json +141 -1
  29. package/dist/site/publication-registry.json +4 -4
  30. package/dist/site/site-manifest.json +5 -5
  31. package/dist/site/workflow-registry.json +522 -7
  32. package/docs/node-api-reference.md +39 -39
  33. package/docs/runtime-entry.md +45 -5
  34. package/docs/workflow-catalog.md +6 -0
  35. package/package.json +4 -3
  36. package/packages/core/build/standalone/build.js +7 -1
  37. package/packages/core/consumer/buildchain-config.js +2 -21
  38. package/packages/core/consumer/contract/entries.js +85 -0
  39. package/packages/core/consumer/contract/examples.js +96 -0
  40. package/packages/core/consumer/contract/identity.js +65 -0
  41. package/packages/core/consumer/contract/inspection.js +45 -0
  42. package/packages/core/consumer/contract/plan.js +146 -0
  43. package/packages/core/consumer/contract/products.js +119 -0
  44. package/packages/core/consumer/contract/reader.js +47 -0
  45. package/packages/core/consumer/contract/shape.js +46 -0
  46. package/packages/core/dev-delivery/candidate/admission.js +9 -5
  47. package/packages/core/dev-delivery/candidate/reservation.js +11 -0
  48. package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
  49. package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
  50. package/packages/core/dev-delivery/native/actions.js +7 -1
  51. package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
  52. package/packages/core/dev-delivery/native/heartbeat.js +6 -4
  53. package/packages/core/dev-delivery/queue/landing-action.js +5 -0
  54. package/packages/core/governance/buildchain-publication-authority.js +6 -0
  55. package/packages/core/paper/operations/bootstrap.js +2 -0
  56. package/packages/core/paper/paper-npm-bootstrap.js +2 -2
  57. package/packages/core/providers/github/attempt-index.js +60 -0
  58. package/packages/core/providers/github/attempt-journal.js +154 -0
  59. package/packages/core/providers/github/discussions/materials.js +7 -2
  60. package/packages/core/providers/github/pipeline-checkout.js +49 -0
  61. package/packages/core/providers/github/pipeline-entry-runs.js +203 -0
  62. package/packages/core/providers/github/pipeline-events.js +52 -0
  63. package/packages/core/providers/github/pipeline-integration.js +95 -0
  64. package/packages/core/providers/github/pipeline-policy.js +213 -0
  65. package/packages/core/providers/github/pipeline-product-payload.js +50 -0
  66. package/packages/core/providers/github/pipeline-product-release.js +191 -0
  67. package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
  68. package/packages/core/providers/github/pipeline-release-evidence.js +111 -0
  69. package/packages/core/providers/github/pipeline-run-entry.js +86 -0
  70. package/packages/core/providers/github/pipeline-runs.js +132 -0
  71. package/packages/core/providers/github/pipeline-source.js +178 -0
  72. package/packages/core/providers/github/pipeline-stable-source.js +354 -0
  73. package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
  74. package/packages/core/providers/github/pipeline-version-readback.js +117 -0
  75. package/packages/core/providers/github/pipeline-version.js +199 -0
  76. package/packages/core/providers/github/pipeline-worker.js +108 -0
  77. package/packages/core/publication/candidate/registry-hydration.js +2 -2
  78. package/packages/core/publication/npm/pack-preview.js +2 -2
  79. package/packages/core/publication/npm/pack-result.js +31 -0
  80. package/packages/core/publication/npm/package.js +3 -6
  81. package/packages/core/publication/npm/pipeline-channel.js +87 -0
  82. package/packages/core/publication/npm/pipeline-provider.js +109 -0
  83. package/packages/core/publication/npm/registry.js +12 -9
  84. package/packages/core/publication/pipeline/actions.js +116 -0
  85. package/packages/core/publication/pipeline/apply.js +188 -0
  86. package/packages/core/publication/pipeline/build-segments.js +107 -0
  87. package/packages/core/publication/pipeline/capsules.js +89 -0
  88. package/packages/core/publication/pipeline/context.js +39 -0
  89. package/packages/core/publication/pipeline/development-anchor.js +91 -0
  90. package/packages/core/publication/pipeline/development-current.js +63 -0
  91. package/packages/core/publication/pipeline/development-pr.js +112 -0
  92. package/packages/core/publication/pipeline/development-proof.js +76 -0
  93. package/packages/core/publication/pipeline/development-transition.js +112 -0
  94. package/packages/core/publication/pipeline/distribution.js +124 -0
  95. package/packages/core/publication/pipeline/documents.js +192 -0
  96. package/packages/core/publication/pipeline/effects.js +162 -0
  97. package/packages/core/publication/pipeline/files.js +66 -0
  98. package/packages/core/publication/pipeline/imported-materials.js +32 -0
  99. package/packages/core/publication/pipeline/journal.js +110 -0
  100. package/packages/core/publication/pipeline/next-development.js +117 -0
  101. package/packages/core/publication/pipeline/pack.js +157 -0
  102. package/packages/core/publication/pipeline/package-policy.js +22 -0
  103. package/packages/core/publication/pipeline/plan.js +136 -0
  104. package/packages/core/publication/pipeline/prepare.js +139 -0
  105. package/packages/core/publication/pipeline/qualification.js +175 -0
  106. package/packages/core/publication/pipeline/qualify.js +104 -0
  107. package/packages/core/publication/pipeline/recovery-admission.js +122 -0
  108. package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
  109. package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
  110. package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
  111. package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
  112. package/packages/core/publication/pipeline/recovery-import.js +80 -0
  113. package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
  114. package/packages/core/publication/pipeline/recovery-materials.js +104 -0
  115. package/packages/core/publication/pipeline/recovery-plan.js +46 -0
  116. package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
  117. package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
  118. package/packages/core/publication/pipeline/recovery-readback.js +56 -0
  119. package/packages/core/publication/pipeline/recovery-signing.js +58 -0
  120. package/packages/core/publication/pipeline/sealed-products.js +79 -0
  121. package/packages/core/publication/pipeline/settle.js +43 -0
  122. package/packages/core/publication/pipeline/signing.js +140 -0
  123. package/packages/core/publication/pipeline/source-plan.js +83 -0
  124. package/packages/core/publication/pipeline/stable-entry.js +215 -0
  125. package/packages/core/publication/pipeline/stable-products.js +317 -0
  126. package/packages/core/publication/pipeline/stable-wait.js +170 -0
  127. package/packages/core/publication/pipeline/stable.js +82 -0
  128. package/packages/core/publication/pipeline/version-actions.js +93 -0
  129. package/packages/core/publication/pipeline/version-build.js +130 -0
  130. package/packages/core/publication/pipeline/version-context.js +143 -0
  131. package/packages/core/publication/pipeline/version-material.js +36 -0
  132. package/packages/core/publication/pipeline/version-preparation.js +69 -0
  133. package/packages/core/publication/pipeline/version-regeneration.js +162 -0
  134. package/packages/core/publication/pipeline/version-source-guard.js +69 -0
  135. package/packages/core/publication/pipeline/version.js +81 -0
  136. package/packages/core/publication/pipeline/worker.js +72 -0
  137. package/packages/core/publication/publication-reproducibility.js +2 -2
  138. package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
  139. package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
  140. package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
  141. package/packages/core/release/stable-release-gate.js +11 -0
  142. package/packages/core/runtime/buildchain-domain.wasm +0 -0
  143. package/packages/core/runtime/domain-wasm-artifact.js +2 -2
  144. package/packages/core/runtime/entry/actions.js +27 -25
  145. package/packages/core/runtime/entry/attempt.js +25 -0
  146. package/packages/core/runtime/entry/selection.js +14 -0
  147. package/packages/core/runtime/entry/source.js +62 -0
  148. package/packages/core/workflow/attempt/identity.js +170 -0
  149. package/packages/core/workflow/attempt/journal.js +87 -0
  150. package/packages/core/workflow/attempt/materials.js +72 -0
  151. package/packages/core/workflow/attempt/reader-entry.js +12 -0
  152. package/packages/core/workflow/attempt/reader.js +149 -0
  153. package/packages/core/workflow/attempt/records.js +153 -0
  154. package/packages/core/workflow/attempt/store.js +75 -0
  155. package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
  156. package/packages/core/workflow/pipeline/action-output.js +11 -0
  157. package/packages/core/workflow/pipeline/actions.js +75 -0
  158. package/packages/core/workflow/pipeline/build-control.js +140 -0
  159. package/packages/core/workflow/pipeline/build-evidence.js +92 -0
  160. package/packages/core/workflow/pipeline/build-qualification.js +145 -0
  161. package/packages/core/workflow/pipeline/build-result.js +69 -0
  162. package/packages/core/workflow/pipeline/build.js +142 -0
  163. package/packages/core/workflow/pipeline/cancellation.js +169 -0
  164. package/packages/core/workflow/pipeline/channel-control.js +127 -0
  165. package/packages/core/workflow/pipeline/controller.js +133 -0
  166. package/packages/core/workflow/pipeline/delivery-control.js +204 -0
  167. package/packages/core/workflow/pipeline/delivery-observation.js +118 -0
  168. package/packages/core/workflow/pipeline/delivery-request.js +198 -0
  169. package/packages/core/workflow/pipeline/events.js +84 -0
  170. package/packages/core/workflow/pipeline/fence.js +42 -0
  171. package/packages/core/workflow/pipeline/group-control.js +56 -0
  172. package/packages/core/workflow/pipeline/guard-build.js +37 -0
  173. package/packages/core/workflow/pipeline/guard.js +156 -0
  174. package/packages/core/workflow/pipeline/host.js +114 -0
  175. package/packages/core/workflow/pipeline/materials.js +58 -0
  176. package/packages/core/workflow/pipeline/notifications.js +36 -0
  177. package/packages/core/workflow/pipeline/parent-notification.js +27 -0
  178. package/packages/core/workflow/pipeline/platforms.js +21 -0
  179. package/packages/core/workflow/pipeline/progress.js +108 -0
  180. package/packages/core/workflow/pipeline/projection.js +20 -0
  181. package/packages/core/workflow/pipeline/reconcile.js +194 -0
  182. package/packages/core/workflow/pipeline/recovery-action.js +30 -0
  183. package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
  184. package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
  185. package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
  186. package/packages/core/workflow/pipeline/recovery-build.js +121 -0
  187. package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
  188. package/packages/core/workflow/pipeline/recovery-integration.js +66 -0
  189. package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
  190. package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
  191. package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
  192. package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
  193. package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
  194. package/packages/core/workflow/pipeline/recovery-session.js +129 -0
  195. package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
  196. package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
  197. package/packages/core/workflow/pipeline/runtime-source.js +111 -0
  198. package/packages/core/workflow/pipeline/selection.js +106 -0
  199. package/packages/core/workflow/pipeline/session.js +72 -0
  200. package/packages/core/workflow/pipeline/settlement.js +137 -0
  201. package/packages/core/workflow/pipeline/wake.js +38 -0
  202. package/packages/core/workflow/pipeline/web-status.js +108 -0
  203. package/scripts/build-release-discussion-reader.mjs +11 -7
  204. package/scripts/check-pipeline-publication-topology.mjs +48 -0
  205. package/scripts/check-release-topology.mjs +7 -1
  206. package/scripts/generate-minimal-consumer-contract.mjs +155 -0
  207. package/scripts/generate-site-bundle.mjs +1 -1
  208. package/scripts/verify-golden-path.mjs +5 -1
  209. package/scripts/verify-standalone-product.mjs +42 -0
  210. package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
  211. package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
  212. package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
  213. package/templates/minimal-consumer/binary/package.json +6 -0
  214. package/templates/minimal-consumer/binary/src/build.mjs +5 -0
  215. package/templates/minimal-consumer/binary/src/hello.c +2 -0
  216. package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
  217. package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
  218. package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
  219. package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
  220. package/templates/minimal-consumer/npm/package.json +6 -0
  221. package/templates/minimal-consumer/npm/src/build.mjs +5 -0
  222. package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
  223. package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
  224. package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
  225. package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
  226. package/templates/minimal-consumer/paper/package.json +6 -0
  227. package/templates/minimal-consumer/paper/src/build.mjs +12 -0
  228. package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
@@ -0,0 +1,118 @@
1
+ import { GitHubDevDeliveryStore } from "../../providers/dev-delivery/store.js";
2
+ import { defaultDevDeliveryStateRef } from "../../dev-delivery/warrant/values.js";
3
+ import { createDeliveryWarrantService } from "../../dev-delivery/warrant/service.js";
4
+ import { PIPELINE_WORKER } from "../../providers/github/pipeline-worker.js";
5
+ import { pipelineCandidate } from "./reconcile.js";
6
+
7
+ function workerJob(jobs, name) {
8
+ const selected = jobs.filter(
9
+ (job) => job.name === name || job.name.endsWith(` / ${name}`),
10
+ );
11
+ if (selected.length > 1)
12
+ throw new Error("Native worker job identity is ambiguous");
13
+ return selected[0];
14
+ }
15
+
16
+ export async function observePipelineWorker(session, current, queue, host) {
17
+ const warrant = queue.activeWarrant;
18
+ const candidate = pipelineCandidate(queue, current);
19
+ if (
20
+ !warrant ||
21
+ candidate?.candidateId !== warrant.candidateId ||
22
+ warrant.sourceHead !== current.generation.source.commit ||
23
+ warrant.pullRequestNumber !== current.intent.source.pullRequest
24
+ )
25
+ return null;
26
+ const phase = [...current.events]
27
+ .reverse()
28
+ .find((event) =>
29
+ event.payload.materials.some((material) =>
30
+ material.id.startsWith("delivery/execution-"),
31
+ ),
32
+ );
33
+ const references =
34
+ phase?.payload.materials.filter((material) =>
35
+ material.id.startsWith("delivery/execution-"),
36
+ ) || [];
37
+ if (!references.length) return null;
38
+ if (references.length !== 1)
39
+ throw new Error("Native worker requires one admitted execution");
40
+ const execution = await host.materialStore(session).read(references[0]);
41
+ if (
42
+ execution.attempt !== current.identity.id ||
43
+ execution.generation !== current.generation.id
44
+ )
45
+ throw new Error("Native worker execution belongs to another attempt");
46
+ const { run, jobs } = await host.runs.read(
47
+ execution.runId,
48
+ execution.runAttempt,
49
+ );
50
+ if (run.status !== "completed") {
51
+ const terminalJobs = [
52
+ "Credentialless native execution",
53
+ "Credentialless native evidence seal",
54
+ ].map((name) => workerJob(jobs, name));
55
+ if (terminalJobs.some((job) => !job || job.status !== "completed"))
56
+ return {
57
+ status: "in_progress",
58
+ reason: "admitted-execution-not-terminal",
59
+ run,
60
+ };
61
+ }
62
+ const entries = (run.referenced_workflows || []).filter((entry) =>
63
+ entry.path?.startsWith(
64
+ "kungfu-systems/buildchain/.github/workflows/public-ops-dev-auto-merge.yml@",
65
+ ),
66
+ );
67
+ if (entries.length !== 1) return null;
68
+ const native = workerJob(jobs, "Credentialless native execution"),
69
+ seal = workerJob(jobs, "Credentialless native evidence seal");
70
+ if (!native || !seal) return null;
71
+ if (
72
+ run.status !== "completed" &&
73
+ run.id !== host.runId &&
74
+ native.status === "completed" &&
75
+ seal.status === "completed"
76
+ )
77
+ await host.runs.completed(execution.runId, execution.runAttempt);
78
+ return host.workers.observe(
79
+ {
80
+ schema: PIPELINE_WORKER,
81
+ repository: host.repository,
82
+ attempt: current.identity.id,
83
+ generation: current.generation.id,
84
+ sourceHead: warrant.sourceHead,
85
+ candidateId: warrant.candidateId,
86
+ fencingToken: warrant.fencingToken,
87
+ leaseGeneration: warrant.generation,
88
+ runId: execution.runId,
89
+ runAttempt: execution.runAttempt,
90
+ nativeJobId: native.id,
91
+ sealJobId: seal.id,
92
+ },
93
+ current,
94
+ warrant,
95
+ );
96
+ }
97
+
98
+ export function pipelineDeliveryStore(session, host) {
99
+ const branch = session.intent.source.targetBranch;
100
+ const store = new GitHubDevDeliveryStore({
101
+ repository: host.repository,
102
+ token: host.token,
103
+ });
104
+ return {
105
+ read: async () =>
106
+ (
107
+ await store.read({
108
+ stateRef: defaultDevDeliveryStateRef(branch),
109
+ protectedBase: branch,
110
+ now: new Date().toISOString(),
111
+ })
112
+ ).queue,
113
+ service: createDeliveryWarrantService(
114
+ { repository: host.repository, token: host.token, branch },
115
+ store,
116
+ ),
117
+ };
118
+ }
@@ -0,0 +1,198 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { recordDigest } from "../../release/discussion/envelope.js";
3
+ import { sourceQualificationPredicates } from "../../dev-delivery/source-proof/predicates.js";
4
+ import { createNativeCommandContract } from "../../dev-delivery/dev-delivery-warrant.js";
5
+ import { withPipelineSourceObjects } from "../../providers/github/pipeline-checkout.js";
6
+ import { pipelinePlatforms } from "./platforms.js";
7
+ import { pipelineCandidateRoot } from "./reconcile.js";
8
+ import { consumerWorkflows } from "../../consumer/contract/entries.js";
9
+ import { pipelineRunEntry } from "../../providers/github/pipeline-run-entry.js";
10
+ import {
11
+ PIPELINE_BUILD_QUALIFICATION,
12
+ qualifyPipelineBuild,
13
+ } from "./build-qualification.js";
14
+
15
+ const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
16
+
17
+ export function qualifyPipelineSourceRun(run, current, build) {
18
+ const source = current.generation.source;
19
+ const pull = run.pull_requests?.find(
20
+ (pr) => pr.number === current.intent.source.pullRequest,
21
+ );
22
+ if (
23
+ !Number.isSafeInteger(run.id) ||
24
+ run.id < 1 ||
25
+ run.status !== "completed" ||
26
+ run.conclusion !== "success" ||
27
+ ![
28
+ "pull_request",
29
+ "repository_dispatch",
30
+ "pull_request_review",
31
+ "workflow_dispatch",
32
+ ].includes(run.event) ||
33
+ run.repository?.full_name !== current.intent.repository ||
34
+ (run.event === "pull_request" && run.head_sha !== source.commit) ||
35
+ (run.event === "pull_request" &&
36
+ pull?.base?.sha !== current.generation.baseCommit)
37
+ )
38
+ throw new Error(
39
+ "Pipeline source run does not qualify the exact PR generation",
40
+ );
41
+ if (build?.schema === PIPELINE_BUILD_QUALIFICATION) {
42
+ const verified = qualifyPipelineBuild({ ...build, source });
43
+ if (
44
+ recordDigest(verified) !== recordDigest(build) ||
45
+ build.runId !== run.id ||
46
+ build.runAttempt !== run.run_attempt ||
47
+ build.outcome !== "success"
48
+ )
49
+ throw new Error(
50
+ "Recovered source run does not bind complete independently qualified product segments",
51
+ );
52
+ } else if (run.event !== "pull_request") {
53
+ const { root, ...body } = build || {};
54
+ if (
55
+ root !== recordDigest(body) ||
56
+ body.schema !== "buildchain.pipeline-build-readback/v1" ||
57
+ body.runId !== run.id ||
58
+ body.runAttempt !== run.run_attempt ||
59
+ body.outcome !== "success" ||
60
+ recordDigest(body.source) !== recordDigest(source)
61
+ )
62
+ throw new Error(
63
+ "Attempt wake requires retained exact product build provider readback",
64
+ );
65
+ }
66
+ pipelineEntry(run);
67
+ return run;
68
+ }
69
+
70
+ function pipelineEntry(run) {
71
+ return pipelineRunEntry(run).channel;
72
+ }
73
+
74
+ function predicateInput(directory, current, run) {
75
+ const source = current.generation.source;
76
+ for (const [file, expected] of Object.entries(
77
+ consumerWorkflows(pipelineEntry(run), source.configPath),
78
+ )) {
79
+ const bytes = execFileSync(
80
+ "git",
81
+ ["-C", directory, "show", `${source.commit}:${file}`],
82
+ { encoding: "utf8", maxBuffer: 1024 * 1024 },
83
+ );
84
+ if (bytes !== expected)
85
+ throw new Error(
86
+ "Qualified source changed the minimal consumer workflow contract",
87
+ );
88
+ }
89
+ const paths = execFileSync(
90
+ "git",
91
+ ["-C", directory, "ls-tree", "-r", "--name-only", "-z", source.commit],
92
+ { maxBuffer: 32 * 1024 * 1024 },
93
+ )
94
+ .toString("utf8")
95
+ .split("\0")
96
+ .filter(Boolean)
97
+ .sort();
98
+ if (
99
+ !paths.length ||
100
+ paths.length > 50_000 ||
101
+ paths.some((file) => /[\r\n]/u.test(file))
102
+ )
103
+ throw new Error(
104
+ "Pipeline source inventory exceeds its qualification contract",
105
+ );
106
+ const policyPaths = paths.filter(
107
+ (file) =>
108
+ file === source.configPath || file.startsWith(".github/workflows/"),
109
+ );
110
+ const dependencies = paths.filter((file) =>
111
+ /(?:^|\/)(?:package(?:-lock)?\.json|pnpm-lock\.yaml|yarn\.lock|Cargo\.(?:toml|lock)|go\.(?:mod|sum))$/u.test(
112
+ file,
113
+ ),
114
+ );
115
+ return {
116
+ cwd: directory,
117
+ repository: current.intent.repository,
118
+ protectedBase: current.intent.source.targetBranch,
119
+ sourceHead: source.commit,
120
+ qualifiedBase: current.generation.baseCommit,
121
+ nodeVersion: "24",
122
+ workingDirectory: ".",
123
+ policyPaths: JSON.stringify(policyPaths),
124
+ closurePaths: JSON.stringify(paths),
125
+ dependencyPaths: JSON.stringify(
126
+ dependencies.length ? dependencies : policyPaths,
127
+ ),
128
+ requiredContexts: JSON.stringify(["check"]),
129
+ };
130
+ }
131
+
132
+ export async function preparePipelineDelivery(
133
+ { current, run, build, runtimeSha, token, plan },
134
+ withObjects = withPipelineSourceObjects,
135
+ ) {
136
+ qualifyPipelineSourceRun(run, current, build);
137
+ if (!/^[0-9a-f]{40}$/u.test(runtimeSha || ""))
138
+ throw new Error("Pipeline delivery requires the prepared exact runtime");
139
+ const source = current.generation.source;
140
+ const platforms = plan
141
+ ? pipelinePlatforms(plan).map((entry) => entry.platform)
142
+ : ["linux-x64"];
143
+ const nativePlatform = platforms.includes("linux-x64")
144
+ ? "linux-x64"
145
+ : platforms[0];
146
+ const predicates = await withObjects(
147
+ {
148
+ repository: current.intent.repository,
149
+ sourceHead: source.commit,
150
+ baseCommit: current.generation.baseCommit,
151
+ token,
152
+ },
153
+ (directory) =>
154
+ sourceQualificationPredicates(predicateInput(directory, current, run)),
155
+ );
156
+ // Native composition owns .buildchain/candidate beside the prepared runtime.
157
+ // Do not bind one job's absolute workspace path into another runner's command.
158
+ const nativeCommand = `node ../runtime/packages/core/workflow/commands/pipeline-build.mjs --platform ${nativePlatform} --config-path ${quote(source.configPath)}`;
159
+ const environmentRoot = recordDigest({
160
+ schema: "buildchain.pipeline-native-environment/v1",
161
+ runtimeSha,
162
+ node: "24",
163
+ platform: nativePlatform,
164
+ sourceContract: source.contract,
165
+ });
166
+ return {
167
+ "target-branch": current.intent.source.targetBranch,
168
+ "expected-pr-number": current.intent.source.pullRequest,
169
+ "expected-head-sha": source.commit,
170
+ "source-workflow-run-id": run.id,
171
+ "pipeline-attempt": current.identity.id,
172
+ "delivery-warrant-mode": "required",
173
+ "delivery-class": "native-proof-required",
174
+ "source-root": pipelineCandidateRoot(current),
175
+ "source-identity-root": predicates.sourceIdentityRoot,
176
+ "source-patch-root": predicates.sourcePatchRoot,
177
+ "plan-root": predicates.planRoot,
178
+ "closure-root": predicates.closureRoot,
179
+ "dependency-root": predicates.dependencyRoot,
180
+ "toolchain-root": predicates.toolchainRoot,
181
+ "environment-root": environmentRoot,
182
+ "native-command": nativeCommand,
183
+ "native-platform": nativePlatform,
184
+ "native-command-root":
185
+ createNativeCommandContract(nativeCommand).commandRoot,
186
+ "affected-paths-json": JSON.stringify(predicates.affectedPaths),
187
+ "shard-evidence-roots-json": "[]",
188
+ "required-status-checks": "check",
189
+ "ready-label": "ready",
190
+ "landing-mode": "queue",
191
+ "queue-admission-context": "Queue admission lease",
192
+ "active-lease-context": "Queue family lease/exact",
193
+ "require-approval": true,
194
+ "same-repository-only": true,
195
+ "max-merges": 1,
196
+ "dry-run": false,
197
+ };
198
+ }
@@ -0,0 +1,84 @@
1
+ import { admitEvent } from "../../consumer/contract/entries.js";
2
+
3
+ const SHA = /^[0-9a-f]{40}$/u;
4
+ function prNumber(value) {
5
+ if (!Number.isSafeInteger(value) || value < 1)
6
+ throw new Error("Pipeline event requires an exact PR number");
7
+ return value;
8
+ }
9
+
10
+ // Webhook payloads select readback work only. They never carry source, approval,
11
+ // Warrant or mutation authority. Live provider state owns every decision.
12
+ export function pipelineEvent(name, event, repository) {
13
+ admitEvent(name, event.action || "");
14
+ if (event.repository?.full_name !== repository)
15
+ throw new Error("Pipeline event repository mismatch");
16
+ if (
17
+ ["pull_request", "pull_request_target", "pull_request_review"].includes(
18
+ name,
19
+ )
20
+ )
21
+ return {
22
+ kind: "pull-request",
23
+ pullRequest: prNumber(event.pull_request?.number),
24
+ terminalOnly: name === "pull_request_target",
25
+ };
26
+ if (name === "repository_dispatch") {
27
+ const payload = event.client_payload;
28
+ if (
29
+ !payload ||
30
+ Object.keys(payload).length !== 1 ||
31
+ !/^attempt-[0-9a-f]{64}$/u.test(payload.attempt || "")
32
+ )
33
+ throw new Error("Attempt wake requires only its exact business selector");
34
+ return { kind: "attempt", attempt: payload.attempt, terminalOnly: false };
35
+ }
36
+ if (name === "merge_group") {
37
+ const group = event.merge_group;
38
+ if (
39
+ !SHA.test(group?.head_sha || "") ||
40
+ !SHA.test(group?.base_sha || "") ||
41
+ !group.base_ref?.startsWith("refs/heads/")
42
+ )
43
+ throw new Error("Merge group requires exact provider coordinates");
44
+ return {
45
+ kind: "merge-group",
46
+ commit: group.head_sha,
47
+ baseCommit: group.base_sha,
48
+ branch: group.base_ref.slice(11),
49
+ terminalOnly: false,
50
+ };
51
+ }
52
+ if (!event.ref?.startsWith("refs/heads/") || event.deleted === true)
53
+ return { kind: "ignore", reason: "not-a-live-branch" };
54
+ if (!SHA.test(event.after || ""))
55
+ throw new Error("Branch event requires an exact commit");
56
+ // Internal journal and lock writes must not recursively wake the pipeline.
57
+ const branch = event.ref.slice(11);
58
+ return /^(?:dev|alpha|release|publish-gate)\//u.test(branch)
59
+ ? { kind: "branch", branch, commit: event.after, terminalOnly: false }
60
+ : { kind: "ignore", reason: "not-a-channel-branch" };
61
+ }
62
+
63
+ function matches(pattern, branch) {
64
+ const escaped = pattern
65
+ .split("*")
66
+ .map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"))
67
+ .join(".*");
68
+ return new RegExp(`^${escaped}$`, "u").test(branch);
69
+ }
70
+
71
+ export function channelRoute(plan, pullRequest, repository) {
72
+ if (
73
+ pullRequest.base?.repo?.full_name !== repository ||
74
+ pullRequest.head?.repo?.full_name !== repository
75
+ )
76
+ throw new Error("Pipeline delivery requires a repository-owned channel PR");
77
+ const routes = plan.channels.filter(
78
+ (channel) =>
79
+ channel.to === pullRequest.base.ref &&
80
+ matches(channel.from, pullRequest.head.ref),
81
+ );
82
+ if (routes.length > 1) throw new Error("Ambiguous protected channel route");
83
+ return routes[0] || null;
84
+ }
@@ -0,0 +1,42 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+
3
+ export const STOP_REQUESTED = "pipeline-stop-requested:";
4
+
5
+ // Credentialed controllers observe this source/attempt fence before Warrant
6
+ // renewal and finalization. The isolated native job receives no read token.
7
+ // Stopping renewal does not release its Warrant: provider-terminal native/seal
8
+ // evidence is still required before a separate controller can transfer ownership.
9
+ export function assertPipelineExecution(observed, expected) {
10
+ if (
11
+ observed.attempt !== expected.attempt ||
12
+ observed.generation !== expected.generation ||
13
+ observed.intent.id !== expected.intent
14
+ )
15
+ throw new Error("Pipeline execution lost its current attempt fence");
16
+ const current = observed.history.at(-1);
17
+ if (
18
+ !current ||
19
+ recordDigest(current.generation.source) !== expected.sourceRoot
20
+ )
21
+ throw new Error("Pipeline execution source fence changed");
22
+ if (
23
+ ["cancelled", "superseded", "failure", "complete"].includes(
24
+ observed.status,
25
+ ) ||
26
+ Object.values(observed.phases).some((record) =>
27
+ record.payload.reason.startsWith(STOP_REQUESTED),
28
+ )
29
+ )
30
+ throw new Error("Pipeline execution has a terminal or requested stop");
31
+ return true;
32
+ }
33
+
34
+ export function pipelineHeartbeat(journal, expected, warrantHeartbeat) {
35
+ return async () => {
36
+ assertPipelineExecution(await journal.read(), expected);
37
+ await warrantHeartbeat();
38
+ // Catch cancellation that raced the provider heartbeat without granting
39
+ // another controller interval from the stale pre-heartbeat read.
40
+ assertPipelineExecution(await journal.read(), expected);
41
+ };
42
+ }
@@ -0,0 +1,56 @@
1
+ import { recordDigest } from "../../release/discussion/envelope.js";
2
+ import { object } from "../../consumer/contract/shape.js";
3
+
4
+ export async function recordPipelineGroup(context, host) {
5
+ object(context, [
6
+ "schema",
7
+ "source",
8
+ "branch",
9
+ "baseCommit",
10
+ "runId",
11
+ "runAttempt",
12
+ "platforms",
13
+ ]);
14
+ if (
15
+ context.schema !== "buildchain.pipeline-group-build-context/v1" ||
16
+ context.runId !== host.runId ||
17
+ context.runAttempt !== host.runAttempt
18
+ )
19
+ throw new Error("Merge group callback execution drift");
20
+ const { run } = await host.runs.read(context.runId, context.runAttempt);
21
+ if (run.event !== "merge_group" || run.head_sha !== context.source.commit)
22
+ throw new Error("Merge group build is not the exact provider execution");
23
+ const queue = await host.queue.getMergeQueueState(context.branch);
24
+ if (
25
+ !queue.enabled ||
26
+ !queue.entries.some(
27
+ (entry) =>
28
+ entry.headSha === context.source.commit &&
29
+ entry.baseSha === context.baseCommit,
30
+ )
31
+ )
32
+ throw new Error("Merge group was removed or changed before verification");
33
+ const readback = await host.runs.build(
34
+ context.runId,
35
+ context.runAttempt,
36
+ context.source,
37
+ context.platforms.map((platform) => platform.platform),
38
+ );
39
+ await host.request(`/repos/${host.repository}/check-runs`, {
40
+ method: "POST",
41
+ body: {
42
+ name: "check",
43
+ head_sha: context.source.commit,
44
+ external_id: `buildchain:merge-group:${recordDigest(context)}`,
45
+ status: "completed",
46
+ conclusion: readback.outcome,
47
+ output: {
48
+ title: "Buildchain exact merge-group verification",
49
+ summary: `Provider product jobs: ${readback.root}`,
50
+ },
51
+ },
52
+ });
53
+ if (readback.outcome !== "success")
54
+ throw new Error("Merge-group products failed verification");
55
+ return readback;
56
+ }
@@ -0,0 +1,37 @@
1
+ import { githubPipelineRuns } from "../../providers/github/pipeline-runs.js";
2
+ import { readPipelineCaller } from "../../providers/github/pipeline-run-entry.js";
3
+ import { pipelineBuildEvidence } from "./build-evidence.js";
4
+ import { qualifyPipelineSourceRun } from "./delivery-request.js";
5
+
6
+ export async function guardPipelineBuild(
7
+ input,
8
+ observed,
9
+ {
10
+ material,
11
+ source,
12
+ request,
13
+ repository,
14
+ runs = githubPipelineRuns(request, repository),
15
+ },
16
+ ) {
17
+ const session = { observed, journal: { read: async () => observed } };
18
+ const build = await pipelineBuildEvidence(session, {
19
+ runId: 0,
20
+ runs,
21
+ source,
22
+ materialStore: () => material,
23
+ });
24
+ if (!build || build.run.id !== Number(input["source-workflow-run-id"]))
25
+ throw new Error(
26
+ "Delivery requires its independently qualified exact product build",
27
+ );
28
+ const current = { ...observed.history.at(-1), intent: observed.intent };
29
+ qualifyPipelineSourceRun(build.run, current, build.readback);
30
+ await readPipelineCaller(
31
+ build.run,
32
+ current.generation.source.configPath,
33
+ request,
34
+ repository,
35
+ );
36
+ return build;
37
+ }
@@ -0,0 +1,156 @@
1
+ import { githubJsonClient } from "../../providers/github/json-client.js";
2
+ import { githubAttemptJournal } from "../../providers/github/attempt-journal.js";
3
+ import { githubAttemptIndex } from "../../providers/github/attempt-index.js";
4
+ import { readBusinessAttempt } from "../attempt/reader.js";
5
+ import { assertPipelineExecution } from "./fence.js";
6
+ import { recordDigest } from "../../release/discussion/envelope.js";
7
+ import { githubPipelineSource } from "../../providers/github/pipeline-source.js";
8
+ import { githubPipelinePolicy } from "../../providers/github/pipeline-policy.js";
9
+ import { getOctokit } from "@actions/github";
10
+ import { discussionMaterials } from "../../providers/github/discussions/materials.js";
11
+ import { pipelineMaterials } from "./materials.js";
12
+ import { guardPipelineBuild } from "./guard-build.js";
13
+
14
+ async function assertRetainedRequest(input, connection, observed) {
15
+ const current = observed.history.at(-1);
16
+ const event = [...current.events]
17
+ .reverse()
18
+ .find((entry) =>
19
+ entry.payload.materials.some((material) =>
20
+ material.id.startsWith("delivery/execution-"),
21
+ ),
22
+ );
23
+ const reference = event?.payload.materials.find((material) =>
24
+ material.id.startsWith("delivery/execution-"),
25
+ );
26
+ if (!reference)
27
+ throw new Error("Pipeline delivery has no admitted execution request");
28
+ const archive = discussionMaterials({
29
+ octokit: getOctokit(connection.token),
30
+ repository: connection.repository,
31
+ intentId: observed.intent.id,
32
+ });
33
+ const material = pipelineMaterials(archive, {
34
+ repository: connection.repository,
35
+ attempt: current.identity,
36
+ });
37
+ const execution = await material.read(reference);
38
+ validatePipelineExecutionRequest(execution, input, observed, {
39
+ runId: Number(process.env.GITHUB_RUN_ID),
40
+ runAttempt: Number(process.env.GITHUB_RUN_ATTEMPT),
41
+ });
42
+ return material;
43
+ }
44
+
45
+ export function validatePipelineExecutionRequest(
46
+ execution,
47
+ input,
48
+ observed,
49
+ coordinates,
50
+ ) {
51
+ if (execution.schema !== "buildchain.pipeline-delivery-execution/v1")
52
+ throw new Error("Pipeline delivery request schema is unsupported");
53
+ if (
54
+ execution.attempt !== observed.attempt ||
55
+ execution.generation !== observed.generation ||
56
+ execution.runId !== coordinates.runId ||
57
+ execution.runAttempt !== coordinates.runAttempt
58
+ )
59
+ throw new Error(
60
+ "Pipeline delivery is not the exact admitted provider execution",
61
+ );
62
+ for (const [key, value] of Object.entries(execution.request))
63
+ if (
64
+ !Object.hasOwn(input, key) ||
65
+ recordDigest(input[key]) !== recordDigest(value)
66
+ )
67
+ throw new Error(
68
+ `Pipeline delivery changed its retained request field: ${key}`,
69
+ );
70
+ }
71
+
72
+ export async function guardPipelineAdmission(input, connection) {
73
+ const observed = await guardPipelineDelivery(input, connection);
74
+ if (!observed) return null;
75
+ const material = await assertRetainedRequest(input, connection, observed);
76
+ const request = githubJsonClient({
77
+ token: connection.token,
78
+ userAgent: "buildchain-pipeline-admission",
79
+ });
80
+ const current = { ...observed.history.at(-1), intent: observed.intent };
81
+ const source = githubPipelineSource(request, connection.repository);
82
+ const admission = await source.observeIntent(
83
+ observed.intent,
84
+ current.generation,
85
+ { "config-path": current.generation.source.configPath },
86
+ );
87
+ const { live } = admission;
88
+ if (
89
+ admission.cleanupOnly ||
90
+ !live.source ||
91
+ live.state !== "open" ||
92
+ live.draft ||
93
+ !live.ready ||
94
+ live.baseCommit !== current.generation.baseCommit ||
95
+ recordDigest(live.source) !== recordDigest(current.generation.source)
96
+ )
97
+ throw new Error(
98
+ "Pipeline source is no longer admitted for a delivery effect",
99
+ );
100
+ const policy = await githubPipelinePolicy(
101
+ request,
102
+ connection.repository,
103
+ ).observe(current, admission.protectedPlan.review);
104
+ if (!policy.review || !policy.checksPassing)
105
+ throw new Error(
106
+ "Pipeline protected review or required checks changed before delivery effect",
107
+ );
108
+ await guardPipelineBuild(input, observed, {
109
+ material,
110
+ source,
111
+ request,
112
+ repository: connection.repository,
113
+ });
114
+ return guardPipelineDelivery(input, connection);
115
+ }
116
+
117
+ export async function guardPipelineDelivery(input, connection, lookup) {
118
+ const attempt = input["pipeline-attempt"];
119
+ // Existing advanced delivery callers retain their domain authority. The new
120
+ // normal entry always supplies its internally selected business attempt.
121
+ if (!attempt) return null;
122
+ if (!connection.token)
123
+ throw new Error("Pipeline controller requires its scoped read credential");
124
+ const request = githubJsonClient({
125
+ token: connection.token,
126
+ userAgent: "buildchain-pipeline",
127
+ });
128
+ const index =
129
+ lookup ||
130
+ githubAttemptIndex(
131
+ request,
132
+ githubAttemptJournal(request),
133
+ connection.repository,
134
+ );
135
+ const loaded = await index.resolve(attempt);
136
+ const observed = readBusinessAttempt(loaded.snapshot);
137
+ const current = observed.history.at(-1);
138
+ if (
139
+ !current ||
140
+ observed.intent.repository !== connection.repository ||
141
+ observed.intent.source.targetBranch !== input["target-branch"] ||
142
+ observed.intent.source.pullRequest !==
143
+ Number(input["expected-pr-number"]) ||
144
+ current.generation.source.commit !== input["expected-head-sha"]
145
+ )
146
+ throw new Error(
147
+ "Pipeline delivery request changed its admitted source intent",
148
+ );
149
+ assertPipelineExecution(observed, {
150
+ attempt,
151
+ intent: observed.intent.id,
152
+ generation: current.generation.id,
153
+ sourceRoot: recordDigest(current.generation.source),
154
+ });
155
+ return observed;
156
+ }