@kungfu-tech/buildchain 4.0.1-alpha.4 → 4.0.1-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 (87) hide show
  1. package/architecture/decisions/0002-next-development-transition.md +101 -0
  2. package/architecture/internal-capabilities.json +52 -3
  3. package/architecture/maintainability-policy.json +22 -10
  4. package/architecture/v3-core-mechanism-inventory.json +89 -0
  5. package/architecture/v4-capability-state-machine-manifest.json +73 -0
  6. package/architecture/v4-compatibility-facts-parity.json +77 -0
  7. package/architecture/v4-next-development-parity.json +192 -0
  8. package/architecture/v4-release-train-parity.json +1 -0
  9. package/architecture/v4-tail-reseal-parity.json +150 -0
  10. package/bin/buildchain.mjs +16 -1
  11. package/bin/internal/command-registry.mjs +2 -0
  12. package/bin/internal/compatibility-facts-cli.mjs +101 -0
  13. package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
  14. package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
  15. package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
  16. package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
  17. package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
  18. package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
  19. package/contracts/next-development-request-v1.schema.json +66 -0
  20. package/contracts/next-development-transition-v1.schema.json +292 -0
  21. package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
  22. package/contracts/v4-tail-reseal-v1.schema.json +276 -0
  23. package/dist/site/agent-index.json +1 -0
  24. package/dist/site/artifact-schemas.json +2 -0
  25. package/dist/site/buildchain-contract.json +1388 -23
  26. package/dist/site/buildchain-site.json +257 -19
  27. package/dist/site/capability-registry.json +10 -8
  28. package/dist/site/cli-registry.json +106 -0
  29. package/dist/site/kfd-claims.json +187 -12
  30. package/dist/site/kfd-upstream-aggregate.json +1 -1
  31. package/dist/site/manual-registry.json +35 -4
  32. package/dist/site/node-api-registry.json +6579 -4493
  33. package/dist/site/page-registry.json +233 -11
  34. package/dist/site/public-surface-audit.json +241 -7
  35. package/dist/site/publication-authority-registry.json +6 -1
  36. package/dist/site/publication-registry.json +4 -4
  37. package/dist/site/release-provenance.json +7 -0
  38. package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
  39. package/dist/site/site-manifest.json +24 -8
  40. package/dist/site/workflow-registry.json +36 -4
  41. package/docs/MAP.md +4 -1
  42. package/docs/cli-reference.md +125 -0
  43. package/docs/dev-alpha-candidate-patrol.md +8 -0
  44. package/docs/next-development-transition.md +118 -0
  45. package/docs/node-api-reference.md +250 -117
  46. package/docs/v4-compatibility-facts.md +91 -0
  47. package/docs/v4-tail-reseal.md +73 -0
  48. package/docs/versioning.md +4 -3
  49. package/package.json +14 -5
  50. package/packages/core/buildchain-agent-manuals.js +2 -0
  51. package/packages/core/buildchain-compatibility-authority.js +336 -0
  52. package/packages/core/buildchain-compatibility-fact.js +394 -0
  53. package/packages/core/buildchain-compatibility-facts.json +217 -0
  54. package/packages/core/buildchain-compatibility-proof.js +566 -0
  55. package/packages/core/buildchain-contract.js +93 -141
  56. package/packages/core/channel-candidate.js +8 -0
  57. package/packages/core/channel-promotion-baseline.js +55 -0
  58. package/packages/core/dev-alpha-candidate-selection.js +10 -2
  59. package/packages/core/dev-delivery-execution-transfer.js +21 -1
  60. package/packages/core/dev-delivery-process-boundary.js +19 -6
  61. package/packages/core/dev-delivery-provider-heartbeat.js +18 -6
  62. package/packages/core/kungfu-temporal-fact.js +557 -0
  63. package/packages/core/next-development-candidate-reservation.js +186 -0
  64. package/packages/core/next-development-controller.js +728 -0
  65. package/packages/core/next-development-projection.js +287 -0
  66. package/packages/core/next-development-transition.js +738 -0
  67. package/packages/core/paper-agent-entry.js +11 -5
  68. package/packages/core/paper.js +16 -5
  69. package/packages/core/v4-canonical-contracts.js +5 -0
  70. package/packages/core/v4-floating-consumer-policy.js +29 -9
  71. package/packages/core/v4-tail-reseal-contract.js +26 -0
  72. package/packages/core/v4-tail-reseal-github.js +178 -0
  73. package/packages/core/v4-tail-reseal-receipt.js +225 -0
  74. package/packages/core/v4-tail-reseal.js +594 -0
  75. package/scripts/buildchain-cli-help.mjs +20 -0
  76. package/scripts/check-inventory.mjs +11 -0
  77. package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
  78. package/scripts/generate-next-development-guidance.mjs +49 -0
  79. package/scripts/generate-site-bundle.mjs +16 -3
  80. package/scripts/init-repo.mjs +22 -4
  81. package/scripts/next-development-self-dogfood-harness.mjs +460 -0
  82. package/scripts/next-development-self-dogfood.mjs +597 -0
  83. package/scripts/next-development-transition.mjs +47 -0
  84. package/scripts/site-capability-metadata.mjs +12 -0
  85. package/scripts/v4-consumer-policy.mjs +1 -5
  86. package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
  87. package/scripts/v4-tail-reseal.mjs +416 -0
