@kungfu-tech/buildchain 2.12.9-alpha.1 → 2.13.0-alpha.0

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 (39) hide show
  1. package/actions/run-lifecycle/README.md +5 -0
  2. package/bin/buildchain.mjs +62 -0
  3. package/dist/site/buildchain-contract.json +33 -23
  4. package/dist/site/buildchain-site.json +72 -36
  5. package/dist/site/capability-registry.json +3 -3
  6. package/dist/site/cli-registry.json +24 -0
  7. package/dist/site/controller-registry.json +11 -3
  8. package/dist/site/kfd-claims.json +64 -13
  9. package/dist/site/kfd-upstream-aggregate.json +1 -1
  10. package/dist/site/manual-registry.json +9 -9
  11. package/dist/site/node-api-registry.json +19 -6
  12. package/dist/site/page-registry.json +59 -23
  13. package/dist/site/public-surface-audit.json +47 -10
  14. package/dist/site/publication-registry.json +4 -4
  15. package/dist/site/release-provenance.json +1 -0
  16. package/dist/site/site-manifest.json +13 -13
  17. package/dist/site/workflow-registry.json +7 -4
  18. package/docs/MAP.md +1 -0
  19. package/docs/artifact-verification-envelope.md +113 -0
  20. package/docs/cli.md +22 -0
  21. package/docs/install.md +1 -3
  22. package/docs/publish-transaction.md +1 -1
  23. package/docs/release-candidate.md +5 -0
  24. package/docs/release-governance.md +7 -5
  25. package/docs/release-propagation.md +3 -0
  26. package/docs/reusable-build-surface.md +13 -0
  27. package/docs/versioning.md +1 -0
  28. package/package.json +2 -1
  29. package/packages/core/README.md +13 -0
  30. package/packages/core/artifact-passport.js +6 -1
  31. package/packages/core/artifact-verification-envelope.js +386 -0
  32. package/packages/core/buildchain-config.js +6 -2
  33. package/packages/core/controller-evidence.js +2 -3
  34. package/packages/core/index.js +10 -0
  35. package/packages/core/release-line-bootstrap.js +24 -0
  36. package/scripts/check-inventory.mjs +5 -0
  37. package/scripts/generate-site-bundle.mjs +4 -0
  38. package/scripts/release-candidate-resolver.mjs +52 -29
  39. package/scripts/run-lifecycle-core.mjs +50 -6
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import http from "node:http";
4
4
  import https from "node:https";
5
5
  import path from "node:path";
6
+ import { sealArtifactVerificationReport } from "./artifact-verification-envelope.js";
6
7
  import { readJsonFromLocation, sha256File, verifyReleasePassport } from "./release-passport.js";
7
8
 
8
9
  export const ARTIFACT_VERIFICATION_CONTRACT = "kungfu-buildchain-artifact-verification";
