@kungfu-tech/buildchain 4.1.3-alpha.2 → 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 (72) hide show
  1. package/architecture/action-taxonomy.json +8 -0
  2. package/architecture/agent-change-map.md +17 -0
  3. package/architecture/ci-lane-change-budget.json +85 -0
  4. package/architecture/decisions/0009-pipeline-version-preparation.md +186 -0
  5. package/architecture/internal-capabilities.json +32 -2
  6. package/architecture/maintainability-debt.json +3 -3
  7. package/architecture/maintainability-policy.json +7 -7
  8. package/architecture/minimal-consumer-migration.json +9 -0
  9. package/architecture/product-upstreams.json +20 -0
  10. package/architecture/release-topology.json +62 -25
  11. package/architecture/universal-workflow-bootstrap.json +2 -0
  12. package/architecture/universal-workflow-capability-policy.json +1 -1
  13. package/architecture/workflow-taxonomy.json +11 -0
  14. package/dist/readers/business-attempt.cjs +6 -2
  15. package/dist/site/buildchain-contract.json +4 -4
  16. package/dist/site/buildchain-site.json +9 -9
  17. package/dist/site/capability-registry.json +2 -2
  18. package/dist/site/kfd-claims.json +82 -8
  19. package/dist/site/kfd-upstream-aggregate.json +1 -1
  20. package/dist/site/manual-registry.json +1 -1
  21. package/dist/site/node-api-registry.json +18 -18
  22. package/dist/site/page-registry.json +4 -4
  23. package/dist/site/public-surface-audit.json +64 -7
  24. package/dist/site/publication-authority-registry.json +21 -1
  25. package/dist/site/publication-registry.json +4 -4
  26. package/dist/site/site-manifest.json +5 -5
  27. package/dist/site/workflow-registry.json +113 -7
  28. package/docs/node-api-reference.md +34 -34
  29. package/docs/workflow-catalog.md +1 -0
  30. package/package.json +1 -1
  31. package/packages/core/build/standalone/build.js +7 -1
  32. package/packages/core/consumer/buildchain-config.js +2 -21
  33. package/packages/core/consumer/contract/plan.js +48 -1
  34. package/packages/core/consumer/contract/products.js +20 -2
  35. package/packages/core/consumer/contract/reader.js +47 -0
  36. package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
  37. package/packages/core/governance/buildchain-publication-authority.js +1 -0
  38. package/packages/core/providers/github/pipeline-entry-runs.js +203 -0
  39. package/packages/core/providers/github/pipeline-release-evidence.js +111 -0
  40. package/packages/core/providers/github/pipeline-stable-source.js +354 -0
  41. package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
  42. package/packages/core/providers/github/pipeline-version-readback.js +117 -0
  43. package/packages/core/providers/github/pipeline-version.js +10 -12
  44. package/packages/core/publication/pipeline/actions.js +17 -1
  45. package/packages/core/publication/pipeline/apply.js +23 -17
  46. package/packages/core/publication/pipeline/development-pr.js +28 -1
  47. package/packages/core/publication/pipeline/documents.js +37 -1
  48. package/packages/core/publication/pipeline/next-development.js +1 -0
  49. package/packages/core/publication/pipeline/pack.js +7 -1
  50. package/packages/core/publication/pipeline/plan.js +22 -0
  51. package/packages/core/publication/pipeline/prepare.js +44 -56
  52. package/packages/core/publication/pipeline/qualification.js +5 -0
  53. package/packages/core/publication/pipeline/stable-entry.js +215 -0
  54. package/packages/core/publication/pipeline/stable-products.js +317 -0
  55. package/packages/core/publication/pipeline/stable-wait.js +170 -0
  56. package/packages/core/publication/pipeline/stable.js +82 -0
  57. package/packages/core/publication/pipeline/version-actions.js +93 -0
  58. package/packages/core/publication/pipeline/version-build.js +130 -0
  59. package/packages/core/publication/pipeline/version-context.js +143 -0
  60. package/packages/core/publication/pipeline/version-material.js +36 -0
  61. package/packages/core/publication/pipeline/version-preparation.js +69 -0
  62. package/packages/core/publication/pipeline/version-regeneration.js +162 -0
  63. package/packages/core/publication/pipeline/version-source-guard.js +69 -0
  64. package/packages/core/publication/pipeline/worker.js +72 -0
  65. package/packages/core/release/stable-release-gate.js +11 -0
  66. package/packages/core/workflow/attempt/identity.js +7 -2
  67. package/packages/core/workflow/pipeline/actions.js +14 -1
  68. package/packages/core/workflow/pipeline/build.js +49 -2
  69. package/scripts/check-pipeline-publication-topology.mjs +12 -0
  70. package/scripts/check-release-topology.mjs +1 -0
  71. package/scripts/generate-site-bundle.mjs +1 -1
  72. package/scripts/verify-standalone-product.mjs +42 -0