@@ -139,10 +139,6 @@ export function certifyCommand(options = {}) {
139
139
  stableLockPath,
140
140
  alphaLockPath,
141
141
  });
142
- const selectedLock =
143
- receipt?.invocation?.visibleSelector === "v4-alpha"
144
- ? readJson(path.join(callerRoot, alphaLockPath))
145
- : readJson(path.join(callerRoot, stableLockPath));
146
142
  const authorityScan = scanV4FloatingConsumerPolicy({
147
143
  root: callerRoot,
148
144
  repository,
@@ -150,7 +146,7 @@ export function certifyCommand(options = {}) {
150
146
  invokedWorkflow,
151
147
  invocationSourcePath: receipt?.invocation?.sourcePath,
152
148
  expectedInvocationChannel: receipt?.invocation?.channel,
153
- resolvedWorkflowSha: selectedLock.buildchain?.resolvedSha,
149
+ resolvedWorkflowSha: receipt?.invocation?.resolvedWorkflowSha,
154
150
  resolvedRuntimeSha,
155
151
  stableLockPath,
156
152
  alphaLockPath,
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+
9
+ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
10
+ import { verifyV4TailResealPlatform } from "./v4-tail-reseal.mjs";
11
+
12
+ function flag(args, name, fallback = "") {
13
+ const index = args.indexOf(`--${name}`);
14
+ return index < 0 ? fallback : String(args[index + 1] || "");
15
+ }
16
+
17
+ function fileRoot(file) {
18
+ return `sha256:${crypto
19
+ .createHash("sha256")
20
+ .update(fs.readFileSync(file))
21
+ .digest("hex")}`;
22
+ }
23
+
24
+ function writeCandidate(root, request, content) {
25
+ const platform = request.platforms.find(({ id }) => id === "macos-arm64");
26
+ const payload = path.join(root, "product/release/macos-arm64/payload.bin");
27
+ fs.mkdirSync(path.dirname(payload), { recursive: true });
28
+ fs.writeFileSync(payload, content);
29
+ const relative = path.relative(root, payload);
30
+ const file = {
31
+ path: relative,
32
+ size: fs.statSync(payload).size,
33
+ digest: fileRoot(payload),
34
+ };
35
+ const manifest = {
36
+ contract: "kungfu-buildchain-artifact",
37
+ artifactName: platform.artifactName,
38
+ git: {
39
+ repository: request.repository,
40
+ sha: request.source.sha,
41
+ treeSha: request.source.treeSha,
42
+ runId: String(request.source.runId),
43
+ runAttempt: String(request.source.runAttempt),
44
+ },
45
+ platform: { id: platform.id, name: platform.name },
46
+ files: [
47
+ {
48
+ path: relative,
49
+ size: file.size,
50
+ sha256: file.digest.replace(/^sha256:/u, ""),
51
+ },
52
+ ],
53
+ summary: { fileCount: 1, totalBytes: file.size },
54
+ };
55
+ const manifestPath = path.join(root, "evidence/macos-arm64/manifest.json");
56
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
57
+ fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
58
+ return {
59
+ artifactRoot: v4ContentRoot("tail-reseal-artifact-files", [file]),
60
+ manifestRoot: fileRoot(manifestPath),
61
+ };
62
+ }
63
+
64
+ export function rehearseV4TailResealMacos({
65
+ fixturePath = "contracts/fixtures/v4-tail-reseal-v1/valid.json",
66
+ outputPath = "",
67
+ } = {}) {
68
+ const request = JSON.parse(
69
+ fs.readFileSync(path.resolve(fixturePath), "utf8"),
70
+ );
71
+ const workRoot = fs.mkdtempSync(
72
+ path.join(os.tmpdir(), "buildchain-v4-tail-macos-"),
73
+ );
74
+ try {
75
+ const retainedRoots = writeCandidate(workRoot, request, "unsigned");
76
+ const platform = request.platforms.find(({ id }) => id === "macos-arm64");
77
+ Object.assign(platform, retainedRoots);
78
+ const retained = verifyV4TailResealPlatform({
79
+ request,
80
+ platformId: "macos-arm64",
81
+ artifactRoot: workRoot,
82
+ mode: "retained",
83
+ });
84
+ writeCandidate(workRoot, request, "signed-and-notarized");
85
+ const resealed = verifyV4TailResealPlatform({
86
+ request,
87
+ platformId: "macos-arm64",
88
+ artifactRoot: workRoot,
89
+ mode: "resealed",
90
+ providerReadbackRoot: request.signing.providerReadbackRoot,
91
+ });
92
+ const payload = {
93
+ schema: "kungfu-buildchain-v4-tail-reseal-macos-rehearsal/v1",
94
+ platformId: "macos-arm64",
95
+ retained,
96
+ resealed,
97
+ buildStagesExecuted: 0,
98
+ signingFinalizationExecutions: 1,
99
+ providerReadbackRoot: request.signing.providerReadbackRoot,
100
+ };
101
+ const evidence = {
102
+ ...payload,
103
+ evidenceRoot: v4ContentRoot("tail-reseal-admission", payload),
104
+ };
105
+ if (outputPath) {
106
+ const output = path.resolve(outputPath);
107
+ fs.mkdirSync(path.dirname(output), { recursive: true });
108
+ fs.writeFileSync(output, `${JSON.stringify(evidence, null, 2)}\n`);
109
+ }
110
+ return evidence;
111
+ } finally {
112
+ fs.rmSync(workRoot, { recursive: true, force: true });
113
+ }
114
+ }
115
+
116
+ if (
117
+ process.argv[1] &&
118
+ import.meta.url === pathToFileURL(process.argv[1]).href
119
+ ) {
120
+ try {
121
+ const evidence = rehearseV4TailResealMacos({
122
+ fixturePath: flag(
123
+ process.argv.slice(2),
124
+ "fixture",
125
+ "contracts/fixtures/v4-tail-reseal-v1/valid.json",
126
+ ),
127
+ outputPath: flag(process.argv.slice(2), "output"),
128
+ });
129
+ process.stdout.write(`${JSON.stringify(evidence, null, 2)}\n`);
130
+ } catch (error) {
131
+ console.error(`v4-tail-reseal-macos-rehearsal: ${error.message}`);
132
+ process.exitCode = 1;
133
+ }
134
+ }
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+
8
+ import {
9
+ V4_TAIL_RESEAL_PLATFORMS,
10
+ normalizeV4TailResealRequest,
11
+ planV4TailReseal,
12
+ } from "../packages/core/v4-tail-reseal.js";
13
+ import { createV4TailResealReceipt } from "../packages/core/v4-tail-reseal-receipt.js";
14
+ import { validateV4TailResealGitHubEvidence } from "../packages/core/v4-tail-reseal-github.js";
15
+ import { v4ContentRoot } from "../packages/core/v4-canonical-contracts.js";
16
+
17
+ function flag(args, name, fallback = "") {
18
+ const index = args.indexOf(`--${name}`);
19
+ return index < 0 ? fallback : String(args[index + 1] || "");
20
+ }
21
+
22
+ function required(value, label) {
23
+ const normalized = String(value || "").trim();
24
+ if (!normalized) throw new Error(`${label} is required`);
25
+ return normalized;
26
+ }
27
+
28
+ function readJson(file, label = file) {
29
+ try {
30
+ return JSON.parse(fs.readFileSync(path.resolve(file), "utf8"));
31
+ } catch (error) {
32
+ throw new Error(`could not read ${label}: ${error.message}`);
33
+ }
34
+ }
35
+
36
+ function writeJson(file, value) {
37
+ const output = path.resolve(file);
38
+ fs.mkdirSync(path.dirname(output), { recursive: true });
39
+ fs.writeFileSync(output, `${JSON.stringify(value, null, 2)}\n`);
40
+ return output;
41
+ }
42
+
43
+ function sha256File(file) {
44
+ const hash = crypto.createHash("sha256");
45
+ const descriptor = fs.openSync(file, "r");
46
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
47
+ try {
48
+ for (;;) {
49
+ const count = fs.readSync(descriptor, buffer, 0, buffer.length, null);
50
+ if (count === 0) break;
51
+ hash.update(buffer.subarray(0, count));
52
+ }
53
+ } finally {
54
+ fs.closeSync(descriptor);
55
+ }
56
+ return `sha256:${hash.digest("hex")}`;
57
+ }
58
+
59
+ function appendOutputs(values) {
60
+ if (!process.env.GITHUB_OUTPUT) return;
61
+ fs.appendFileSync(
62
+ process.env.GITHUB_OUTPUT,
63
+ `${Object.entries(values)
64
+ .map(([key, value]) => `${key}=${String(value)}`)
65
+ .join("\n")}\n`,
66
+ );
67
+ }
68
+
69
+ async function githubJson(repository, apiPath, token) {
70
+ const response = await fetch(
71
+ `https://api.github.com/repos/${repository}${apiPath}`,
72
+ {
73
+ headers: {
74
+ accept: "application/vnd.github+json",
75
+ authorization: `Bearer ${required(token, "GITHUB_TOKEN")}`,
76
+ "x-github-api-version": "2022-11-28",
77
+ },
78
+ },
79
+ );
80
+ if (!response.ok)
81
+ throw new Error(`GitHub API ${apiPath} failed: HTTP ${response.status}`);
82
+ return response.json();
83
+ }
84
+
85
+ async function pagedGithubItems(repository, apiPath, key, token) {
86
+ const values = [];
87
+ for (let page = 1; ; page += 1) {
88
+ const separator = apiPath.includes("?") ? "&" : "?";
89
+ const response = await githubJson(
90
+ repository,
91
+ `${apiPath}${separator}per_page=100&page=${page}`,
92
+ token,
93
+ );
94
+ const items = Array.isArray(response?.[key]) ? response[key] : [];
95
+ values.push(...items);
96
+ if (items.length < 100) return values;
97
+ }
98
+ }
99
+
100
+ async function admitFromGitHub(request) {
101
+ const token = required(process.env.GITHUB_TOKEN, "GITHUB_TOKEN");
102
+ const [
103
+ sourceCommit,
104
+ sourceRun,
105
+ sourceJobs,
106
+ sourceArtifacts,
107
+ signingRun,
108
+ signingArtifacts,
109
+ ] = await Promise.all([
110
+ githubJson(request.repository, `/git/commits/${request.source.sha}`, token),
111
+ githubJson(
112
+ request.repository,
113
+ `/actions/runs/${request.source.runId}`,
114
+ token,
115
+ ),
116
+ pagedGithubItems(
117
+ request.repository,
118
+ `/actions/runs/${request.source.runId}/jobs?filter=latest`,
119
+ "jobs",
120
+ token,
121
+ ),
122
+ pagedGithubItems(
123
+ request.repository,
124
+ `/actions/runs/${request.source.runId}/artifacts`,
125
+ "artifacts",
126
+ token,
127
+ ),
128
+ githubJson(
129
+ request.signing.authorityRepository,
130
+ `/actions/runs/${request.signing.authorityRunId}`,
131
+ token,
132
+ ),
133
+ pagedGithubItems(
134
+ request.signing.authorityRepository,
135
+ `/actions/runs/${request.signing.authorityRunId}/artifacts`,
136
+ "artifacts",
137
+ token,
138
+ ),
139
+ ]);
140
+ return validateV4TailResealGitHubEvidence({
141
+ request,
142
+ sourceCommit,
143
+ sourceRun,
144
+ sourceJobs,
145
+ sourceArtifacts,
146
+ signingRun,
147
+ signingArtifacts,
148
+ });
149
+ }
150
+
151
+ function locateManifest(root, platformId) {
152
+ const matches = [];
153
+ const visit = (directory) => {
154
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
155
+ const absolute = path.join(directory, entry.name);
156
+ if (entry.isDirectory()) visit(absolute);
157
+ else if (entry.name === "manifest.json") {
158
+ const value = readJson(absolute);
159
+ if (
160
+ value?.contract === "kungfu-buildchain-artifact" &&
161
+ value?.platform?.id === platformId
162
+ )
163
+ matches.push({ absolute, value });
164
+ }
165
+ }
166
+ };
167
+ visit(root);
168
+ if (matches.length !== 1)
169
+ throw new Error(
170
+ `expected exactly one ${platformId} manifest under ${root}, found ${matches.length}`,
171
+ );
172
+ return matches[0];
173
+ }
174
+
175
+ function resolvePayload(root, relative, platformId) {
176
+ const absolute = path.resolve(root, relative);
177
+ if (
178
+ !absolute.startsWith(`${root}${path.sep}`) ||
179
+ !fs.existsSync(absolute) ||
180
+ !fs.statSync(absolute).isFile()
181
+ )
182
+ throw new Error(`${platformId} manifest file is missing: ${relative}`);
183
+ return absolute;
184
+ }
185
+
186
+ export function verifyV4TailResealPlatform({
187
+ request,
188
+ platformId,
189
+ artifactRoot,
190
+ mode = "retained",
191
+ providerReadbackRoot = null,
192
+ } = {}) {
193
+ const normalized = normalizeV4TailResealRequest(request);
194
+ const platform = normalized.platforms.find(({ id }) => id === platformId);
195
+ if (!platform)
196
+ throw new Error(`tail reseal request does not bind platform ${platformId}`);
197
+ if (!new Set(["retained", "resealed"]).has(mode))
198
+ throw new Error(
199
+ "tail reseal verification mode must be retained or resealed",
200
+ );
201
+ if (mode === "resealed" && platformId !== "macos-arm64")
202
+ throw new Error("only macos-arm64 may produce a resealed byte set");
203
+ const root = path.resolve(artifactRoot || ".");
204
+ const { absolute: manifestPath, value: manifest } = locateManifest(
205
+ root,
206
+ platformId,
207
+ );
208
+ if (manifest.artifactName !== platform.artifactName)
209
+ throw new Error(`${platformId} manifest artifact name mismatch`);
210
+ if (
211
+ manifest.git?.repository !== normalized.repository ||
212
+ manifest.git?.sha !== normalized.source.sha ||
213
+ manifest.git?.treeSha !== normalized.source.treeSha
214
+ )
215
+ throw new Error(`${platformId} manifest source mismatch`);
216
+ if (
217
+ Number(manifest.git?.runId) !== normalized.source.runId ||
218
+ Number(manifest.git?.runAttempt) !== normalized.source.runAttempt
219
+ )
220
+ throw new Error(`${platformId} manifest original run mismatch`);
221
+ const files = (Array.isArray(manifest.files) ? manifest.files : [])
222
+ .map((file, index) => {
223
+ const relative = required(
224
+ file.path || file.name,
225
+ `${platformId} manifest files[${index}] path`,
226
+ );
227
+ const absolute = resolvePayload(root, relative, platformId);
228
+ const size = fs.statSync(absolute).size;
229
+ if (Number(file.size ?? file.bytes) !== size)
230
+ throw new Error(
231
+ `${platformId} manifest file size mismatch: ${relative}`,
232
+ );
233
+ const digest = sha256File(absolute);
234
+ const expected = `sha256:${String(file.sha256 || "").replace(/^sha256:/u, "")}`;
235
+ if (digest !== expected)
236
+ throw new Error(
237
+ `${platformId} manifest file digest mismatch: ${relative}`,
238
+ );
239
+ return { path: relative, size, digest };
240
+ })
241
+ .sort((left, right) =>
242
+ Buffer.from(left.path).compare(Buffer.from(right.path)),
243
+ );
244
+ if (files.length === 0)
245
+ throw new Error(`${platformId} manifest has no retained files`);
246
+ const observedArtifactRoot = v4ContentRoot(
247
+ "tail-reseal-artifact-files",
248
+ files,
249
+ );
250
+ const observedManifestRoot = sha256File(manifestPath);
251
+ const retained = mode === "retained";
252
+ if (
253
+ retained &&
254
+ (observedArtifactRoot !== platform.artifactRoot ||
255
+ observedManifestRoot !== platform.manifestRoot)
256
+ )
257
+ throw new Error(
258
+ `${platformId} retained artifact or manifest root mismatch`,
259
+ );
260
+ if (!retained) {
261
+ if (
262
+ observedArtifactRoot === platform.artifactRoot ||
263
+ observedManifestRoot === platform.manifestRoot
264
+ )
265
+ throw new Error(
266
+ "macos-arm64 reseal did not produce a distinct signed manifest",
267
+ );
268
+ if (providerReadbackRoot !== normalized.signing.providerReadbackRoot)
269
+ throw new Error("macos-arm64 provider readback root mismatch");
270
+ }
271
+ return {
272
+ platformId,
273
+ artifactRoot: observedArtifactRoot,
274
+ manifestRoot: observedManifestRoot,
275
+ capsuleRoot: platform.capsuleRoot,
276
+ byteIdentical: retained,
277
+ providerReadbackRoot: retained ? null : providerReadbackRoot,
278
+ };
279
+ }
280
+
281
+ function collectReadbacks(directory) {
282
+ const byPlatform = new Map();
283
+ for (const entry of fs.readdirSync(path.resolve(directory), {
284
+ recursive: true,
285
+ withFileTypes: true,
286
+ })) {
287
+ if (!entry.isFile() || !entry.name.endsWith("readback.json")) continue;
288
+ const value = readJson(path.join(entry.parentPath, entry.name));
289
+ if (byPlatform.has(value.platformId))
290
+ throw new Error(`duplicate tail reseal readback for ${value.platformId}`);
291
+ byPlatform.set(value.platformId, value);
292
+ }
293
+ return V4_TAIL_RESEAL_PLATFORMS.map((platformId) => {
294
+ if (!byPlatform.has(platformId))
295
+ throw new Error(`missing tail reseal readback for ${platformId}`);
296
+ return byPlatform.get(platformId);
297
+ });
298
+ }
299
+
300
+ export function runV4TailResealCli(args = process.argv.slice(2)) {
301
+ const [command, ...options] = args;
302
+ const requestPath = flag(options, "request");
303
+ if (command === "plan") {
304
+ const request = normalizeV4TailResealRequest(
305
+ readJson(required(requestPath, "--request"), "tail reseal request"),
306
+ );
307
+ const plan = planV4TailReseal(request);
308
+ const output = writeJson(
309
+ flag(options, "output", ".buildchain/tail-reseal/plan.json"),
310
+ plan,
311
+ );
312
+ appendOutputs({
313
+ "plan-root": plan.planRoot,
314
+ "plan-path": path.relative(process.cwd(), output),
315
+ "source-run-id": request.source.runId,
316
+ "source-run-attempt": request.source.runAttempt,
317
+ "source-sha": request.source.sha,
318
+ "source-tree-sha": request.source.treeSha,
319
+ "runtime-sha": request.runtime.sha,
320
+ "consumer-policy-receipt-root": request.runtime.consumerPolicyReceiptRoot,
321
+ "warrant-readback-root": request.warrant.stateReadbackRoot,
322
+ "signing-provider-readback-root": request.signing.providerReadbackRoot,
323
+ "release-tail-provider-readback-root":
324
+ request.releaseTail.providerReadbackRoot,
325
+ "signing-authority-repository": request.signing.authorityRepository,
326
+ "signing-authority-run-id": request.signing.authorityRunId,
327
+ "signing-result-artifact": request.signing.resultArtifact,
328
+ "target-version": request.target.version,
329
+ "platforms-json": JSON.stringify(request.platforms),
330
+ });
331
+ return plan;
332
+ }
333
+ if (command === "admit") {
334
+ const request = normalizeV4TailResealRequest(
335
+ readJson(required(requestPath, "--request"), "tail reseal request"),
336
+ );
337
+ return admitFromGitHub(request).then((admission) => {
338
+ const output = writeJson(
339
+ flag(options, "output", ".buildchain/tail-reseal/admission.json"),
340
+ admission,
341
+ );
342
+ appendOutputs({
343
+ "admission-root": admission.admissionRoot,
344
+ "admission-path": path.relative(process.cwd(), output),
345
+ });
346
+ return admission;
347
+ });
348
+ }
349
+ if (command === "verify-platform") {
350
+ const readback = verifyV4TailResealPlatform({
351
+ request: readJson(required(requestPath, "--request")),
352
+ platformId: required(flag(options, "platform"), "--platform"),
353
+ artifactRoot: flag(options, "artifact-root", process.cwd()),
354
+ mode: flag(options, "mode", "retained"),
355
+ providerReadbackRoot: flag(options, "provider-readback-root") || null,
356
+ });
357
+ const output = writeJson(
358
+ flag(
359
+ options,
360
+ "output",
361
+ `.buildchain/tail-reseal/${readback.platformId}-readback.json`,
362
+ ),
363
+ readback,
364
+ );
365
+ appendOutputs({
366
+ "readback-path": path.relative(process.cwd(), output),
367
+ "artifact-root": readback.artifactRoot,
368
+ "manifest-root": readback.manifestRoot,
369
+ });
370
+ return readback;
371
+ }
372
+ if (command === "seal") {
373
+ const request = readJson(required(requestPath, "--request"));
374
+ const receipt = createV4TailResealReceipt({
375
+ request,
376
+ plan: readJson(required(flag(options, "plan"), "--plan")),
377
+ readbacks: collectReadbacks(
378
+ required(flag(options, "readbacks"), "--readbacks"),
379
+ ),
380
+ passport: readJson(required(flag(options, "passport"), "--passport")),
381
+ protectedReadbackRoot: required(
382
+ flag(options, "protected-readback-root"),
383
+ "--protected-readback-root",
384
+ ),
385
+ currentRun: {
386
+ id: Number(required(process.env.GITHUB_RUN_ID, "GITHUB_RUN_ID")),
387
+ attempt: Number(
388
+ required(process.env.GITHUB_RUN_ATTEMPT, "GITHUB_RUN_ATTEMPT"),
389
+ ),
390
+ },
391
+ });
392
+ const output = writeJson(
393
+ flag(options, "output", ".buildchain/tail-reseal/receipt.json"),
394
+ receipt,
395
+ );
396
+ appendOutputs({
397
+ "receipt-root": receipt.receiptRoot,
398
+ "receipt-path": path.relative(process.cwd(), output),
399
+ });
400
+ return receipt;
401
+ }
402
+ throw new Error(
403
+ "usage: buildchain tail-reseal <plan|admit|verify-platform|seal> --request <request.json> [options]",
404
+ );
405
+ }
406
+
407
+ if (
408
+ !process.env.BUILDCHAIN_EMBEDDED_ENTRYPOINT &&
409
+ process.argv[1] &&
410
+ import.meta.url === pathToFileURL(process.argv[1]).href
411
+ ) {
412
+ runV4TailResealCli().catch((error) => {
413
+ console.error(`tail-reseal: ${error.message}`);
414
+ process.exitCode = 1;
415
+ });
416
+ }