@kungfu-tech/buildchain 3.0.2-alpha.0 → 3.0.2-alpha.10

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 (83) hide show
  1. package/README.md +4 -2
  2. package/actions/github-artifact-attestation/README.md +10 -0
  3. package/actions/promote-buildchain-ref/README.md +7 -0
  4. package/bin/buildchain.mjs +5 -0
  5. package/bin/internal/trust-release-cli.mjs +74 -3
  6. package/contracts/auditable-demo-media-profiles-v1.json +168 -0
  7. package/contracts/evidence/auditable-demo-web-delivery-v1.json +103 -0
  8. package/contracts/fixtures/auditable-demo-web-delivery-v1/complete-transcript.txt +2 -0
  9. package/contracts/fixtures/auditable-demo-web-delivery-v1/public-projection.json +16 -0
  10. package/contracts/fixtures/auditable-demo-web-delivery-v1/scene.json +12 -0
  11. package/dist/site/artifact-schemas.json +5 -1
  12. package/dist/site/buildchain-contract.json +133 -34
  13. package/dist/site/buildchain-site.json +159 -40
  14. package/dist/site/capability-registry.json +14 -13
  15. package/dist/site/cli-registry.json +12 -0
  16. package/dist/site/controller-registry.json +72 -4
  17. package/dist/site/kfd-claims.json +338 -20
  18. package/dist/site/kfd-upstream-aggregate.json +1 -1
  19. package/dist/site/manual-registry.json +24 -9
  20. package/dist/site/node-api-registry.json +89 -11
  21. package/dist/site/page-registry.json +135 -25
  22. package/dist/site/public-surface-audit.json +203 -21
  23. package/dist/site/publication-authority-registry.json +72 -2
  24. package/dist/site/publication-registry.json +4 -4
  25. package/dist/site/release-model.json +2 -1
  26. package/dist/site/release-passport-check-manifest.json +1 -0
  27. package/dist/site/release-provenance.json +6 -0
  28. package/dist/site/schemas/release-passport-v1.schema.json +6 -0
  29. package/dist/site/site-manifest.json +21 -13
  30. package/dist/site/workflow-registry.json +167 -13
  31. package/docs/MAP.md +5 -1
  32. package/docs/auditable-demo.md +55 -3
  33. package/docs/binary-distribution.md +7 -0
  34. package/docs/cli.md +9 -0
  35. package/docs/dev-alpha-candidate-patrol.md +111 -0
  36. package/docs/github-artifact-attestation.md +219 -0
  37. package/docs/release-governance.md +32 -0
  38. package/docs/release-passport.md +13 -0
  39. package/docs/reusable-build-surface.md +83 -61
  40. package/docs/runtime-train-validation.md +21 -0
  41. package/docs/versioning.md +1 -0
  42. package/package.json +8 -1
  43. package/packages/core/artifact-signing-result.js +228 -0
  44. package/packages/core/artifact-signing.js +412 -0
  45. package/packages/core/buildchain-config.js +58 -0
  46. package/packages/core/buildchain-contract.js +14 -0
  47. package/packages/core/buildchain-kfd-claims.js +5 -0
  48. package/packages/core/buildchain-publication-authority.js +3 -0
  49. package/packages/core/cache-evidence.js +288 -0
  50. package/packages/core/channel-candidate.js +186 -0
  51. package/packages/core/detached-artifact-signature.js +121 -0
  52. package/packages/core/diagnostics.js +276 -10
  53. package/packages/core/github-artifact-attestation.js +642 -0
  54. package/packages/core/github-governance-authority.js +77 -16
  55. package/packages/core/index.js +62 -0
  56. package/packages/core/publication-authority.js +1 -1
  57. package/packages/core/release-passport-contract.js +2 -0
  58. package/packages/core/release-passport.js +60 -3
  59. package/scripts/auditable-demo.mjs +520 -28
  60. package/scripts/build-contract-core.mjs +31 -0
  61. package/scripts/buildchain-channel-router.mjs +8 -2
  62. package/scripts/check-inventory.mjs +9 -0
  63. package/scripts/create-github-artifact-attestation-policy.mjs +62 -0
  64. package/scripts/dev-alpha-candidate-patrol.mjs +864 -0
  65. package/scripts/dispatch-artifact-signing-authority.mjs +152 -0
  66. package/scripts/finalize-native-artifact-signing-result.mjs +96 -0
  67. package/scripts/generate-channel-promotion-workflow.mjs +6 -0
  68. package/scripts/generate-site-bundle.mjs +20 -0
  69. package/scripts/import-artifact-signing-results.mjs +76 -0
  70. package/scripts/inspect-artifact-signing-requests.mjs +101 -0
  71. package/scripts/locked-source-checkout.mjs +48 -0
  72. package/scripts/materialize-artifact-signing-request.mjs +66 -0
  73. package/scripts/merge-artifact-signing-results.mjs +76 -0
  74. package/scripts/publish-github-artifact-attestation-evidence.mjs +201 -0
  75. package/scripts/reconcile-github-governance.mjs +158 -25
  76. package/scripts/release-candidate-resolver.mjs +12 -0
  77. package/scripts/release-line-policy.mjs +27 -0
  78. package/scripts/runtime-ref-core.mjs +10 -6
  79. package/scripts/seal-artifact-signing-requests.mjs +368 -0
  80. package/scripts/shifu-gate-profile.mjs +26 -20
  81. package/scripts/sign-detached-artifact-requests.mjs +237 -0
  82. package/scripts/stage-github-artifact-attestation-inputs.mjs +65 -0
  83. package/scripts/verify-artifact-signing-results.mjs +99 -0