@@ -184,8 +184,19 @@ export function evaluateStableReleaseGate({
184
184
  "version-bound impact declares a summary and at least one surface",
185
185
  { summary: string(impact?.summary), surfaceIds: surfaceImpacts.map((entry) => string(entry?.id)).filter(Boolean) },
186
186
  ));
187
+ checks.push(check(
188
+ string(impact?.release?.version) === candidateTag.replace(/^v/, ""),
189
+ "stable.impact_version",
190
+ "product impact belongs to the exact candidate version",
191
+ { expectedVersion: candidateTag.replace(/^v/, ""), observedVersion: string(impact?.release?.version) },
192
+ ));
187
193
 
188
194
  const canaryById = new Map(canaries.map((canary) => [string(canary?.id), canary]));
195
+ checks.push(check(
196
+ canaryById.size === canaries.length,
197
+ "canary.evidence_inventory",
198
+ "each canary has one unambiguous observation",
199
+ ));
189
200
  const canaryCompletionTimes = [];
190
201
  for (const required of policy.requiredCanaries) {
191
202
  const evidence = canaryById.get(required.id);
@@ -67,7 +67,7 @@ export function pipelineIntent({
67
67
  });
68
68
  }
69
69
 
70
- export function sourceGeneration(intent, source, baseCommit) {
70
+ export function validateConsumerSource(source, repository) {
71
71
  object(
72
72
  source,
73
73
  [
@@ -86,13 +86,18 @@ export function sourceGeneration(intent, source, baseCommit) {
86
86
  if (
87
87
  source.schema !== "buildchain.consumer-source/v1" ||
88
88
  source.contract !== CONSUMER_CONTRACT ||
89
- source.repository !== intent.repository
89
+ source.repository !== repository
90
90
  )
91
91
  throw new Error("Generation requires the admitted consumer source");
92
92
  for (const key of ["commit", "tree", "configBlob"])
93
93
  text(source[key], `source.${key}`, /^[0-9a-f]{40}$/u);
94
94
  relativePath(source.configPath, "source.configPath");
95
95
  text(source.configDigest, "source.configDigest", ROOT);
96
+ return source;
97
+ }
98
+
99
+ export function sourceGeneration(intent, source, baseCommit) {
100
+ validateConsumerSource(source, intent.repository);
96
101
  text(baseCommit, "baseCommit", /^[0-9a-f]{40}$/u);
97
102
  const value = {
98
103
  schema: SOURCE_GENERATION,
@@ -6,6 +6,7 @@ import { pipelineHost } from "./host.js";
6
6
  import { controlPipeline } from "./controller.js";
7
7
  import { buildPipelineSource } from "./build.js";
8
8
  import { recordPipelineBuild } from "./build-control.js";
9
+ import { wakePipelineStableWait } from "../../publication/pipeline/stable-wait.js";
9
10
  import {
10
11
  RECOVERY_BUILD_CONTEXT,
11
12
  recordRecoveryBuild,
@@ -57,6 +58,18 @@ export async function recordPipelineBuildAction(core, env) {
57
58
  }
58
59
 
59
60
  export async function wakePipelineAction(core, env) {
60
- const host = await pipelineHost(core, env, "Record delivery attempt");
61
+ const waiting = core.getInput("stable-wait");
62
+ const host = await pipelineHost(
63
+ core,
64
+ env,
65
+ waiting ? "Recheck stable publication" : "Record delivery attempt",
66
+ );
67
+ if (waiting) {
68
+ const wait = JSON.parse(waiting);
69
+ if (wait.attempt !== core.getInput("attempt", { required: true }))
70
+ throw new Error("Stable wake changed its exact business attempt");
71
+ core.info(JSON.stringify(await wakePipelineStableWait(wait, host)));
72
+ return;
73
+ }
61
74
  await host.wake(core.getInput("attempt", { required: true }));
62
75
  }
@@ -1,12 +1,57 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { execFileSync } from "node:child_process";
4
+ import { createHash } from "node:crypto";
4
5
  import { bindConsumerSource } from "../../consumer/contract/identity.js";
5
6
  import { recordDigest } from "../../release/discussion/envelope.js";
6
7
  import { consumerCommandSession } from "../../runtime/consumer-shell.js";
7
8
  import { createNativeChildEnvironment } from "../../dev-delivery/native/execution.js";
8
9
 
9
- function sourceCheckout(cwd, expected) {
10
+ function inspectTrackedBytes(cwd, tree) {
11
+ const root = fs.realpathSync(cwd);
12
+ const entries = execFileSync(
13
+ "git",
14
+ ["-C", root, "ls-tree", "-r", "-z", tree],
15
+ {
16
+ encoding: "utf8",
17
+ maxBuffer: 16 * 1024 * 1024,
18
+ },
19
+ )
20
+ .split("\0")
21
+ .filter(Boolean);
22
+ for (const entry of entries) {
23
+ const match =
24
+ /^(100644|100755|120000) blob ([0-9a-f]{40})\t([\s\S]+)$/u.exec(entry);
25
+ if (!match) throw new Error("Build source requires resolved tracked files");
26
+ const [, mode, blob, file] = match;
27
+ const absolute = path.join(root, file);
28
+ if (fs.realpathSync(path.dirname(absolute)) !== path.dirname(absolute))
29
+ throw new Error(`Build source parent became a symbolic link: ${file}`);
30
+ const stat = fs.lstatSync(absolute);
31
+ const link = mode === "120000";
32
+ if (link ? !stat.isSymbolicLink() : !stat.isFile())
33
+ throw new Error(`Build tracked source kind changed: ${file}`);
34
+ if (
35
+ !link &&
36
+ process.platform !== "win32" &&
37
+ Boolean(stat.mode & 0o111) !== (mode === "100755")
38
+ )
39
+ throw new Error(`Build tracked source executable mode changed: ${file}`);
40
+ const bytes = link
41
+ ? fs.readlinkSync(absolute, { encoding: "buffer" })
42
+ : fs.readFileSync(absolute);
43
+ const actual = createHash("sha1")
44
+ .update(`blob ${bytes.length}\0`)
45
+ .update(bytes)
46
+ .digest("hex");
47
+ if (actual !== blob)
48
+ throw new Error(
49
+ `Build tracked source bytes differ from the admitted tree: ${file}`,
50
+ );
51
+ }
52
+ }
53
+
54
+ export function inspectPipelineSource(cwd, expected) {
10
55
  const git = (...args) =>
11
56
  execFileSync("git", ["-C", cwd, ...args], {
12
57
  encoding: "utf8",
@@ -36,6 +81,8 @@ function sourceCheckout(cwd, expected) {
36
81
  throw new Error(
37
82
  "Build source has tracked modifications at its verification boundary",
38
83
  );
84
+ // Index flags and local Git filters cannot qualify different product bytes.
85
+ inspectTrackedBytes(cwd, expected.tree);
39
86
  return plan;
40
87
  }
41
88
 
@@ -54,7 +101,7 @@ function productDirectory(cwd, product) {
54
101
  // jobs must bind provider completion and exact artifacts before granting reuse.
55
102
  export async function buildPipelineSource(
56
103
  { cwd, source, platform, environment = process.env },
57
- { inspect = sourceCheckout, session = consumerCommandSession } = {},
104
+ { inspect = inspectPipelineSource, session = consumerCommandSession } = {},
58
105
  ) {
59
106
  const plan = inspect(cwd, source);
60
107
  const result = await buildPipelineProducts(
@@ -12,6 +12,7 @@ export function assertPipelinePublicationTopology(
12
12
  ".github/workflows/public-ops-pipeline.yml",
13
13
  ".github/workflows/public-ops-recover.yml",
14
14
  ".github/workflows/.ops-pipeline-execute.yml",
15
+ ".github/workflows/.release-pipeline-version.yml",
15
16
  ]);
16
17
  const pipelineTopology = discoverReleaseTopology(
17
18
  pipeline.workflowPaths,
@@ -33,4 +34,15 @@ export function assertPipelinePublicationTopology(
33
34
  module,
34
35
  ),
35
36
  );
37
+ const version = ".github/workflows/.release-pipeline-version.yml";
38
+ assert.ok(
39
+ inspectWorkflowJob(version, "build", root).modules.has(
40
+ "packages/core/publication/pipeline/version-build.js",
41
+ ),
42
+ );
43
+ assert.ok(
44
+ inspectWorkflowJob(version, "materialize", root).modules.has(
45
+ "packages/core/publication/pipeline/version-context.js",
46
+ ),
47
+ );
36
48
  }
@@ -30,6 +30,7 @@ const PRIVILEGED_ENTRYPOINTS = [
30
30
  "packages/core/publication/oci/preview-actions.js",
31
31
  "packages/core/publication/settlement/actions.js",
32
32
  "packages/core/publication/pipeline/actions.js",
33
+ "packages/core/publication/pipeline/version-actions.js",
33
34
  ];
34
35
 
35
36
  export function localModuleSpecifiers(source) {
@@ -1343,7 +1343,7 @@ function buildSiteBundle() {
1343
1343
  "badge-endpoint-registry.json": badgeEndpointRegistry,
1344
1344
  "publication-registry.json": publicationRegistry,
1345
1345
  "buildchain-contract.json": createBuildchainContractWorld({ root, controllerRegistry }),
1346
- "kfd-upstream-aggregate.json": collectKfdUpstreamFacts({ cwd: root, includeOwn: false }),
1346
+ "kfd-upstream-aggregate.json": collectKfdUpstreamFacts({ cwd: root, components: readJson("architecture/product-upstreams.json"), includeOwn: false }),
1347
1347
  "kfd-claims.json": createBuildchainKfdClaimRegistry({ root }),
1348
1348
  "product-mechanism.json": productMechanism,
1349
1349
  "release-provenance.json": releaseProvenance,
@@ -0,0 +1,42 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+ import {
7
+ platformTriple,
8
+ packageVersion,
9
+ sourceSha,
10
+ } from "../packages/core/build/standalone/identity.js";
11
+
12
+ const cwd = process.cwd();
13
+ const manifest = JSON.parse(
14
+ fs.readFileSync(
15
+ path.join(cwd, "dist/binary", `buildchain-${platformTriple()}.json`),
16
+ "utf8",
17
+ ),
18
+ );
19
+ const version = packageVersion(cwd);
20
+ assert.equal(manifest.version, version);
21
+ assert.equal(manifest.sourceSha, sourceSha(cwd));
22
+ assert.equal(manifest.platform, platformTriple());
23
+ const binary = path.join(
24
+ cwd,
25
+ "dist/binary",
26
+ process.platform === "win32" ? "buildchain.exe" : "buildchain",
27
+ );
28
+ assert.equal(
29
+ createHash("sha256").update(fs.readFileSync(binary)).digest("hex"),
30
+ manifest.sha256,
31
+ );
32
+ assert.equal(
33
+ execFileSync(binary, ["--version"], { encoding: "utf8" }).trim(),
34
+ version,
35
+ );
36
+ assert.match(
37
+ execFileSync(binary, ["help"], { encoding: "utf8" }),
38
+ /^Usage:\s+buildchain --help/mu,
39
+ );
40
+ process.stdout.write(
41
+ `Verified standalone product ${platformTriple()} at ${version}\n`,
42
+ );