@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.
Files changed (62) hide show
  1. package/README.md +0 -31
  2. package/actions/macos-credential-island/README.md +0 -8
  3. package/bin/buildchain.mjs +1 -1
  4. package/contracts/auditable-demo-media-profiles-v1.json +0 -61
  5. package/contracts/evidence/auditable-demo-responsive-web-delivery-v1.json +3 -3
  6. package/contracts/evidence/auditable-demo-web-delivery-v1.json +3 -3
  7. package/dist/site/buildchain-contract.json +25 -86
  8. package/dist/site/buildchain-site.json +38 -64
  9. package/dist/site/capability-registry.json +1 -1
  10. package/dist/site/cli-registry.json +2 -6
  11. package/dist/site/controller-registry.json +3 -11
  12. package/dist/site/kfd-claims.json +11 -53
  13. package/dist/site/kfd-upstream-aggregate.json +1 -1
  14. package/dist/site/manual-registry.json +7 -7
  15. package/dist/site/node-api-registry.json +31 -213
  16. package/dist/site/page-registry.json +20 -50
  17. package/dist/site/public-surface-audit.json +8 -98
  18. package/dist/site/publication-authority-registry.json +1 -42
  19. package/dist/site/publication-registry.json +4 -4
  20. package/dist/site/site-manifest.json +11 -11
  21. package/dist/site/workflow-registry.json +2 -67
  22. package/docs/auditable-demo.md +7 -74
  23. package/docs/aws-us-elastic-runner-burst-plane.md +16 -151
  24. package/docs/cli-reference.md +2 -24
  25. package/docs/node-api-reference.md +72 -90
  26. package/docs/publish-transaction.md +5 -7
  27. package/docs/release-flow.md +0 -4
  28. package/docs/release-governance.md +0 -39
  29. package/docs/reusable-build-surface.md +0 -9
  30. package/package.json +1 -1
  31. package/packages/core/buildchain-contract.js +1 -36
  32. package/packages/core/buildchain-publication-authority.js +0 -2
  33. package/packages/core/public-surface-audit.js +35 -4
  34. package/packages/core/release-propagation-release.js +18 -61
  35. package/packages/core/release-propagation-work.js +3 -7
  36. package/packages/core/release-propagation.js +2 -12
  37. package/scripts/auditable-demo-renditions.mjs +2 -7
  38. package/scripts/auditable-demo.mjs +21 -27
  39. package/scripts/aws-windows-jit-controller-core.mjs +2 -20
  40. package/scripts/aws-windows-jit-controller.mjs +109 -184
  41. package/scripts/aws-windows-jit-core.mjs +3 -21
  42. package/scripts/aws-windows-jit.mjs +0 -2
  43. package/scripts/build-standalone-binary.mjs +0 -4
  44. package/scripts/buildchain-cli-help.mjs +1 -2
  45. package/scripts/capture-package-release-propagation.mjs +1 -24
  46. package/scripts/check-inventory.mjs +0 -5
  47. package/scripts/finalize-native-artifact-signing-result.mjs +15 -37
  48. package/scripts/generate-site-bundle.mjs +0 -2
  49. package/scripts/locked-source-checkout.mjs +3 -17
  50. package/scripts/release-propagation.mjs +2 -77
  51. package/contracts/auditable-demo-scenario-v1.schema.json +0 -153
  52. package/packages/core/release-propagation-pickup.js +0 -387
  53. package/packages/core/workflow-call-contract.js +0 -308
  54. package/packages/core/workflow-yaml-contract.js +0 -272
  55. package/scripts/auditable-demo-platform.mjs +0 -506
  56. package/scripts/aws-macos-jit-controller-core.mjs +0 -389
  57. package/scripts/aws-macos-jit-controller-runtime.mjs +0 -82
  58. package/scripts/aws-macos-jit-controller.mjs +0 -558
  59. package/scripts/aws-macos-jit-job-controller.mjs +0 -401
  60. package/scripts/aws-windows-jit-campaign-core.mjs +0 -296
  61. package/scripts/aws-windows-jit-campaign.mjs +0 -202
  62. 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 tag at the transaction source SHA; moves floating
419
- refs to the transaction release SHA; and completes the passport from the
420
- transaction source tree. It does not rerun the provider mutation and does not
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
- source SHA (while accepting legacy release/material targets for recovery), and
541
- retries the remaining floating refs. An exact tag at an unrelated SHA is a
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
 
@@ -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-alpha.8",
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
- }), declarativeAuditableDemoSurface(root, pkg, majorLine),
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 = parseReusableWorkflowInterface(readText(root, relPath));
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.map((entry) => entry.name),
112
+ inputs: contract.inputs,
82
113
  inputCount: contract.inputs.length,
83
- secrets: contract.secrets.map((entry) => entry.name),
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 evidence = normalizeReleaseEvidence(release, packageFact, repository, sourceSha, version);
175
- if (!evidence.registryProvenance && !evidence.releasePassport) normalizeReleasePassport();
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: evidence.tag,
183
- tagTargetSha: evidence.tagTargetSha,
141
+ tag,
142
+ tagTargetSha,
184
143
  sourceSha,
185
144
  package: packageFact,
186
145
  publicationArtifact,
187
- releasePassport: evidence.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
- const lockedProfile = normalizeExecutionProfile(
321
- downstream.releaseLock.downstream.executionProfile,
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 (sha256Json(plan.commands) !== sha256Json(expectedCommands)) {
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 || null,
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?.sha256 || null,
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 maximumDurationMs = scene.durationClass === "long-form" ? 180000 : 60000;
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, files: Object.fromEntries(["transcript", "projection", "scene", "terminalCapture"].map((key) => [key, entry[key]])), transcript, lines, scene, projection, capture };
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
- ["durationClass", "commandLabel", "background", "accent"],
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
- const durationClass = value.durationClass ?? "standard";
201
- invariant(durationClass === "standard" || durationClass === "long-form", "scene.durationClass is invalid");
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, 128, `${label}.budgetBasis.multiplier`);
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: renditionInputRoots(output, normalized.renditionSet.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
- && stableJson(qualifiedRenditionSet.renditions)
1195
- === stableJson(renditionInputRoots(bundle, normalized.renditionSet.renditions)),
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,