@kungfu-tech/buildchain 3.0.2-alpha.3 → 3.0.2-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -0
  2. package/actions/github-artifact-attestation/README.md +10 -0
  3. package/actions/promote-buildchain-ref/README.md +7 -0
  4. package/bin/buildchain.mjs +5 -0
  5. package/bin/internal/trust-release-cli.mjs +74 -3
  6. package/dist/site/artifact-schemas.json +5 -1
  7. package/dist/site/buildchain-contract.json +79 -28
  8. package/dist/site/buildchain-site.json +110 -27
  9. package/dist/site/capability-registry.json +8 -7
  10. package/dist/site/cli-registry.json +12 -0
  11. package/dist/site/controller-registry.json +40 -4
  12. package/dist/site/kfd-claims.json +203 -18
  13. package/dist/site/kfd-upstream-aggregate.json +1 -1
  14. package/dist/site/manual-registry.json +19 -5
  15. package/dist/site/node-api-registry.json +23 -10
  16. package/dist/site/page-registry.json +91 -17
  17. package/dist/site/public-surface-audit.json +125 -15
  18. package/dist/site/publication-authority-registry.json +27 -2
  19. package/dist/site/publication-registry.json +4 -4
  20. package/dist/site/release-model.json +2 -1
  21. package/dist/site/release-passport-check-manifest.json +1 -0
  22. package/dist/site/release-provenance.json +1 -0
  23. package/dist/site/schemas/release-passport-v1.schema.json +6 -0
  24. package/dist/site/site-manifest.json +17 -9
  25. package/dist/site/workflow-registry.json +84 -7
  26. package/docs/MAP.md +3 -1
  27. package/docs/binary-distribution.md +7 -0
  28. package/docs/cli.md +9 -0
  29. package/docs/dev-alpha-candidate-patrol.md +16 -4
  30. package/docs/github-artifact-attestation.md +219 -0
  31. package/docs/release-passport.md +13 -0
  32. package/docs/reusable-build-surface.md +6 -0
  33. package/package.json +2 -1
  34. package/packages/core/buildchain-contract.js +6 -0
  35. package/packages/core/buildchain-kfd-claims.js +5 -0
  36. package/packages/core/buildchain-publication-authority.js +1 -0
  37. package/packages/core/channel-candidate.js +67 -16
  38. package/packages/core/github-artifact-attestation.js +642 -0
  39. package/packages/core/index.js +19 -0
  40. package/packages/core/publication-authority.js +1 -1
  41. package/packages/core/release-passport-contract.js +2 -0
  42. package/packages/core/release-passport.js +51 -0
  43. package/scripts/check-inventory.mjs +4 -0
  44. package/scripts/create-github-artifact-attestation-policy.mjs +62 -0
  45. package/scripts/dev-alpha-candidate-patrol.mjs +256 -46
  46. package/scripts/generate-site-bundle.mjs +12 -0
  47. package/scripts/publish-github-artifact-attestation-evidence.mjs +201 -0
  48. package/scripts/release-candidate-resolver.mjs +12 -0
  49. package/scripts/stage-github-artifact-attestation-inputs.mjs +65 -0
