@kungfu-tech/buildchain 3.0.8 → 3.0.9-alpha.0
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 +8 -7
- package/bin/buildchain.mjs +34 -33
- package/bin/internal/trust-release-release-handlers.mjs +5 -0
- package/dist/site/agent-index.json +0 -1
- package/dist/site/artifact-schemas.json +0 -2
- package/dist/site/badge-endpoint-registry.json +9 -9
- package/dist/site/badges/v1/kfd-8/aligned.json +1 -1
- package/dist/site/badges/v1/kfd-8/declared.json +1 -1
- package/dist/site/badges/v1/kfd-8/downgraded.json +1 -1
- package/dist/site/badges/v1/kfd-8/draft.json +1 -1
- package/dist/site/badges/v1/kfd-8/failed.json +1 -1
- package/dist/site/badges/v1/kfd-8/missing.json +1 -1
- package/dist/site/badges/v1/kfd-8/passed.json +1 -1
- package/dist/site/badges/v1/kfd-8/planned.json +1 -1
- package/dist/site/buildchain-contract.json +27 -22
- package/dist/site/buildchain-site.json +38 -28
- package/dist/site/capability-registry.json +1 -1
- package/dist/site/cli-registry.json +8 -7
- package/dist/site/controller-registry.json +6 -2
- package/dist/site/kfd-claims.json +34 -7
- package/dist/site/kfd-upstream-aggregate.json +10 -10
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +754 -228
- package/dist/site/page-registry.json +28 -18
- package/dist/site/public-surface-audit.json +19 -9
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-passport-check-manifest.json +33 -5
- package/dist/site/release-provenance.json +2 -1
- package/dist/site/schemas/release-passport-v1.schema.json +3 -0
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +19 -9
- package/docs/aws-us-elastic-runner-burst-plane.md +15 -3
- package/docs/cli-reference.md +6 -6
- package/docs/cli.md +2 -3
- package/docs/dev-alpha-candidate-patrol.md +45 -20
- package/docs/kfd-support.md +7 -7
- package/docs/node-api-reference.md +179 -140
- package/docs/release-passport.md +18 -12
- package/docs/release-train.md +8 -0
- package/package.json +4 -3
- package/packages/core/artifact-verification-envelope.js +114 -0
- package/packages/core/dev-alpha-active-release-train.js +460 -0
- package/packages/core/dev-alpha-candidate-selection.js +181 -0
- package/packages/core/index.js +4 -5
- package/packages/core/kfd-adopter-manifest.js +390 -0
- package/packages/core/kfd-product-gates.js +0 -300
- package/packages/core/release-passport-contract.js +219 -6
- package/packages/core/release-passport.js +139 -242
- package/scripts/aws-macos-jit-controller-core.mjs +76 -0
- package/scripts/aws-macos-jit-controller.mjs +29 -0
- package/scripts/aws-macos-jit-source-rebind.mjs +503 -0
- package/scripts/buildchain-cli-help.mjs +4 -2
- package/scripts/check-inventory.mjs +2 -2
- package/scripts/dev-alpha-candidate-patrol.mjs +196 -201
- package/scripts/generate-site-bundle.mjs +1 -8
- package/scripts/site-capability-metadata.mjs +3 -1
- package/dist/site/schemas/kfd-support-projection-v1.schema.json +0 -106
|
@@ -9,6 +9,19 @@ import {
|
|
|
9
9
|
resolveManagedPromotionBaseline,
|
|
10
10
|
resolvePatrolSourceInputs,
|
|
11
11
|
} from "../packages/core/channel-promotion-baseline.js";
|
|
12
|
+
import {
|
|
13
|
+
cutInitialReleaseTrain,
|
|
14
|
+
reconcileActiveReleaseTrain,
|
|
15
|
+
resolveManagedCandidate,
|
|
16
|
+
runActiveReleaseTrain,
|
|
17
|
+
} from "../packages/core/dev-alpha-active-release-train.js";
|
|
18
|
+
import {
|
|
19
|
+
blockedCandidateDecision,
|
|
20
|
+
candidateFromDecision,
|
|
21
|
+
selectLatestQualifiedSource,
|
|
22
|
+
} from "../packages/core/dev-alpha-candidate-selection.js";
|
|
23
|
+
|
|
24
|
+
export { reconcileActiveReleaseTrain, selectLatestQualifiedSource };
|
|
12
25
|
|
|
13
26
|
export const DEV_ALPHA_CANDIDATE_STATE_SCHEMA =
|
|
14
27
|
"kungfu-buildchain-dev-alpha-candidate-state/v1";
|
|
@@ -191,6 +204,8 @@ export function managedCandidateFromPullRequest(pullRequest, targetBranch) {
|
|
|
191
204
|
sourceLockRef: headRef,
|
|
192
205
|
decisionRoot: text(marker.activeCandidate?.decisionRoot),
|
|
193
206
|
nextCandidate: marker.nextCandidate || null,
|
|
207
|
+
releaseTrain: marker.releaseTrain || null,
|
|
208
|
+
hold: marker.hold || null,
|
|
194
209
|
state: marker,
|
|
195
210
|
};
|
|
196
211
|
}
|
|
@@ -215,6 +230,8 @@ export function managedCandidateFromPullRequest(pullRequest, targetBranch) {
|
|
|
215
230
|
sourceLockRef: headRef,
|
|
216
231
|
decisionRoot: "",
|
|
217
232
|
nextCandidate: null,
|
|
233
|
+
releaseTrain: null,
|
|
234
|
+
hold: null,
|
|
218
235
|
state: null,
|
|
219
236
|
};
|
|
220
237
|
}
|
|
@@ -274,6 +291,25 @@ export function normalizeDevAlphaPatrolOptions(options = {}) {
|
|
|
274
291
|
process.env.BUILDCHAIN_CHANNEL_PATROL_EXPECTED_PRIOR_STATE_ROOT,
|
|
275
292
|
"expectedPriorStateRoot",
|
|
276
293
|
),
|
|
294
|
+
expectedCutRoot: optionalStateRoot(
|
|
295
|
+
options.expectedCutRoot ??
|
|
296
|
+
process.env.BUILDCHAIN_CHANNEL_PATROL_EXPECTED_CUT_ROOT,
|
|
297
|
+
"expectedCutRoot",
|
|
298
|
+
),
|
|
299
|
+
cutCreatedAt: text(
|
|
300
|
+
options.cutCreatedAt ??
|
|
301
|
+
process.env.BUILDCHAIN_CHANNEL_PATROL_CUT_CREATED_AT,
|
|
302
|
+
),
|
|
303
|
+
requireActiveReleaseTrain: bool(
|
|
304
|
+
options.requireActiveReleaseTrain ??
|
|
305
|
+
process.env.BUILDCHAIN_CHANNEL_PATROL_REQUIRE_ACTIVE_TRAIN,
|
|
306
|
+
true,
|
|
307
|
+
),
|
|
308
|
+
buildchainRuntimeSha: optionalExactSha(
|
|
309
|
+
options.buildchainRuntimeSha ??
|
|
310
|
+
process.env.BUILDCHAIN_CHANNEL_PATROL_RUNTIME_SHA,
|
|
311
|
+
"buildchainRuntimeSha",
|
|
312
|
+
),
|
|
277
313
|
reactivationAuthorized: bool(
|
|
278
314
|
options.reactivationAuthorized ??
|
|
279
315
|
process.env.BUILDCHAIN_CHANNEL_PATROL_REACTIVATION_AUTHORIZED,
|
|
@@ -321,157 +357,6 @@ export function normalizeDevAlphaPatrolOptions(options = {}) {
|
|
|
321
357
|
};
|
|
322
358
|
}
|
|
323
359
|
|
|
324
|
-
function latestWorkflowEvidence(runs, workflowPathValue, sourceSha) {
|
|
325
|
-
const matching = runs
|
|
326
|
-
.filter(
|
|
327
|
-
(run) =>
|
|
328
|
-
run.conclusion !== "cancelled" &&
|
|
329
|
-
run.path === workflowPathValue &&
|
|
330
|
-
run.head_sha === sourceSha,
|
|
331
|
-
)
|
|
332
|
-
.sort((left, right) => Number(right.id) - Number(left.id));
|
|
333
|
-
if (matching.length === 0)
|
|
334
|
-
throw new Error(
|
|
335
|
-
`missing completed same-SHA workflow run: ${workflowPathValue}`,
|
|
336
|
-
);
|
|
337
|
-
const run = matching[0];
|
|
338
|
-
return {
|
|
339
|
-
workflowPath: workflowPathValue,
|
|
340
|
-
workflowName: run.name,
|
|
341
|
-
runId: run.id,
|
|
342
|
-
runAttempt: run.run_attempt,
|
|
343
|
-
headSha: run.head_sha,
|
|
344
|
-
status: run.status,
|
|
345
|
-
conclusion: run.conclusion,
|
|
346
|
-
completedAt: run.updated_at,
|
|
347
|
-
url: run.html_url,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function workflowEvidenceIsFreshAndSuccessful(run, { now, maxAgeSeconds }) {
|
|
352
|
-
const completedAt = Date.parse(run.updated_at);
|
|
353
|
-
const ageSeconds = (Date.parse(now) - completedAt) / 1000;
|
|
354
|
-
return (
|
|
355
|
-
run.status === "completed" &&
|
|
356
|
-
run.conclusion === "success" &&
|
|
357
|
-
Number.isFinite(ageSeconds) &&
|
|
358
|
-
ageSeconds >= 0 &&
|
|
359
|
-
ageSeconds <= maxAgeSeconds
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function latestRunsBySha(runs, workflowPathValue) {
|
|
364
|
-
const latest = new Map();
|
|
365
|
-
for (const run of runs.filter(
|
|
366
|
-
(row) => row.path === workflowPathValue && row.conclusion !== "cancelled",
|
|
367
|
-
)) {
|
|
368
|
-
const current = latest.get(run.head_sha);
|
|
369
|
-
if (!current || Number(run.id) > Number(current.id))
|
|
370
|
-
latest.set(run.head_sha, run);
|
|
371
|
-
}
|
|
372
|
-
return latest;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export function selectLatestQualifiedSource({
|
|
376
|
-
sourceHistory,
|
|
377
|
-
workflowRunsByPath,
|
|
378
|
-
requiredWorkflowPaths,
|
|
379
|
-
now,
|
|
380
|
-
maxAgeSeconds,
|
|
381
|
-
}) {
|
|
382
|
-
const latestByPath = new Map(
|
|
383
|
-
requiredWorkflowPaths.map((workflow) => [
|
|
384
|
-
workflow,
|
|
385
|
-
latestRunsBySha(workflowRunsByPath.get(workflow) || [], workflow),
|
|
386
|
-
]),
|
|
387
|
-
);
|
|
388
|
-
for (let index = 0; index < sourceHistory.length; index += 1) {
|
|
389
|
-
const sourceSha = sourceHistory[index];
|
|
390
|
-
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
391
|
-
latestByPath.get(workflow).get(sourceSha),
|
|
392
|
-
);
|
|
393
|
-
if (
|
|
394
|
-
rows.every((run) =>
|
|
395
|
-
workflowEvidenceIsFreshAndSuccessful(run || {}, { now, maxAgeSeconds }),
|
|
396
|
-
)
|
|
397
|
-
) {
|
|
398
|
-
return {
|
|
399
|
-
sourceSha,
|
|
400
|
-
skippedNewerCommitCount: index,
|
|
401
|
-
workflowEvidence: requiredWorkflowPaths.map((workflow) =>
|
|
402
|
-
latestWorkflowEvidence(
|
|
403
|
-
workflowRunsByPath.get(workflow) || [],
|
|
404
|
-
workflow,
|
|
405
|
-
sourceSha,
|
|
406
|
-
),
|
|
407
|
-
),
|
|
408
|
-
};
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
const staleSuccessfulPair = sourceHistory.some((sourceSha) => {
|
|
412
|
-
const rows = requiredWorkflowPaths.map((workflow) =>
|
|
413
|
-
latestByPath.get(workflow).get(sourceSha),
|
|
414
|
-
);
|
|
415
|
-
return (
|
|
416
|
-
rows.every(
|
|
417
|
-
(run) =>
|
|
418
|
-
run?.status === "completed" &&
|
|
419
|
-
run?.conclusion === "success" &&
|
|
420
|
-
run?.head_sha === sourceSha,
|
|
421
|
-
) &&
|
|
422
|
-
rows.some(
|
|
423
|
-
(run) =>
|
|
424
|
-
!workflowEvidenceIsFreshAndSuccessful(run, { now, maxAgeSeconds }),
|
|
425
|
-
)
|
|
426
|
-
);
|
|
427
|
-
});
|
|
428
|
-
if (staleSuccessfulPair)
|
|
429
|
-
throw new Error(
|
|
430
|
-
"same-SHA workflow evidence is stale for every qualified source commit",
|
|
431
|
-
);
|
|
432
|
-
throw new Error(
|
|
433
|
-
"no source commit ahead of target has fresh completed successful same-SHA workflow evidence",
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function blockedCandidateDecision({
|
|
438
|
-
options,
|
|
439
|
-
sourceSha,
|
|
440
|
-
targetSha,
|
|
441
|
-
comparison,
|
|
442
|
-
reason,
|
|
443
|
-
}) {
|
|
444
|
-
const body = {
|
|
445
|
-
schema: "kungfu-buildchain-channel-candidate-decision/v1",
|
|
446
|
-
eligible: false,
|
|
447
|
-
reason: "qualification-evidence-blocked",
|
|
448
|
-
repository: options.repository,
|
|
449
|
-
source: { branch: options.sourceBranch, sha: sourceSha },
|
|
450
|
-
target: { branch: options.targetBranch, sha: targetSha },
|
|
451
|
-
comparison: {
|
|
452
|
-
status: text(comparison.status || "unknown"),
|
|
453
|
-
aheadBy: Number(comparison.ahead_by || 0),
|
|
454
|
-
},
|
|
455
|
-
blockReason: text(reason),
|
|
456
|
-
decidedAt: options.now,
|
|
457
|
-
};
|
|
458
|
-
return { ...body, decisionRoot: evidenceRoot(body) };
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function candidateFromDecision(decision) {
|
|
462
|
-
if (!decision.eligible) return null;
|
|
463
|
-
return {
|
|
464
|
-
sourceSha: decision.source.sha,
|
|
465
|
-
sourceLockRef: decision.sourceLockRef,
|
|
466
|
-
decisionRoot: decision.decisionRoot,
|
|
467
|
-
workflowEvidence: decision.workflowEvidence,
|
|
468
|
-
qualificationRoot: evidenceRoot({
|
|
469
|
-
sourceSha: decision.source.sha,
|
|
470
|
-
workflowEvidence: decision.workflowEvidence,
|
|
471
|
-
}),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
|
|
475
360
|
function candidateStateBody({
|
|
476
361
|
options,
|
|
477
362
|
targetSha,
|
|
@@ -482,6 +367,8 @@ function candidateStateBody({
|
|
|
482
367
|
priorStateRoot = ABSENT_STATE_ROOT,
|
|
483
368
|
generation = 1,
|
|
484
369
|
tombstones = [],
|
|
370
|
+
releaseTrain = null,
|
|
371
|
+
hold = null,
|
|
485
372
|
}) {
|
|
486
373
|
const body = {
|
|
487
374
|
schema: DEV_ALPHA_CANDIDATE_STATE_SCHEMA,
|
|
@@ -494,6 +381,8 @@ function candidateStateBody({
|
|
|
494
381
|
activeCandidate,
|
|
495
382
|
nextCandidate,
|
|
496
383
|
tombstones,
|
|
384
|
+
...(releaseTrain ? { releaseTrain } : {}),
|
|
385
|
+
...(hold ? { hold } : {}),
|
|
497
386
|
observationDecisionRoot: decision.decisionRoot || null,
|
|
498
387
|
observedAt: options.now,
|
|
499
388
|
...(supersededCandidate ? { supersededCandidate } : {}),
|
|
@@ -615,6 +504,80 @@ function activePullRequest(candidate) {
|
|
|
615
504
|
};
|
|
616
505
|
}
|
|
617
506
|
|
|
507
|
+
async function resumeActiveTrainIfPresent(options, client, candidate) {
|
|
508
|
+
if (!options.requireActiveReleaseTrain || !candidate) return null;
|
|
509
|
+
if (!candidate.releaseTrain) {
|
|
510
|
+
throw new Error(
|
|
511
|
+
`candidate PR #${candidate.number} has no authoritative active Release Train`,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return runActiveReleaseTrain({
|
|
515
|
+
options,
|
|
516
|
+
client,
|
|
517
|
+
activeCandidate: candidate,
|
|
518
|
+
adapters: {
|
|
519
|
+
stateSchema: DEV_ALPHA_CANDIDATE_STATE_SCHEMA,
|
|
520
|
+
persistedStateRoot,
|
|
521
|
+
candidateStateBody,
|
|
522
|
+
replaceCandidateStateMarker,
|
|
523
|
+
activePullRequest,
|
|
524
|
+
armCandidateAutoMerge,
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function assertExpectedSelection(options, decision) {
|
|
530
|
+
if (
|
|
531
|
+
options.expectedSelectedSha &&
|
|
532
|
+
decision.source.sha !== options.expectedSelectedSha
|
|
533
|
+
) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`selected source changed between observation and settlement: expected ${options.expectedSelectedSha}, observed ${decision.source.sha}`,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function assertExpectedStateRoot(options, priorStateRoot) {
|
|
541
|
+
if (
|
|
542
|
+
options.expectedPriorStateRoot &&
|
|
543
|
+
options.expectedPriorStateRoot !== priorStateRoot
|
|
544
|
+
) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
`candidate controller compare-and-swap failed: expected prior state root ${options.expectedPriorStateRoot}, observed ${priorStateRoot}`,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function maybeCutReleaseTrain({
|
|
552
|
+
options,
|
|
553
|
+
client,
|
|
554
|
+
activeCandidate,
|
|
555
|
+
decision,
|
|
556
|
+
observedCandidate,
|
|
557
|
+
sourceSha,
|
|
558
|
+
targetSha,
|
|
559
|
+
observedSourceHeadSha,
|
|
560
|
+
}) {
|
|
561
|
+
if (!options.requireActiveReleaseTrain || activeCandidate) {
|
|
562
|
+
return activeCandidate?.releaseTrain || null;
|
|
563
|
+
}
|
|
564
|
+
return cutInitialReleaseTrain({
|
|
565
|
+
options,
|
|
566
|
+
client,
|
|
567
|
+
decision,
|
|
568
|
+
candidate: observedCandidate,
|
|
569
|
+
sourceSha,
|
|
570
|
+
targetSha,
|
|
571
|
+
observedSourceHeadSha,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function initialControllerState(decision, qualificationError) {
|
|
576
|
+
if (decision.eligible) return "eligible-for-settlement";
|
|
577
|
+
if (!qualificationError) return "observed";
|
|
578
|
+
return /stale/u.test(qualificationError.message) ? "stale" : "blocked";
|
|
579
|
+
}
|
|
580
|
+
|
|
618
581
|
export async function runDevAlphaCandidatePatrol(
|
|
619
582
|
optionsInput = {},
|
|
620
583
|
clientInput,
|
|
@@ -632,6 +595,19 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
632
595
|
options.devWorkflowPath,
|
|
633
596
|
options.alphaWorkflowPath,
|
|
634
597
|
];
|
|
598
|
+
const persistedCandidate = await resolveManagedCandidate({
|
|
599
|
+
client,
|
|
600
|
+
targetBranch: options.targetBranch,
|
|
601
|
+
repository: options.repository,
|
|
602
|
+
sourceBranch: options.sourceBranch,
|
|
603
|
+
parseCandidate: managedCandidateFromPullRequest,
|
|
604
|
+
});
|
|
605
|
+
const resumed = await resumeActiveTrainIfPresent(
|
|
606
|
+
options,
|
|
607
|
+
client,
|
|
608
|
+
persistedCandidate,
|
|
609
|
+
);
|
|
610
|
+
if (resumed) return resumed;
|
|
635
611
|
const {
|
|
636
612
|
observedSourceHeadSha,
|
|
637
613
|
targetSha,
|
|
@@ -676,66 +652,31 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
676
652
|
maxAgeSeconds: options.maxAgeSeconds,
|
|
677
653
|
now: options.now,
|
|
678
654
|
});
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
decision.source.sha !== options.expectedSelectedSha
|
|
682
|
-
) {
|
|
683
|
-
throw new Error(
|
|
684
|
-
`selected source changed between observation and settlement: expected ${options.expectedSelectedSha}, observed ${decision.source.sha}`,
|
|
685
|
-
);
|
|
686
|
-
}
|
|
687
|
-
const openPullRequests = await client.listOpenPullRequests(
|
|
688
|
-
options.targetBranch,
|
|
689
|
-
);
|
|
690
|
-
const managedCandidates = openPullRequests
|
|
691
|
-
.map((pullRequest) =>
|
|
692
|
-
managedCandidateFromPullRequest(pullRequest, options.targetBranch),
|
|
693
|
-
)
|
|
694
|
-
.filter(Boolean);
|
|
695
|
-
for (const candidate of managedCandidates) {
|
|
696
|
-
if (
|
|
697
|
-
candidate.state &&
|
|
698
|
-
(candidate.state.repository !== options.repository ||
|
|
699
|
-
candidate.state.sourceBranch !== options.sourceBranch)
|
|
700
|
-
) {
|
|
701
|
-
throw new Error(
|
|
702
|
-
`candidate PR #${candidate.number} state does not bind ${options.repository} ${options.sourceBranch}`,
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
if (managedCandidates.length > 1) {
|
|
707
|
-
throw new Error(
|
|
708
|
-
`multiple open Buildchain candidate PRs target ${options.targetBranch}: ${managedCandidates
|
|
709
|
-
.map((candidate) => `#${candidate.number}`)
|
|
710
|
-
.join(", ")}`,
|
|
711
|
-
);
|
|
712
|
-
}
|
|
713
|
-
let activeCandidate = managedCandidates[0] || null;
|
|
655
|
+
assertExpectedSelection(options, decision);
|
|
656
|
+
let activeCandidate = persistedCandidate;
|
|
714
657
|
const priorStateRoot = persistedStateRoot(activeCandidate);
|
|
715
|
-
|
|
716
|
-
options.expectedPriorStateRoot &&
|
|
717
|
-
options.expectedPriorStateRoot !== priorStateRoot
|
|
718
|
-
) {
|
|
719
|
-
throw new Error(
|
|
720
|
-
`candidate controller compare-and-swap failed: expected prior state root ${options.expectedPriorStateRoot}, observed ${priorStateRoot}`,
|
|
721
|
-
);
|
|
722
|
-
}
|
|
658
|
+
assertExpectedStateRoot(options, priorStateRoot);
|
|
723
659
|
const priorGeneration = Number(activeCandidate?.state?.generation || 0);
|
|
724
660
|
let generation = Math.max(priorGeneration, 1);
|
|
725
661
|
const priorTombstones = Array.isArray(activeCandidate?.state?.tombstones)
|
|
726
662
|
? activeCandidate.state.tombstones
|
|
727
663
|
: [];
|
|
728
664
|
const observedCandidate = candidateFromDecision(decision);
|
|
665
|
+
const releaseTrain = await maybeCutReleaseTrain({
|
|
666
|
+
options,
|
|
667
|
+
client,
|
|
668
|
+
activeCandidate,
|
|
669
|
+
decision,
|
|
670
|
+
observedCandidate,
|
|
671
|
+
sourceSha,
|
|
672
|
+
targetSha,
|
|
673
|
+
observedSourceHeadSha,
|
|
674
|
+
});
|
|
675
|
+
generation = releaseTrain?.releaseCut.generation || generation;
|
|
729
676
|
let nextCandidate = null;
|
|
730
677
|
let supersededCandidate = null;
|
|
731
678
|
let tombstones = [...priorTombstones];
|
|
732
|
-
let controllerState = decision
|
|
733
|
-
? "eligible-for-settlement"
|
|
734
|
-
: qualificationError
|
|
735
|
-
? /stale/u.test(qualificationError.message)
|
|
736
|
-
? "stale"
|
|
737
|
-
: "blocked"
|
|
738
|
-
: "observed";
|
|
679
|
+
let controllerState = initialControllerState(decision, qualificationError);
|
|
739
680
|
if (activeCandidate) {
|
|
740
681
|
controllerState = "active";
|
|
741
682
|
nextCandidate = activeCandidate.nextCandidate || null;
|
|
@@ -820,6 +761,7 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
820
761
|
priorStateRoot,
|
|
821
762
|
generation,
|
|
822
763
|
tombstones,
|
|
764
|
+
releaseTrain,
|
|
823
765
|
});
|
|
824
766
|
let pullRequest;
|
|
825
767
|
let settlementAction = "none";
|
|
@@ -848,6 +790,8 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
848
790
|
activeCandidate: observedCandidate,
|
|
849
791
|
nextCandidate: null,
|
|
850
792
|
supersededCandidate: null,
|
|
793
|
+
generation,
|
|
794
|
+
releaseTrain,
|
|
851
795
|
});
|
|
852
796
|
pullRequest = await client.ensurePullRequest({
|
|
853
797
|
head: decision.sourceLockRef,
|
|
@@ -885,6 +829,7 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
885
829
|
priorStateRoot,
|
|
886
830
|
generation,
|
|
887
831
|
tombstones,
|
|
832
|
+
releaseTrain,
|
|
888
833
|
});
|
|
889
834
|
const nextBody = replaceCandidateStateMarker(pullRequest.body, state);
|
|
890
835
|
await client.updatePullRequestBody(
|
|
@@ -925,6 +870,20 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
925
870
|
settlementAuthorized: options.settlementAuthorized,
|
|
926
871
|
autoMerge,
|
|
927
872
|
decision,
|
|
873
|
+
releaseTrain,
|
|
874
|
+
drift: releaseTrain
|
|
875
|
+
? {
|
|
876
|
+
observedDevSha: observedSourceHeadSha,
|
|
877
|
+
originDevSha: releaseTrain.releaseCut.originDevSha,
|
|
878
|
+
moved: observedSourceHeadSha !== releaseTrain.releaseCut.originDevSha,
|
|
879
|
+
observationRoot:
|
|
880
|
+
releaseTrain.observations.find(
|
|
881
|
+
(observation) =>
|
|
882
|
+
observation.observedDevSha === observedSourceHeadSha,
|
|
883
|
+
)?.observationRoot || null,
|
|
884
|
+
}
|
|
885
|
+
: null,
|
|
886
|
+
hold: null,
|
|
928
887
|
controller: {
|
|
929
888
|
schema: DEV_ALPHA_CANDIDATE_STATE_SCHEMA,
|
|
930
889
|
state: controllerState,
|
|
@@ -935,6 +894,16 @@ export async function runDevAlphaCandidatePatrol(
|
|
|
935
894
|
stateRoot: state.stateRoot,
|
|
936
895
|
priorStateRoot,
|
|
937
896
|
generation: state.generation,
|
|
897
|
+
trainRoot: releaseTrain?.trainRoot || null,
|
|
898
|
+
cutRoot: releaseTrain?.releaseCut.cutRoot || null,
|
|
899
|
+
candidateTreeSha: releaseTrain?.releaseCut.candidateTreeSha || null,
|
|
900
|
+
buildchainRuntimeSha:
|
|
901
|
+
releaseTrain?.releaseCut.buildchainRuntimeSha ||
|
|
902
|
+
options.buildchainRuntimeSha ||
|
|
903
|
+
null,
|
|
904
|
+
cutCreatedAt: releaseTrain?.releaseCut.createdAt || null,
|
|
905
|
+
observedAt: options.now,
|
|
906
|
+
holdRoot: null,
|
|
938
907
|
tombstones: state.tombstones || [],
|
|
939
908
|
},
|
|
940
909
|
pullRequest: pullRequest || null,
|
|
@@ -1002,6 +971,17 @@ export function createGitHubChannelCandidateClient({
|
|
|
1002
971
|
);
|
|
1003
972
|
return text(payload.object?.sha);
|
|
1004
973
|
},
|
|
974
|
+
async resolveCommitTreeSha(commitSha) {
|
|
975
|
+
const payload = await api(
|
|
976
|
+
`/repos/${owner}/${repo}/git/commits/${encodeURIComponent(commitSha)}`,
|
|
977
|
+
);
|
|
978
|
+
if (text(payload.sha) !== commitSha) {
|
|
979
|
+
throw new Error(
|
|
980
|
+
`candidate commit readback returned ${payload.sha || "<empty>"}, not ${commitSha}`,
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
return optionalExactSha(payload.tree?.sha, "candidateTreeSha");
|
|
984
|
+
},
|
|
1005
985
|
async compare(baseSha, headSha) {
|
|
1006
986
|
return api(`/repos/${owner}/${repo}/compare/${baseSha}...${headSha}`);
|
|
1007
987
|
},
|
|
@@ -1133,6 +1113,12 @@ function markdown(result) {
|
|
|
1133
1113
|
`Source: \`${result.decision.source.branch}@${result.decision.source.sha}\``,
|
|
1134
1114
|
`Target: \`${result.decision.target.branch}@${result.decision.target.sha}\``,
|
|
1135
1115
|
`Controller state: \`${result.controller.state}\``,
|
|
1116
|
+
`Release Train: \`${result.controller.trainRoot || "none"}\``,
|
|
1117
|
+
`Candidate generation: \`${result.controller.generation}\``,
|
|
1118
|
+
`Candidate tree: \`${result.controller.candidateTreeSha || "none"}\``,
|
|
1119
|
+
`Buildchain runtime: \`${result.controller.buildchainRuntimeSha || "none"}\``,
|
|
1120
|
+
`Drift observation: \`${result.drift?.observationRoot || "none"}\``,
|
|
1121
|
+
`Hold: \`${result.controller.holdRoot || "none"}\``,
|
|
1136
1122
|
`Active candidate: ${result.controller.activeCandidate?.url || "none"}`,
|
|
1137
1123
|
`Next candidate: \`${result.controller.nextCandidate?.sourceSha || "none"}\``,
|
|
1138
1124
|
`Settlement action: \`${result.controller.settlementAction}\``,
|
|
@@ -1163,6 +1149,15 @@ async function main() {
|
|
|
1163
1149
|
"next-candidate-sha": result.controller.nextCandidate?.sourceSha || "",
|
|
1164
1150
|
"settlement-action": result.controller.settlementAction,
|
|
1165
1151
|
"prior-state-root": result.controller.priorStateRoot,
|
|
1152
|
+
"train-root": result.controller.trainRoot || "",
|
|
1153
|
+
"cut-root": result.controller.cutRoot || "",
|
|
1154
|
+
"candidate-generation": String(result.controller.generation),
|
|
1155
|
+
"candidate-tree-sha": result.controller.candidateTreeSha || "",
|
|
1156
|
+
"runtime-sha": result.controller.buildchainRuntimeSha || "",
|
|
1157
|
+
"cut-created-at": result.controller.cutCreatedAt || "",
|
|
1158
|
+
"observed-at": result.controller.observedAt || "",
|
|
1159
|
+
"drift-root": result.drift?.observationRoot || "",
|
|
1160
|
+
"hold-root": result.controller.holdRoot || "",
|
|
1166
1161
|
};
|
|
1167
1162
|
fs.appendFileSync(
|
|
1168
1163
|
process.env.GITHUB_OUTPUT,
|
|
@@ -15,10 +15,7 @@ import {
|
|
|
15
15
|
collectKfdUpstreamFacts,
|
|
16
16
|
} from "../packages/core/kfd.js";
|
|
17
17
|
import { KFD_AGENT_HUB_ADOPTION_SCHEMA } from "../packages/core/kfd-agent-hub.js";
|
|
18
|
-
import {
|
|
19
|
-
KFD_PRODUCT_GATE_INPUT_SCHEMA,
|
|
20
|
-
KFD_SUPPORT_PROJECTION_SCHEMA,
|
|
21
|
-
} from "../packages/core/kfd-product-gates.js";
|
|
18
|
+
import { KFD_PRODUCT_GATE_INPUT_SCHEMA } from "../packages/core/kfd-product-gates.js";
|
|
22
19
|
import {
|
|
23
20
|
createReadmeBadgeEndpointRegistry,
|
|
24
21
|
} from "../packages/core/readme-badges.js";
|
|
@@ -933,7 +930,6 @@ function buildSiteBundle() {
|
|
|
933
930
|
"schemas/release-tail-provider-bindings-v1.schema.json",
|
|
934
931
|
"schemas/kfd-agent-hub-adoption.schema.json",
|
|
935
932
|
"schemas/kfd-product-gate-input-v1.schema.json",
|
|
936
|
-
"schemas/kfd-support-projection-v1.schema.json",
|
|
937
933
|
"kfd-support.json",
|
|
938
934
|
"artifact-evidence.json",
|
|
939
935
|
"product-mechanism.json",
|
|
@@ -971,7 +967,6 @@ function buildSiteBundle() {
|
|
|
971
967
|
"schemas/release-tail-provider-bindings-v1.schema.json",
|
|
972
968
|
"schemas/kfd-agent-hub-adoption.schema.json",
|
|
973
969
|
"schemas/kfd-product-gate-input-v1.schema.json",
|
|
974
|
-
"schemas/kfd-support-projection-v1.schema.json",
|
|
975
970
|
"buildchain-contract.json",
|
|
976
971
|
"kfd-claims.json",
|
|
977
972
|
"product-mechanism.json",
|
|
@@ -1105,7 +1100,6 @@ function buildSiteBundle() {
|
|
|
1105
1100
|
"schemas/release-tail-provider-bindings-v1.schema.json",
|
|
1106
1101
|
"schemas/kfd-agent-hub-adoption.schema.json",
|
|
1107
1102
|
"schemas/kfd-product-gate-input-v1.schema.json",
|
|
1108
|
-
"schemas/kfd-support-projection-v1.schema.json",
|
|
1109
1103
|
"buildchain-contract.json",
|
|
1110
1104
|
"kfd-upstream-aggregate.json",
|
|
1111
1105
|
"kfd-claims.json",
|
|
@@ -1289,7 +1283,6 @@ function buildSiteBundle() {
|
|
|
1289
1283
|
),
|
|
1290
1284
|
"schemas/kfd-agent-hub-adoption.schema.json": KFD_AGENT_HUB_ADOPTION_SCHEMA,
|
|
1291
1285
|
"schemas/kfd-product-gate-input-v1.schema.json": KFD_PRODUCT_GATE_INPUT_SCHEMA,
|
|
1292
|
-
"schemas/kfd-support-projection-v1.schema.json": KFD_SUPPORT_PROJECTION_SCHEMA,
|
|
1293
1286
|
"badge-endpoint-registry.json": badgeEndpointRegistry,
|
|
1294
1287
|
"publication-registry.json": publicationRegistry,
|
|
1295
1288
|
"buildchain-contract.json": createBuildchainContractWorld({ root, controllerRegistry }),
|
|
@@ -84,7 +84,7 @@ export function cliCommandMeta(id) {
|
|
|
84
84
|
"kfd-7-schema": { group: "kfd-trust", purpose: "Print the default KFD-7 schema exposed by the KFD package standards metadata." },
|
|
85
85
|
"kfd-7-gate": { group: "kfd-trust", purpose: "Evaluate a source-bound KFD-7 Domain Profile, evidence obligations, and independent review." },
|
|
86
86
|
"kfd-7-verify": { group: "kfd-trust", purpose: "Verify a retained KFD-7 product-gate result without widening product support." },
|
|
87
|
-
"kfd-support": { group: "kfd-trust", purpose: "Project and verify a
|
|
87
|
+
"kfd-support": { group: "kfd-trust", purpose: "Project and verify a legacy KFD support matrix from one exact standard adopter manifest authority closure." },
|
|
88
88
|
"kfd-aggregate": { group: "kfd-trust", purpose: "Return a product KFD view that combines own KFD status with upstream KFD aggregate facts." },
|
|
89
89
|
"kfd-upstream": { group: "kfd-trust", purpose: "Inspect KFD upstream aggregate command families." },
|
|
90
90
|
"kfd-upstream-check": { group: "kfd-trust", purpose: "Validate a KFD upstream aggregate document and fail closed on missing evidence." },
|
|
@@ -218,6 +218,8 @@ export function nodeApiMeta(exportName) {
|
|
|
218
218
|
"./issue-reporting": { group: "observability-diagnostics", summary: "Buildchain-owned issue reporting API for workflow friction feedback." },
|
|
219
219
|
"./buildchain-layout": { group: "kfd-trust", summary: "Versioned Buildchain repository-layout discovery contract plus canonical .buildchain path resolution and migration APIs." },
|
|
220
220
|
"./kfd": { group: "kfd-trust", summary: "Unified KFD standards, schema discovery, KFD-1/KFD-2/KFD-3 grouped APIs, KFD-4 schema discovery, upstream KFD aggregate facts, and Buildchain KFD claim helpers." },
|
|
221
|
+
"./kfd-adopter-manifest": { group: "kfd-trust", summary: "Exact-cut KFD adopter manifest verification, Buildchain evidence binding, and one-way legacy support-matrix projection APIs." },
|
|
222
|
+
"./kfd-adopter-release-binding": { group: "kfd-trust", summary: "Release Passport and artifact-evidence binding for one exact standard adopter manifest closure." },
|
|
221
223
|
"./kfd-product-gates": { group: "kfd-trust", summary: "Fail-closed KFD-4, KFD-5, and KFD-7 product evidence gates plus non-widening KFD support-matrix release projections." },
|
|
222
224
|
"./public-surface-audit": { group: "kfd-trust", summary: "Reverse audit APIs for CLI, workflow, action, site page, and documentation command surfaces." },
|
|
223
225
|
"./kfd-gate": { group: "kfd-trust", summary: "KFD-1/KFD-2/KFD-3 release gate evidence and validation APIs." },
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://buildchain.libkungfu.dev/schemas/kfd-support-projection-v1.schema.json",
|
|
4
|
-
"title": "Buildchain KFD support projection v1",
|
|
5
|
-
"description": "A release-bound, non-authoritative projection of one product-owned KFD support matrix.",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"additionalProperties": false,
|
|
8
|
-
"required": [
|
|
9
|
-
"schemaVersion",
|
|
10
|
-
"contract",
|
|
11
|
-
"matrix",
|
|
12
|
-
"standardPackage",
|
|
13
|
-
"gateResults",
|
|
14
|
-
"rows",
|
|
15
|
-
"status",
|
|
16
|
-
"projectionRoot",
|
|
17
|
-
"nonClaims"
|
|
18
|
-
],
|
|
19
|
-
"properties": {
|
|
20
|
-
"schemaVersion": {
|
|
21
|
-
"const": 1
|
|
22
|
-
},
|
|
23
|
-
"contract": {
|
|
24
|
-
"const": "kungfu-buildchain-kfd-support-projection"
|
|
25
|
-
},
|
|
26
|
-
"matrix": {
|
|
27
|
-
"type": "object",
|
|
28
|
-
"additionalProperties": false,
|
|
29
|
-
"required": [
|
|
30
|
-
"contract",
|
|
31
|
-
"root",
|
|
32
|
-
"authorityPath"
|
|
33
|
-
],
|
|
34
|
-
"properties": {
|
|
35
|
-
"contract": {
|
|
36
|
-
"type": "string",
|
|
37
|
-
"minLength": 1
|
|
38
|
-
},
|
|
39
|
-
"root": {
|
|
40
|
-
"type": "string",
|
|
41
|
-
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
42
|
-
},
|
|
43
|
-
"authorityPath": {
|
|
44
|
-
"type": "string",
|
|
45
|
-
"minLength": 1
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
"standardPackage": {
|
|
50
|
-
"type": "object",
|
|
51
|
-
"additionalProperties": false,
|
|
52
|
-
"required": [
|
|
53
|
-
"name",
|
|
54
|
-
"version",
|
|
55
|
-
"standardsSha256"
|
|
56
|
-
],
|
|
57
|
-
"properties": {
|
|
58
|
-
"name": {
|
|
59
|
-
"type": "string",
|
|
60
|
-
"minLength": 1
|
|
61
|
-
},
|
|
62
|
-
"version": {
|
|
63
|
-
"type": "string",
|
|
64
|
-
"minLength": 1
|
|
65
|
-
},
|
|
66
|
-
"standardsSha256": {
|
|
67
|
-
"type": "string",
|
|
68
|
-
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
"gateResults": {
|
|
73
|
-
"type": "array",
|
|
74
|
-
"minItems": 3,
|
|
75
|
-
"items": {
|
|
76
|
-
"type": "object"
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"rows": {
|
|
80
|
-
"type": "array",
|
|
81
|
-
"minItems": 13,
|
|
82
|
-
"maxItems": 13,
|
|
83
|
-
"items": {
|
|
84
|
-
"type": "object"
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
"status": {
|
|
88
|
-
"enum": [
|
|
89
|
-
"passed",
|
|
90
|
-
"failed"
|
|
91
|
-
]
|
|
92
|
-
},
|
|
93
|
-
"projectionRoot": {
|
|
94
|
-
"type": "string",
|
|
95
|
-
"pattern": "^sha256:[0-9a-f]{64}$"
|
|
96
|
-
},
|
|
97
|
-
"nonClaims": {
|
|
98
|
-
"type": "array",
|
|
99
|
-
"minItems": 1,
|
|
100
|
-
"items": {
|
|
101
|
-
"type": "string",
|
|
102
|
-
"minLength": 1
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|