@@ -0,0 +1,121 @@
1
+ import crypto from "node:crypto";
2
+
3
+ import {
4
+ artifactSigningDigest,
5
+ createArtifactSigningReceipt,
6
+ validateArtifactSigningRequest,
7
+ } from "./artifact-signing.js";
8
+ import { artifactSigningEvidenceDigest } from "./artifact-signing-result.js";
9
+
10
+ export const DETACHED_ARTIFACT_SIGNATURE_CONTRACT =
11
+ "kungfu-buildchain-detached-artifact-signature/v1";
12
+
13
+ function required(value, label) {
14
+ const normalized = String(value || "").trim();
15
+ if (!normalized) throw new Error(`${label} must be a non-empty string`);
16
+ return normalized;
17
+ }
18
+
19
+ function signatureBasis(request, keyId) {
20
+ return {
21
+ schemaVersion: 1,
22
+ contract: DETACHED_ARTIFACT_SIGNATURE_CONTRACT,
23
+ algorithm: "ed25519",
24
+ keyId: required(keyId, "key id"),
25
+ requestDigest: request.digest,
26
+ artifactDigest: request.artifact.digest,
27
+ runtimeSha: request.runtime.sha,
28
+ };
29
+ }
30
+
31
+ function envelopeDigest(envelope) {
32
+ const { digest: _digest, ...basis } = envelope;
33
+ return artifactSigningDigest(basis);
34
+ }
35
+
36
+ export function signDetachedArtifactRequest({
37
+ request,
38
+ privateKey,
39
+ keyId,
40
+ authority = {},
41
+ } = {}) {
42
+ const check = validateArtifactSigningRequest(request);
43
+ if (!check.ok) {
44
+ throw new Error(
45
+ `invalid artifact signing request: ${check.issues.join(", ")}`,
46
+ );
47
+ }
48
+ if (request.signature.profile !== "detached-signature-v1") {
49
+ throw new Error("detached signer requires detached-signature-v1 profile");
50
+ }
51
+ const basis = signatureBasis(request, keyId);
52
+ const payload = Buffer.from(JSON.stringify(basis), "utf8");
53
+ const signature = crypto.sign(null, payload, privateKey);
54
+ const envelope = {
55
+ ...basis,
56
+ signature: signature.toString("base64"),
57
+ };
58
+ envelope.digest = envelopeDigest(envelope);
59
+ const receipt = createArtifactSigningReceipt({
60
+ request,
61
+ authority,
62
+ result: {
63
+ artifactDigest: request.artifact.digest,
64
+ evidenceDigest: artifactSigningEvidenceDigest([
65
+ {
66
+ kind: "ed25519-detached",
67
+ path: "signature.json",
68
+ digest: envelope.digest,
69
+ },
70
+ ]),
71
+ },
72
+ signatures: [
73
+ {
74
+ kind: "ed25519-detached",
75
+ digest: `sha256:${crypto.createHash("sha256").update(signature).digest("hex")}`,
76
+ },
77
+ ],
78
+ });
79
+ return { envelope, receipt };
80
+ }
81
+
82
+ export function verifyDetachedArtifactSignature({
83
+ request,
84
+ envelope,
85
+ publicKey,
86
+ } = {}) {
87
+ const issues = [];
88
+ try {
89
+ const check = validateArtifactSigningRequest(request);
90
+ if (!check.ok) issues.push(...check.issues);
91
+ if (envelope?.contract !== DETACHED_ARTIFACT_SIGNATURE_CONTRACT) {
92
+ issues.push("detached signature contract mismatch");
93
+ }
94
+ const basis = signatureBasis(request, envelope?.keyId);
95
+ for (const [key, expected] of Object.entries(basis)) {
96
+ if (envelope?.[key] !== expected) {
97
+ issues.push(`detached signature ${key} mismatch`);
98
+ }
99
+ }
100
+ const signature = Buffer.from(
101
+ required(envelope?.signature, "signature"),
102
+ "base64",
103
+ );
104
+ if (
105
+ !crypto.verify(
106
+ null,
107
+ Buffer.from(JSON.stringify(basis), "utf8"),
108
+ publicKey,
109
+ signature,
110
+ )
111
+ ) {
112
+ issues.push("detached signature verification failed");
113
+ }
114
+ if (envelope?.digest !== envelopeDigest(envelope)) {
115
+ issues.push("detached signature envelope digest mismatch");
116
+ }
117
+ } catch (error) {
118
+ issues.push(String(error?.message || error));
119
+ }
120
+ return { ok: issues.length === 0, issues };
121
+ }
@@ -19,6 +19,11 @@ import {
19
19
  readBuildchainLogEvents,
20
20
  summarizeBuildchainLogEvents,
21
21
  } from "./logging.js";
