@kungfu-tech/buildchain 3.0.7-alpha.0 → 3.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/actions/promote-buildchain-ref/README.md +10 -0
- package/contracts/auditable-demo-scenario-v1.schema.json +1 -1
- package/contracts/engineering-housekeeper-v1.schema.json +143 -0
- package/contracts/fixtures/engineering-housekeeper-v1/cases.json +68 -0
- package/dist/site/buildchain-contract.json +24 -24
- package/dist/site/buildchain-site.json +91 -30
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/controller-registry.json +6 -2
- package/dist/site/kfd-claims.json +122 -11
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +8 -7
- package/dist/site/node-api-registry.json +683 -105
- package/dist/site/page-registry.json +80 -19
- package/dist/site/public-surface-audit.json +98 -7
- package/dist/site/publication-authority-registry.json +81 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +2 -0
- package/dist/site/site-manifest.json +11 -11
- package/dist/site/workflow-registry.json +119 -2
- package/docs/MAP.md +1 -0
- package/docs/auditable-demo.md +2 -2
- package/docs/dev-delivery-warrant.md +49 -4
- package/docs/engineering-housekeeper.md +138 -0
- package/docs/lifecycle-protocol.md +4 -2
- package/docs/node-api-reference.md +277 -212
- package/docs/release-governance.md +17 -2
- package/docs/release-tail-provider-plane.md +1 -1
- package/docs/reusable-build-surface.md +11 -0
- package/package.json +4 -1
- package/packages/core/artifact-signing.js +61 -0
- package/packages/core/buildchain-config.js +66 -6
- package/packages/core/buildchain-publication-authority.js +4 -0
- package/packages/core/controller-evidence.js +2 -1
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-shadow.js +502 -0
- package/packages/core/dev-delivery-warrant.js +15 -6
- package/packages/core/diagnostics.js +8 -3
- package/packages/core/engineering-housekeeper-github-client.js +222 -0
- package/packages/core/engineering-housekeeper-github.js +501 -0
- package/packages/core/engineering-housekeeper.js +259 -0
- package/packages/core/index.js +3 -0
- package/packages/core/kfd-gate.js +45 -15
- package/packages/core/publication-rehearsal-runtime.js +13 -1
- package/packages/core/release-passport.js +130 -20
- package/scripts/assemble-self-publication-admission.mjs +1 -1
- package/scripts/audit-publication-control-plane.mjs +1 -1
- package/scripts/auditable-demo-bundle-verification.mjs +2 -3
- package/scripts/auditable-demo-platform.mjs +2 -2
- package/scripts/auditable-demo-renditions.mjs +1 -1
- package/scripts/auditable-demo.mjs +2 -2
- package/scripts/build-contract-core.mjs +8 -3
- package/scripts/build-standalone-binary.mjs +14 -3
- package/scripts/check-inventory.mjs +3 -1
- package/scripts/dev-delivery-warrant.mjs +31 -4
- package/scripts/dev-pr-auto-merge.mjs +30 -4
- package/scripts/dev-pr-delivery-warrant.mjs +50 -0
- package/scripts/engineering-housekeeper-workflow.mjs +394 -0
- package/scripts/generate-site-bundle.mjs +23 -4
- package/scripts/inspect-artifact-signing-requests.mjs +6 -0
- package/scripts/materialize-self-release-candidate-version.mjs +6 -0
- package/scripts/publication-commit-evidence.mjs +69 -23
- package/scripts/release-candidate-resolver.mjs +16 -10
- package/scripts/resume-from-candidate-run.mjs +123 -9
- package/scripts/seal-artifact-signing-requests.mjs +6 -0
- package/scripts/site-capability-metadata.mjs +2 -0
- package/scripts/web-surface-core.mjs +8 -2
- package/scripts/workflow-call-contract.mjs +1 -1
|
@@ -351,16 +351,22 @@ export function generatePublishRequiredArtifacts({
|
|
|
351
351
|
const files = Array.isArray(manifest.files) ? manifest.files : [];
|
|
352
352
|
return files
|
|
353
353
|
.filter((file) => file?.sha256)
|
|
354
|
-
.map((file) =>
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
?
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
354
|
+
.map((file) => {
|
|
355
|
+
const name = String(file.path || file.name || manifest.artifactName || platform)
|
|
356
|
+
.replaceAll("\\", "/")
|
|
357
|
+
.replace(/^\.\//, "");
|
|
358
|
+
return {
|
|
359
|
+
...(platform ? { group: platform } : {}),
|
|
360
|
+
kind,
|
|
361
|
+
name,
|
|
362
|
+
ref,
|
|
363
|
+
digest: String(file.sha256).startsWith("sha256:")
|
|
364
|
+
? String(file.sha256)
|
|
365
|
+
: `sha256:${file.sha256}`,
|
|
366
|
+
role: "platform",
|
|
367
|
+
platform,
|
|
368
|
+
};
|
|
369
|
+
});
|
|
364
370
|
});
|
|
365
371
|
}
|
|
366
372
|
|
|
@@ -95,12 +95,18 @@ function findFiles(root, predicate) {
|
|
|
95
95
|
return collectFiles(root).filter((file) => predicate(file.path, file.absolutePath));
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
export function recoveredArtifactPathsByBasename(downloads, filename) {
|
|
99
|
+
return downloads.flatMap((download) => download.files
|
|
100
|
+
.filter((file) => path.basename(file.path) === filename)
|
|
101
|
+
.map((file) => outputPath(file.absolutePath)));
|
|
102
|
+
}
|
|
103
|
+
|
|
98
104
|
function readOnlyJson(files, label) {
|
|
99
105
|
if (files.length !== 1) throw new Error(`expected exactly one ${label}, found ${files.length}`);
|
|
100
106
|
return JSON.parse(fs.readFileSync(files[0].absolutePath, "utf8"));
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version }) {
|
|
109
|
+
export async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, version }) {
|
|
104
110
|
const stateRef = releaseTransactionStateRef(version);
|
|
105
111
|
const response = await githubJson({
|
|
106
112
|
apiUrl,
|
|
@@ -110,10 +116,31 @@ async function readExistingTransaction({ repoInfo, apiUrl, token, fetchImpl, ver
|
|
|
110
116
|
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/contents/state.json?ref=${encodeURIComponent(stateRef)}`,
|
|
111
117
|
});
|
|
112
118
|
if (!response) return undefined;
|
|
113
|
-
if (response.type !== "file"
|
|
114
|
-
throw new Error(`durable transaction ${stateRef} did not expose a
|
|
119
|
+
if (response.type !== "file") {
|
|
120
|
+
throw new Error(`durable transaction ${stateRef} did not expose a state.json file`);
|
|
121
|
+
}
|
|
122
|
+
let encoded = response.encoding === "base64" && response.content
|
|
123
|
+
? response
|
|
124
|
+
: undefined;
|
|
125
|
+
if (!encoded) {
|
|
126
|
+
const blobSha = String(response.sha || "").trim();
|
|
127
|
+
if (!/^[0-9a-f]{40}$/i.test(blobSha)) {
|
|
128
|
+
throw new Error(`durable transaction ${stateRef} did not expose inline content or an exact blob identity`);
|
|
129
|
+
}
|
|
130
|
+
encoded = await githubJson({
|
|
131
|
+
apiUrl,
|
|
132
|
+
token,
|
|
133
|
+
fetchImpl,
|
|
134
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/git/blobs/${blobSha}`,
|
|
135
|
+
});
|
|
136
|
+
if (String(encoded?.sha || "").trim() !== blobSha) {
|
|
137
|
+
throw new Error(`durable transaction ${stateRef} blob identity drifted from ${blobSha}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (encoded?.encoding !== "base64" || !encoded.content) {
|
|
141
|
+
throw new Error(`durable transaction ${stateRef} did not expose base64 state.json content`);
|
|
115
142
|
}
|
|
116
|
-
return JSON.parse(Buffer.from(String(
|
|
143
|
+
return JSON.parse(Buffer.from(String(encoded.content).replace(/\s/g, ""), "base64").toString("utf8"));
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
function outputPath(filePath) {
|
|
@@ -180,6 +207,7 @@ function candidateArtifactNames({ passport, selected, artifacts, artifactPattern
|
|
|
180
207
|
|
|
181
208
|
export function normalizePlatformManifests(downloads, passport) {
|
|
182
209
|
const manifests = [];
|
|
210
|
+
const paths = [];
|
|
183
211
|
const evidenceByArtifact = new Map();
|
|
184
212
|
const platformById = new Map((passport.platformMatrix || []).map((entry) => [String(entry.platformId || ""), entry]));
|
|
185
213
|
const seenPlatformIds = new Set();
|
|
@@ -208,6 +236,7 @@ export function normalizePlatformManifests(downloads, passport) {
|
|
|
208
236
|
manifest.artifactName = expectedPlatform.artifactName;
|
|
209
237
|
seenPlatformIds.add(platformId);
|
|
210
238
|
manifests.push(manifest);
|
|
239
|
+
paths.push(file.absolutePath);
|
|
211
240
|
addEvidence(manifest.artifactName, download.record.files);
|
|
212
241
|
}
|
|
213
242
|
if (String(download.artifact.name).includes("-diagnostics-")) {
|
|
@@ -222,7 +251,7 @@ export function normalizePlatformManifests(downloads, passport) {
|
|
|
222
251
|
artifactName,
|
|
223
252
|
files: [...files.values()].sort((left, right) => left.path.localeCompare(right.path)),
|
|
224
253
|
}));
|
|
225
|
-
return { manifests, evidence };
|
|
254
|
+
return { manifests, paths, evidence };
|
|
226
255
|
}
|
|
227
256
|
|
|
228
257
|
function normalizeControllerReceipts(downloads, passport) {
|
|
@@ -268,16 +297,53 @@ export function createRecoveredPublicationCandidate({
|
|
|
268
297
|
return { ...payload, candidateDigest: publicationArtifactCandidateDigest(payload) };
|
|
269
298
|
}
|
|
270
299
|
|
|
300
|
+
export function deduplicateReleaseAssets(files = []) {
|
|
301
|
+
const assets = new Map();
|
|
302
|
+
for (const file of files) {
|
|
303
|
+
const name = path.basename(file.path);
|
|
304
|
+
const existing = assets.get(name);
|
|
305
|
+
if (!existing) {
|
|
306
|
+
assets.set(name, file);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
const digest = String(file.sha256 || "").replace(/^sha256:/, "");
|
|
310
|
+
const existingDigest = String(existing.sha256 || "").replace(/^sha256:/, "");
|
|
311
|
+
if (digest !== existingDigest || Number(file.size) !== Number(existing.size)) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`recovered GitHub Release asset basename collision: ${name} maps to different sealed bytes`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (
|
|
317
|
+
String(file.path).length < String(existing.path).length ||
|
|
318
|
+
(String(file.path).length === String(existing.path).length &&
|
|
319
|
+
String(file.path).localeCompare(String(existing.path)) < 0)
|
|
320
|
+
) {
|
|
321
|
+
assets.set(name, file);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return [...assets.values()].sort((left, right) =>
|
|
325
|
+
path.basename(left.path).localeCompare(path.basename(right.path)),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
271
329
|
export function createRecoveredPublication({ downloads, bundleRoot, repository, passport, candidateRuntimeSha, publishArtifactKind, publishPackageMain, releasePatterns, platformManifests }) {
|
|
272
330
|
const allFiles = downloads.flatMap((download) => download.files.map((file) => ({
|
|
273
331
|
path: path.relative(bundleRoot, file.absolutePath).split(path.sep).join("/"),
|
|
274
332
|
size: file.size,
|
|
275
333
|
sha256: file.sha256.replace(/^sha256:/, ""),
|
|
276
334
|
absolutePath: file.absolutePath,
|
|
335
|
+
artifactName: String(download.artifact?.name || ""),
|
|
277
336
|
}))).sort((left, right) => left.path.localeCompare(right.path));
|
|
278
337
|
const kind = String(publishArtifactKind || "npm");
|
|
279
338
|
const releaseMatchers = splitPatterns(releasePatterns).map(patternMatcher);
|
|
280
|
-
const
|
|
339
|
+
const platformArtifactNames = new Set(
|
|
340
|
+
platformManifests.map((manifest) => String(manifest.artifactName || "")).filter(Boolean),
|
|
341
|
+
);
|
|
342
|
+
const releaseAssetFiles = kind !== "npm" && platformArtifactNames.size > 0
|
|
343
|
+
? allFiles.filter((file) => platformArtifactNames.has(file.artifactName))
|
|
344
|
+
: allFiles;
|
|
345
|
+
const releaseAssets = deduplicateReleaseAssets(releaseAssetFiles.filter((file) =>
|
|
346
|
+
releaseMatchers.some((matcher) => matcher.test(path.basename(file.path)))));
|
|
281
347
|
if (kind !== "npm") {
|
|
282
348
|
createRecoveredPublicationCandidate({ allFiles, repository, passport, candidateRuntimeSha });
|
|
283
349
|
const version = String(passport.target?.version || "").trim();
|
|
@@ -367,6 +433,42 @@ async function recoverCandidateEvidence({
|
|
|
367
433
|
};
|
|
368
434
|
}
|
|
369
435
|
|
|
436
|
+
export function exposeRecoveredPayloadRoot({ resolvedOutput, bundleRoot }) {
|
|
437
|
+
const recoveredPayloadRoot = path.join(bundleRoot, "artifacts");
|
|
438
|
+
if (!fs.existsSync(recoveredPayloadRoot) || !fs.statSync(recoveredPayloadRoot).isDirectory()) {
|
|
439
|
+
throw new Error(`recovered candidate payload root is missing: ${recoveredPayloadRoot}`);
|
|
440
|
+
}
|
|
441
|
+
const compatibilityRoot = path.join(resolvedOutput, "payloads");
|
|
442
|
+
try {
|
|
443
|
+
fs.lstatSync(compatibilityRoot);
|
|
444
|
+
if (fs.realpathSync(compatibilityRoot) !== fs.realpathSync(recoveredPayloadRoot)) {
|
|
445
|
+
throw new Error(`recovered candidate payload compatibility path is already occupied: ${compatibilityRoot}`);
|
|
446
|
+
}
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (error?.code !== "ENOENT") throw error;
|
|
449
|
+
fs.symlinkSync(path.relative(resolvedOutput, recoveredPayloadRoot), compatibilityRoot, "dir");
|
|
450
|
+
}
|
|
451
|
+
return compatibilityRoot;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function exposeRecoveredPassportPath({ resolvedOutput, recoveredPassportPath }) {
|
|
455
|
+
if (!fs.existsSync(recoveredPassportPath) || !fs.statSync(recoveredPassportPath).isFile()) {
|
|
456
|
+
throw new Error(`recovered candidate passport is missing: ${recoveredPassportPath}`);
|
|
457
|
+
}
|
|
458
|
+
const recoveredPassportRoot = path.dirname(recoveredPassportPath);
|
|
459
|
+
const compatibilityRoot = path.join(resolvedOutput, "passport");
|
|
460
|
+
try {
|
|
461
|
+
fs.lstatSync(compatibilityRoot);
|
|
462
|
+
if (fs.realpathSync(compatibilityRoot) !== fs.realpathSync(recoveredPassportRoot)) {
|
|
463
|
+
throw new Error(`recovered candidate passport compatibility path is already occupied: ${compatibilityRoot}`);
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
if (error?.code !== "ENOENT") throw error;
|
|
467
|
+
fs.symlinkSync(path.relative(resolvedOutput, recoveredPassportRoot), compatibilityRoot, "dir");
|
|
468
|
+
}
|
|
469
|
+
return path.join(compatibilityRoot, path.basename(recoveredPassportPath));
|
|
470
|
+
}
|
|
471
|
+
|
|
370
472
|
async function resolveTargetAdvance({ observedTargetSha, targetSha, transactionId, existingTransaction, repoInfo, apiUrl, token, fetchImpl }) {
|
|
371
473
|
if (observedTargetSha === targetSha || !transactionId || existingTransaction?.id !== transactionId) return undefined;
|
|
372
474
|
const comparison = await githubJson({ apiUrl, token, fetchImpl, path: `/repos/${repoInfo.owner}/${repoInfo.repo}/compare/${targetSha}...${observedTargetSha}` });
|
|
@@ -494,7 +596,16 @@ export async function resumeFromCandidateRun({
|
|
|
494
596
|
if (publication.manifest) fs.writeFileSync(sealedManifestPath, `${JSON.stringify(publication.manifest, null, 2)}\n`);
|
|
495
597
|
const publishRequiredArtifacts = publication.publishRequiredArtifacts;
|
|
496
598
|
fs.writeFileSync(requiredArtifactsPath, `${JSON.stringify(publishRequiredArtifacts, null, 2)}\n`);
|
|
599
|
+
const payloadRoot = exposeRecoveredPayloadRoot({ resolvedOutput, bundleRoot });
|
|
600
|
+
const recoveredPassportPath = initialDownloads[0].files.find(
|
|
601
|
+
(file) => path.basename(file.path) === "release-candidate-passport.json",
|
|
602
|
+
).absolutePath;
|
|
603
|
+
const passportPath = exposeRecoveredPassportPath({ resolvedOutput, recoveredPassportPath });
|
|
497
604
|
const tarballs = publication.npmArtifacts.map((entry) => outputPath(entry.file.absolutePath));
|
|
605
|
+
const githubArtifactAttestationPolicies = recoveredArtifactPathsByBasename(
|
|
606
|
+
downloads,
|
|
607
|
+
"github-artifact-attestation-policy.json",
|
|
608
|
+
);
|
|
498
609
|
return {
|
|
499
610
|
enabled: true,
|
|
500
611
|
action: "reused",
|
|
@@ -507,10 +618,11 @@ export async function resumeFromCandidateRun({
|
|
|
507
618
|
receipt: recovery.receipt,
|
|
508
619
|
publishRequiredArtifacts,
|
|
509
620
|
paths: {
|
|
510
|
-
passport: outputPath(
|
|
621
|
+
passport: outputPath(passportPath),
|
|
511
622
|
buildSummary: outputPath(initialDownloads[1].files.find((file) => path.basename(file.path) === "build-summary.json").absolutePath),
|
|
512
|
-
payloads: outputPath(
|
|
513
|
-
platformManifests:
|
|
623
|
+
payloads: outputPath(payloadRoot),
|
|
624
|
+
platformManifests: platformManifestEvidence.paths.map(outputPath),
|
|
625
|
+
githubArtifactAttestationPolicies,
|
|
514
626
|
npmTarballs: tarballs,
|
|
515
627
|
releaseAssets: publication.releaseAssets.map((asset) => outputPath(asset.absolutePath)),
|
|
516
628
|
publishRequiredArtifacts: outputPath(requiredArtifactsPath),
|
|
@@ -560,6 +672,8 @@ export async function resumeFromCandidateRunCli() {
|
|
|
560
672
|
"release-candidate-payload-artifacts": result.artifacts.payloads.join(","),
|
|
561
673
|
"release-candidate-payload-dir": result.paths.payloads,
|
|
562
674
|
"release-candidate-platform-manifest-paths": result.paths.platformManifests.join(","),
|
|
675
|
+
"release-candidate-github-artifact-attestation-policy-paths": result.paths.githubArtifactAttestationPolicies.join(","),
|
|
676
|
+
"release-candidate-github-artifact-attestation-policy-count": String(result.paths.githubArtifactAttestationPolicies.length),
|
|
563
677
|
"release-candidate-npm-tarball-paths": result.paths.npmTarballs.join(","),
|
|
564
678
|
"release-candidate-github-release-artifact-paths": result.paths.releaseAssets.join("\n"),
|
|
565
679
|
"publish-required-artifacts-json": JSON.stringify(result.publishRequiredArtifacts),
|
|
@@ -376,6 +376,12 @@ export function sealArtifactSigningRequests({
|
|
|
376
376
|
signature: {
|
|
377
377
|
profile: declaration.profile,
|
|
378
378
|
required: declaration.required,
|
|
379
|
+
...(declaration.entitlementsProfile !== "none"
|
|
380
|
+
? {
|
|
381
|
+
entitlementsProfile: declaration.entitlementsProfile,
|
|
382
|
+
entitlementsPaths: declaration.entitlementsPaths,
|
|
383
|
+
}
|
|
384
|
+
: {}),
|
|
379
385
|
},
|
|
380
386
|
});
|
|
381
387
|
const check = validateArtifactSigningRequest(request);
|
|
@@ -188,6 +188,8 @@ export function nodeApiMeta(exportName) {
|
|
|
188
188
|
"./publication-control-plane-audit": { group: "release-passport-trust", summary: "Read-only publication control-plane snapshot evaluation APIs." },
|
|
189
189
|
"./buildchain-publication-authority": { group: "release-passport-trust", summary: "Buildchain-owned closed-world publication authority descriptor registry." },
|
|
190
190
|
"./github-governance-authority": { group: "governance-versioning", summary: "Fail-closed GitHub ownership, effective-policy, managed-zone admission, rollout-plan, and immutable receipt APIs." },
|
|
191
|
+
"./engineering-housekeeper": { group: "governance-versioning", summary: "Provider-neutral repository hygiene classification, deterministic plan and receipt, apply-time revalidation, and replay APIs." },
|
|
192
|
+
"./engineering-housekeeper-github": { group: "governance-versioning", summary: "Paginated GitHub repository hygiene inventory, default-dry-run execution, exact-head mutation fences, and rooted provider receipts." },
|
|
191
193
|
"./artifact-passport": { group: "release-passport-trust", summary: "Artifact passport digest and evidence helper APIs." },
|
|
192
194
|
"./artifact-verification-envelope": { group: "release-passport-trust", summary: "Sealed exact-root, lifecycle, identity, and existing KFD assessment inputs for KFX admission." },
|
|
193
195
|
"./artifact-signing": { group: "reusable-build", summary: "Credential-free artifact signing declarations, source-bound requests, authority receipts, profile resolution, and fail-closed validation APIs." },
|
|
@@ -1725,12 +1725,13 @@ async function fetchWithRetry(url, {
|
|
|
1725
1725
|
attempts = 3,
|
|
1726
1726
|
intervalMs = 5000,
|
|
1727
1727
|
shouldRetry = () => false,
|
|
1728
|
+
requestOptions = { redirect: "follow" },
|
|
1728
1729
|
} = {}) {
|
|
1729
1730
|
const maxAttempts = Math.max(1, Number(attempts) || 1);
|
|
1730
1731
|
let lastError = null;
|
|
1731
1732
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
1732
1733
|
try {
|
|
1733
|
-
const response = await fetchImpl(url,
|
|
1734
|
+
const response = await fetchImpl(url, requestOptions);
|
|
1734
1735
|
if (attempt >= maxAttempts || !shouldRetry(response)) {
|
|
1735
1736
|
return { response, attempts: attempt };
|
|
1736
1737
|
}
|
|
@@ -1751,6 +1752,11 @@ function retryableHealthResponse(response) {
|
|
|
1751
1752
|
return response.status === 403 || response.status === 404 || response.status >= 500;
|
|
1752
1753
|
}
|
|
1753
1754
|
|
|
1755
|
+
function retryingHealthFetch(fetchImpl, attempts, intervalMs) {
|
|
1756
|
+
return (url, requestOptions) => fetchWithRetry(url, { fetchImpl, attempts, intervalMs,
|
|
1757
|
+
shouldRetry: retryableHealthResponse, requestOptions }).then(({ response }) => response);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1754
1760
|
const DEFAULT_HEALTH_HTTP_RETRY_ATTEMPTS = 12;
|
|
1755
1761
|
const DEFAULT_HEALTH_HTTP_RETRY_INTERVAL_MS = 10000;
|
|
1756
1762
|
|
|
@@ -1984,7 +1990,7 @@ export async function checkWebSurfaceHealth({
|
|
|
1984
1990
|
};
|
|
1985
1991
|
const evidence = await verifyInstallerPublicReadback({
|
|
1986
1992
|
publication: projected,
|
|
1987
|
-
fetchImpl,
|
|
1993
|
+
fetchImpl: retryingHealthFetch(fetchImpl, httpRetryAttempts, httpRetryIntervalMs),
|
|
1988
1994
|
});
|
|
1989
1995
|
checks.push({
|
|
1990
1996
|
surface: "__installer__",
|
|
@@ -6,7 +6,7 @@ import { pathToFileURL } from "node:url";
|
|
|
6
6
|
import { evaluateWorkflowCallContract } from "../packages/core/workflow-call-contract.js";
|
|
7
7
|
import { parseWorkflowDocument } from "../packages/core/workflow-yaml-contract.js";
|
|
8
8
|
|
|
9
|
-
export const SELF_RELEASE_REF = "
|
|
9
|
+
export const SELF_RELEASE_REF = "769b221bad7a6b9104afad4c2628d9dca396ab0f";
|
|
10
10
|
export const SELF_RELEASE_PUBLIC_WORKFLOW = `kungfu-systems/buildchain/.github/workflows/release-candidate-promote.yml@${SELF_RELEASE_REF}`;
|
|
11
11
|
|
|
12
12
|
function parityFailure(message) {
|