@kungfu-tech/buildchain 2.8.8-alpha.5 → 2.8.8-alpha.7
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.
- package/actions/promote-buildchain-ref/README.md +7 -0
- package/dist/site/buildchain-contract.json +8 -8
- package/dist/site/buildchain-site.json +58 -23
- package/dist/site/kfd-claims.json +13 -1
- package/dist/site/manual-registry.json +7 -7
- package/dist/site/node-api-registry.json +10 -4
- package/dist/site/page-registry.json +21 -16
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/site-manifest.json +37 -7
- package/docs/lifecycle-protocol.md +6 -4
- package/docs/publish-transaction.md +4 -1
- package/docs/release-governance.md +5 -2
- package/docs/release-passport.md +4 -0
- package/docs/reusable-build-surface.md +7 -3
- package/docs/site-bundle-contract.md +28 -0
- package/docs/web-surface-deployments.md +26 -0
- package/package.json +2 -1
- package/packages/core/index.js +6 -0
- package/packages/core/release-passport.js +20 -1
- package/packages/core/surface-manifest.js +87 -0
- package/scripts/check-inventory.mjs +26 -0
- package/scripts/generate-site-bundle.mjs +32 -0
- package/scripts/web-surface-core.mjs +19 -0
- package/scripts/web-surface.mjs +10 -0
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"./release-candidate": "./packages/core/release-candidate.js",
|
|
20
20
|
"./release-passport": "./packages/core/release-passport.js",
|
|
21
21
|
"./release-propagation": "./packages/core/release-propagation.js",
|
|
22
|
+
"./surface-manifest": "./packages/core/surface-manifest.js",
|
|
22
23
|
"./buildchain-kfd-claims": "./packages/core/buildchain-kfd-claims.js",
|
|
23
24
|
"./site/buildchain-site.json": "./dist/site/buildchain-site.json",
|
|
24
25
|
"./site/site-manifest.json": "./dist/site/site-manifest.json",
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"contract": "kungfu-buildchain-site-manifest",
|
|
4
|
+
"generatedAt": "2026-07-07T05:58:59.518Z",
|
|
5
|
+
"publishedAt": "2026-07-07T05:58:59.518Z",
|
|
6
|
+
"reproducible": true,
|
|
7
|
+
"timestampPolicy": "ci-injected",
|
|
8
|
+
"deterministicInputs": [
|
|
9
|
+
"README.md",
|
|
10
|
+
"docs/*.md",
|
|
11
|
+
"actions/*/README.md",
|
|
12
|
+
"fixtures/*/README.md",
|
|
13
|
+
"packages/core/README.md",
|
|
14
|
+
"package.json#exports",
|
|
15
|
+
"tests/buildchain-inventory.json",
|
|
16
|
+
"sourceRevision",
|
|
17
|
+
"sourceDateEpoch",
|
|
18
|
+
"package content",
|
|
19
|
+
"declared Buildchain surface manifest contract"
|
|
20
|
+
],
|
|
21
|
+
"sourceDateEpoch": "0",
|
|
22
|
+
"sourceRevision": "5fdcc9bfde15861e9fd12a29f880bcd3ffc8d719",
|
|
23
|
+
"timestampPolicyDetails": {
|
|
24
|
+
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
25
|
+
"timestampFields": [
|
|
26
|
+
"generatedAt",
|
|
27
|
+
"publishedAt"
|
|
28
|
+
],
|
|
29
|
+
"timestampFieldsParticipateInArtifactDigest": true,
|
|
30
|
+
"artifactDigestScope": "npm package dist/site JSON files",
|
|
31
|
+
"note": "Human-readable timestamps are separate from reproducibility inputs; do not infer reproducibility from epoch timestamps."
|
|
32
|
+
},
|
|
4
33
|
"product": {
|
|
5
34
|
"name": "Buildchain",
|
|
6
35
|
"formalName": "Buildchain by Kungfu",
|
|
@@ -8,6 +37,7 @@
|
|
|
8
37
|
},
|
|
9
38
|
"package": {
|
|
10
39
|
"name": "@kungfu-tech/buildchain",
|
|
40
|
+
"version": "2.8.8-alpha.7",
|
|
11
41
|
"versionSource": "package.json#version"
|
|
12
42
|
},
|
|
13
43
|
"entrypoint": "buildchain-site.json",
|
|
@@ -39,7 +69,7 @@
|
|
|
39
69
|
"path": "docs/release-passport.md",
|
|
40
70
|
"plane": "verify",
|
|
41
71
|
"exists": true,
|
|
42
|
-
"digest": "sha256:
|
|
72
|
+
"digest": "sha256:56c9000305e9dc23993e6c401568c4063dd496c3586382a44cd44db108a95094"
|
|
43
73
|
},
|
|
44
74
|
{
|
|
45
75
|
"id": "release-propagation",
|
|
@@ -87,7 +117,7 @@
|
|
|
87
117
|
"path": "docs/site-bundle-contract.md",
|
|
88
118
|
"plane": "use",
|
|
89
119
|
"exists": true,
|
|
90
|
-
"digest": "sha256:
|
|
120
|
+
"digest": "sha256:466318ea255da5a3c70a260773384341afb91b929135f2253ecd8f4e6dae7f7b"
|
|
91
121
|
},
|
|
92
122
|
{
|
|
93
123
|
"id": "migration-inventory",
|
|
@@ -127,7 +157,7 @@
|
|
|
127
157
|
"path": "docs/lifecycle-protocol.md",
|
|
128
158
|
"plane": "use",
|
|
129
159
|
"exists": true,
|
|
130
|
-
"digest": "sha256:
|
|
160
|
+
"digest": "sha256:43dcaaa53efa8b7e6cdcdaa92a0d39d3ee263b840ed2bfea0a47fe4af922cd59"
|
|
131
161
|
},
|
|
132
162
|
{
|
|
133
163
|
"id": "reusable-build-surface",
|
|
@@ -135,7 +165,7 @@
|
|
|
135
165
|
"path": "docs/reusable-build-surface.md",
|
|
136
166
|
"plane": "use",
|
|
137
167
|
"exists": true,
|
|
138
|
-
"digest": "sha256:
|
|
168
|
+
"digest": "sha256:165011e2a932461805925c714c2c1c07f1ee60c4b3a614b6dcdda4b81ec22d68"
|
|
139
169
|
},
|
|
140
170
|
{
|
|
141
171
|
"id": "publish-transaction",
|
|
@@ -143,7 +173,7 @@
|
|
|
143
173
|
"path": "docs/publish-transaction.md",
|
|
144
174
|
"plane": "verify",
|
|
145
175
|
"exists": true,
|
|
146
|
-
"digest": "sha256:
|
|
176
|
+
"digest": "sha256:94844f7bb38dbd85c02f1bc9d55e34dcebd9fc96585789766e8075ac635bc6c0"
|
|
147
177
|
},
|
|
148
178
|
{
|
|
149
179
|
"id": "release-governance",
|
|
@@ -151,7 +181,7 @@
|
|
|
151
181
|
"path": "docs/release-governance.md",
|
|
152
182
|
"plane": "why",
|
|
153
183
|
"exists": true,
|
|
154
|
-
"digest": "sha256:
|
|
184
|
+
"digest": "sha256:7fb654a147104125eccbbbcccdeb28c5cad5a6097731e560e72919b9231f985f"
|
|
155
185
|
},
|
|
156
186
|
{
|
|
157
187
|
"id": "release-flow",
|
|
@@ -183,7 +213,7 @@
|
|
|
183
213
|
"path": "docs/web-surface-deployments.md",
|
|
184
214
|
"plane": "use",
|
|
185
215
|
"exists": true,
|
|
186
|
-
"digest": "sha256:
|
|
216
|
+
"digest": "sha256:5b374e65dc2740748205db597a7cd9779d71daa1b30ad46e542f95b093e56c98"
|
|
187
217
|
}
|
|
188
218
|
],
|
|
189
219
|
"facts": [
|
|
@@ -250,10 +250,12 @@ checks, and admin enforcement for human channel changes, but adds the
|
|
|
250
250
|
authenticated promotion token user or app to the managed bypass allowlist for
|
|
251
251
|
generated release bookkeeping. Buildchain also creates the configured required
|
|
252
252
|
check on the exact generated version-state commit before patching the protected
|
|
253
|
-
ref,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
ref, then applies the protected ref update with the declared generated ref
|
|
254
|
+
update token. The reusable wrapper uses
|
|
255
|
+
`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token` for that protected
|
|
256
|
+
bookkeeping update. If the direct generated update is rejected, promotion fails
|
|
257
|
+
with a token/protection diagnostic instead of asking humans to review a
|
|
258
|
+
post-publish version-state PR.
|
|
257
259
|
|
|
258
260
|
For `version.strategy = "anchored"` with `version.next = "manual"`, release
|
|
259
261
|
promotion does not generate a Buildchain-owned version-state commit. In that
|
|
@@ -293,7 +293,10 @@ Buildchain-managed promotions must finish generated version-state bookkeeping
|
|
|
293
293
|
with the promotion token directly; they must not require a post-publish human
|
|
294
294
|
PR. Before patching a protected generated bookkeeping ref, Buildchain emits the
|
|
295
295
|
configured required check on the exact generated version-state commit so strict
|
|
296
|
-
branch protection can validate the automation path without a second build
|
|
296
|
+
branch protection can validate the automation path without a second build, then
|
|
297
|
+
uses the generated ref update token for the protected ref PATCH. The reusable
|
|
298
|
+
wrapper defaults that token to `secrets.BUILDCHAIN_PROMOTION_TOKEN ||
|
|
299
|
+
github.token`.
|
|
297
300
|
|
|
298
301
|
If finalization fails after an exact Git tag, a channel branch, or dev/alpha
|
|
299
302
|
sync ref has already moved, the next run reads the durable `finalizing` state
|
|
@@ -416,8 +416,11 @@ token's authenticated user or app to the managed bypass allowlist. Before
|
|
|
416
416
|
patching a protected generated bookkeeping ref, the action creates the
|
|
417
417
|
configured required check on the exact generated version-state commit, so strict
|
|
418
418
|
status checks are satisfied by machine-verifiable Buildchain evidence rather
|
|
419
|
-
than a human PR.
|
|
420
|
-
|
|
419
|
+
than a human PR. The protected ref PATCH itself uses the generated ref update
|
|
420
|
+
token; the reusable wrapper defaults it to
|
|
421
|
+
`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token`. If direct generated
|
|
422
|
+
bookkeeping is still rejected, Buildchain fails with a token/protection
|
|
423
|
+
diagnostic instead of creating a post-publish PR.
|
|
421
424
|
Buildchain's own promotion workflow reads `BUILDCHAIN_PROMOTION_BYPASS_APPS`,
|
|
422
425
|
`BUILDCHAIN_PROMOTION_BYPASS_USERS`, and
|
|
423
426
|
`BUILDCHAIN_PROMOTION_BYPASS_TEAMS` repository variables so the declared bypass
|
package/docs/release-passport.md
CHANGED
|
@@ -47,6 +47,10 @@ Additive passport sections:
|
|
|
47
47
|
trusted publishing was enabled.
|
|
48
48
|
- `transaction`: durable Buildchain release transaction id, state, exact tag,
|
|
49
49
|
release SHA, state ref, and state SHA.
|
|
50
|
+
- `surfaceTimestampPolicy`: the common Buildchain surface manifest timestamp
|
|
51
|
+
policy. It records real CI/release generation and publication times,
|
|
52
|
+
reproducibility inputs, source revision or source-date-epoch, and whether
|
|
53
|
+
timestamp fields participate in the release artifact digest.
|
|
50
54
|
- `artifacts`: release assets and registry artifacts in one list, each pointing
|
|
51
55
|
back to the evidence file that proves its digest.
|
|
52
56
|
|
|
@@ -515,9 +515,13 @@ so the workflow automation can apply generated version-state and channel
|
|
|
515
515
|
bookkeeping on protected `dev`/`alpha`/`release` branches after the reviewed
|
|
516
516
|
channel PR has merged; consumers using a different promotion identity can pass
|
|
517
517
|
`branch-protection-bypass-users`, `branch-protection-bypass-teams`, or a
|
|
518
|
-
different app slug declaratively.
|
|
519
|
-
|
|
520
|
-
|
|
518
|
+
different app slug declaratively. The wrapper uses
|
|
519
|
+
`secrets.BUILDCHAIN_PROMOTION_TOKEN || github.token` as the generated ref update
|
|
520
|
+
token for protected bookkeeping PATCH calls, so a bypass-capable promotion token
|
|
521
|
+
can sync dev immediately after alpha/release publish without a post-publish PR.
|
|
522
|
+
It does not call `.build.yml`, does not create a matrix, and must fail before
|
|
523
|
+
publish if the RC evidence, payload set, or source-lock ref is missing or
|
|
524
|
+
ambiguous.
|
|
521
525
|
|
|
522
526
|
```yaml
|
|
523
527
|
jobs:
|
|
@@ -52,6 +52,34 @@ from `packages/core/buildchain-kfd-claims.js` and enumerates the public release
|
|
|
52
52
|
claims plus the KFD-3 collaboration surfaces that Buildchain self-verifies
|
|
53
53
|
during release promotion.
|
|
54
54
|
|
|
55
|
+
## Timestamp and Reproducibility Policy
|
|
56
|
+
|
|
57
|
+
Every Buildchain-owned surface manifest uses the same timestamp policy fields:
|
|
58
|
+
|
|
59
|
+
- `generatedAt`: when the manifest JSON was generated.
|
|
60
|
+
- `publishedAt`: when the surface was published, when known.
|
|
61
|
+
- `reproducible`: whether the manifest declares its reproducibility inputs.
|
|
62
|
+
- `timestampPolicy`: `ci-injected` for release/workflow-generated public
|
|
63
|
+
artifacts, or `source-date-epoch` for local deterministic source checks.
|
|
64
|
+
- `deterministicInputs`: the source files, revisions, package metadata, and
|
|
65
|
+
declared Buildchain contracts that determine the manifest bytes.
|
|
66
|
+
- `sourceDateEpoch` / `sourceRevision`: the deterministic time input or source
|
|
67
|
+
revision used to reproduce the manifest.
|
|
68
|
+
- `timestampPolicyDetails.timestampFieldsParticipateInArtifactDigest`: whether
|
|
69
|
+
timestamp fields are included in the artifact digest being audited.
|
|
70
|
+
|
|
71
|
+
The policy is defined by `@kungfu-tech/buildchain/surface-manifest` and applies
|
|
72
|
+
to the root site bundle, `site-manifest.json`, and web-surface deployment
|
|
73
|
+
manifests for named surfaces such as KFD, Buildchain, and Core. Site
|
|
74
|
+
repositories should render these fields; they should not invent their own
|
|
75
|
+
manifest time semantics.
|
|
76
|
+
|
|
77
|
+
Source checkouts may use `SOURCE_DATE_EPOCH` for deterministic local checks.
|
|
78
|
+
Published CI/release artifacts should inject real timestamps with
|
|
79
|
+
`BUILDCHAIN_SITE_GENERATED_AT` / `BUILDCHAIN_SITE_PUBLISHED_AT` or the matching
|
|
80
|
+
`BUILDCHAIN_SURFACE_*` variables, so public manifests do not expose epoch time
|
|
81
|
+
as if it were a real metadata time.
|
|
82
|
+
|
|
55
83
|
## npm Consumption
|
|
56
84
|
|
|
57
85
|
```bash
|
|
@@ -195,6 +195,25 @@ rollback:
|
|
|
195
195
|
"channel": "preview",
|
|
196
196
|
"alias": "sha-abcdef123456",
|
|
197
197
|
"url": "https://sha-abcdef123456.preview.libkungfu.dev",
|
|
198
|
+
"generatedAt": "2026-07-01T00:00:00.000Z",
|
|
199
|
+
"publishedAt": "2026-07-01T00:00:00.000Z",
|
|
200
|
+
"reproducible": true,
|
|
201
|
+
"timestampPolicy": "ci-injected",
|
|
202
|
+
"deterministicInputs": [
|
|
203
|
+
"web-surface artifact content",
|
|
204
|
+
"buildchain.toml web-surface channels/deploy/surfaces",
|
|
205
|
+
"sourceSha",
|
|
206
|
+
"artifactHash",
|
|
207
|
+
"deployment channel",
|
|
208
|
+
"deployment alias"
|
|
209
|
+
],
|
|
210
|
+
"sourceRevision": "...",
|
|
211
|
+
"timestampPolicyDetails": {
|
|
212
|
+
"contract": "kungfu-buildchain-surface-timestamp-policy",
|
|
213
|
+
"timestampFields": ["generatedAt", "publishedAt", "deployedAt"],
|
|
214
|
+
"timestampFieldsParticipateInArtifactDigest": false,
|
|
215
|
+
"artifactDigestScope": "web-surface artifactHash excludes deployment manifest timestamps"
|
|
216
|
+
},
|
|
198
217
|
"sourceSha": "...",
|
|
199
218
|
"artifactHash": "...",
|
|
200
219
|
"deployTarget": "libkungfu-dev-preview",
|
|
@@ -231,6 +250,13 @@ Dynamic adapters can also fill `runtimeId`, `configFingerprint`,
|
|
|
231
250
|
`rollbackLimitations`. Buildchain records secret reference names only, never
|
|
232
251
|
secret values.
|
|
233
252
|
|
|
253
|
+
The timestamp policy is shared with package site bundles. Public deployment
|
|
254
|
+
manifests should expose real workflow generation/publication times while
|
|
255
|
+
separately declaring why the deployed artifact remains reproducible. For
|
|
256
|
+
web-surface deployment manifests, `artifactHash` is the static site artifact
|
|
257
|
+
digest and does not include deployment timestamp fields; the manifest itself
|
|
258
|
+
still records those fields for human and agent audit.
|
|
259
|
+
|
|
234
260
|
## Deploy Plans
|
|
235
261
|
|
|
236
262
|
Deploy planning is the default behavior. It plans the adapter steps and writes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.8.8-alpha.
|
|
3
|
+
"version": "2.8.8-alpha.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"./release-candidate": "./packages/core/release-candidate.js",
|
|
23
23
|
"./release-passport": "./packages/core/release-passport.js",
|
|
24
24
|
"./release-propagation": "./packages/core/release-propagation.js",
|
|
25
|
+
"./surface-manifest": "./packages/core/surface-manifest.js",
|
|
25
26
|
"./buildchain-kfd-claims": "./packages/core/buildchain-kfd-claims.js",
|
|
26
27
|
"./site/buildchain-site.json": "./dist/site/buildchain-site.json",
|
|
27
28
|
"./site/site-manifest.json": "./dist/site/site-manifest.json",
|
package/packages/core/index.js
CHANGED
|
@@ -203,3 +203,9 @@ export {
|
|
|
203
203
|
resolvePropagationChannel,
|
|
204
204
|
writeReleasePropagationLock,
|
|
205
205
|
} from "./release-propagation.js";
|
|
206
|
+
|
|
207
|
+
export {
|
|
208
|
+
SURFACE_TIMESTAMP_POLICY_CONTRACT,
|
|
209
|
+
applySurfaceTimestampPolicy,
|
|
210
|
+
createSurfaceTimestampPolicy,
|
|
211
|
+
} from "./surface-manifest.js";
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
validateKfd1ReleaseGateEvidence,
|
|
15
15
|
validateKfd3CollaborationInterfaceReleaseGateEvidence,
|
|
16
16
|
} from "./kfd-gate.js";
|
|
17
|
+
import { createSurfaceTimestampPolicy } from "./surface-manifest.js";
|
|
17
18
|
|
|
18
19
|
export const RELEASE_PASSPORT_CONTRACT = "kungfu-buildchain-release-passport";
|
|
19
20
|
export const ARTIFACT_EVIDENCE_CONTRACT = "kungfu-buildchain-artifact-evidence";
|
|
@@ -953,6 +954,7 @@ export function createReleasePassport({
|
|
|
953
954
|
? normalizeEvidenceDocument(distTagPromotionEvidence, "distTagPromotionEvidence")
|
|
954
955
|
: undefined;
|
|
955
956
|
const normalizedImpact = normalizeImpactLedger(impact, { tag: normalizedTag, line });
|
|
957
|
+
const generatedAt = nowIso();
|
|
956
958
|
const kfd1Metadata = resolveKfd1Metadata();
|
|
957
959
|
const normalizedKfd1 = kfd1?.passportSection ? kfd1 : undefined;
|
|
958
960
|
const normalizedKfd3 = kfd3?.passportSection ? kfd3 : undefined;
|
|
@@ -1001,7 +1003,24 @@ export function createReleasePassport({
|
|
|
1001
1003
|
return {
|
|
1002
1004
|
schemaVersion: 1,
|
|
1003
1005
|
contract: RELEASE_PASSPORT_CONTRACT,
|
|
1004
|
-
generatedAt
|
|
1006
|
+
generatedAt,
|
|
1007
|
+
surfaceTimestampPolicy: createSurfaceTimestampPolicy({
|
|
1008
|
+
generatedAt,
|
|
1009
|
+
publishedAt: optionalString(release.publishedAt || release.published_at || normalizedPublishEvidence?.publishedAt),
|
|
1010
|
+
sourceRevision: optionalString(sourceSha || releaseSha || normalizedTransaction?.releaseSha),
|
|
1011
|
+
timestampPolicy: "ci-injected",
|
|
1012
|
+
deterministicInputs: [
|
|
1013
|
+
"release.sourceSha",
|
|
1014
|
+
"release.releaseSha",
|
|
1015
|
+
"release.releaseMaterialSha",
|
|
1016
|
+
"artifact-evidence.json",
|
|
1017
|
+
"publish evidence",
|
|
1018
|
+
"release-state transaction",
|
|
1019
|
+
],
|
|
1020
|
+
timestampFields: ["generatedAt", "publishedAt", "surfaceTimestampPolicy.generatedAt", "surfaceTimestampPolicy.publishedAt"],
|
|
1021
|
+
timestampFieldsParticipateInArtifactDigest: true,
|
|
1022
|
+
artifactDigestScope: "release passport JSON and release evidence bundle",
|
|
1023
|
+
}),
|
|
1005
1024
|
product: {
|
|
1006
1025
|
name: optionalString(productName || "Buildchain"),
|
|
1007
1026
|
repository: optionalString(repository),
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const SURFACE_TIMESTAMP_POLICY_CONTRACT = "kungfu-buildchain-surface-timestamp-policy";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_SOURCE_DATE_EPOCH = "0";
|
|
4
|
+
|
|
5
|
+
function nonEmptyString(value, fallback = "") {
|
|
6
|
+
const normalized = String(value ?? "").trim();
|
|
7
|
+
return normalized || fallback;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isoFromEpochSeconds(value) {
|
|
11
|
+
const raw = nonEmptyString(value, DEFAULT_SOURCE_DATE_EPOCH);
|
|
12
|
+
if (!/^-?\d+$/.test(raw)) {
|
|
13
|
+
throw new Error(`sourceDateEpoch must be integer seconds since Unix epoch: ${value}`);
|
|
14
|
+
}
|
|
15
|
+
return new Date(Number(raw) * 1000).toISOString();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function normalizeIso(value, label) {
|
|
19
|
+
const normalized = nonEmptyString(value);
|
|
20
|
+
if (!normalized) {
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
const date = new Date(normalized);
|
|
24
|
+
if (Number.isNaN(date.getTime())) {
|
|
25
|
+
throw new Error(`${label} must be an ISO-8601 timestamp`);
|
|
26
|
+
}
|
|
27
|
+
return date.toISOString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function uniqueStrings(values = []) {
|
|
31
|
+
return [...new Set(values.map((value) => nonEmptyString(value)).filter(Boolean))];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createSurfaceTimestampPolicy({
|
|
35
|
+
generatedAt = "",
|
|
36
|
+
publishedAt = "",
|
|
37
|
+
sourceDateEpoch = process.env.SOURCE_DATE_EPOCH || DEFAULT_SOURCE_DATE_EPOCH,
|
|
38
|
+
sourceRevision = "",
|
|
39
|
+
deterministicInputs = [],
|
|
40
|
+
timestampPolicy = "",
|
|
41
|
+
timestampFields = ["generatedAt", "publishedAt"],
|
|
42
|
+
timestampFieldsParticipateInArtifactDigest = true,
|
|
43
|
+
artifactDigestScope = "manifest-and-artifact",
|
|
44
|
+
reproducible = true,
|
|
45
|
+
} = {}) {
|
|
46
|
+
const normalizedGeneratedAt = normalizeIso(
|
|
47
|
+
generatedAt || isoFromEpochSeconds(sourceDateEpoch),
|
|
48
|
+
"generatedAt",
|
|
49
|
+
);
|
|
50
|
+
const normalizedPublishedAt = normalizeIso(publishedAt || generatedAt || "", "publishedAt");
|
|
51
|
+
const normalizedPolicy = nonEmptyString(
|
|
52
|
+
timestampPolicy,
|
|
53
|
+
generatedAt || publishedAt ? "ci-injected" : "source-date-epoch",
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
generatedAt: normalizedGeneratedAt,
|
|
57
|
+
...(normalizedPublishedAt ? { publishedAt: normalizedPublishedAt } : {}),
|
|
58
|
+
reproducible: Boolean(reproducible),
|
|
59
|
+
timestampPolicy: normalizedPolicy,
|
|
60
|
+
deterministicInputs: uniqueStrings([
|
|
61
|
+
...deterministicInputs,
|
|
62
|
+
sourceRevision ? "sourceRevision" : "",
|
|
63
|
+
sourceDateEpoch ? "sourceDateEpoch" : "",
|
|
64
|
+
"package content",
|
|
65
|
+
"declared Buildchain surface manifest contract",
|
|
66
|
+
]),
|
|
67
|
+
sourceDateEpoch: nonEmptyString(sourceDateEpoch),
|
|
68
|
+
...(sourceRevision ? { sourceRevision: nonEmptyString(sourceRevision) } : {}),
|
|
69
|
+
timestampPolicyDetails: {
|
|
70
|
+
contract: SURFACE_TIMESTAMP_POLICY_CONTRACT,
|
|
71
|
+
timestampFields: uniqueStrings(timestampFields),
|
|
72
|
+
timestampFieldsParticipateInArtifactDigest: Boolean(timestampFieldsParticipateInArtifactDigest),
|
|
73
|
+
artifactDigestScope: nonEmptyString(artifactDigestScope, "manifest-and-artifact"),
|
|
74
|
+
note: "Human-readable timestamps are separate from reproducibility inputs; do not infer reproducibility from epoch timestamps.",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function applySurfaceTimestampPolicy(manifest, options = {}) {
|
|
80
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
81
|
+
throw new Error("surface manifest must be a JSON object");
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
...manifest,
|
|
85
|
+
...createSurfaceTimestampPolicy(options),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -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()
|
|
@@ -215,6 +220,30 @@ function buildSiteBundle() {
|
|
|
215
220
|
const readme = parseReadme(readText(README_PATH));
|
|
216
221
|
const docs = BUILDCHAIN_AGENT_MANUALS.map((manual) => docEntry(manual.id, manual.title, manual.path, manual.plane));
|
|
217
222
|
const pages = buildSitePages();
|
|
223
|
+
const generatedAtInput = env("BUILDCHAIN_SITE_GENERATED_AT") || env("BUILDCHAIN_SURFACE_GENERATED_AT");
|
|
224
|
+
const publishedAtInput = env("BUILDCHAIN_SITE_PUBLISHED_AT") || env("BUILDCHAIN_SURFACE_PUBLISHED_AT");
|
|
225
|
+
const timestampPolicyInput = env("BUILDCHAIN_SITE_TIMESTAMP_POLICY") || env("BUILDCHAIN_SURFACE_TIMESTAMP_POLICY");
|
|
226
|
+
const sourceRevisionInput =
|
|
227
|
+
env("BUILDCHAIN_SOURCE_SHA") ||
|
|
228
|
+
(generatedAtInput || publishedAtInput || timestampPolicyInput ? env("GITHUB_SHA") : "");
|
|
229
|
+
const timestampPolicy = createSurfaceTimestampPolicy({
|
|
230
|
+
generatedAt: generatedAtInput,
|
|
231
|
+
publishedAt: publishedAtInput,
|
|
232
|
+
sourceDateEpoch: env("SOURCE_DATE_EPOCH") || "0",
|
|
233
|
+
sourceRevision: sourceRevisionInput,
|
|
234
|
+
timestampPolicy: timestampPolicyInput,
|
|
235
|
+
deterministicInputs: [
|
|
236
|
+
"README.md",
|
|
237
|
+
"docs/*.md",
|
|
238
|
+
"actions/*/README.md",
|
|
239
|
+
"fixtures/*/README.md",
|
|
240
|
+
"packages/core/README.md",
|
|
241
|
+
"package.json#exports",
|
|
242
|
+
"tests/buildchain-inventory.json",
|
|
243
|
+
],
|
|
244
|
+
timestampFieldsParticipateInArtifactDigest: true,
|
|
245
|
+
artifactDigestScope: "npm package dist/site JSON files",
|
|
246
|
+
});
|
|
218
247
|
|
|
219
248
|
const cliRegistry = {
|
|
220
249
|
schemaVersion: 1,
|
|
@@ -438,6 +467,7 @@ function buildSiteBundle() {
|
|
|
438
467
|
const siteManifest = {
|
|
439
468
|
schemaVersion: 1,
|
|
440
469
|
contract: "kungfu-buildchain-site-manifest",
|
|
470
|
+
...timestampPolicy,
|
|
441
471
|
product: {
|
|
442
472
|
name: "Buildchain",
|
|
443
473
|
formalName: "Buildchain by Kungfu",
|
|
@@ -445,6 +475,7 @@ function buildSiteBundle() {
|
|
|
445
475
|
},
|
|
446
476
|
package: {
|
|
447
477
|
name: packageJson.name,
|
|
478
|
+
version: packageJson.version,
|
|
448
479
|
versionSource: "package.json#version",
|
|
449
480
|
},
|
|
450
481
|
entrypoint: "buildchain-site.json",
|
|
@@ -549,6 +580,7 @@ function buildSiteBundle() {
|
|
|
549
580
|
const siteBundle = {
|
|
550
581
|
schemaVersion: 1,
|
|
551
582
|
contract: SITE_BUNDLE_CONTRACT,
|
|
583
|
+
...timestampPolicy,
|
|
552
584
|
product: siteManifest.product,
|
|
553
585
|
package: siteManifest.package,
|
|
554
586
|
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,
|
package/scripts/web-surface.mjs
CHANGED
|
@@ -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({
|