22
+ import {
23
+ cacheEvidenceDigest,
24
+ createCacheEvidenceSet,
25
+ createCacheOperationReceipt,
26
+ } from "./cache-evidence.js";
22
27
 
23
28
  export const BUILDCHAIN_DIAGNOSTICS_CONTRACT = "kungfu-buildchain-diagnostics";
24
29
  export const BUILDCHAIN_LIFECYCLE_OBSERVABILITY_CONTRACT =
@@ -568,18 +573,37 @@ function collectCompilerCacheTool({ command, attempts, cwd, runCommand }) {
568
573
  export function collectCompilerCacheDiagnostics({
569
574
  cwd = process.cwd(),
570
575
  runCommand = defaultDiagnosticCommandRunner,
576
+ env = process.env,
571
577
  } = {}) {
572
578
  const resolvedCwd = path.resolve(cwd);
579
+ const ccache = collectCompilerCacheTool({
580
+ command: "ccache",
581
+ attempts: [
582
+ { args: ["--show-stats", "--json"], format: "json" },
583
+ { args: ["--show-stats"], format: "text" },
584
+ ],
585
+ cwd: resolvedCwd,
586
+ runCommand,
587
+ });
588
+ ccache.logStats = env.CCACHE_STATSLOG
589
+ ? collectCompilerCacheTool({
590
+ command: "ccache",
591
+ attempts: [
592
+ {
593
+ args: ["--print-log-stats", "--format", "json"],
594
+ format: "json",
595
+ },
596
+ ],
597
+ cwd: resolvedCwd,
598
+ runCommand,
599
+ })
600
+ : {
601
+ available: false,
602
+ command: "ccache",
603
+ error: "CCACHE_STATSLOG is not configured",
604
+ };
573
605
  return {
574
- ccache: collectCompilerCacheTool({
575
- command: "ccache",
576
- attempts: [
577
- { args: ["--show-stats", "--json"], format: "json" },
578
- { args: ["--show-stats"], format: "text" },
579
- ],
580
- cwd: resolvedCwd,
581
- runCommand,
582
- }),
606
+ ccache,
583
607
  sccache: collectCompilerCacheTool({
584
608
  command: "sccache",
585
609
  attempts: [
@@ -591,6 +615,238 @@ export function collectCompilerCacheDiagnostics({
591
615
  };
592
616
  }
593
617
 
618
+ function unavailableMetric(unit, reason, source = null, evidenceRoot = null) {
619
+ return {
620
+ status: "unavailable",
621
+ unit,
622
+ value: null,
623
+ source,
624
+ reason,
625
+ evidenceRoot,
626
+ };
627
+ }
628
+
629
+ function notApplicableMetric(unit, reason) {
630
+ return {
631
+ status: "not-applicable",
632
+ unit,
633
+ value: null,
634
+ reason,
635
+ };
636
+ }
637
+
638
+ function observedMetric(unit, value, source, evidenceRoot) {
639
+ return {
640
+ status: "observed",
641
+ unit,
642
+ value: Number(value),
643
+ source,
644
+ evidenceRoot,
645
+ };
646
+ }
647
+
648
+ function cacheStatsCount(stats, names) {
649
+ return names.reduce((sum, name) => sum + Number(stats?.[name] || 0), 0);
650
+ }
651
+
652
+ function compilerCacheOutcome(compilerCaches) {
653
+ const logStats = compilerCaches?.ccache?.logStats;
654
+ if (!logStats?.available || !logStats.stats) return "unavailable";
655
+ const hits = cacheStatsCount(logStats.stats, [
656
+ "direct_cache_hit",
657
+ "preprocessed_cache_hit",
658
+ ]);
659
+ const misses = cacheStatsCount(logStats.stats, ["cache_miss"]);
660
+ if (hits > 0 && misses > 0) return "partial";
661
+ if (hits > 0) return "hit";
662
+ if (misses > 0) return "miss";
663
+ return "bypassed";
664
+ }
665
+
666
+ function createStructuredCacheEvidence({
667
+ sourceCheckout,
668
+ compilerCaches,
669
+ platform,
670
+ env = process.env,
671
+ }) {
672
+ const repository = env.GITHUB_REPOSITORY || sourceCheckout?.repository || "unknown/unknown";
673
+ const sourceCommit =
674
+ env.BUILDCHAIN_SOURCE_SHA ||
675
+ sourceCheckout?.source?.sha ||
676
+ sourceCheckout?.verification?.head ||
677
+ "unknown";
678
+ const sourceTree =
679
+ env.BUILDCHAIN_SOURCE_TREE_SHA ||
680
+ sourceCheckout?.source?.treeSha ||
681
+ sourceCheckout?.verification?.tree ||
682
+ "";
683
+ const runtimeCommit = env.BUILDCHAIN_RUNTIME_SHA || "";
684
+ const policyRoot = env.SHIFU_CACHE_PROFILE_DIGEST || "";
685
+ const bindings = {
686
+ sourceCommit,
687
+ ...(sourceTree ? { sourceTree } : {}),
688
+ ...(runtimeCommit ? { runtimeCommit } : {}),
689
+ ...(env.BUILDCHAIN_DEPENDENCY_LOCK_ROOT
690
+ ? { dependencyLockRoot: env.BUILDCHAIN_DEPENDENCY_LOCK_ROOT }
691
+ : {}),
692
+ ...(env.BUILDCHAIN_TOOLCHAIN_ROOT
693
+ ? { toolchainRoot: env.BUILDCHAIN_TOOLCHAIN_ROOT }
694
+ : {}),
695
+ ...(env.BUILDCHAIN_CACHE_POLICY_ROOT
696
+ ? { policyRoot: env.BUILDCHAIN_CACHE_POLICY_ROOT }
697
+ : {}),
698
+ ...(policyRoot ? { cacheProfileRoot: policyRoot } : {}),
699
+ };
700
+ const operations = [];
701
+ if (sourceCheckout) {
702
+ const evidenceRoot = cacheEvidenceDigest(sourceCheckout);
703
+ const cache = sourceCheckout.cache || {};
704
+ const outcome = cache.hit
705
+ ? "hit"
706
+ : cache.attempted === false || sourceCheckout.policy?.mode === "off"
707
+ ? "bypassed"
708
+ : cache.fallbackUsed
709
+ ? "miss"
710
+ : "unavailable";
711
+ operations.push(
712
+ createCacheOperationReceipt({
713
+ operationId: `source-checkout:${platform}`,
714
+ operation: "restore",
715
+ provider: `git-${cache.transport || "unknown"}`,
716
+ producer: "kungfu-systems/buildchain",
717
+ platform,
718
+ cacheKey: sourceCommit,
719
+ cacheRoot:
720
+ sourceCheckout.policy?.referenceRepository?.display ||
721
+ sourceCheckout.policy?.mirror?.display ||
722
+ `transport:${cache.transport || "unknown"}`,
723
+ outcome,
724
+ bindings,
725
+ metrics: {
726
+ lookupDuration:
727
+ cache.attempted === false
728
+ ? notApplicableMetric("ms", "source checkout cache was disabled")
729
+ : observedMetric(
730
+ "ms",
731
+ cache.lookupDurationMs || 0,
732
+ "locked-source-checkout",
733
+ evidenceRoot,
734
+ ),
735
+ restoreDuration: cache.hit
736
+ ? observedMetric(
737
+ "ms",
738
+ cache.restoreDurationMs || 0,
739
+ "locked-source-checkout",
740
+ evidenceRoot,
741
+ )
742
+ : notApplicableMetric(
743
+ "ms",
744
+ "no cache payload was admitted for restore",
745
+ ),
746
+ saveDuration: notApplicableMetric(
747
+ "ms",
748
+ "locked source checkout does not save cache state",
749
+ ),
750
+ restoredBytes:
751
+ cache.restoredBytesStatus === "observed"
752
+ ? observedMetric(
753
+ "bytes",
754
+ cache.restoredBytes || 0,
755
+ cache.restoredBytesMethod || "git-object-store-delta",
756
+ evidenceRoot,
757
+ )
758
+ : unavailableMetric(
759
+ "bytes",
760
+ "checkout provider did not expose restored byte evidence",
761
+ "locked-source-checkout",
762
+ evidenceRoot,
763
+ ),
764
+ writtenBytes: notApplicableMetric(
765
+ "bytes",
766
+ "locked source checkout does not save cache state",
767
+ ),
768
+ savedTime: unavailableMetric(
769
+ "ms",
770
+ "checkout provider did not report a measured cold-path comparison",
771
+ "locked-source-checkout",
772
+ evidenceRoot,
773
+ ),
774
+ },
775
+ evidence: {
776
+ kind: "locked-source-checkout",
777
+ root: evidenceRoot,
778
+ locator: ".buildchain/diagnostics/source-checkout.json",
779
+ },
780
+ }),
781
+ );
782
+ }
783
+ const compilerEvidenceRoot = cacheEvidenceDigest(compilerCaches || {});
784
+ operations.push(
785
+ createCacheOperationReceipt({
786
+ operationId: `compiler-cache:${platform}`,
787
+ operation: "restore",
788
+ provider: compilerCaches?.ccache?.available ? "ccache" : "compiler-cache",
789
+ producer: compilerCaches?.ccache?.available ? "ccache" : "unavailable",
790
+ platform,
791
+ cacheKey: policyRoot || null,
792
+ cacheRoot: policyRoot || null,
793
+ outcome: compilerCacheOutcome(compilerCaches),
794
+ bindings,
795
+ metrics: {
796
+ lookupDuration: unavailableMetric(
797
+ "ms",
798
+ "ccache stats log exposes outcomes but not lookup duration",
799
+ "ccache-stats-log",
800
+ compilerEvidenceRoot,
801
+ ),
802
+ restoreDuration: unavailableMetric(
803
+ "ms",
804
+ "ccache stats log exposes outcomes but not transfer duration",
805
+ "ccache-stats-log",
806
+ compilerEvidenceRoot,
807
+ ),
808
+ saveDuration: unavailableMetric(
809
+ "ms",
810
+ "ccache stats log exposes writes but not save duration",
811
+ "ccache-stats-log",
812
+ compilerEvidenceRoot,
813
+ ),
814
+ restoredBytes: unavailableMetric(
815
+ "bytes",
816
+ "ccache does not expose per-run restored bytes",
817
+ "ccache-stats-log",
818
+ compilerEvidenceRoot,
819
+ ),
820
+ writtenBytes: unavailableMetric(
821
+ "bytes",
822
+ "ccache does not expose per-run written bytes",
823
+ "ccache-stats-log",
824
+ compilerEvidenceRoot,
825
+ ),
826
+ savedTime: unavailableMetric(
827
+ "ms",
828
+ "ccache does not report evidence-backed compile time saved",
829
+ "ccache-stats-log",
830
+ compilerEvidenceRoot,
831
+ ),
832
+ },
833
+ evidence: {
834
+ kind: "compiler-cache-diagnostics",
835
+ root: compilerEvidenceRoot,
836
+ locator: ".buildchain/artifacts/<platform>/diagnostics.json#compilerCaches",
837
+ },
838
+ }),
839
+ );
840
+ return createCacheEvidenceSet({
841
+ repository,
842
+ sourceCommit,
843
+ sourceTree,
844
+ runtimeCommit,
845
+ platform,
846
+ operations,
847
+ });
848
+ }
849
+
594
850
  export function collectCacheDiagnostics({ cwd = process.cwd(), cacheDirs = [], runCommand = defaultDiagnosticCommandRunner } = {}) {
595
851
  const resolvedCwd = path.resolve(cwd);
596
852
  return {
@@ -1095,6 +1351,11 @@ export function createDiagnosticsArtifact({
1095
1351
  const nativeProfile = getNativeDiagnosticsProfile(loadedConfig);
1096
1352
  const native = collectNativeDiagnostics({ cwd: resolvedCwd, profile: nativeProfile });
1097
1353
  const cache = collectCacheDiagnostics({ cwd: resolvedCwd, cacheDirs });
1354
+ const compilerCaches = native.compilerCaches || cache.compilerCaches || {};
1355
+ const platform =
1356
+ links.platformId ||
1357
+ process.env.BUILDCHAIN_PLATFORM_ID ||
1358
+ `${process.env.RUNNER_OS || os.platform()}-${process.env.RUNNER_ARCH || os.arch()}`;
1098
1359
  return {
1099
1360
  schemaVersion: 1,
1100
1361
  contract: BUILDCHAIN_DIAGNOSTICS_CONTRACT,
@@ -1105,7 +1366,12 @@ export function createDiagnosticsArtifact({
1105
1366
  tools: collectToolDiagnostics({ cwd: resolvedCwd }),
1106
1367
  cache,
1107
1368
  native,
1108
- compilerCaches: native.compilerCaches || cache.compilerCaches || {},
1369
+ compilerCaches,
1370
+ cacheEvidence: createStructuredCacheEvidence({
1371
+ sourceCheckout,
1372
+ compilerCaches,
1373
+ platform,
1374
+ }),
1109
1375
  nativeCacheDirs: native.cacheDirs || [],
1110
1376
  git: collectGitDiagnostics({ cwd: resolvedCwd }),
1111
1377
  lifecycleObservability: lifecycleObservability || summarizeLifecycleObservability({ events, logPath }),