@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.
Files changed (57) hide show
  1. package/actions/promote-buildchain-ref/README.md +8 -7
  2. package/bin/buildchain.mjs +34 -33
  3. package/bin/internal/trust-release-release-handlers.mjs +5 -0
  4. package/dist/site/agent-index.json +0 -1
  5. package/dist/site/artifact-schemas.json +0 -2
  6. package/dist/site/badge-endpoint-registry.json +9 -9
  7. package/dist/site/badges/v1/kfd-8/aligned.json +1 -1
  8. package/dist/site/badges/v1/kfd-8/declared.json +1 -1
  9. package/dist/site/badges/v1/kfd-8/downgraded.json +1 -1
  10. package/dist/site/badges/v1/kfd-8/draft.json +1 -1
  11. package/dist/site/badges/v1/kfd-8/failed.json +1 -1
  12. package/dist/site/badges/v1/kfd-8/missing.json +1 -1
  13. package/dist/site/badges/v1/kfd-8/passed.json +1 -1
  14. package/dist/site/badges/v1/kfd-8/planned.json +1 -1
  15. package/dist/site/buildchain-contract.json +27 -22
  16. package/dist/site/buildchain-site.json +38 -28
  17. package/dist/site/capability-registry.json +1 -1
  18. package/dist/site/cli-registry.json +8 -7
  19. package/dist/site/controller-registry.json +6 -2
  20. package/dist/site/kfd-claims.json +34 -7
  21. package/dist/site/kfd-upstream-aggregate.json +10 -10
  22. package/dist/site/manual-registry.json +6 -6
  23. package/dist/site/node-api-registry.json +754 -228
  24. package/dist/site/page-registry.json +28 -18
  25. package/dist/site/public-surface-audit.json +19 -9
  26. package/dist/site/publication-registry.json +4 -4
  27. package/dist/site/release-passport-check-manifest.json +33 -5
  28. package/dist/site/release-provenance.json +2 -1
  29. package/dist/site/schemas/release-passport-v1.schema.json +3 -0
  30. package/dist/site/site-manifest.json +10 -10
  31. package/dist/site/workflow-registry.json +19 -9
  32. package/docs/aws-us-elastic-runner-burst-plane.md +15 -3
  33. package/docs/cli-reference.md +6 -6
  34. package/docs/cli.md +2 -3
  35. package/docs/dev-alpha-candidate-patrol.md +45 -20
  36. package/docs/kfd-support.md +7 -7
  37. package/docs/node-api-reference.md +179 -140
  38. package/docs/release-passport.md +18 -12
  39. package/docs/release-train.md +8 -0
  40. package/package.json +4 -3
  41. package/packages/core/artifact-verification-envelope.js +114 -0
  42. package/packages/core/dev-alpha-active-release-train.js +460 -0
  43. package/packages/core/dev-alpha-candidate-selection.js +181 -0
  44. package/packages/core/index.js +4 -5
  45. package/packages/core/kfd-adopter-manifest.js +390 -0
  46. package/packages/core/kfd-product-gates.js +0 -300
  47. package/packages/core/release-passport-contract.js +219 -6
  48. package/packages/core/release-passport.js +139 -242
  49. package/scripts/aws-macos-jit-controller-core.mjs +76 -0
  50. package/scripts/aws-macos-jit-controller.mjs +29 -0
  51. package/scripts/aws-macos-jit-source-rebind.mjs +503 -0
  52. package/scripts/buildchain-cli-help.mjs +4 -2
  53. package/scripts/check-inventory.mjs +2 -2
  54. package/scripts/dev-alpha-candidate-patrol.mjs +196 -201
  55. package/scripts/generate-site-bundle.mjs +1 -8
  56. package/scripts/site-capability-metadata.mjs +3 -1
  57. 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
- if (
680
- options.expectedSelectedSha &&
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
- if (
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.eligible
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 product-owned KFD-1 through KFD-13 support matrix against KFD-4/5/7 gates." },
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
- }