@kungfu-tech/buildchain 3.0.2-alpha.0 → 3.0.2-alpha.2
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/dist/site/buildchain-contract.json +49 -19
- package/dist/site/buildchain-site.json +4 -4
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/controller-registry.json +27 -3
- package/dist/site/kfd-claims.json +23 -5
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/node-api-registry.json +17 -4
- package/dist/site/public-surface-audit.json +9 -3
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +1 -0
- package/dist/site/site-manifest.json +4 -4
- package/dist/site/workflow-registry.json +8 -2
- package/package.json +2 -1
- package/packages/core/cache-evidence.js +288 -0
- package/packages/core/diagnostics.js +276 -10
- package/packages/core/github-governance-authority.js +71 -16
- package/packages/core/index.js +10 -0
- package/scripts/auditable-demo.mjs +29 -6
- package/scripts/generate-site-bundle.mjs +1 -0
- package/scripts/locked-source-checkout.mjs +48 -0
- package/scripts/reconcile-github-governance.mjs +158 -25
- package/scripts/shifu-gate-profile.mjs +26 -20
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const BUILDCHAIN_CACHE_OPERATION_RECEIPT_CONTRACT =
|
|
4
|
+
"buildchain.cache-operation-receipt/v1";
|
|
5
|
+
export const BUILDCHAIN_CACHE_EVIDENCE_SET_CONTRACT =
|
|
6
|
+
"buildchain.cache-evidence-set/v1";
|
|
7
|
+
|
|
8
|
+
const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
|
|
9
|
+
const OUTCOMES = new Set([
|
|
10
|
+
"hit",
|
|
11
|
+
"miss",
|
|
12
|
+
"partial",
|
|
13
|
+
"bypassed",
|
|
14
|
+
"poisoned",
|
|
15
|
+
"unavailable",
|
|
16
|
+
]);
|
|
17
|
+
const METRIC_UNITS = Object.freeze({
|
|
18
|
+
lookupDuration: "ms",
|
|
19
|
+
restoreDuration: "ms",
|
|
20
|
+
saveDuration: "ms",
|
|
21
|
+
restoredBytes: "bytes",
|
|
22
|
+
writtenBytes: "bytes",
|
|
23
|
+
savedTime: "ms",
|
|
24
|
+
});
|
|
25
|
+
const METRIC_STATUSES = new Set(["observed", "unavailable", "not-applicable"]);
|
|
26
|
+
const SAVED_TIME_METHODS = new Set(["producer-measured", "provider-reported"]);
|
|
27
|
+
|
|
28
|
+
function assert(condition, message) {
|
|
29
|
+
if (!condition) throw new Error(message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function ordered(value) {
|
|
33
|
+
if (Array.isArray(value)) return value.map(ordered);
|
|
34
|
+
if (value && typeof value === "object") {
|
|
35
|
+
return Object.fromEntries(
|
|
36
|
+
Object.keys(value)
|
|
37
|
+
.sort()
|
|
38
|
+
.map((key) => [key, ordered(value[key])]),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stableJson(value) {
|
|
45
|
+
return JSON.stringify(ordered(value));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function cacheEvidenceDigest(value) {
|
|
49
|
+
return `sha256:${crypto
|
|
50
|
+
.createHash("sha256")
|
|
51
|
+
.update(typeof value === "string" ? value : stableJson(value))
|
|
52
|
+
.digest("hex")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function exactKeys(value, allowed, label) {
|
|
56
|
+
assert(
|
|
57
|
+
value && typeof value === "object" && !Array.isArray(value),
|
|
58
|
+
`${label} must be an object`,
|
|
59
|
+
);
|
|
60
|
+
for (const key of Object.keys(value)) {
|
|
61
|
+
assert(allowed.has(key), `${label}.${key} is not allowed`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function text(value, label) {
|
|
66
|
+
assert(
|
|
67
|
+
typeof value === "string" && value.trim() === value && value.length > 0,
|
|
68
|
+
`${label} is required`,
|
|
69
|
+
);
|
|
70
|
+
assert(!/[\r\n\0]/.test(value), `${label} contains control characters`);
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function digest(value, label) {
|
|
75
|
+
assert(DIGEST_RE.test(value), `${label} must be a sha256 digest`);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeBindings(bindings = {}) {
|
|
80
|
+
exactKeys(
|
|
81
|
+
bindings,
|
|
82
|
+
new Set([
|
|
83
|
+
"sourceCommit",
|
|
84
|
+
"sourceTree",
|
|
85
|
+
"runtimeCommit",
|
|
86
|
+
"dependencyLockRoot",
|
|
87
|
+
"toolchainRoot",
|
|
88
|
+
"policyRoot",
|
|
89
|
+
"platformRoot",
|
|
90
|
+
"cacheProfileRoot",
|
|
91
|
+
]),
|
|
92
|
+
"bindings",
|
|
93
|
+
);
|
|
94
|
+
return Object.fromEntries(
|
|
95
|
+
Object.entries(bindings)
|
|
96
|
+
.filter(([, value]) => value !== null && value !== undefined && value !== "")
|
|
97
|
+
.map(([key, value]) => [key, text(value, `bindings.${key}`)]),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizeMetric(metric, name) {
|
|
102
|
+
exactKeys(
|
|
103
|
+
metric,
|
|
104
|
+
new Set([
|
|
105
|
+
"status",
|
|
106
|
+
"unit",
|
|
107
|
+
"value",
|
|
108
|
+
"source",
|
|
109
|
+
"reason",
|
|
110
|
+
"evidenceRoot",
|
|
111
|
+
"method",
|
|
112
|
+
]),
|
|
113
|
+
`metrics.${name}`,
|
|
114
|
+
);
|
|
115
|
+
const status = text(metric.status, `metrics.${name}.status`);
|
|
116
|
+
assert(
|
|
117
|
+
METRIC_STATUSES.has(status),
|
|
118
|
+
`metrics.${name}.status must be observed, unavailable, or not-applicable`,
|
|
119
|
+
);
|
|
120
|
+
assert(
|
|
121
|
+
metric.unit === METRIC_UNITS[name],
|
|
122
|
+
`metrics.${name}.unit must be ${METRIC_UNITS[name]}`,
|
|
123
|
+
);
|
|
124
|
+
if (status === "observed") {
|
|
125
|
+
assert(
|
|
126
|
+
Number.isFinite(metric.value) && metric.value >= 0,
|
|
127
|
+
`metrics.${name}.value must be a non-negative finite number`,
|
|
128
|
+
);
|
|
129
|
+
const normalized = {
|
|
130
|
+
status,
|
|
131
|
+
unit: metric.unit,
|
|
132
|
+
value: metric.value,
|
|
133
|
+
source: text(metric.source, `metrics.${name}.source`),
|
|
134
|
+
reason: null,
|
|
135
|
+
evidenceRoot: digest(
|
|
136
|
+
metric.evidenceRoot,
|
|
137
|
+
`metrics.${name}.evidenceRoot`,
|
|
138
|
+
),
|
|
139
|
+
};
|
|
140
|
+
if (name === "savedTime") {
|
|
141
|
+
assert(
|
|
142
|
+
typeof metric.method === "string" &&
|
|
143
|
+
SAVED_TIME_METHODS.has(metric.method),
|
|
144
|
+
"observed saved time requires producer-measured or provider-reported evidence",
|
|
145
|
+
);
|
|
146
|
+
normalized.method = metric.method;
|
|
147
|
+
} else if (metric.method) {
|
|
148
|
+
normalized.method = text(metric.method, `metrics.${name}.method`);
|
|
149
|
+
}
|
|
150
|
+
return normalized;
|
|
151
|
+
}
|
|
152
|
+
assert(
|
|
153
|
+
metric.value === null || metric.value === undefined,
|
|
154
|
+
`metrics.${name}.value must be null when ${status}`,
|
|
155
|
+
);
|
|
156
|
+
return {
|
|
157
|
+
status,
|
|
158
|
+
unit: metric.unit,
|
|
159
|
+
value: null,
|
|
160
|
+
source: metric.source ? text(metric.source, `metrics.${name}.source`) : null,
|
|
161
|
+
reason: text(metric.reason, `metrics.${name}.reason`),
|
|
162
|
+
evidenceRoot: metric.evidenceRoot
|
|
163
|
+
? digest(metric.evidenceRoot, `metrics.${name}.evidenceRoot`)
|
|
164
|
+
: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function createCacheOperationReceipt({
|
|
169
|
+
operationId,
|
|
170
|
+
operation,
|
|
171
|
+
provider,
|
|
172
|
+
producer,
|
|
173
|
+
platform,
|
|
174
|
+
cacheKey,
|
|
175
|
+
cacheRoot,
|
|
176
|
+
outcome,
|
|
177
|
+
bindings = {},
|
|
178
|
+
metrics,
|
|
179
|
+
evidence,
|
|
180
|
+
} = {}) {
|
|
181
|
+
assert(
|
|
182
|
+
["restore", "save", "lookup"].includes(operation),
|
|
183
|
+
"operation must be restore, save, or lookup",
|
|
184
|
+
);
|
|
185
|
+
assert(OUTCOMES.has(outcome), "unsupported cache outcome");
|
|
186
|
+
exactKeys(
|
|
187
|
+
metrics,
|
|
188
|
+
new Set(Object.keys(METRIC_UNITS)),
|
|
189
|
+
"metrics",
|
|
190
|
+
);
|
|
191
|
+
for (const name of Object.keys(METRIC_UNITS)) {
|
|
192
|
+
assert(metrics[name], `metrics.${name} is required`);
|
|
193
|
+
}
|
|
194
|
+
exactKeys(
|
|
195
|
+
evidence,
|
|
196
|
+
new Set(["kind", "root", "locator"]),
|
|
197
|
+
"evidence",
|
|
198
|
+
);
|
|
199
|
+
const receipt = {
|
|
200
|
+
schema: BUILDCHAIN_CACHE_OPERATION_RECEIPT_CONTRACT,
|
|
201
|
+
operationId: text(operationId, "operationId"),
|
|
202
|
+
operation,
|
|
203
|
+
provider: text(provider, "provider"),
|
|
204
|
+
producer: text(producer, "producer"),
|
|
205
|
+
platform: text(platform, "platform"),
|
|
206
|
+
cacheKey: cacheKey ? text(cacheKey, "cacheKey") : null,
|
|
207
|
+
cacheRoot: cacheRoot ? text(cacheRoot, "cacheRoot") : null,
|
|
208
|
+
outcome,
|
|
209
|
+
bindings: normalizeBindings(bindings),
|
|
210
|
+
metrics: Object.fromEntries(
|
|
211
|
+
Object.keys(METRIC_UNITS).map((name) => [
|
|
212
|
+
name,
|
|
213
|
+
normalizeMetric(metrics[name], name),
|
|
214
|
+
]),
|
|
215
|
+
),
|
|
216
|
+
evidence: {
|
|
217
|
+
kind: text(evidence.kind, "evidence.kind"),
|
|
218
|
+
root: digest(evidence.root, "evidence.root"),
|
|
219
|
+
locator: text(evidence.locator, "evidence.locator"),
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
return { ...receipt, receiptRoot: cacheEvidenceDigest(receipt) };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function verifyCacheOperationReceipt(receipt) {
|
|
226
|
+
assert(
|
|
227
|
+
receipt?.schema === BUILDCHAIN_CACHE_OPERATION_RECEIPT_CONTRACT,
|
|
228
|
+
"cache operation receipt schema mismatch",
|
|
229
|
+
);
|
|
230
|
+
const { receiptRoot, ...body } = receipt;
|
|
231
|
+
assert(
|
|
232
|
+
receiptRoot === cacheEvidenceDigest(body),
|
|
233
|
+
"cache operation receipt root mismatch",
|
|
234
|
+
);
|
|
235
|
+
const rebuilt = createCacheOperationReceipt(body);
|
|
236
|
+
assert(
|
|
237
|
+
stableJson(rebuilt) === stableJson(receipt),
|
|
238
|
+
"cache operation receipt normalization drift",
|
|
239
|
+
);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function createCacheEvidenceSet({
|
|
244
|
+
repository,
|
|
245
|
+
sourceCommit,
|
|
246
|
+
sourceTree = "",
|
|
247
|
+
runtimeCommit = "",
|
|
248
|
+
platform,
|
|
249
|
+
operations = [],
|
|
250
|
+
} = {}) {
|
|
251
|
+
assert(Array.isArray(operations), "cache evidence operations must be an array");
|
|
252
|
+
operations.forEach(verifyCacheOperationReceipt);
|
|
253
|
+
const operationIds = operations.map(({ operationId }) => operationId);
|
|
254
|
+
assert(
|
|
255
|
+
operationIds.length === new Set(operationIds).size,
|
|
256
|
+
"cache evidence operation ids must be unique",
|
|
257
|
+
);
|
|
258
|
+
const value = {
|
|
259
|
+
schema: BUILDCHAIN_CACHE_EVIDENCE_SET_CONTRACT,
|
|
260
|
+
repository: text(repository, "repository"),
|
|
261
|
+
sourceCommit: text(sourceCommit, "sourceCommit"),
|
|
262
|
+
sourceTree: sourceTree ? text(sourceTree, "sourceTree") : null,
|
|
263
|
+
runtimeCommit: runtimeCommit ? text(runtimeCommit, "runtimeCommit") : null,
|
|
264
|
+
platform: text(platform, "platform"),
|
|
265
|
+
operations: [...operations].sort((left, right) =>
|
|
266
|
+
left.operationId.localeCompare(right.operationId),
|
|
267
|
+
),
|
|
268
|
+
};
|
|
269
|
+
return { ...value, evidenceRoot: cacheEvidenceDigest(value) };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function verifyCacheEvidenceSet(receipt) {
|
|
273
|
+
assert(
|
|
274
|
+
receipt?.schema === BUILDCHAIN_CACHE_EVIDENCE_SET_CONTRACT,
|
|
275
|
+
"cache evidence set schema mismatch",
|
|
276
|
+
);
|
|
277
|
+
const { evidenceRoot, ...body } = receipt;
|
|
278
|
+
assert(
|
|
279
|
+
evidenceRoot === cacheEvidenceDigest(body),
|
|
280
|
+
"cache evidence set root mismatch",
|
|
281
|
+
);
|
|
282
|
+
const rebuilt = createCacheEvidenceSet(body);
|
|
283
|
+
assert(
|
|
284
|
+
stableJson(rebuilt) === stableJson(receipt),
|
|
285
|
+
"cache evidence set normalization drift",
|
|
286
|
+
);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
@@ -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
|
|
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
|
|
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 }),
|