@kungfu-tech/buildchain 2.8.8-alpha.6 → 2.8.8-alpha.8

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.
@@ -123,6 +123,9 @@ if (rootPackage.exports?.["./release-passport"] !== "./packages/core/release-pas
123
123
  if (rootPackage.exports?.["./release-propagation"] !== "./packages/core/release-propagation.js") {
124
124
  throw new Error("root package must export @kungfu-tech/buildchain/release-propagation");
125
125
  }
126
+ if (rootPackage.exports?.["./surface-manifest"] !== "./packages/core/surface-manifest.js") {
127
+ throw new Error("root package must export @kungfu-tech/buildchain/surface-manifest");
128
+ }
126
129
  if (rootPackage.exports?.["./buildchain-kfd-claims"] !== "./packages/core/buildchain-kfd-claims.js") {
127
130
  throw new Error("root package must export @kungfu-tech/buildchain/buildchain-kfd-claims");
128
131
  }
@@ -193,9 +196,18 @@ if (!coreIndexSource.includes("KFD2_RELEASE_TRUST_PASSPORT_CONTRACT")) {
193
196
  if (!coreIndexSource.includes("KFD2_TRUST_PROOF_CONTRACT")) {
194
197
  throw new Error("packages/core/index.js must export KFD-2 trust proof contract");
195
198
  }
199
+ if (!coreIndexSource.includes("createSurfaceTimestampPolicy")) {
200
+ throw new Error("packages/core/index.js must export surface manifest timestamp policy APIs");
201
+ }
196
202
  if (siteBundle.contract !== "kungfu-buildchain-site-bundle") {
197
203
  throw new Error("buildchain-site.json must expose the Buildchain site bundle contract");
198
204
  }
205
+ if (siteBundle.package?.version !== rootPackage.version) {
206
+ throw new Error("buildchain-site.json package.version must match package.json version");
207
+ }
208
+ if (siteManifest.package?.version !== rootPackage.version) {
209
+ throw new Error("site-manifest.json package.version must match package.json version");
210
+ }
199
211
  if (siteBundle.source?.homepageTextSource !== "README.md") {
200
212
  throw new Error("buildchain-site.json source.homepageTextSource must be README.md");
201
213
  }
@@ -241,6 +253,20 @@ if (siteBundle.pageRegistry?.path !== "page-registry.json" || siteBundle.pageReg
241
253
  if (!siteManifest.facts?.includes("page-registry.json") || !siteBundle.entrypoints?.includes("page-registry.json")) {
242
254
  throw new Error("site bundle entrypoints must include page-registry.json");
243
255
  }
256
+ for (const [name, manifest] of [["buildchain-site.json", siteBundle], ["site-manifest.json", siteManifest]]) {
257
+ if (!manifest.generatedAt) {
258
+ throw new Error(`${name} must expose generatedAt`);
259
+ }
260
+ if (manifest.timestampPolicy === "ci-injected" && manifest.generatedAt === "1970-01-01T00:00:00.000Z") {
261
+ throw new Error(`${name} must not use epoch generatedAt when timestampPolicy=ci-injected`);
262
+ }
263
+ if (!manifest.timestampPolicy || !manifest.reproducible || !Array.isArray(manifest.deterministicInputs)) {
264
+ throw new Error(`${name} must expose the Buildchain surface timestamp/reproducibility policy`);
265
+ }
266
+ if (manifest.timestampPolicyDetails?.contract !== "kungfu-buildchain-surface-timestamp-policy") {
267
+ throw new Error(`${name} must expose timestampPolicyDetails.contract`);
268
+ }
269
+ }
244
270
  function immediateReadmes(dir) {
245
271
  const absoluteDir = path.join(root, dir);
246
272
  if (!fs.existsSync(absoluteDir)) return [];
@@ -8,6 +8,7 @@ import {
8
8
  BUILDCHAIN_AGENT_MANUALS,
9
9
  createBuildchainKfdClaimRegistry,
10
10
  } from "../packages/core/buildchain-kfd-claims.js";
11
+ import { createSurfaceTimestampPolicy } from "../packages/core/surface-manifest.js";
11
12
 
12
13
  const SITE_BUNDLE_CONTRACT = "kungfu-buildchain-site-bundle";
13
14
  const README_PATH = "README.md";
@@ -31,6 +32,10 @@ function stableJson(value) {
31
32
  return `${JSON.stringify(value, null, 2)}\n`;
32
33
  }
33
34
 
35
+ function env(name) {
36
+ return String(process.env[name] || "").trim();
37
+ }
38
+
34
39
  function sha256File(rel) {
35
40
  const filePath = path.join(root, rel);
36
41
  return fs.existsSync(filePath) && fs.statSync(filePath).isFile()
@@ -42,6 +47,26 @@ function existingJson(filePath) {
42
47
  return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
43
48
  }
44
49
 
50
+ function readExistingSiteTimestampPolicy(packageVersion) {
51
+ const manifestPath = path.join(outputDir, "site-manifest.json");
52
+ if (!fs.existsSync(manifestPath)) return null;
53
+ try {
54
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
55
+ if (manifest?.package?.version !== packageVersion) return null;
56
+ if (manifest?.timestampPolicy !== "ci-injected") return null;
57
+ if (!manifest?.generatedAt || manifest.generatedAt === "1970-01-01T00:00:00.000Z") return null;
58
+ return {
59
+ generatedAt: manifest.generatedAt,
60
+ publishedAt: manifest.publishedAt || manifest.generatedAt,
61
+ sourceDateEpoch: manifest.sourceDateEpoch,
62
+ sourceRevision: manifest.sourceRevision,
63
+ timestampPolicy: manifest.timestampPolicy,
64
+ };
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
45
70
  function normalizeMarkdown(value) {
46
71
  return String(value || "").replace(/\r\n/g, "\n").trim();
47
72
  }
@@ -215,6 +240,38 @@ function buildSiteBundle() {
215
240
  const readme = parseReadme(readText(README_PATH));
216
241
  const docs = BUILDCHAIN_AGENT_MANUALS.map((manual) => docEntry(manual.id, manual.title, manual.path, manual.plane));
217
242
  const pages = buildSitePages();
243
+ const preservedTimestampPolicy = readExistingSiteTimestampPolicy(packageJson.version);
244
+ const generatedAtInput = env("BUILDCHAIN_SITE_GENERATED_AT") || env("BUILDCHAIN_SURFACE_GENERATED_AT");
245
+ const publishedAtInput = env("BUILDCHAIN_SITE_PUBLISHED_AT") || env("BUILDCHAIN_SURFACE_PUBLISHED_AT");
246
+ const timestampPolicyInput = env("BUILDCHAIN_SITE_TIMESTAMP_POLICY") || env("BUILDCHAIN_SURFACE_TIMESTAMP_POLICY");
247
+ const sourceRevisionEnv = env("BUILDCHAIN_SOURCE_SHA");
248
+ const shouldPreserveExistingTimestampPolicy =
249
+ !generatedAtInput && !publishedAtInput && !timestampPolicyInput && !sourceRevisionEnv;
250
+ const sourceRevisionInput =
251
+ sourceRevisionEnv ||
252
+ (generatedAtInput || publishedAtInput || timestampPolicyInput
253
+ ? env("GITHUB_SHA")
254
+ : shouldPreserveExistingTimestampPolicy
255
+ ? preservedTimestampPolicy?.sourceRevision || ""
256
+ : "");
257
+ const timestampPolicy = createSurfaceTimestampPolicy({
258
+ generatedAt: generatedAtInput || (shouldPreserveExistingTimestampPolicy ? preservedTimestampPolicy?.generatedAt : ""),
259
+ publishedAt: publishedAtInput || (shouldPreserveExistingTimestampPolicy ? preservedTimestampPolicy?.publishedAt : ""),
260
+ sourceDateEpoch: env("SOURCE_DATE_EPOCH") || (shouldPreserveExistingTimestampPolicy ? preservedTimestampPolicy?.sourceDateEpoch : "") || "0",
261
+ sourceRevision: sourceRevisionInput,
262
+ timestampPolicy: timestampPolicyInput || (shouldPreserveExistingTimestampPolicy ? preservedTimestampPolicy?.timestampPolicy : ""),
263
+ deterministicInputs: [
264
+ "README.md",
265
+ "docs/*.md",
266
+ "actions/*/README.md",
267
+ "fixtures/*/README.md",
268
+ "packages/core/README.md",
269
+ "package.json#exports",
270
+ "tests/buildchain-inventory.json",
271
+ ],
272
+ timestampFieldsParticipateInArtifactDigest: true,
273
+ artifactDigestScope: "npm package dist/site JSON files",
274
+ });
218
275
 
219
276
  const cliRegistry = {
220
277
  schemaVersion: 1,
@@ -438,6 +495,7 @@ function buildSiteBundle() {
438
495
  const siteManifest = {
439
496
  schemaVersion: 1,
440
497
  contract: "kungfu-buildchain-site-manifest",
498
+ ...timestampPolicy,
441
499
  product: {
442
500
  name: "Buildchain",
443
501
  formalName: "Buildchain by Kungfu",
@@ -445,6 +503,7 @@ function buildSiteBundle() {
445
503
  },
446
504
  package: {
447
505
  name: packageJson.name,
506
+ version: packageJson.version,
448
507
  versionSource: "package.json#version",
449
508
  },
450
509
  entrypoint: "buildchain-site.json",
@@ -549,6 +608,7 @@ function buildSiteBundle() {
549
608
  const siteBundle = {
550
609
  schemaVersion: 1,
551
610
  contract: SITE_BUNDLE_CONTRACT,
611
+ ...timestampPolicy,
552
612
  product: siteManifest.product,
553
613
  package: siteManifest.package,
554
614
  source: {
@@ -5,6 +5,7 @@ import fs from "node:fs";
5
5
  import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { loadBuildchainConfig, validateBuildchainConfig } from "../packages/core/buildchain-config.js";
8
+ import { createSurfaceTimestampPolicy } from "../packages/core/surface-manifest.js";
8
9
 
9
10
  const DEFAULT_RETENTION = Object.freeze({
10
11
  preview: {
@@ -609,9 +610,27 @@ export function createWebSurfaceDeploymentManifest({
609
610
  deployConfig,
610
611
  });
611
612
  const primaryBinding = surfaceBindings[0];
613
+ const timestampPolicy = createSurfaceTimestampPolicy({
614
+ generatedAt: deployedAt,
615
+ publishedAt: deployedAt,
616
+ sourceRevision: sourceSha,
617
+ timestampPolicy: "ci-injected",
618
+ deterministicInputs: [
619
+ "web-surface artifact content",
620
+ "buildchain.toml web-surface channels/deploy/surfaces",
621
+ "sourceSha",
622
+ "artifactHash",
623
+ "deployment channel",
624
+ "deployment alias",
625
+ ],
626
+ timestampFields: ["generatedAt", "publishedAt", "deployedAt"],
627
+ timestampFieldsParticipateInArtifactDigest: false,
628
+ artifactDigestScope: "web-surface artifactHash excludes deployment manifest timestamps",
629
+ });
612
630
  return {
613
631
  schemaVersion: 1,
614
632
  contract: "kungfu-buildchain-web-surface-deployment",
633
+ ...timestampPolicy,
615
634
  site: config.project.site || config.project.name || "",
616
635
  channel,
617
636
  alias,
@@ -77,6 +77,10 @@ export function webSurfaceCli() {
77
77
  "alias",
78
78
  process.env.BUILDCHAIN_WEB_SURFACE_ALIAS || defaultWebSurfaceAlias({ channel, sourceSha, pullNumber }),
79
79
  );
80
+ const generatedAt = readArg(
81
+ "generated-at",
82
+ process.env.BUILDCHAIN_SITE_GENERATED_AT || process.env.BUILDCHAIN_SURFACE_GENERATED_AT || "",
83
+ );
80
84
  const result = planWebSurfaceDeploy({
81
85
  cwd,
82
86
  channel,
@@ -89,6 +93,7 @@ export function webSurfaceCli() {
89
93
  rollbackPointer: readArg("rollback-pointer", process.env.BUILDCHAIN_ROLLBACK_REF || ""),
90
94
  rollbackLimitations: readArg("rollback-limitations", process.env.BUILDCHAIN_ROLLBACK_LIMITATIONS || ""),
91
95
  dryRun: readBooleanArg("dry-run", true),
96
+ ...(generatedAt ? { deployedAt: generatedAt } : {}),
92
97
  });
93
98
  const outputResult = mode === "manifest" ? result.manifest : result;
94
99
  writeJson(outputResult, output);
@@ -111,6 +116,10 @@ export function webSurfaceCli() {
111
116
  "alias",
112
117
  process.env.BUILDCHAIN_WEB_SURFACE_ALIAS || defaultWebSurfaceAlias({ channel, sourceSha, pullNumber }),
113
118
  );
119
+ const publishedAt = readArg(
120
+ "published-at",
121
+ process.env.BUILDCHAIN_SITE_PUBLISHED_AT || process.env.BUILDCHAIN_SURFACE_PUBLISHED_AT || "",
122
+ );
114
123
  const result = applyWebSurfaceDeploy({
115
124
  cwd,
116
125
  channel,
@@ -122,6 +131,7 @@ export function webSurfaceCli() {
122
131
  dryRun: readBooleanArg("dry-run", true),
123
132
  actor: readArg("actor", process.env.GITHUB_ACTOR || ""),
124
133
  runId: readArg("run-id", process.env.GITHUB_RUN_ID || ""),
134
+ ...(publishedAt ? { appliedAt: publishedAt } : {}),
125
135
  });
126
136
  writeJson(result, output);
127
137
  writeGitHubOutputs({