@kungfu-tech/buildchain 3.0.5-alpha.8 → 3.0.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.
- package/README.md +0 -31
- package/actions/macos-credential-island/README.md +0 -8
- package/bin/buildchain.mjs +1 -1
- package/contracts/auditable-demo-media-profiles-v1.json +0 -61
- package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +3 -3
- package/contracts/evidence/auditable-demo-web-delivery-v1.json +3 -3
- package/dist/site/buildchain-contract.json +25 -86
- package/dist/site/buildchain-site.json +38 -64
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +2 -6
- package/dist/site/controller-registry.json +3 -11
- package/dist/site/kfd-claims.json +11 -53
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +7 -7
- package/dist/site/node-api-registry.json +31 -213
- package/dist/site/page-registry.json +20 -50
- package/dist/site/public-surface-audit.json +8 -98
- package/dist/site/publication-authority-registry.json +1 -42
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +11 -11
- package/dist/site/workflow-registry.json +2 -67
- package/docs/auditable-demo.md +7 -74
- package/docs/aws-us-elastic-runner-burst-plane.md +16 -151
- package/docs/cli-reference.md +2 -24
- package/docs/node-api-reference.md +72 -90
- package/docs/publish-transaction.md +5 -7
- package/docs/release-flow.md +0 -4
- package/docs/release-governance.md +0 -39
- package/docs/reusable-build-surface.md +0 -9
- package/package.json +1 -1
- package/packages/core/buildchain-contract.js +1 -36
- package/packages/core/buildchain-publication-authority.js +0 -2
- package/packages/core/public-surface-audit.js +35 -4
- package/packages/core/release-propagation-release.js +18 -61
- package/packages/core/release-propagation-work.js +3 -7
- package/packages/core/release-propagation.js +2 -12
- package/scripts/auditable-demo-renditions.mjs +2 -7
- package/scripts/auditable-demo.mjs +21 -27
- package/scripts/aws-windows-jit-controller-core.mjs +2 -20
- package/scripts/aws-windows-jit-controller.mjs +109 -184
- package/scripts/aws-windows-jit-core.mjs +3 -21
- package/scripts/aws-windows-jit.mjs +0 -2
- package/scripts/build-standalone-binary.mjs +0 -4
- package/scripts/buildchain-cli-help.mjs +1 -2
- package/scripts/capture-package-release-propagation.mjs +1 -24
- package/scripts/check-inventory.mjs +0 -5
- package/scripts/finalize-native-artifact-signing-result.mjs +15 -37
- package/scripts/generate-site-bundle.mjs +0 -2
- package/scripts/locked-source-checkout.mjs +3 -17
- package/scripts/release-propagation.mjs +2 -77
- package/contracts/auditable-demo-scenario-v1.schema.json +0 -153
- package/packages/core/release-propagation-pickup.js +0 -387
- package/packages/core/workflow-call-contract.js +0 -308
- package/packages/core/workflow-yaml-contract.js +0 -272
- package/scripts/auditable-demo-platform.mjs +0 -506
- package/scripts/aws-macos-jit-controller-core.mjs +0 -389
- package/scripts/aws-macos-jit-controller-runtime.mjs +0 -82
- package/scripts/aws-macos-jit-controller.mjs +0 -558
- package/scripts/aws-macos-jit-job-controller.mjs +0 -401
- package/scripts/aws-windows-jit-campaign-core.mjs +0 -296
- package/scripts/aws-windows-jit-campaign.mjs +0 -202
- package/scripts/workflow-call-contract.mjs +0 -133
|
@@ -415,10 +415,9 @@ first run can stop at `finalizing` after registry publication. If the reviewed
|
|
|
415
415
|
merge commit later contains that exact transaction release material but the
|
|
416
416
|
exact tag is still absent, a retry performs finalization only: it reloads the
|
|
417
417
|
same durable source, release material, tooling, evidence, version, and target
|
|
418
|
-
bindings; creates the exact
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
authorize the newer composite channel tree
|
|
418
|
+
bindings; creates the exact and floating tags at the transaction release SHA;
|
|
419
|
+
and completes the passport from the transaction source tree. It does not rerun
|
|
420
|
+
the provider mutation and does not authorize the newer composite channel tree
|
|
422
421
|
as published material. A different source tree still requires a new version and
|
|
423
422
|
a fresh release candidate.
|
|
424
423
|
|
|
@@ -537,9 +536,8 @@ inside the mutation boundary.
|
|
|
537
536
|
|
|
538
537
|
If finalization fails after an exact Git tag is created, the next run reads the
|
|
539
538
|
durable `finalizing` state, verifies the exact tag points at the recorded
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
material conflict and blocks recovery.
|
|
539
|
+
release SHA, and retries the remaining floating refs. An exact tag at a
|
|
540
|
+
different SHA is a material conflict and blocks recovery.
|
|
543
541
|
|
|
544
542
|
## CLI Recovery
|
|
545
543
|
|
package/docs/release-flow.md
CHANGED
|
@@ -96,10 +96,6 @@ published artifacts are already durable.
|
|
|
96
96
|
The intended governance split is:
|
|
97
97
|
|
|
98
98
|
- exact tags such as `v3.0.2` and `v3.0.3-alpha.0` are immutable audit refs;
|
|
99
|
-
- for publish transactions, the exact tag points to the transaction
|
|
100
|
-
`source_sha`, matching package-registry source metadata such as npm
|
|
101
|
-
`gitHead`; generated version-state commits remain on protected branches and
|
|
102
|
-
floating channel refs;
|
|
103
99
|
- floating tags such as `v3`, `v3.0`, `v3.0-alpha`, and `v3-alpha` are mutable channel refs
|
|
104
100
|
owned by the Buildchain promotion token;
|
|
105
101
|
- protected branches still require reviewed channel PRs before Buildchain can
|
|
@@ -575,38 +575,6 @@ context shape into the other. Consumer repositories can keep their context
|
|
|
575
575
|
stable while changing the actual verification command declaratively in
|
|
576
576
|
`buildchain.toml`:
|
|
577
577
|
|
|
578
|
-
Before a release caller is merged, consumers should also verify its exact
|
|
579
|
-
reusable-workflow call contract. The checker reads the caller and an already
|
|
580
|
-
checked-out exact Buildchain commit; it never resolves a floating ref or starts
|
|
581
|
-
a release. It rejects unknown or missing inputs and secrets, literal type
|
|
582
|
-
drift, insufficient permissions, untrusted event classes, and a caller pin that
|
|
583
|
-
does not equal the checked callee commit. Defaults, workflow bytes, and the
|
|
584
|
-
complete interface are bound into `contractRoot`; the receipt additionally
|
|
585
|
-
binds the caller commit/tree and both workflow digests.
|
|
586
|
-
|
|
587
|
-
```sh
|
|
588
|
-
node .buildchain/workflow-contract-runtime/scripts/workflow-call-contract.mjs check \
|
|
589
|
-
--caller-root . \
|
|
590
|
-
--caller-workflow .github/workflows/release-new-version.yml \
|
|
591
|
-
--caller-repository kungfu-systems/example \
|
|
592
|
-
--job promote \
|
|
593
|
-
--callee-root .buildchain/workflow-contract-runtime \
|
|
594
|
-
--callee-workflow .github/workflows/release-candidate-promote.yml \
|
|
595
|
-
--callee-repository kungfu-systems/buildchain \
|
|
596
|
-
--trusted-event workflow_dispatch \
|
|
597
|
-
--trusted-event pull_request:closed \
|
|
598
|
-
--expected-contract-root "$(cat .buildchain/release-call-contract-root)" \
|
|
599
|
-
--output .buildchain/workflow-call-receipts/release-new-version.json
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
The checkout at `.buildchain/workflow-contract-runtime` must use the same
|
|
603
|
-
40-character SHA written in the caller's `uses:` edge. To accept an intentional
|
|
604
|
-
contract change, first run without `--expected-contract-root`, review the full
|
|
605
|
-
diagnostic and exact coordinates, then replace only the committed root. The
|
|
606
|
-
ordinary PR check runs this command before any candidate or promotion dispatch.
|
|
607
|
-
Local pre-commit rehearsal may add `--allow-dirty`; that result is marked
|
|
608
|
-
`receiptReusable: false` and cannot replace the clean exact-source receipt.
|
|
609
|
-
|
|
610
578
|
```toml
|
|
611
579
|
[lifecycle.install]
|
|
612
580
|
command = "cargo fetch --locked"
|
|
@@ -863,13 +831,6 @@ must be absent or already point at accepted release material. This preserves an
|
|
|
863
831
|
immutable tag that represents a completed transaction while allowing a tag
|
|
864
832
|
collision discovered after registry publication to recover without republishing.
|
|
865
833
|
|
|
866
|
-
For package publish transactions, the immutable public version tag points to
|
|
867
|
-
the transaction `source_sha`, so registry source metadata such as npm `gitHead`
|
|
868
|
-
and the Git tag identify the same source commit. Protected branches and mutable
|
|
869
|
-
channel tags continue to point to the generated `release_sha`. Recovery accepts
|
|
870
|
-
older completed transactions whose exact tags already point to recorded release
|
|
871
|
-
or release-material SHAs, but new tags are source-bound.
|
|
872
|
-
|
|
873
834
|
Every Buildchain publish model that can run registry side effects must bind the
|
|
874
835
|
publish entrypoint to an immutable `publish-gate/*` source lock. The reusable
|
|
875
836
|
`release-candidate-promote.yml@v3` wrapper creates or updates that gate ref and
|
|
@@ -439,7 +439,6 @@ jobs:
|
|
|
439
439
|
checkout-cache-timeout-seconds: 60
|
|
440
440
|
checkout-cache-github-timeout-seconds: 600
|
|
441
441
|
checkout-cache-fetch-attempts: 3
|
|
442
|
-
checkout-history-mode: shallow
|
|
443
442
|
```
|
|
444
443
|
|
|
445
444
|
`checkout-cache-mode` accepts:
|
|
@@ -450,14 +449,6 @@ jobs:
|
|
|
450
449
|
| `auto` | Try the trusted cache first; on miss, record the miss and fall back according to `checkout-cache-fallback`. |
|
|
451
450
|
| `require` | Require the cache to provide the locked commit and fail before lifecycle work if unavailable. |
|
|
452
451
|
|
|
453
|
-
`checkout-history-mode` defaults to `shallow`, preserving the bounded single-
|
|
454
|
-
commit transport used by ordinary builds. Set it to `full` only when a
|
|
455
|
-
consumer gate must inspect source ancestry, for example when an Alpha pull
|
|
456
|
-
request qualifies GitHub's synthetic merge ref while retained evidence is
|
|
457
|
-
bound to an ancestor of the source-lock head. Full mode still verifies the
|
|
458
|
-
resolved immutable `HEAD` and tree; it changes only whether the advertised
|
|
459
|
-
source ref is fetched with depth one or with its reachable history.
|
|
460
|
-
|
|
461
452
|
The cache can be a local/LAN mirror URL template or a runner-local bare
|
|
462
453
|
reference repository template. Templates support `{owner}`, `{repo}`,
|
|
463
454
|
`{repository}`, `{repositorySlug}`, and `{sha}`. The workflow also reads
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "3.0.5
|
|
3
|
+
"version": "3.0.5",
|
|
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",
|
|
@@ -72,41 +72,6 @@ function majorLineFromPackageVersion(version = "") {
|
|
|
72
72
|
return match ? `v${match[1]}` : "v3";
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
function declarativeAuditableDemoSurface(root, pkg, majorLine) {
|
|
76
|
-
return surface(root, {
|
|
77
|
-
id: "declarative-auditable-demo",
|
|
78
|
-
kind: "workflow",
|
|
79
|
-
path: ".github/workflows/.declarative-auditable-demo.yml",
|
|
80
|
-
publicRef: `${pkg.repository ? "kungfu-systems/buildchain" : "buildchain"}/.github/workflows/.declarative-auditable-demo.yml@${majorLine}`,
|
|
81
|
-
requiredInputs: ["binary-artifact-name", "binary-artifact-digest", "renderer-image"],
|
|
82
|
-
requiredOutputs: [
|
|
83
|
-
"source-sha", "capture-artifact-name", "capture-artifact-digest",
|
|
84
|
-
"evidence-artifact-name", "evidence-artifact-digest", "publication-pr-url",
|
|
85
|
-
],
|
|
86
|
-
breakingDefaults: {
|
|
87
|
-
scenarioPathDefault: ".buildchain/auditable-demo.json",
|
|
88
|
-
renderMediaDefault: false,
|
|
89
|
-
materializeDefault: false,
|
|
90
|
-
mediaProfileDefault: "responsive-web-delivery-v1",
|
|
91
|
-
artifactRetentionDaysDefault: 14,
|
|
92
|
-
executionBoundary: "exact-binary-network-none-secret-free-60-seconds",
|
|
93
|
-
},
|
|
94
|
-
optionalInputs: [
|
|
95
|
-
"buildchain-repository", "source-ref", "scenario-path", "render-media",
|
|
96
|
-
"media-profile", "materialize", "materialize-base-ref", "artifact-retention-days",
|
|
97
|
-
],
|
|
98
|
-
guarantees: [
|
|
99
|
-
"one versioned declaration can contain multiple demos with multiple ordered literal argv steps",
|
|
100
|
-
"every demo uses the exact same-run standalone binary admitted by producer-owned artifact name and digest",
|
|
101
|
-
"capture runs without network or inherited secrets and retains independent native 1080p and 720p terminal dimensions",
|
|
102
|
-
"the generic adapter feeds the required auditable demo Gate and immutable renderer without product-specific glue",
|
|
103
|
-
"full rendering emits a content-addressed Release Passport and idempotent protected README update pull request",
|
|
104
|
-
"manual, alpha, and release callers use the same capture, Gate, renderer, passport, and materializer implementation",
|
|
105
|
-
"identity, compliance, Product System metadata, package metadata, registry history, scans, and generation grant no authority",
|
|
106
|
-
],
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
75
|
export function createBuildchainContractWorld({
|
|
111
76
|
root = process.cwd(),
|
|
112
77
|
packageJson = undefined,
|
|
@@ -395,7 +360,7 @@ export function createBuildchainContractWorld({
|
|
|
395
360
|
"Build Images owns encoding, Buildchain owns qualification and receipts, and site repositories own browser loading and accessibility behavior",
|
|
396
361
|
"media qualification does not claim browser playback, responsive layout, reduced-motion behavior, accessibility, or production deployment",
|
|
397
362
|
],
|
|
398
|
-
}),
|
|
363
|
+
}),
|
|
399
364
|
surface(root, {
|
|
400
365
|
id: "promote-buildchain-ref-action",
|
|
401
366
|
kind: "action",
|
|
@@ -5,7 +5,6 @@ import { createPublicationAuthorityRegistry } from "./publication-authority.js";
|
|
|
5
5
|
|
|
6
6
|
const DESCRIPTORS = Object.freeze([
|
|
7
7
|
[".github/workflows/.auditable-demo.yml", "non-publication-oidc"],
|
|
8
|
-
[".github/workflows/.declarative-auditable-demo.yml", "governance-write"],
|
|
9
8
|
[".github/workflows/.build.yml", "non-publication-oidc"],
|
|
10
9
|
[".github/workflows/.release-candidate-promote.yml", "product-publication", true, ["npm-publish", "github-release", "channel-ref"], "trusted-publishing", "none", "fixed", "caller-bound"],
|
|
11
10
|
[".github/workflows/.publication-authority.yml", "evidence-publication"],
|
|
@@ -20,7 +19,6 @@ const DESCRIPTORS = Object.freeze([
|
|
|
20
19
|
[".github/workflows/binary-distribution.yml", "evidence-publication"],
|
|
21
20
|
[".github/workflows/binary-release-assets.yml", "governance-write"],
|
|
22
21
|
[".github/workflows/artifact-signing-authority.yml", "evidence-publication"],
|
|
23
|
-
[".github/workflows/auditable-demo.yml", "governance-write"],
|
|
24
22
|
[".github/workflows/build-surface-fixture.yml", "non-publication-oidc"],
|
|
25
23
|
[".github/workflows/build.yml", "non-publication-oidc"],
|
|
26
24
|
[".github/workflows/buildchain-alpha-self-dogfood.yml", "non-publication-oidc"],
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
commandId,
|
|
6
6
|
enumerateCliCommandsFromBin,
|
|
7
7
|
} from "./public-surface-cli.js";
|
|
8
|
-
import { parseReusableWorkflowInterface } from "./workflow-yaml-contract.js";
|
|
9
8
|
|
|
10
9
|
export { enumerateCliCommandsFromBin } from "./public-surface-cli.js";
|
|
11
10
|
|
|
@@ -47,6 +46,38 @@ function listDirectories(root, dir) {
|
|
|
47
46
|
.sort();
|
|
48
47
|
}
|
|
49
48
|
|
|
49
|
+
function parseYamlWorkflowCall(text) {
|
|
50
|
+
const lines = text.split(/\r?\n/);
|
|
51
|
+
const result = { reusable: false, inputs: [], secrets: [], outputs: [] };
|
|
52
|
+
const workflowCallIndex = lines.findIndex((line) => /^(\s*)workflow_call:\s*$/.test(line));
|
|
53
|
+
if (workflowCallIndex === -1) return result;
|
|
54
|
+
result.reusable = true;
|
|
55
|
+
const workflowCallIndent = lines[workflowCallIndex].match(/^(\s*)/)?.[1].length || 0;
|
|
56
|
+
let section = "";
|
|
57
|
+
for (const line of lines.slice(workflowCallIndex + 1)) {
|
|
58
|
+
const currentIndent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
59
|
+
if (line.trim() && currentIndent <= workflowCallIndent) break;
|
|
60
|
+
const sectionMatch = line.match(new RegExp(`^\\s{${workflowCallIndent + 2}}(inputs|secrets|outputs):\\s*$`));
|
|
61
|
+
if (sectionMatch) {
|
|
62
|
+
section = sectionMatch[1];
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (line.trim() && currentIndent <= workflowCallIndent + 2) {
|
|
66
|
+
section = "";
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!section) continue;
|
|
70
|
+
const fieldMatch = line.match(new RegExp(`^\\s{${workflowCallIndent + 4}}([A-Za-z0-9_-]+):\\s*$`));
|
|
71
|
+
if (fieldMatch) result[section].push(fieldMatch[1]);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
reusable: result.reusable,
|
|
75
|
+
inputs: uniqueSorted(result.inputs),
|
|
76
|
+
secrets: uniqueSorted(result.secrets),
|
|
77
|
+
outputs: uniqueSorted(result.outputs),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
50
81
|
function parseYamlTopLevelInputs(text) {
|
|
51
82
|
const lines = text.split(/\r?\n/);
|
|
52
83
|
const inputs = [];
|
|
@@ -73,14 +104,14 @@ function parseYamlTopLevelInputs(text) {
|
|
|
73
104
|
|
|
74
105
|
export function enumerateWorkflowInputs({ root = process.cwd() } = {}) {
|
|
75
106
|
return listFiles(root, ".github/workflows", (name) => /\.ya?ml$/.test(name)).map((relPath) => {
|
|
76
|
-
const contract =
|
|
107
|
+
const contract = parseYamlWorkflowCall(readText(root, relPath));
|
|
77
108
|
return {
|
|
78
109
|
id: relPath.replace(/^\.github\/workflows\//, "").replace(/\.ya?ml$/, ""),
|
|
79
110
|
path: relPath,
|
|
80
111
|
reusable: contract.reusable,
|
|
81
|
-
inputs: contract.inputs
|
|
112
|
+
inputs: contract.inputs,
|
|
82
113
|
inputCount: contract.inputs.length,
|
|
83
|
-
secrets: contract.secrets
|
|
114
|
+
secrets: contract.secrets,
|
|
84
115
|
secretCount: contract.secrets.length,
|
|
85
116
|
outputs: contract.outputs,
|
|
86
117
|
outputCount: contract.outputs.length,
|
|
@@ -31,9 +31,7 @@ function normalizePackageFact(pkg = {}) {
|
|
|
31
31
|
name: assertString(pkg.name, "upstreamRelease.package.name"),
|
|
32
32
|
version: assertString(pkg.version, "upstreamRelease.package.version"),
|
|
33
33
|
integrity: assertString(pkg.integrity, "upstreamRelease.package.integrity"),
|
|
34
|
-
gitHead: pkg.gitHead || pkg.git_head
|
|
35
|
-
? assertCommitSha(pkg.gitHead || pkg.git_head, "upstreamRelease.package.gitHead")
|
|
36
|
-
: "",
|
|
34
|
+
gitHead: assertCommitSha(pkg.gitHead || pkg.git_head, "upstreamRelease.package.gitHead"),
|
|
37
35
|
};
|
|
38
36
|
if (!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(normalized.integrity)) {
|
|
39
37
|
throw new Error("upstreamRelease.package.integrity must be an exact sha512 SRI value");
|
|
@@ -41,57 +39,6 @@ function normalizePackageFact(pkg = {}) {
|
|
|
41
39
|
return normalized;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
function normalizeRegistryProvenance(value, packageFact, repository, sourceSha) {
|
|
45
|
-
if (!value) return undefined;
|
|
46
|
-
if (!packageFact) throw new Error("upstreamRelease.registryProvenance requires package");
|
|
47
|
-
const input = assertPlainObject(value, "upstreamRelease.registryProvenance");
|
|
48
|
-
const normalized = {
|
|
49
|
-
registry: assertString(input.registry, "upstreamRelease.registryProvenance.registry"),
|
|
50
|
-
attestationUrl: assertString(input.attestationUrl || input.attestation_url, "upstreamRelease.registryProvenance.attestationUrl"),
|
|
51
|
-
predicateType: assertString(input.predicateType || input.predicate_type, "upstreamRelease.registryProvenance.predicateType"),
|
|
52
|
-
subjectSha512: assertString(input.subjectSha512 || input.subject_sha512, "upstreamRelease.registryProvenance.subjectSha512"),
|
|
53
|
-
repository: assertString(input.repository, "upstreamRelease.registryProvenance.repository"),
|
|
54
|
-
sourceSha: assertCommitSha(input.sourceSha || input.source_sha, "upstreamRelease.registryProvenance.sourceSha"),
|
|
55
|
-
workflowPath: assertString(input.workflowPath || input.workflow_path, "upstreamRelease.registryProvenance.workflowPath"),
|
|
56
|
-
workflowRef: assertString(input.workflowRef || input.workflow_ref, "upstreamRelease.registryProvenance.workflowRef"),
|
|
57
|
-
runUrl: assertString(input.runUrl || input.run_url, "upstreamRelease.registryProvenance.runUrl"),
|
|
58
|
-
};
|
|
59
|
-
if (normalized.registry !== "https://registry.npmjs.org") throw new Error("upstreamRelease.registryProvenance.registry must be the public npm registry");
|
|
60
|
-
if (!normalized.attestationUrl.startsWith("https://registry.npmjs.org/-/npm/v1/attestations/")) throw new Error("upstreamRelease.registryProvenance.attestationUrl must use the npm attestation API");
|
|
61
|
-
if (normalized.predicateType !== "https://slsa.dev/provenance/v1") throw new Error("upstreamRelease.registryProvenance must use SLSA provenance v1");
|
|
62
|
-
if (!/^[0-9a-f]{128}$/.test(normalized.subjectSha512)) throw new Error("upstreamRelease.registryProvenance.subjectSha512 must be an exact SHA-512 digest");
|
|
63
|
-
const sriDigest = Buffer.from(packageFact.integrity.slice("sha512-".length), "base64").toString("hex");
|
|
64
|
-
if (normalized.subjectSha512 !== sriDigest) throw new Error("upstreamRelease registry provenance subject must match package integrity");
|
|
65
|
-
if (normalized.repository !== repository || normalized.sourceSha !== sourceSha) throw new Error("upstreamRelease registry provenance source must match the release envelope");
|
|
66
|
-
return normalized;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function normalizeReleaseEvidence(release, packageFact, repository, sourceSha, version) {
|
|
70
|
-
const registryProvenance = normalizeRegistryProvenance(
|
|
71
|
-
release.registryProvenance || release.registry_provenance,
|
|
72
|
-
packageFact,
|
|
73
|
-
repository,
|
|
74
|
-
sourceSha,
|
|
75
|
-
);
|
|
76
|
-
const tag = optionalString(release.tag);
|
|
77
|
-
const target = release.tagTargetSha || release.tag_target_sha;
|
|
78
|
-
const tagTargetSha = target ? assertCommitSha(target, "upstreamRelease.tagTargetSha") : "";
|
|
79
|
-
if (!registryProvenance) {
|
|
80
|
-
if (tag !== `v${version}`) throw new Error("upstreamRelease.tag must exactly match the published version");
|
|
81
|
-
if (tagTargetSha !== sourceSha) throw new Error("upstreamRelease tag target must match sourceSha");
|
|
82
|
-
if (packageFact && packageFact.gitHead !== sourceSha) throw new Error("upstreamRelease package gitHead must match sourceSha");
|
|
83
|
-
} else if ((tag && tag !== `v${version}`) || (tagTargetSha && tagTargetSha !== sourceSha)) {
|
|
84
|
-
throw new Error("upstreamRelease optional tag evidence must match the provenance source");
|
|
85
|
-
}
|
|
86
|
-
const passportInput = release.releasePassport || release.release_passport;
|
|
87
|
-
return {
|
|
88
|
-
tag,
|
|
89
|
-
tagTargetSha,
|
|
90
|
-
registryProvenance,
|
|
91
|
-
releasePassport: passportInput ? normalizeReleasePassport(passportInput) : undefined,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
42
|
function normalizeDigestUrlFact(value = {}, label) {
|
|
96
43
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
97
44
|
throw new Error(`${label} must be an object`);
|
|
@@ -170,22 +117,33 @@ export function normalizeUpstreamRelease(input) {
|
|
|
170
117
|
throw new Error("upstreamRelease.repository must be owner/repo");
|
|
171
118
|
}
|
|
172
119
|
const sourceSha = assertCommitSha(release.sourceSha || release.source_sha, "upstreamRelease.sourceSha");
|
|
120
|
+
const tagTargetSha = assertCommitSha(
|
|
121
|
+
release.tagTargetSha || release.tag_target_sha,
|
|
122
|
+
"upstreamRelease.tagTargetSha",
|
|
123
|
+
);
|
|
173
124
|
const version = packageFact?.version || publicationArtifact?.version || "";
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
125
|
+
const tag = assertString(release.tag, "upstreamRelease.tag");
|
|
126
|
+
if (tag !== `v${version}`) {
|
|
127
|
+
throw new Error("upstreamRelease.tag must exactly match the published version");
|
|
128
|
+
}
|
|
129
|
+
if (tagTargetSha !== sourceSha) {
|
|
130
|
+
throw new Error("upstreamRelease tag target must match sourceSha");
|
|
131
|
+
}
|
|
132
|
+
if (packageFact && packageFact.gitHead !== sourceSha) {
|
|
133
|
+
throw new Error("upstreamRelease package gitHead must match sourceSha");
|
|
134
|
+
}
|
|
176
135
|
if (packageFact && publicationArtifact && packageFact.version !== publicationArtifact.version) {
|
|
177
136
|
throw new Error("upstreamRelease package and publication artifact versions must match");
|
|
178
137
|
}
|
|
179
138
|
return {
|
|
180
139
|
repository,
|
|
181
140
|
channel: normalizeChannel(release.channel, "upstreamRelease.channel"),
|
|
182
|
-
tag
|
|
183
|
-
tagTargetSha
|
|
141
|
+
tag,
|
|
142
|
+
tagTargetSha,
|
|
184
143
|
sourceSha,
|
|
185
144
|
package: packageFact,
|
|
186
145
|
publicationArtifact,
|
|
187
|
-
releasePassport:
|
|
188
|
-
registryProvenance: evidence.registryProvenance,
|
|
146
|
+
releasePassport: normalizeReleasePassport(release.releasePassport || release.release_passport),
|
|
189
147
|
siteBundle: release.siteBundle || release.site_bundle
|
|
190
148
|
? {
|
|
191
149
|
manifestSha256: assertString(
|
|
@@ -208,7 +166,6 @@ export function verifyReleaseLockBinding({ release, lock, downstream, propagatio
|
|
|
208
166
|
package: lock.upstream?.package,
|
|
209
167
|
publicationArtifact: lock.upstream?.publicationArtifact,
|
|
210
168
|
releasePassport: lock.upstream?.releasePassport,
|
|
211
|
-
registryProvenance: lock.upstream?.registryProvenance,
|
|
212
169
|
siteBundle: lock.upstream?.siteBundle,
|
|
213
170
|
});
|
|
214
171
|
if (JSON.stringify(lockRelease) !== JSON.stringify(normalizedRelease)) {
|
|
@@ -317,12 +317,8 @@ function verifyWorkRelease(work) {
|
|
|
317
317
|
downstream.executionProfile,
|
|
318
318
|
"release propagation work downstream.executionProfile",
|
|
319
319
|
);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"release propagation work releaseLock.downstream.executionProfile",
|
|
323
|
-
);
|
|
324
|
-
if (!profile || !lockedProfile
|
|
325
|
-
|| sha256Json(profile) !== sha256Json(lockedProfile)) {
|
|
320
|
+
if (!profile
|
|
321
|
+
|| JSON.stringify(profile) !== JSON.stringify(downstream.releaseLock.downstream.executionProfile)) {
|
|
326
322
|
throw new Error("release propagation work execution profile disagrees with the exact release lock");
|
|
327
323
|
}
|
|
328
324
|
return { downstream, profile };
|
|
@@ -404,7 +400,7 @@ function verifyWorkPlanAndState(work, downstream, profile) {
|
|
|
404
400
|
downstream.branch,
|
|
405
401
|
downstream.baseRef,
|
|
406
402
|
);
|
|
407
|
-
if (
|
|
403
|
+
if (JSON.stringify(plan.commands) !== JSON.stringify(expectedCommands)) {
|
|
408
404
|
throw new Error("release propagation work commands are not canonical");
|
|
409
405
|
}
|
|
410
406
|
const state = assertExactFields(
|
|
@@ -219,7 +219,6 @@ export function createReleasePropagationLock({
|
|
|
219
219
|
package: upstreamRelease.package,
|
|
220
220
|
publicationArtifact: upstreamRelease.publicationArtifact,
|
|
221
221
|
releasePassport: upstreamRelease.releasePassport,
|
|
222
|
-
registryProvenance: upstreamRelease.registryProvenance,
|
|
223
222
|
siteBundle: upstreamRelease.siteBundle,
|
|
224
223
|
},
|
|
225
224
|
downstream: {
|
|
@@ -407,7 +406,7 @@ export function createReleasePropagationReceipt({
|
|
|
407
406
|
name: upstreamRelease.package.name,
|
|
408
407
|
version: upstreamRelease.package.version,
|
|
409
408
|
integrity: upstreamRelease.package.integrity,
|
|
410
|
-
gitHead: upstreamRelease.package.gitHead
|
|
409
|
+
gitHead: upstreamRelease.package.gitHead,
|
|
411
410
|
}
|
|
412
411
|
: null,
|
|
413
412
|
},
|
|
@@ -415,8 +414,7 @@ export function createReleasePropagationReceipt({
|
|
|
415
414
|
state: release.channel === "alpha" ? "complete" : "not-applicable",
|
|
416
415
|
evidence: release.channel === "alpha"
|
|
417
416
|
? {
|
|
418
|
-
releasePassportSha256: upstreamRelease.releasePassport
|
|
419
|
-
registryProvenance: upstreamRelease.registryProvenance || null,
|
|
417
|
+
releasePassportSha256: upstreamRelease.releasePassport.sha256,
|
|
420
418
|
tag: upstreamRelease.tag,
|
|
421
419
|
}
|
|
422
420
|
: null,
|
|
@@ -478,11 +476,3 @@ export {
|
|
|
478
476
|
createPackageReleasePropagationCapture,
|
|
479
477
|
normalizePackageReleasePropagationConfig,
|
|
480
478
|
} from "./release-propagation-capture.js";
|
|
481
|
-
export {
|
|
482
|
-
MANUAL_UPSTREAM_PICKUP_CONFIG_CONTRACT,
|
|
483
|
-
MANUAL_UPSTREAM_PICKUP_PLAN_CONTRACT,
|
|
484
|
-
createManualUpstreamPickupCapture,
|
|
485
|
-
createManualUpstreamPickupPlan,
|
|
486
|
-
normalizeManualUpstreamPickupConfig,
|
|
487
|
-
resolveNpmRegistryRelease,
|
|
488
|
-
} from "./release-propagation-pickup.js";
|
|
@@ -29,8 +29,7 @@ export function validateTerminalCapture(value, scene, helpers) {
|
|
|
29
29
|
exactKeys(value.dimensions, ["columns", "rows"], [], "terminalCapture.dimensions");
|
|
30
30
|
integer(value.dimensions.columns, 80, 200, "terminalCapture.dimensions.columns");
|
|
31
31
|
integer(value.dimensions.rows, 24, 80, "terminalCapture.dimensions.rows");
|
|
32
|
-
const
|
|
33
|
-
const durationMs = integer(value.durationMs, 500, maximumDurationMs, "terminalCapture.durationMs");
|
|
32
|
+
const durationMs = integer(value.durationMs, 500, 60000, "terminalCapture.durationMs");
|
|
34
33
|
invariant(
|
|
35
34
|
durationMs <= scene.durationMs && scene.durationMs - durationMs <= 2000,
|
|
36
35
|
"terminal capture duration must end within two seconds of the scene",
|
|
@@ -115,13 +114,9 @@ export function validateRenditionSet(output, helpers) {
|
|
|
115
114
|
const captureBytes = readRegular(path.join(output, entry.terminalCapture), `${label} terminal capture`, maxBytes);
|
|
116
115
|
const capture = validateTerminalCapture(JSON.parse(decodeUtf8(captureBytes, `${label} terminal capture`)), scene, helpers);
|
|
117
116
|
invariant(entry.captureRoot === sha256(captureBytes), `${label}.captureRoot mismatch`);
|
|
118
|
-
return { ...entry,
|
|
117
|
+
return { ...entry, transcript, lines, scene, projection, capture };
|
|
119
118
|
});
|
|
120
119
|
invariant(normalized[0].captureRoot !== normalized[1].captureRoot, "native rendition capture roots must be distinct");
|
|
121
|
-
invariant(
|
|
122
|
-
(normalized[0].scene.durationClass ?? "standard") === (normalized[1].scene.durationClass ?? "standard"),
|
|
123
|
-
"native rendition duration classes must match",
|
|
124
|
-
);
|
|
125
120
|
invariant(
|
|
126
121
|
JSON.stringify(normalized[0].capture.dimensions) !== JSON.stringify(normalized[1].capture.dimensions),
|
|
127
122
|
"native rendition PTY dimensions must be distinct",
|
|
@@ -36,10 +36,6 @@ const OPTIONAL_ADAPTER_FILES = [
|
|
|
36
36
|
];
|
|
37
37
|
const MAX_TERMINAL_CAPTURE_BYTES = 4 * 1024 * 1024;
|
|
38
38
|
const MAX_TERMINAL_CAPTURE_EVENTS = 10_000;
|
|
39
|
-
const STANDARD_MAX_DURATION_MS = 60_000;
|
|
40
|
-
const LONG_FORM_MAX_DURATION_MS = 180_000;
|
|
41
|
-
const LONG_FORM_MAX_FPS = 10;
|
|
42
|
-
const MAX_RENDER_FRAMES = 1_800;
|
|
43
39
|
|
|
44
40
|
function invariant(condition, message) {
|
|
45
41
|
if (!condition) throw new Error(message);
|
|
@@ -190,20 +186,15 @@ function validateScene(value) {
|
|
|
190
186
|
exactKeys(
|
|
191
187
|
value,
|
|
192
188
|
["schema", "id", "width", "height", "fps", "durationMs", "title"],
|
|
193
|
-
["
|
|
189
|
+
["commandLabel", "background", "accent"],
|
|
194
190
|
"scene",
|
|
195
191
|
);
|
|
196
192
|
invariant(value.schema === "build-images.demo-scene/v1", "unsupported scene schema");
|
|
197
193
|
invariant(/^[a-z0-9][a-z0-9._-]{0,63}$/.test(value.id), "scene.id is invalid");
|
|
198
194
|
integer(value.width, 640, 1920, "scene.width");
|
|
199
195
|
integer(value.height, 360, 1080, "scene.height");
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const maximumDurationMs = durationClass === "long-form" ? LONG_FORM_MAX_DURATION_MS : STANDARD_MAX_DURATION_MS;
|
|
203
|
-
const maximumFps = durationClass === "long-form" ? LONG_FORM_MAX_FPS : 30;
|
|
204
|
-
integer(value.fps, 1, maximumFps, "scene.fps");
|
|
205
|
-
integer(value.durationMs, 500, maximumDurationMs, "scene.durationMs");
|
|
206
|
-
invariant(Math.ceil((value.durationMs / 1000) * value.fps) <= MAX_RENDER_FRAMES, "scene exceeds the deterministic source-frame bound");
|
|
196
|
+
integer(value.fps, 1, 30, "scene.fps");
|
|
197
|
+
integer(value.durationMs, 500, 60000, "scene.durationMs");
|
|
207
198
|
text(value.title, 1, 120, "scene.title");
|
|
208
199
|
if (value.commandLabel !== undefined) text(value.commandLabel, 0, 160, "scene.commandLabel");
|
|
209
200
|
for (const key of ["background", "accent"]) {
|
|
@@ -215,7 +206,6 @@ function validateScene(value) {
|
|
|
215
206
|
width: value.width,
|
|
216
207
|
height: value.height,
|
|
217
208
|
fps: value.fps,
|
|
218
|
-
...(value.durationClass === undefined ? {} : { durationClass }),
|
|
219
209
|
durationMs: value.durationMs,
|
|
220
210
|
title: value.title,
|
|
221
211
|
commandLabel: value.commandLabel ?? "",
|
|
@@ -534,7 +524,7 @@ function validateBudgetBasis(entry, label) {
|
|
|
534
524
|
MAX_BUNDLE_MEMBER_BYTES,
|
|
535
525
|
`${label}.budgetBasis.observedBytes`,
|
|
536
526
|
);
|
|
537
|
-
const multiplier = integer(entry.budgetBasis.multiplier, 1,
|
|
527
|
+
const multiplier = integer(entry.budgetBasis.multiplier, 1, 64, `${label}.budgetBasis.multiplier`);
|
|
538
528
|
invariant(entry.budgetBasis.rounding === "next-power-of-two", `${label}.budgetBasis.rounding is unsupported`);
|
|
539
529
|
const expected = 2 ** Math.ceil(Math.log2(observedBytes * multiplier));
|
|
540
530
|
invariant(entry.maximumBytes === expected, `${label}.maximumBytes does not match its measured budget basis`);
|
|
@@ -1027,15 +1017,6 @@ function verifyRendererOutput(renderOutput, expectedImage, expectedInputs, optio
|
|
|
1027
1017
|
return { manifest, probe, qualification };
|
|
1028
1018
|
}
|
|
1029
1019
|
|
|
1030
|
-
function renditionInputRoots(root, renditions) {
|
|
1031
|
-
return renditions.map((rendition) => ({
|
|
1032
|
-
id: rendition.id, role: rendition.role, captureRoot: rendition.captureRoot,
|
|
1033
|
-
sceneRoot: sha256(readRegular(path.join(root, rendition.files.scene), `${rendition.id} scene`)),
|
|
1034
|
-
transcriptRoot: sha256(readRegular(path.join(root, rendition.files.transcript), `${rendition.id} transcript`)),
|
|
1035
|
-
projectionRoot: sha256(readRegular(path.join(root, rendition.files.projection), `${rendition.id} projection`)),
|
|
1036
|
-
}));
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
1020
|
function finalizeGate(values) {
|
|
1040
1021
|
const adapterOutput = path.resolve(required(values, "--adapter-output"));
|
|
1041
1022
|
const smokeInput = path.resolve(required(values, "--smoke-input"));
|
|
@@ -1128,7 +1109,14 @@ function finalizeGate(values) {
|
|
|
1128
1109
|
renditionSet: {
|
|
1129
1110
|
schema: normalized.renditionSet.schema,
|
|
1130
1111
|
root: sha256(readRegular(path.join(output, "rendition-set.json"), "rendition set")),
|
|
1131
|
-
renditions:
|
|
1112
|
+
renditions: normalized.renditionSet.renditions.map((rendition) => ({
|
|
1113
|
+
id: rendition.id,
|
|
1114
|
+
role: rendition.role,
|
|
1115
|
+
captureRoot: rendition.captureRoot,
|
|
1116
|
+
sceneRoot: sha256(readRegular(path.join(output, rendition.scene), `${rendition.id} scene`)),
|
|
1117
|
+
transcriptRoot: sha256(readRegular(path.join(output, rendition.transcript), `${rendition.id} transcript`)),
|
|
1118
|
+
projectionRoot: sha256(readRegular(path.join(output, rendition.projection), `${rendition.id} projection`)),
|
|
1119
|
+
})),
|
|
1132
1120
|
},
|
|
1133
1121
|
}
|
|
1134
1122
|
: {}),
|
|
@@ -1191,8 +1179,15 @@ function verifyGate(values) {
|
|
|
1191
1179
|
invariant(
|
|
1192
1180
|
qualifiedRenditionSet.schema === normalized.renditionSet.schema
|
|
1193
1181
|
&& qualifiedRenditionSet.root === sha256(readRegular(path.join(bundle, "rendition-set.json"), "rendition set"))
|
|
1194
|
-
&&
|
|
1195
|
-
===
|
|
1182
|
+
&& JSON.stringify(qualifiedRenditionSet.renditions)
|
|
1183
|
+
=== JSON.stringify(normalized.renditionSet.renditions.map((rendition) => ({
|
|
1184
|
+
id: rendition.id,
|
|
1185
|
+
role: rendition.role,
|
|
1186
|
+
captureRoot: rendition.captureRoot,
|
|
1187
|
+
sceneRoot: sha256(readRegular(path.join(bundle, rendition.scene), `${rendition.id} scene`)),
|
|
1188
|
+
transcriptRoot: sha256(readRegular(path.join(bundle, rendition.transcript), `${rendition.id} transcript`)),
|
|
1189
|
+
projectionRoot: sha256(readRegular(path.join(bundle, rendition.projection), `${rendition.id} projection`)),
|
|
1190
|
+
}))),
|
|
1196
1191
|
"gate native rendition roots mismatch",
|
|
1197
1192
|
);
|
|
1198
1193
|
}
|
|
@@ -1316,7 +1311,6 @@ export {
|
|
|
1316
1311
|
inspectRendererMedia,
|
|
1317
1312
|
parseAdapterArguments,
|
|
1318
1313
|
qualifyMediaFixture,
|
|
1319
|
-
renditionInputRoots,
|
|
1320
1314
|
prepareSmoke,
|
|
1321
1315
|
runAdapter,
|
|
1322
1316
|
sha256,
|