@@ -708,6 +709,7 @@ export async function verifyArtifactPassport({
708
709
  subjectDigest = "",
709
710
  subjectKind = "",
710
711
  npmRegistryBaseUrl = "",
712
+ verificationEnvelope = undefined,
711
713
  } = {}) {
712
714
  const resolvedSubject = await resolveArtifactSubject(subject, { cwd, subjectDigest, subjectKind, npmRegistryBaseUrl });
713
715
  const issues = [];
@@ -789,7 +791,7 @@ export async function verifyArtifactPassport({
789
791
  }
790
792
  const ok = passportReport.ok && Boolean(match);
791
793
  const outcome = ok ? "pass" : "fail";
792
- return {
794
+ const report = {
793
795
  schemaVersion: 1,
794
796
  contract: ARTIFACT_VERIFICATION_CONTRACT,
795
797
  outcome,
@@ -817,6 +819,9 @@ export async function verifyArtifactPassport({
817
819
  : undefined,
818
820
  issues,
819
821
  };
822
+ return verificationEnvelope
823
+ ? sealArtifactVerificationReport({ report, ...verificationEnvelope })
824
+ : report;
820
825
  }
821
826
 
822
827
  export async function explainArtifactPassport(options = {}) {
@@ -0,0 +1,386 @@
1
+ import crypto from "node:crypto";
2
+
3
+ export const ARTIFACT_VERIFICATION_ENVELOPE_CONTRACT =
4
+ "kungfu-buildchain-artifact-verification-envelope";
5
+ export const ARTIFACT_VERIFICATION_ENVELOPE_CHECK_CONTRACT =
6
+ "kungfu-buildchain-artifact-verification-envelope-check";
7
+ export const KFX_ADMISSION_INPUTS_CONTRACT =
8
+ "kungfu-buildchain-kfx-admission-inputs";
9
+
10
+ const ARTIFACT_VERIFICATION_CONTRACT =
11
+ "kungfu-buildchain-artifact-verification";
12
+ const KFX_TRUST_INPUTS_SCHEMA = "kungfu.kfx-trust-inputs/v1";
13
+ const KFD_ASSESSMENT_SCHEMA = "kungfu.trust.assessment/v1";
14
+ const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
15
+ const ROOT_FIELDS = Object.freeze([
16
+ "packageRoot",
17
+ "sourceRoot",
18
+ "dependencyRoot",
19
+ "buildPlanRoot",
20
+ "toolchainRoot",
21
+ "artifactRoot",
22
+ "qualificationRoot",
23
+ "verifierRoot",
24
+ ]);
25
+ const BINDING_FIELDS = Object.freeze([
26
+ "schema",
27
+ ...ROOT_FIELDS,
28
+ "issuer",
29
+ "publisher",
30
+ "contractVersion",
31
+ ]);
32
+
33
+ function stableJson(value) {
34
+ if (Array.isArray(value)) {
35
+ return `[${value.map(stableJson).join(",")}]`;
36
+ }
37
+ if (value && typeof value === "object") {
38
+ return `{${Object.keys(value)
39
+ .sort()
40
+ .map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
41
+ .join(",")}}`;
42
+ }
43
+ return JSON.stringify(value);
44
+ }
45
+
46
+ function jsonClone(value) {
47
+ return JSON.parse(JSON.stringify(value));
48
+ }
49
+
50
+ export function artifactVerificationEnvelopeDigest(value) {
51
+ return `sha256:${crypto.createHash("sha256").update(stableJson(value)).digest("hex")}`;
52
+ }
53
+
54
+ function issue(code, message, details = {}) {
55
+ return { level: "error", code, message, details };
56
+ }
57
+
58
+ function isObject(value) {
59
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
60
+ }
61
+
62
+ function isRoot(value) {
63
+ return SHA256_PATTERN.test(String(value || ""));
64
+ }
65
+
66
+ function nonEmptyString(value, label) {
67
+ const normalized = String(value || "").trim();
68
+ if (!normalized) {
69
+ throw new Error(`${label} must be a non-empty string`);
70
+ }
71
+ return normalized;
72
+ }
73
+
74
+ function nonNegativeInteger(value, label) {
75
+ if (!Number.isSafeInteger(value) || value < 0) {
76
+ throw new Error(`${label} must be a non-negative safe integer`);
77
+ }
78
+ return value;
79
+ }
80
+
81
+ function normalizeBindings(value = {}) {
82
+ if (!isObject(value)) {
83
+ throw new Error("bindings must be an object");
84
+ }
85
+ const supplied = Object.keys(value).sort();
86
+ const expected = [...BINDING_FIELDS].sort();
87
+ if (stableJson(supplied) !== stableJson(expected)) {
88
+ throw new Error(`bindings must contain exactly: ${BINDING_FIELDS.join(", ")}`);
89
+ }
90
+ if (value.schema !== KFX_TRUST_INPUTS_SCHEMA) {
91
+ throw new Error(`bindings.schema must be ${KFX_TRUST_INPUTS_SCHEMA}`);
92
+ }
93
+ const normalized = { schema: KFX_TRUST_INPUTS_SCHEMA };
94
+ for (const field of ROOT_FIELDS) {
95
+ const root = String(value[field] || "");
96
+ if (!isRoot(root)) {
97
+ throw new Error(`bindings.${field} must be a canonical lowercase sha256 root`);
98
+ }
99
+ normalized[field] = root;
100
+ }
101
+ normalized.issuer = nonEmptyString(value.issuer, "bindings.issuer");
102
+ normalized.publisher = nonEmptyString(value.publisher, "bindings.publisher");
103
+ normalized.contractVersion = nonEmptyString(value.contractVersion, "bindings.contractVersion");
104
+ return normalized;
105
+ }
106
+
107
+ function normalizeRevocation(value = {}, { issuedAt } = {}) {
108
+ if (!isObject(value)) {
109
+ throw new Error("revocation must be an object");
110
+ }
111
+ const revoked = value.revoked === true;
112
+ const status = nonEmptyString(value.status || (revoked ? "revoked" : "active"), "revocation.status");
113
+ if (!new Set(["active", "revoked"]).has(status) || revoked !== (status === "revoked")) {
114
+ throw new Error("revocation.status must agree with revocation.revoked");
115
+ }
116
+ const checkedAt = nonNegativeInteger(value.checkedAt ?? issuedAt, "revocation.checkedAt");
117
+ const root = String(value.root || "");
118
+ if (!isRoot(root)) {
119
+ throw new Error("revocation.root must be a canonical lowercase sha256 root");
120
+ }
121
+ return {
122
+ status,
123
+ revoked,
124
+ checkedAt,
125
+ source: nonEmptyString(value.source, "revocation.source"),
126
+ root,
127
+ };
128
+ }
129
+
130
+ function envelopeRoot(value) {
131
+ const basis = jsonClone(value);
132
+ if (isObject(basis.envelope)) {
133
+ delete basis.envelope.root;
134
+ }
135
+ return artifactVerificationEnvelopeDigest(basis);
136
+ }
137
+
138
+ function recomputeKfdReportRoot(report) {
139
+ const basis = jsonClone(report);
140
+ delete basis.report_hash;
141
+ return artifactVerificationEnvelopeDigest(basis);
142
+ }
143
+
144
+ export function verifyArtifactVerificationEnvelope({
145
+ envelope,
146
+ assessmentTime = Math.floor(Date.now() / 1000),
147
+ expectedEnvelopeRoot = "",
148
+ expectedIssuer = "",
149
+ expectedPublisher = "",
150
+ expectedContractVersion = "",
151
+ } = {}) {
152
+ const issues = [];
153
+ const at = Number.isSafeInteger(assessmentTime) && assessmentTime >= 0
154
+ ? assessmentTime
155
+ : -1;
156
+ if (!isObject(envelope)) {
157
+ issues.push(issue("envelope.object", "artifact verification envelope must be an object"));
158
+ return {
159
+ schemaVersion: 1,
160
+ contract: ARTIFACT_VERIFICATION_ENVELOPE_CHECK_CONTRACT,
161
+ ok: false,
162
+ outcome: "fail",
163
+ assessmentTime,
164
+ issues,
165
+ };
166
+ }
167
+
168
+ const metadata = isObject(envelope.envelope) ? envelope.envelope : {};
169
+ if (
170
+ metadata.contract !== ARTIFACT_VERIFICATION_ENVELOPE_CONTRACT ||
171
+ metadata.schemaVersion !== 1 ||
172
+ metadata.canonicalization !== "buildchain-stable-json/v1"
173
+ ) {
174
+ issues.push(issue("envelope.contract", "artifact verification envelope contract metadata is invalid"));
175
+ }
176
+ const computedEnvelopeRoot = envelopeRoot(envelope);
177
+ if (!isRoot(metadata.root) || metadata.root !== computedEnvelopeRoot) {
178
+ issues.push(issue("envelope.root", "artifact verification envelope root does not match canonical content", {
179
+ expected: computedEnvelopeRoot,
180
+ actual: metadata.root || "",
181
+ }));
182
+ }
183
+ if (expectedEnvelopeRoot && metadata.root !== expectedEnvelopeRoot) {
184
+ issues.push(issue("envelope.root.expected", "artifact verification envelope root does not match the pinned root"));
185
+ }
186
+
187
+ const verificationOk =
188
+ envelope.contract === ARTIFACT_VERIFICATION_CONTRACT &&
189
+ envelope.schemaVersion === 1 &&
190
+ envelope.outcome === "pass" &&
191
+ envelope.ok === true &&
192
+ envelope.trust === "pass" &&
193
+ envelope.passport?.verification?.ok === true &&
194
+ envelope.passport?.verification?.trust === "pass";
195
+ if (!verificationOk) {
196
+ issues.push(issue("verification.invalid", "base artifact verification must be a passing v1 report"));
197
+ }
198
+
199
+ let bindings;
200
+ try {
201
+ bindings = normalizeBindings(envelope.bindings);
202
+ } catch (error) {
203
+ issues.push(issue("bindings.invalid", error.message));
204
+ }
205
+ if (bindings) {
206
+ const subjectRoot = String(envelope.subject?.digest || "");
207
+ const matchedRoot = String(envelope.match?.artifact?.digest || "");
208
+ if (
209
+ !isRoot(subjectRoot) ||
210
+ subjectRoot !== matchedRoot ||
211
+ bindings.artifactRoot !== subjectRoot
212
+ ) {
213
+ issues.push(issue("bindings.artifactRoot", "artifact root must match the exact verified subject and passport artifact"));
214
+ }
215
+ if (expectedIssuer && bindings.issuer !== expectedIssuer) {
216
+ issues.push(issue("bindings.issuer", "issuer does not match the expected authority"));
217
+ }
218
+ if (expectedPublisher && bindings.publisher !== expectedPublisher) {
219
+ issues.push(issue("bindings.publisher", "publisher does not match the expected identity"));
220
+ }
221
+ if (expectedContractVersion && bindings.contractVersion !== expectedContractVersion) {
222
+ issues.push(issue("bindings.contractVersion", "verification contract version does not match the expected version"));
223
+ }
224
+ }
225
+
226
+ const issuedAt = envelope.issuedAt;
227
+ const expiresAt = envelope.expiresAt;
228
+ if (
229
+ at < 0 ||
230
+ !Number.isSafeInteger(issuedAt) ||
231
+ issuedAt < 0 ||
232
+ !Number.isSafeInteger(expiresAt) ||
233
+ expiresAt <= issuedAt ||
234
+ at < issuedAt ||
235
+ at >= expiresAt
236
+ ) {
237
+ issues.push(issue("lifecycle.invalid", "envelope must be active at assessmentTime with valid issuedAt/expiresAt bounds"));
238
+ }
239
+ const revocation = envelope.revocation;
240
+ if (
241
+ !isObject(revocation) ||
242
+ !isRoot(revocation.root) ||
243
+ !Number.isSafeInteger(revocation.checkedAt) ||
244
+ revocation.checkedAt < issuedAt ||
245
+ (at >= 0 && revocation.checkedAt > at) ||
246
+ !["active", "revoked"].includes(revocation.status) ||
247
+ revocation.revoked !== (revocation.status === "revoked") ||
248
+ envelope.revoked !== revocation.revoked
249
+ ) {
250
+ issues.push(issue("lifecycle.revocation", "revocation binding is invalid or inconsistent"));
251
+ }
252
+ if (envelope.revoked === true) {
253
+ issues.push(issue("lifecycle.revoked", "artifact verification envelope has been revoked"));
254
+ }
255
+
256
+ const assessment = envelope.kfdAssessment;
257
+ const report = isObject(assessment?.report) ? assessment.report : {};
258
+ if (
259
+ !isObject(assessment) ||
260
+ assessment.schema !== KFD_ASSESSMENT_SCHEMA ||
261
+ assessment.state !== "fresh" ||
262
+ report.state !== "fresh"
263
+ ) {
264
+ issues.push(issue("kfd.assessment.state", "KFD assessment must be a fresh ADR-0052 assessment"));
265
+ }
266
+ if (
267
+ !isRoot(assessment?.assessment_key) ||
268
+ report.assessment_key !== assessment?.assessment_key
269
+ ) {
270
+ issues.push(issue("kfd.assessment.key", "KFD assessment key must be canonical and match the report"));
271
+ }
272
+ const computedReportRoot = isObject(report) ? recomputeKfdReportRoot(report) : "";
273
+ if (!isRoot(report.report_hash) || report.report_hash !== computedReportRoot) {
274
+ issues.push(issue("kfd.report.root", "KFD report hash does not match canonical report content", {
275
+ expected: computedReportRoot,
276
+ actual: report.report_hash || "",
277
+ }));
278
+ }
279
+ if (
280
+ !String(report.purpose || "").trim() ||
281
+ !isRoot(report.query_proof_root) ||
282
+ !isRoot(report.contract_world?.root) ||
283
+ !isRoot(report.policy?.root) ||
284
+ !Array.isArray(report.fact_surfaces) ||
285
+ report.fact_surfaces.length === 0 ||
286
+ report.fact_surfaces.some((surface) => !isRoot(surface?.root))
287
+ ) {
288
+ issues.push(issue("kfd.report.bindings", "KFD report must bind purpose, query proof, contract world, policy, and fact-surface roots"));
289
+ }
290
+ if (bindings && bindings.qualificationRoot !== report.report_hash) {
291
+ issues.push(issue("bindings.qualificationRoot", "qualification root must equal the KFD report hash"));
292
+ }
293
+
294
+ const ok = issues.length === 0;
295
+ return {
296
+ schemaVersion: 1,
297
+ contract: ARTIFACT_VERIFICATION_ENVELOPE_CHECK_CONTRACT,
298
+ ok,
299
+ outcome: ok ? "pass" : "fail",
300
+ assessmentTime,
301
+ envelopeRoot: metadata.root || "",
302
+ bindings: bindings || undefined,
303
+ kfdAssessment: isObject(assessment)
304
+ ? {
305
+ state: assessment.state || "",
306
+ assessmentKey: assessment.assessment_key || "",
307
+ reportRoot: report.report_hash || "",
308
+ }
309
+ : undefined,
310
+ issues,
311
+ };
312
+ }
313
+
314
+ export function sealArtifactVerificationReport({
315
+ report,
316
+ bindings,
317
+ kfdAssessment,
318
+ issuedAt,
319
+ expiresAt,
320
+ revocation,
321
+ } = {}) {
322
+ if (!isObject(report)) {
323
+ throw new Error("report must be an artifact verification object");
324
+ }
325
+ const normalizedIssuedAt = nonNegativeInteger(issuedAt, "issuedAt");
326
+ const normalizedExpiresAt = nonNegativeInteger(expiresAt, "expiresAt");
327
+ if (normalizedExpiresAt <= normalizedIssuedAt) {
328
+ throw new Error("expiresAt must be greater than issuedAt");
329
+ }
330
+ const normalizedRevocation = normalizeRevocation(revocation, {
331
+ issuedAt: normalizedIssuedAt,
332
+ });
333
+ const result = {
334
+ ...jsonClone(report),
335
+ issuedAt: normalizedIssuedAt,
336
+ expiresAt: normalizedExpiresAt,
337
+ revoked: normalizedRevocation.revoked,
338
+ revocation: normalizedRevocation,
339
+ bindings: normalizeBindings(bindings),
340
+ kfdAssessment: jsonClone(kfdAssessment),
341
+ envelope: {
342
+ schemaVersion: 1,
343
+ contract: ARTIFACT_VERIFICATION_ENVELOPE_CONTRACT,
344
+ canonicalization: "buildchain-stable-json/v1",
345
+ root: "",
346
+ },
347
+ };
348
+ result.envelope.root = envelopeRoot(result);
349
+ const check = verifyArtifactVerificationEnvelope({
350
+ envelope: result,
351
+ assessmentTime: normalizedRevocation.checkedAt,
352
+ });
353
+ if (!check.ok) {
354
+ throw new Error(`artifact verification envelope is invalid: ${check.issues.map((entry) => entry.code).join(", ")}`);
355
+ }
356
+ return result;
357
+ }
358
+
359
+ export function projectArtifactVerificationEnvelopeToKfx({
360
+ envelope,
361
+ assessmentTime = Math.floor(Date.now() / 1000),
362
+ expectedEnvelopeRoot = "",
363
+ expectedIssuer = "",
364
+ expectedPublisher = "",
365
+ expectedContractVersion = "",
366
+ } = {}) {
367
+ const check = verifyArtifactVerificationEnvelope({
368
+ envelope,
369
+ assessmentTime,
370
+ expectedEnvelopeRoot,
371
+ expectedIssuer,
372
+ expectedPublisher,
373
+ expectedContractVersion,
374
+ });
375
+ if (!check.ok) {
376
+ throw new Error(`artifact verification envelope failed closed: ${check.issues.map((entry) => entry.code).join(", ")}`);
377
+ }
378
+ return {
379
+ schemaVersion: 1,
380
+ contract: KFX_ADMISSION_INPUTS_CONTRACT,
381
+ envelopeRoot: envelope.envelope.root,
382
+ attestation: jsonClone(envelope),
383
+ trustInputs: jsonClone(envelope.bindings),
384
+ kfdAssessment: jsonClone(envelope.kfdAssessment),
385
+ };
386
+ }
@@ -1181,7 +1181,7 @@ export function getPublishContract(loadedConfig) {
1181
1181
  return loadedConfig?.config?.publish;
1182
1182
  }
1183
1183
 
1184
- export function runLifecycleStage({ cwd = process.cwd(), loadedConfig, name, stage, env: extraEnv }) {
1184
+ export function runLifecycleStage({ cwd = process.cwd(), loadedConfig, name, stage, env: extraEnv, timeoutMinutes }) {
1185
1185
  const lifecycle = loadedConfig?.config?.lifecycle || {};
1186
1186
  const selected = stage || getLifecycleStage(loadedConfig, name);
1187
1187
  if (!selected) {
@@ -1193,7 +1193,11 @@ export function runLifecycleStage({ cwd = process.cwd(), loadedConfig, name, sta
1193
1193
  ...(selected.env || {}),
1194
1194
  ...(extraEnv || {}),
1195
1195
  };
1196
- const timeout = selected.timeoutMinutes ? selected.timeoutMinutes * 60_000 : undefined;
1196
+ const effectiveTimeoutMinutes = selected.timeoutMinutes ?? timeoutMinutes;
1197
+ if (effectiveTimeoutMinutes !== undefined && (!Number.isFinite(effectiveTimeoutMinutes) || effectiveTimeoutMinutes <= 0)) {
1198
+ throw new Error("lifecycle timeoutMinutes must be a positive number");
1199
+ }
1200
+ const timeout = effectiveTimeoutMinutes ? effectiveTimeoutMinutes * 60_000 : undefined;
1197
1201
  const execOptions = {
1198
1202
  cwd,
1199
1203
  env,
@@ -344,7 +344,6 @@ export function createControllerReceipt({ plan, stages = [], evidence = [], reas
344
344
  const issues = status === "passed"
345
345
  ? missingEvidence.map((kind) => `required controller evidence is missing: ${kind}`)
346
346
  : [];
347
- const qualifying = status === "passed" && plan.qualifying === true && issues.length === 0;
348
347
  const receipt = {
349
348
  schemaVersion: 1,
350
349
  contract: BUILDCHAIN_CONTROLLER_EVIDENCE_CONTRACT,
@@ -354,11 +353,11 @@ export function createControllerReceipt({ plan, stages = [], evidence = [], reas
354
353
  runtime: { ...plan.runtime },
355
354
  planDigest: plan.digest,
356
355
  status,
357
- qualifying,
356
+ qualifying: status === "passed" && plan.qualifying === true && issues.length === 0,
358
357
  stages: normalizedStages,
359
358
  evidence: normalizedEvidence,
360
359
  issues,
361
- ...(reason && !qualifying ? {
360
+ ...(reason ? {
362
361
  reason: {
363
362
  code: nonEmptyString(reason.code, "reason.code"),
364
363
  summary: nonEmptyString(reason.summary, "reason.summary"),
@@ -179,6 +179,16 @@ export {
179
179
  verifyArtifactPassport,
180
180
  } from "./artifact-passport.js";
181
181
 
182
+ export {
183
+ ARTIFACT_VERIFICATION_ENVELOPE_CHECK_CONTRACT,
184
+ ARTIFACT_VERIFICATION_ENVELOPE_CONTRACT,
185
+ KFX_ADMISSION_INPUTS_CONTRACT,
186
+ artifactVerificationEnvelopeDigest,
187
+ projectArtifactVerificationEnvelopeToKfx,
188
+ sealArtifactVerificationReport,
189
+ verifyArtifactVerificationEnvelope,
190
+ } from "./artifact-verification-envelope.js";
191
+
182
192
  export {
183
193
  BUILDCHAIN_CONTRACT_LOCK,
184
194
  BUILDCHAIN_RUNTIME_CONTRACT_WORLD,
@@ -65,6 +65,29 @@ function writeJson(value) {
65
65
  return `${JSON.stringify(value, null, 2)}\n`;
66
66
  }
67
67
 
68
+ function updateReleaseImpactVersionState(cwd, plan) {
69
+ const filePath = path.join(cwd, ".buildchain", "release-impact.json");
70
+ if (!fs.existsSync(filePath)) {
71
+ return;
72
+ }
73
+ const current = readJsonIfExists(filePath);
74
+ if (!current || typeof current !== "object" || Array.isArray(current)) {
75
+ throw new Error(".buildchain/release-impact.json must be a JSON object");
76
+ }
77
+ const next = {
78
+ ...current,
79
+ release: {
80
+ ...(current.release && typeof current.release === "object" ? current.release : {}),
81
+ version: plan.initialVersion,
82
+ line: plan.line,
83
+ },
84
+ };
85
+ const content = writeJson(next);
86
+ if (content !== writeJson(current)) {
87
+ fs.writeFileSync(filePath, content);
88
+ }
89
+ }
90
+
68
91
  function discoverVersionStateFiles(cwd, loadedConfig) {
69
92
  if (loadedConfig?.config?.version?.files?.length) {
70
93
  return discoverConfiguredVersionStateFiles(cwd, loadedConfig);
@@ -212,6 +235,7 @@ export function writeReleaseLineBootstrapVersionState({
212
235
  for (const file of changed) {
213
236
  fs.writeFileSync(path.join(cwd, file.path), file.content);
214
237
  }
238
+ updateReleaseImpactVersionState(cwd, plan);
215
239
  const lifecycleVersionState =
216
240
  getLifecycleStage(loadedConfig, "version-state") ||
217
241
  getLifecycleStage(loadedConfig, "version_state");
@@ -21,6 +21,7 @@ const requiredPaths = [
21
21
  ".github/pull_request_template.md",
22
22
  "bin/buildchain.mjs",
23
23
  "packages/core/homebrew.js",
24
+ "packages/core/artifact-verification-envelope.js",
24
25
  "packages/core/build-facts.js",
25
26
  "packages/core/publication-package.js",
26
27
  "packages/core/release-line-bootstrap.js",
@@ -239,6 +240,9 @@ if (rootPackage.exports?.["./buildchain-contract"] !== "./packages/core/buildcha
239
240
  if (rootPackage.exports?.["./controller-evidence"] !== "./packages/core/controller-evidence.js") {
240
241
  throw new Error("root package must export @kungfu-tech/buildchain/controller-evidence");
241
242
  }
243
+ if (rootPackage.exports?.["./artifact-verification-envelope"] !== "./packages/core/artifact-verification-envelope.js") {
244
+ throw new Error("root package must export @kungfu-tech/buildchain/artifact-verification-envelope");
245
+ }
242
246
  if (rootPackage.exports?.["./issue-reporting"] !== "./packages/core/issue-reporting.js") {
243
247
  throw new Error("root package must export @kungfu-tech/buildchain/issue-reporting");
244
248
  }
@@ -843,6 +847,7 @@ for (const requiredSnippet of [
843
847
  "expected-publication-version: ${{ needs.publication-plan.outputs.version }}",
844
848
  "publication-version: ${{ needs.publication-plan.outputs.version }}",
845
849
  "name: Plan exact publication version",
850
+ "name: Install exact publication planning dependencies",
846
851
  "DRY_RUN: ${{ inputs.dry-run }}",
847
852
  "if (dryRun) {",
848
853
  "Enforce Buildchain stable release canary gate",
@@ -444,6 +444,8 @@ function cliCommandMeta(id) {
444
444
  "publication-artifact": { group: "reusable-build", purpose: "Generate publication artifact manifests, passports, and source bundles for paper/report repositories." },
445
445
  "publication-artifact-manifest": { group: "reusable-build", purpose: "Write a site-consumable publication artifact manifest, publication passport, and source bundle." },
446
446
  "publication-artifact-npm-package": { group: "reusable-build", purpose: "Synthesize the declared npm paper package from a publication artifact manifest, passport, registry, source bundle, and primary artifact." },
447
+ project: { group: "release-passport-trust", purpose: "Inspect read-only consumer projection command families." },
448
+ "project-kfx-admission": { group: "release-passport-trust", purpose: "Project one verified sealed envelope into direct KFX admission inputs without reconstructing evidence." },
447
449
  "release-dry-run": { group: "governance-versioning", purpose: "Explain what a channel merge would publish before the PR is merged." },
448
450
  "release-line-open": { group: "governance-versioning", purpose: "Plan or write the initial version-state commit for a new minor release line." },
449
451
  "release-propagation": { group: "site-and-propagation", purpose: "Plan channel-preserving downstream release PRs and write exact upstream release locks." },
@@ -455,6 +457,7 @@ function cliCommandMeta(id) {
455
457
  validate: { group: "getting-started", purpose: "Validate .buildchain/buildchain.toml and declared lifecycle surfaces." },
456
458
  verify: { group: "release-passport-trust", purpose: "Inspect release and artifact verification command families." },
457
459
  "verify-artifact": { group: "release-passport-trust", purpose: "Verify artifact subjects against release passport evidence." },
460
+ "verify-artifact-envelope": { group: "release-passport-trust", purpose: "Verify exact roots, identity, lifecycle, revocation, and an existing KFD assessment in a sealed artifact envelope." },
458
461
  "verify-infra-contract-evidence-bundle": { group: "governance-versioning", purpose: "Fail closed unless an infra-contract lifecycle evidence bundle is complete, hash-bound, and validation-consistent." },
459
462
  "verify-observability-log": { group: "observability-diagnostics", purpose: "Verify Buildchain observability log events." },
460
463
  "verify-publication-admission": { group: "release-passport-trust", purpose: "Independently verify sealed publication admission, runner provenance, control-plane audit, nonce freshness, and exact artifact bindings." },
@@ -485,6 +488,7 @@ function nodeApiMeta(exportName) {
485
488
  "./publication-control-plane-audit": { group: "release-passport-trust", summary: "Read-only publication control-plane snapshot evaluation APIs." },
486
489
  "./buildchain-publication-authority": { group: "release-passport-trust", summary: "Buildchain-owned closed-world publication authority descriptor registry." },
487
490
  "./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
491
+ "./artifact-verification-envelope": { group: "release-passport-trust", summary: "Sealed exact-root, lifecycle, identity, and existing KFD assessment inputs for KFX admission." },
488
492
  "./release-passport": { group: "release-passport-trust", summary: "Release passport collection, verification, explanation, and evidence APIs." },
489
493
  "./release-candidate": { group: "reusable-build", summary: "PR-stage release-candidate artifact, passport, and promote-only resolver APIs." },
490
494
  "./stable-candidate-ledger": { group: "governance-versioning", summary: "Immutable alpha candidate ledger, qualification, revocation, selection, and exact stable source-lock APIs." },