@kungfu-tech/buildchain 2.5.5-alpha.2 → 2.5.5-alpha.4
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/bin/buildchain.mjs +2 -0
- package/docs/cli.md +8 -0
- package/docs/release-passport.md +9 -0
- package/docs/versioning.md +28 -12
- package/package.json +1 -1
- package/packages/core/release-passport.js +129 -6
- package/scripts/check-inventory.mjs +3 -1
- package/scripts/release-candidate-resolver.mjs +34 -14
package/bin/buildchain.mjs
CHANGED
|
@@ -67,6 +67,7 @@ function usage() {
|
|
|
67
67
|
[--trusted-publishing-json <json-or-path>]
|
|
68
68
|
[--transaction-json <json-or-path>]
|
|
69
69
|
[--anchor-manifest-json <json-or-path>]
|
|
70
|
+
[--impact-json <json-or-path>]
|
|
70
71
|
[--build-summary-json <json-or-path>]
|
|
71
72
|
[--platform-manifest-json <json-or-path>]...
|
|
72
73
|
[--dist-tag-evidence-json <json-or-path>]
|
|
@@ -716,6 +717,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
716
717
|
trustedPublishingJson: readFlag(collectArgs, "trusted-publishing-json", ""),
|
|
717
718
|
transactionJson: readFlag(collectArgs, "transaction-json", ""),
|
|
718
719
|
anchorManifestJson: readFlag(collectArgs, "anchor-manifest-json", ""),
|
|
720
|
+
impactJson: readFlag(collectArgs, "impact-json", ""),
|
|
719
721
|
buildSummaryJson: readFlag(collectArgs, "build-summary-json", ""),
|
|
720
722
|
platformManifestJsons: readRepeatedFlag(collectArgs, "platform-manifest-json"),
|
|
721
723
|
distTagEvidenceJson: readFlag(collectArgs, "dist-tag-evidence-json", ""),
|
package/docs/cli.md
CHANGED
|
@@ -252,6 +252,7 @@ buildchain collect github-release \
|
|
|
252
252
|
--publish-evidence-json .buildchain/release-evidence/v2.3.2/evidence.json \
|
|
253
253
|
--transaction-json .buildchain/release-state/v2.3.2/state.json \
|
|
254
254
|
--package-set-json package-set.json \
|
|
255
|
+
--impact-json impact.json \
|
|
255
256
|
--trusted-publishing-json trusted-publishing.json \
|
|
256
257
|
--anchor-manifest-json libnode.release.json \
|
|
257
258
|
--build-summary-json .buildchain/artifacts/build-summary.json \
|
|
@@ -275,6 +276,13 @@ platform packages with version, dist-tag, and digest evidence. Verification
|
|
|
275
276
|
fails closed if supplied sections are internally incomplete or point to
|
|
276
277
|
artifacts without matching evidence.
|
|
277
278
|
|
|
279
|
+
`--impact-json` supplies the surface-aware impact ledger. When it includes
|
|
280
|
+
`surfaceImpacts[]`, the verifier requires `versionImpact.final` to match the
|
|
281
|
+
highest declared surface impact and copies `versionImpact` plus
|
|
282
|
+
`surfaceImpacts` into `buildchain.release.json`. This lets
|
|
283
|
+
`buildchain explain release --for agent --json` state why a release is patch,
|
|
284
|
+
minor, or major instead of relying on file-path memory.
|
|
285
|
+
|
|
278
286
|
Buildchain dogfoods its observability toolkit in this lane. The standalone
|
|
279
287
|
builder writes API-generated events, while the workflow uses `buildchain mark`,
|
|
280
288
|
`buildchain span`, `buildchain verify observability-log`, and `buildchain log
|
package/docs/release-passport.md
CHANGED
|
@@ -36,6 +36,10 @@ Additive passport sections:
|
|
|
36
36
|
|
|
37
37
|
- `release`: exact tag, line, channel, source SHA, target ref, release SHA,
|
|
38
38
|
release material SHA, publish tooling SHA, and durable release-state ref.
|
|
39
|
+
- `versionImpact`: final patch/minor/major classification, source, and
|
|
40
|
+
rationale.
|
|
41
|
+
- `surfaceImpacts`: per registered surface classification. The final impact is
|
|
42
|
+
the highest entry in this list.
|
|
39
43
|
- `packageSet`: main package, platform packages, package-set order, registry,
|
|
40
44
|
versions, dist-tags, and package digests.
|
|
41
45
|
- `anchorManifest`: anchored/manual version manifest path, digest, and fields.
|
|
@@ -102,6 +106,11 @@ dist-tag, registry, role, platform, and digest, so agents do not need to stitch
|
|
|
102
106
|
npm facts back together from the lower-level evidence files.
|
|
103
107
|
`buildSummary`, `platformArtifactManifests`, and `distTagPromotion` preserve the
|
|
104
108
|
build and npm dist-tag evidence chain in the same passport.
|
|
109
|
+
`impact.json` can be supplied with `--impact-json`; when it contains
|
|
110
|
+
`surfaceImpacts[]`, verification fails closed unless `versionImpact.final`
|
|
111
|
+
matches the highest surface impact. For example, KFD-2 content can remain patch
|
|
112
|
+
while an additive `registry.kind` field on the machine-consumed KFD registry
|
|
113
|
+
schema records a minor `kfd-registry-schema` surface impact.
|
|
105
114
|
|
|
106
115
|
Verify a release passport:
|
|
107
116
|
|
package/docs/versioning.md
CHANGED
|
@@ -20,7 +20,8 @@ latest stable production patch for that minor line.
|
|
|
20
20
|
|
|
21
21
|
## Welded Surfaces
|
|
22
22
|
|
|
23
|
-
These surfaces
|
|
23
|
+
These surfaces are classified independently; the final release impact is the
|
|
24
|
+
highest impact across the affected registered surfaces:
|
|
24
25
|
|
|
25
26
|
- reusable workflow inputs, outputs, and artifact contracts;
|
|
26
27
|
- public CLI command families and their machine-readable JSON shapes;
|
|
@@ -31,20 +32,35 @@ These surfaces require at least a minor bump when first introduced:
|
|
|
31
32
|
ledger, and agent index files;
|
|
32
33
|
- binary distribution shapes that users can install or automate against.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
For each surface:
|
|
36
|
+
|
|
37
|
+
- content, documentation, or implementation-only work that does not touch a
|
|
38
|
+
registered surface is patch;
|
|
39
|
+
- additive fields, new commands, new exports, new evidence sections, or new
|
|
40
|
+
registered surfaces are minor;
|
|
41
|
+
- removals, incompatible renames, changed meanings, newly required fields,
|
|
42
|
+
weakened trust gates, or changed ref flow are major.
|
|
43
|
+
|
|
44
|
+
The release passport records this as `surfaceImpacts[]` plus
|
|
45
|
+
`versionImpact.final`. The final impact must equal the highest surface impact,
|
|
46
|
+
so an agent cannot silently label a release patch when one machine surface needs
|
|
47
|
+
minor review.
|
|
48
|
+
|
|
49
|
+
Example: a KFD document such as KFD-2 is content and remains patch, but adding a
|
|
50
|
+
`kind` field to the machine-consumed KFD `registry.json` is an additive change
|
|
51
|
+
to the `kfd-registry-schema` surface and therefore requires minor-impact
|
|
52
|
+
review. This avoids both false shortcuts: "new KFD means minor" and "all KFD
|
|
53
|
+
repository changes are patch".
|
|
38
54
|
|
|
39
55
|
## Decision Log
|
|
40
56
|
|
|
41
|
-
| Date |
|
|
42
|
-
| --- | --- | --- | --- |
|
|
43
|
-
| 2026-07-
|
|
44
|
-
| 2026-07-
|
|
45
|
-
| 2026-07-02 |
|
|
46
|
-
| 2026-07-
|
|
47
|
-
| 2026-07-
|
|
57
|
+
| Date | Action | Line | Faces | Class | Rationale | PR |
|
|
58
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
59
|
+
| 2026-07-04 | open-minor | `v2.5` | scheduled-integration-governance | additive | Scheduled integration governance adds scheduled feature-branch discovery, conflict-free integration, reporting, and agent-visible governance automation for dev-line maintenance. | |
|
|
60
|
+
| 2026-07-03 | open-minor | `v2.4` | infra-contract-lifecycle | additive | Infra contract lifecycle adds the provider-neutral `infra-contract` CLI command family, project type, adapter capability contract, lifecycle evidence bundle, propagation evidence, CI evidence mode, and consumer-facing contract artifacts. | |
|
|
61
|
+
| 2026-07-02 | open-minor | `v2.3` | web-surface-host-mapping | additive | Web surface host mapping adds first-class multi-host surface bindings, reusable workflow URL outputs, per-surface deployment overrides, and an agent-readable fixture contract. | |
|
|
62
|
+
| 2026-07-02 | open-minor | `v2.2` | release-passport, binary-distribution | additive | Release passport and binary distribution add agent-readable release passport files, artifact evidence, impact ledger, agent index, GitHub Release collection and verification commands, and standalone binary assets. | |
|
|
63
|
+
| 2026-07-02 | open-minor | `v2.1` | logging-sdk, cli-observability, package-subpaths | additive | Buildchain toolkit observability adds the public logging SDK, CLI observability commands, and package subpaths that consumers can import. | |
|
|
48
64
|
|
|
49
65
|
## Runner Policy
|
|
50
66
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungfu-tech/buildchain",
|
|
3
|
-
"version": "2.5.5-alpha.
|
|
3
|
+
"version": "2.5.5-alpha.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Buildchain Release Passport, release governance, CLI toolkit, and site facts.",
|
|
6
6
|
"repository": "https://github.com/kungfu-systems/buildchain",
|
|
@@ -36,6 +36,29 @@ function optionalString(value) {
|
|
|
36
36
|
return value === undefined || value === null ? "" : String(value);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const VERSION_IMPACT_ORDER = new Map([
|
|
40
|
+
["unknown", 0],
|
|
41
|
+
["patch", 1],
|
|
42
|
+
["minor", 2],
|
|
43
|
+
["major", 3],
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
function normalizeImpactLevel(value, fallback = "unknown") {
|
|
47
|
+
const normalized = optionalString(value || fallback).toLowerCase();
|
|
48
|
+
return VERSION_IMPACT_ORDER.has(normalized) ? normalized : optionalString(value || fallback);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function highestImpactLevel(levels = []) {
|
|
52
|
+
let highest = "unknown";
|
|
53
|
+
for (const level of levels) {
|
|
54
|
+
const normalized = normalizeImpactLevel(level);
|
|
55
|
+
if ((VERSION_IMPACT_ORDER.get(normalized) ?? -1) > (VERSION_IMPACT_ORDER.get(highest) ?? -1)) {
|
|
56
|
+
highest = normalized;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return highest;
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
function stableJson(value) {
|
|
40
63
|
if (Array.isArray(value)) {
|
|
41
64
|
return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -123,14 +146,21 @@ function defaultProductMechanism({ repository = "", productName = "Buildchain" }
|
|
|
123
146
|
}
|
|
124
147
|
|
|
125
148
|
function defaultImpact({ tag = "", line = "", decision = "unknown" } = {}) {
|
|
149
|
+
const finalImpact = normalizeImpactLevel(decision);
|
|
126
150
|
return {
|
|
127
151
|
schemaVersion: 1,
|
|
128
152
|
contract: IMPACT_LEDGER_CONTRACT,
|
|
129
153
|
release: { tag, line },
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
versionImpact: {
|
|
155
|
+
final: finalImpact,
|
|
156
|
+
source: "default",
|
|
157
|
+
rationale: "No surface impact classification was supplied.",
|
|
158
|
+
},
|
|
159
|
+
surfaceImpacts: [],
|
|
160
|
+
classification: finalImpact,
|
|
161
|
+
breaking: finalImpact === "major",
|
|
132
162
|
security: false,
|
|
133
|
-
migrationRequired:
|
|
163
|
+
migrationRequired: finalImpact === "major",
|
|
134
164
|
summary: "No release impact summary was supplied.",
|
|
135
165
|
recovery: {
|
|
136
166
|
rollback: "Use the previous exact release tag or previous floating channel ref.",
|
|
@@ -339,6 +369,55 @@ function normalizePublishEvidence(value = undefined) {
|
|
|
339
369
|
};
|
|
340
370
|
}
|
|
341
371
|
|
|
372
|
+
function normalizeSurfaceImpact(entry = {}, index = 0) {
|
|
373
|
+
const id = nonEmptyString(entry.id || entry.surface || entry.surfaceId, `surfaceImpacts[${index}].id`);
|
|
374
|
+
const impact = normalizeImpactLevel(entry.impact || entry.classification || entry.versionImpact);
|
|
375
|
+
return {
|
|
376
|
+
id,
|
|
377
|
+
impact,
|
|
378
|
+
class: optionalString(entry.class || entry.changeClass || entry.change_class),
|
|
379
|
+
rationale: optionalString(entry.rationale || entry.reason),
|
|
380
|
+
source: optionalString(entry.source),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function normalizeImpactLedger(value = undefined, { tag = "", line = "", decision = "unknown" } = {}) {
|
|
385
|
+
if (!value) {
|
|
386
|
+
return defaultImpact({ tag, line, decision });
|
|
387
|
+
}
|
|
388
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
389
|
+
throw new Error("impact must be a JSON object");
|
|
390
|
+
}
|
|
391
|
+
const surfaceImpacts = Array.isArray(value.surfaceImpacts)
|
|
392
|
+
? value.surfaceImpacts.map((entry, index) => normalizeSurfaceImpact(entry, index))
|
|
393
|
+
: [];
|
|
394
|
+
const finalImpact = normalizeImpactLevel(
|
|
395
|
+
value.versionImpact?.final ||
|
|
396
|
+
value.versionImpact ||
|
|
397
|
+
value.classification ||
|
|
398
|
+
decision,
|
|
399
|
+
);
|
|
400
|
+
return {
|
|
401
|
+
...value,
|
|
402
|
+
schemaVersion: Number(value.schemaVersion || 1),
|
|
403
|
+
contract: value.contract || IMPACT_LEDGER_CONTRACT,
|
|
404
|
+
release: {
|
|
405
|
+
tag: optionalString(value.release?.tag || tag),
|
|
406
|
+
line: optionalString(value.release?.line || line),
|
|
407
|
+
...(value.release && typeof value.release === "object" && !Array.isArray(value.release) ? value.release : {}),
|
|
408
|
+
},
|
|
409
|
+
versionImpact: {
|
|
410
|
+
final: finalImpact,
|
|
411
|
+
source: optionalString(value.versionImpact?.source || value.impactSource || "declared"),
|
|
412
|
+
rationale: optionalString(value.versionImpact?.rationale || value.rationale || value.summary),
|
|
413
|
+
},
|
|
414
|
+
surfaceImpacts,
|
|
415
|
+
classification: normalizeImpactLevel(value.classification || finalImpact),
|
|
416
|
+
breaking: value.breaking === undefined ? finalImpact === "major" : Boolean(value.breaking),
|
|
417
|
+
migrationRequired: value.migrationRequired === undefined ? finalImpact === "major" : Boolean(value.migrationRequired),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
342
421
|
function packageSetEntries(packageSet = undefined) {
|
|
343
422
|
if (!packageSet) {
|
|
344
423
|
return [];
|
|
@@ -503,6 +582,7 @@ export function createReleasePassport({
|
|
|
503
582
|
distTagPromotionEvidence = undefined,
|
|
504
583
|
release = {},
|
|
505
584
|
publish = {},
|
|
585
|
+
impact = undefined,
|
|
506
586
|
workflow = {},
|
|
507
587
|
} = {}) {
|
|
508
588
|
const normalizedTag = nonEmptyString(tag, "tag");
|
|
@@ -518,6 +598,7 @@ export function createReleasePassport({
|
|
|
518
598
|
const normalizedDistTagPromotionEvidence = distTagPromotionEvidence
|
|
519
599
|
? normalizeEvidenceDocument(distTagPromotionEvidence, "distTagPromotionEvidence")
|
|
520
600
|
: undefined;
|
|
601
|
+
const normalizedImpact = normalizeImpactLedger(impact, { tag: normalizedTag, line });
|
|
521
602
|
const publishArtifacts = normalizedPublishEvidence?.artifacts || [];
|
|
522
603
|
const normalizedPublishSummary = normalizePublishSummary({
|
|
523
604
|
packageSet: normalizedPackageSet,
|
|
@@ -618,6 +699,8 @@ export function createReleasePassport({
|
|
|
618
699
|
...(normalizedBuildSummary ? { buildSummary: normalizedBuildSummary } : {}),
|
|
619
700
|
...(normalizedPlatformArtifactManifests.length > 0 ? { platformArtifactManifests: normalizedPlatformArtifactManifests } : {}),
|
|
620
701
|
...(normalizedDistTagPromotionEvidence ? { distTagPromotion: normalizedDistTagPromotionEvidence } : {}),
|
|
702
|
+
versionImpact: normalizedImpact.versionImpact,
|
|
703
|
+
surfaceImpacts: normalizedImpact.surfaceImpacts,
|
|
621
704
|
artifacts: [
|
|
622
705
|
...artifactEvidence.artifacts.map((asset) => ({
|
|
623
706
|
group: "release",
|
|
@@ -680,6 +763,7 @@ export function collectGitHubReleasePassport({
|
|
|
680
763
|
trustedPublishingJson = "",
|
|
681
764
|
transactionJson = "",
|
|
682
765
|
anchorManifestJson = "",
|
|
766
|
+
impactJson = "",
|
|
683
767
|
buildSummaryJson = "",
|
|
684
768
|
platformManifestJsons = [],
|
|
685
769
|
distTagEvidenceJson = "",
|
|
@@ -695,6 +779,7 @@ export function collectGitHubReleasePassport({
|
|
|
695
779
|
const trustedPublishing = parseJsonInput(trustedPublishingJson, undefined);
|
|
696
780
|
const transactionMeta = parseJsonInputWithMeta(transactionJson, undefined);
|
|
697
781
|
const anchorManifest = normalizeAnchorManifest(parseJsonInputWithMeta(anchorManifestJson, undefined));
|
|
782
|
+
const impactMeta = parseJsonInputWithMeta(impactJson, undefined);
|
|
698
783
|
const buildSummaryMeta = parseJsonInputWithMeta(buildSummaryJson, undefined);
|
|
699
784
|
const platformManifestMetas = (platformManifestJsons || [])
|
|
700
785
|
.filter(Boolean)
|
|
@@ -710,7 +795,7 @@ export function collectGitHubReleasePassport({
|
|
|
710
795
|
const resolvedOutputDir = path.resolve(cwd, outputDir);
|
|
711
796
|
const productMechanism = defaultProductMechanism({ repository, productName });
|
|
712
797
|
const artifactEvidence = createArtifactEvidence({ assets, repository, tag: resolvedTag, sourceSha, workflow });
|
|
713
|
-
const impact =
|
|
798
|
+
const impact = normalizeImpactLedger(impactMeta.value, { tag: resolvedTag, line, decision: "unknown" });
|
|
714
799
|
const agentIndex = defaultAgentIndex({ tag: resolvedTag });
|
|
715
800
|
const passport = createReleasePassport({
|
|
716
801
|
cwd,
|
|
@@ -747,6 +832,7 @@ export function collectGitHubReleasePassport({
|
|
|
747
832
|
: undefined,
|
|
748
833
|
release: releaseExtra,
|
|
749
834
|
publish,
|
|
835
|
+
impact,
|
|
750
836
|
publishEvidencePath: publishEvidenceMeta.path ? path.relative(resolvedOutputDir, publishEvidenceMeta.path).split(path.sep).join("/") : "",
|
|
751
837
|
transactionStatePath: transactionMeta.path ? path.relative(resolvedOutputDir, transactionMeta.path).split(path.sep).join("/") : "",
|
|
752
838
|
workflow,
|
|
@@ -1042,6 +1128,38 @@ export function createReleaseCheckReport({
|
|
|
1042
1128
|
if (!passport?.runnerPolicy?.productionDefault) {
|
|
1043
1129
|
issues.push(issue("warning", "runnerPolicy.productionDefault", "runner policy should record the production default"));
|
|
1044
1130
|
}
|
|
1131
|
+
const surfaceImpacts = Array.isArray(impact?.surfaceImpacts) ? impact.surfaceImpacts : [];
|
|
1132
|
+
const passportSurfaceImpacts = Array.isArray(passport?.surfaceImpacts) ? passport.surfaceImpacts : [];
|
|
1133
|
+
const declaredFinalImpact = normalizeImpactLevel(impact?.versionImpact?.final || impact?.classification);
|
|
1134
|
+
const computedFinalImpact = highestImpactLevel(surfaceImpacts.map((entry) => entry.impact));
|
|
1135
|
+
if (surfaceImpacts.length > 0) {
|
|
1136
|
+
for (const [index, entry] of surfaceImpacts.entries()) {
|
|
1137
|
+
if (!entry.id) {
|
|
1138
|
+
issues.push(issue("error", `impact.surfaceImpacts[${index}].id`, "surface impact id is required"));
|
|
1139
|
+
}
|
|
1140
|
+
if (!VERSION_IMPACT_ORDER.has(entry.impact)) {
|
|
1141
|
+
issues.push(issue("error", `impact.surfaceImpacts[${index}].impact`, "surface impact must be patch, minor, or major"));
|
|
1142
|
+
}
|
|
1143
|
+
if (!entry.rationale) {
|
|
1144
|
+
issues.push(issue("error", `impact.surfaceImpacts[${index}].rationale`, "surface impact rationale is required"));
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
if (!impact?.versionImpact?.rationale) {
|
|
1148
|
+
issues.push(issue("error", "impact.versionImpact.rationale", "version impact rationale is required when surface impacts are supplied"));
|
|
1149
|
+
}
|
|
1150
|
+
if (declaredFinalImpact !== computedFinalImpact) {
|
|
1151
|
+
issues.push(issue("error", "impact.versionImpact.final", "versionImpact.final must equal the highest surface impact", {
|
|
1152
|
+
declared: declaredFinalImpact,
|
|
1153
|
+
computed: computedFinalImpact,
|
|
1154
|
+
}));
|
|
1155
|
+
}
|
|
1156
|
+
if (stableJson(passportSurfaceImpacts) !== stableJson(surfaceImpacts)) {
|
|
1157
|
+
issues.push(issue("error", "passport.surfaceImpacts", "passport.surfaceImpacts must mirror impact.surfaceImpacts"));
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (passport?.versionImpact?.final && impact?.versionImpact?.final && passport.versionImpact.final !== impact.versionImpact.final) {
|
|
1161
|
+
issues.push(issue("error", "passport.versionImpact.final", "passport.versionImpact.final must match impact.versionImpact.final"));
|
|
1162
|
+
}
|
|
1045
1163
|
const ok = issues.every((entry) => entry.level !== "error");
|
|
1046
1164
|
return {
|
|
1047
1165
|
schemaVersion: 1,
|
|
@@ -1064,6 +1182,8 @@ export function createReleaseCheckReport({
|
|
|
1064
1182
|
impactPresent: Boolean(impact),
|
|
1065
1183
|
agentIndexPresent: Boolean(agentIndex),
|
|
1066
1184
|
productMechanismPresent: Boolean(productMechanism),
|
|
1185
|
+
surfaceImpactCount: surfaceImpacts.length,
|
|
1186
|
+
versionImpact: impact?.versionImpact?.final || "",
|
|
1067
1187
|
},
|
|
1068
1188
|
issues,
|
|
1069
1189
|
};
|
|
@@ -1156,8 +1276,11 @@ export async function explainReleasePassport({ passportLocation, forAudience = "
|
|
|
1156
1276
|
artifactCount: report.completeness.artifactCount,
|
|
1157
1277
|
runnerPolicy: passport.runnerPolicy,
|
|
1158
1278
|
impact: {
|
|
1159
|
-
|
|
1160
|
-
|
|
1279
|
+
versionImpact: passport.versionImpact || {},
|
|
1280
|
+
surfaceImpacts: Array.isArray(passport.surfaceImpacts) ? passport.surfaceImpacts : [],
|
|
1281
|
+
breaking: Boolean(passport.versionImpact?.final === "major"),
|
|
1282
|
+
migrationRequired: Boolean(passport.versionImpact?.final === "major"),
|
|
1283
|
+
summary: passport.versionImpact?.rationale || "",
|
|
1161
1284
|
},
|
|
1162
1285
|
recovery: passport.recovery,
|
|
1163
1286
|
nextAction,
|
|
@@ -130,7 +130,9 @@ if (!coreIndexSource.includes("reportBuildchainIssue")) {
|
|
|
130
130
|
throw new Error("packages/core/index.js must export reportBuildchainIssue");
|
|
131
131
|
}
|
|
132
132
|
for (const requiredSnippet of [
|
|
133
|
-
"
|
|
133
|
+
"surfaceImpacts[]",
|
|
134
|
+
"versionImpact.final",
|
|
135
|
+
"kfd-registry-schema",
|
|
134
136
|
"`v2.2`",
|
|
135
137
|
"GitHub-hosted runners for production",
|
|
136
138
|
"Self-hosted runners remain compatibility fixtures",
|
|
@@ -91,7 +91,7 @@ export function selectMergedChannelPullRequest({ pullRequests = [], targetRef, r
|
|
|
91
91
|
return candidates[0];
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function
|
|
94
|
+
export function selectReleaseCandidateRuns({ runs = [], pullRequest, workflowName = "" }) {
|
|
95
95
|
const prNumber = Number(pullRequest?.number || 0);
|
|
96
96
|
const prHeadSha = String(pullRequest?.head?.sha || pullRequest?.headRefOid || "").trim();
|
|
97
97
|
const prHeadBranch = normalizeBranch(pullRequest?.head?.ref || pullRequest?.headRefName || "");
|
|
@@ -111,7 +111,11 @@ export function selectReleaseCandidateRun({ runs = [], pullRequest, workflowName
|
|
|
111
111
|
return matchesPr && matchesWorkflow && run.event === "pull_request" && run.status === "completed" && run.conclusion === "success";
|
|
112
112
|
});
|
|
113
113
|
candidates.sort((left, right) => Date.parse(right.updated_at || right.created_at || "") - Date.parse(left.updated_at || left.created_at || ""));
|
|
114
|
-
return candidates
|
|
114
|
+
return candidates;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function selectReleaseCandidateRun({ runs = [], pullRequest, workflowName = "" }) {
|
|
118
|
+
return selectReleaseCandidateRuns({ runs, pullRequest, workflowName })[0];
|
|
115
119
|
}
|
|
116
120
|
|
|
117
121
|
function outputPath(filePath) {
|
|
@@ -375,24 +379,40 @@ export async function resolveReleaseCandidateArtifacts({
|
|
|
375
379
|
fetchImpl,
|
|
376
380
|
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/workflows/${encodeURIComponent(workflowFile)}/runs?event=pull_request&status=success&per_page=100`,
|
|
377
381
|
});
|
|
378
|
-
const
|
|
382
|
+
const candidateRuns = selectReleaseCandidateRuns({
|
|
379
383
|
runs: Array.isArray(runs.workflow_runs) ? runs.workflow_runs : [],
|
|
380
384
|
pullRequest,
|
|
381
385
|
workflowName,
|
|
382
386
|
});
|
|
383
|
-
if (!
|
|
387
|
+
if (!candidateRuns.length) {
|
|
384
388
|
throw new Error(`no successful ${workflowName} pull_request run found for channel PR #${pullRequest.number}`);
|
|
385
389
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
390
|
+
let run;
|
|
391
|
+
let artifactResponse;
|
|
392
|
+
let selected;
|
|
393
|
+
const selectionErrors = [];
|
|
394
|
+
for (const candidateRun of candidateRuns) {
|
|
395
|
+
const candidateArtifactResponse = await githubJson({
|
|
396
|
+
apiUrl,
|
|
397
|
+
token,
|
|
398
|
+
fetchImpl,
|
|
399
|
+
path: `/repos/${repoInfo.owner}/${repoInfo.repo}/actions/runs/${candidateRun.id}/artifacts?per_page=100`,
|
|
400
|
+
});
|
|
401
|
+
try {
|
|
402
|
+
selected = selectReleaseCandidateArtifacts({
|
|
403
|
+
artifacts: Array.isArray(candidateArtifactResponse.artifacts) ? candidateArtifactResponse.artifacts : [],
|
|
404
|
+
artifactName,
|
|
405
|
+
});
|
|
406
|
+
run = candidateRun;
|
|
407
|
+
artifactResponse = candidateArtifactResponse;
|
|
408
|
+
break;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
selectionErrors.push(`run ${candidateRun.id}: ${error.message}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (!run || !artifactResponse || !selected) {
|
|
414
|
+
throw new Error(`no successful ${workflowName} pull_request run for channel PR #${pullRequest.number} contained release-candidate artifacts: ${selectionErrors.join("; ")}`);
|
|
415
|
+
}
|
|
396
416
|
const payloadArtifacts = selectPayloadArtifacts({
|
|
397
417
|
artifacts: Array.isArray(artifactResponse.artifacts) ? artifactResponse.artifacts : [],
|
|
398
418
|
artifactName: selected.prefix,
|