@@ -34,28 +34,40 @@ function required(value, name) {
34
34
 
35
35
  function sha(value, name) {
36
36
  const normalized = required(value, name).toLowerCase();
37
- if (!SHA.test(normalized)) throw new Error(`${name} must be an exact 40-character SHA`);
37
+ if (!SHA.test(normalized))
38
+ throw new Error(`${name} must be an exact 40-character SHA`);
38
39
  return normalized;
39
40
  }
40
41
 
41
42
  function qualifyWorkflow(row, { sourceSha, now, maxAgeSeconds }) {
42
- if (!row || typeof row !== "object") throw new Error("workflow evidence is required");
43
+ if (!row || typeof row !== "object")
44
+ throw new Error("workflow evidence is required");
43
45
  const workflowPath = required(row.workflowPath, "workflowPath");
44
46
  if (sha(row.headSha, `${workflowPath} headSha`) !== sourceSha) {
45
- throw new Error(`${workflowPath} evidence does not bind source SHA ${sourceSha}`);
47
+ throw new Error(
48
+ `${workflowPath} evidence does not bind source SHA ${sourceSha}`,
49
+ );
46
50
  }
47
51
  if (row.status !== "completed" || row.conclusion !== "success") {
48
52
  throw new Error(`${workflowPath} is not a completed successful run`);
49
53
  }
50
54
  const completedAt = required(row.completedAt, `${workflowPath} completedAt`);
51
55
  const ageSeconds = (Date.parse(now) - Date.parse(completedAt)) / 1000;
52
- if (!Number.isFinite(ageSeconds) || ageSeconds < 0 || ageSeconds > maxAgeSeconds) {
53
- throw new Error(`${workflowPath} evidence is stale or has an invalid completion time`);
56
+ if (
57
+ !Number.isFinite(ageSeconds) ||
58
+ ageSeconds < 0 ||
59
+ ageSeconds > maxAgeSeconds
60
+ ) {
61
+ throw new Error(
62
+ `${workflowPath} evidence is stale or has an invalid completion time`,
63
+ );
54
64
  }
55
65
  const runId = Number(row.runId);
56
66
  const runAttempt = Number(row.runAttempt || 1);
57
- if (!Number.isSafeInteger(runId) || runId <= 0) throw new Error(`${workflowPath} runId is invalid`);
58
- if (!Number.isSafeInteger(runAttempt) || runAttempt <= 0) throw new Error(`${workflowPath} runAttempt is invalid`);
67
+ if (!Number.isSafeInteger(runId) || runId <= 0)
68
+ throw new Error(`${workflowPath} runId is invalid`);
69
+ if (!Number.isSafeInteger(runAttempt) || runAttempt <= 0)
70
+ throw new Error(`${workflowPath} runAttempt is invalid`);
59
71
  return {
60
72
  workflowPath,
61
73
  workflowName: required(row.workflowName, `${workflowPath} workflowName`),
@@ -79,9 +91,31 @@ export function decideChannelCandidate(input) {
79
91
  const repository = required(input.repository, "repository");
80
92
  const sourceBranch = required(input.sourceBranch, "sourceBranch");
81
93
  const targetBranch = required(input.targetBranch, "targetBranch");
82
- if (sourceBranch === targetBranch) throw new Error("sourceBranch and targetBranch must differ");
94
+ if (sourceBranch === targetBranch)
95
+ throw new Error("sourceBranch and targetBranch must differ");
83
96
  const sourceSha = sha(input.sourceSha, "sourceSha");
84
97
  const targetSha = sha(input.targetSha, "targetSha");
98
+ const selection = input.selection
99
+ ? {
100
+ mode: required(input.selection.mode, "selection.mode"),
101
+ observedSourceHeadSha: sha(
102
+ input.selection.observedSourceHeadSha,
103
+ "selection.observedSourceHeadSha",
104
+ ),
105
+ skippedNewerCommitCount: Number(
106
+ input.selection.skippedNewerCommitCount || 0,
107
+ ),
108
+ }
109
+ : undefined;
110
+ if (
111
+ selection &&
112
+ (!Number.isSafeInteger(selection.skippedNewerCommitCount) ||
113
+ selection.skippedNewerCommitCount < 0)
114
+ ) {
115
+ throw new Error(
116
+ "selection.skippedNewerCommitCount must be a non-negative integer",
117
+ );
118
+ }
85
119
  const now = required(input.now || new Date().toISOString(), "now");
86
120
  const maxAgeSeconds = Number(input.maxAgeSeconds ?? 7 * 24 * 60 * 60);
87
121
  if (!Number.isSafeInteger(maxAgeSeconds) || maxAgeSeconds <= 0) {
@@ -89,7 +123,10 @@ export function decideChannelCandidate(input) {
89
123
  }
90
124
  const comparison = input.comparison || {};
91
125
  if (comparison.status !== "ahead" || Number(comparison.aheadBy) <= 0) {
92
- const reason = comparison.status === "identical" ? "target-already-current" : "source-does-not-lead-target";
126
+ const reason =
127
+ comparison.status === "identical"
128
+ ? "target-already-current"
129
+ : "source-does-not-lead-target";
93
130
  return {
94
131
  schema: CHANNEL_CANDIDATE_DECISION_SCHEMA,
95
132
  eligible: false,
@@ -97,15 +134,25 @@ export function decideChannelCandidate(input) {
97
134
  repository,
98
135
  source: { branch: sourceBranch, sha: sourceSha },
99
136
  target: { branch: targetBranch, sha: targetSha },
100
- comparison: { status: String(comparison.status || "unknown"), aheadBy: Number(comparison.aheadBy || 0) },
137
+ comparison: {
138
+ status: String(comparison.status || "unknown"),
139
+ aheadBy: Number(comparison.aheadBy || 0),
140
+ },
101
141
  decidedAt: now,
102
142
  };
103
143
  }
104
- const rows = Array.isArray(input.workflowEvidence) ? input.workflowEvidence : [];
105
- const expectedPaths = [...new Set((input.requiredWorkflowPaths || []).map(String))];
106
- if (expectedPaths.length === 0) throw new Error("requiredWorkflowPaths must not be empty");
144
+ const rows = Array.isArray(input.workflowEvidence)
145
+ ? input.workflowEvidence
146
+ : [];
147
+ const expectedPaths = [
148
+ ...new Set((input.requiredWorkflowPaths || []).map(String)),
149
+ ];
150
+ if (expectedPaths.length === 0)
151
+ throw new Error("requiredWorkflowPaths must not be empty");
107
152
  if (rows.length !== expectedPaths.length) {
108
- throw new Error(`expected exactly ${expectedPaths.length} workflow evidence rows, got ${rows.length}`);
153
+ throw new Error(
154
+ `expected exactly ${expectedPaths.length} workflow evidence rows, got ${rows.length}`,
155
+ );
109
156
  }
110
157
  const byPath = new Map();
111
158
  for (const row of rows) {
@@ -116,7 +163,8 @@ export function decideChannelCandidate(input) {
116
163
  byPath.set(qualified.workflowPath, qualified);
117
164
  }
118
165
  for (const workflowPath of expectedPaths) {
119
- if (!byPath.has(workflowPath)) throw new Error(`missing workflow evidence: ${workflowPath}`);
166
+ if (!byPath.has(workflowPath))
167
+ throw new Error(`missing workflow evidence: ${workflowPath}`);
120
168
  }
121
169
  const body = {
122
170
  schema: CHANNEL_CANDIDATE_DECISION_SCHEMA,
@@ -126,8 +174,11 @@ export function decideChannelCandidate(input) {
126
174
  source: { branch: sourceBranch, sha: sourceSha },
127
175
  target: { branch: targetBranch, sha: targetSha },
128
176
  comparison: { status: "ahead", aheadBy: Number(comparison.aheadBy) },
177
+ ...(selection ? { selection } : {}),
129
178
  sourceLockRef: channelCandidateSourceLockRef(targetBranch, sourceSha),
130
- workflowEvidence: expectedPaths.map((workflowPath) => byPath.get(workflowPath)),
179
+ workflowEvidence: expectedPaths.map((workflowPath) =>
180
+ byPath.get(workflowPath),
181
+ ),
131
182
  policy: { maxAgeSeconds, requiredWorkflowPaths: expectedPaths },
132
183
  decidedAt: now,
133
184
  };