@kungfu-tech/buildchain 4.1.3-alpha.2 → 4.1.3-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/architecture/action-taxonomy.json +8 -0
- package/architecture/agent-change-map.md +23 -0
- package/architecture/ci-lane-change-budget.json +85 -0
- package/architecture/decisions/0009-pipeline-version-preparation.md +186 -0
- package/architecture/internal-capabilities.json +32 -2
- package/architecture/maintainability-debt.json +10 -4
- package/architecture/maintainability-policy.json +7 -7
- package/architecture/minimal-consumer-migration.json +9 -0
- package/architecture/product-upstreams.json +20 -0
- package/architecture/release-topology.json +63 -25
- package/architecture/universal-workflow-bootstrap.json +2 -0
- package/architecture/universal-workflow-capability-policy.json +1 -1
- package/architecture/workflow-taxonomy.json +11 -0
- package/dist/readers/business-attempt.cjs +6 -2
- package/dist/site/buildchain-contract.json +4 -4
- package/dist/site/buildchain-site.json +9 -9
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/kfd-claims.json +82 -8
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +18 -18
- package/dist/site/page-registry.json +4 -4
- package/dist/site/public-surface-audit.json +64 -7
- package/dist/site/publication-authority-registry.json +21 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +113 -7
- package/docs/node-api-reference.md +34 -34
- package/docs/workflow-catalog.md +1 -0
- package/package.json +1 -1
- package/packages/core/build/standalone/build.js +7 -1
- package/packages/core/consumer/buildchain-config.js +2 -21
- package/packages/core/consumer/contract/plan.js +53 -3
- package/packages/core/consumer/contract/products.js +20 -2
- package/packages/core/consumer/contract/reader.js +47 -0
- package/packages/core/dev-delivery/commands/project-cut-merge-queue-admission.mjs +47 -20
- package/packages/core/governance/buildchain-publication-authority.js +1 -0
- package/packages/core/providers/github/discussions/materials.js +12 -4
- package/packages/core/providers/github/pipeline-entry-runs.js +203 -0
- package/packages/core/providers/github/pipeline-policy.js +44 -17
- package/packages/core/providers/github/pipeline-release-evidence.js +111 -0
- package/packages/core/providers/github/pipeline-stable-source.js +354 -0
- package/packages/core/providers/github/pipeline-version-artifacts.js +91 -0
- package/packages/core/providers/github/pipeline-version-readback.js +117 -0
- package/packages/core/providers/github/pipeline-version.js +10 -12
- package/packages/core/publication/pipeline/actions.js +17 -1
- package/packages/core/publication/pipeline/apply.js +23 -17
- package/packages/core/publication/pipeline/development-pr.js +28 -1
- package/packages/core/publication/pipeline/documents.js +37 -1
- package/packages/core/publication/pipeline/next-development.js +1 -0
- package/packages/core/publication/pipeline/pack.js +7 -1
- package/packages/core/publication/pipeline/plan.js +22 -0
- package/packages/core/publication/pipeline/prepare.js +44 -56
- package/packages/core/publication/pipeline/qualification.js +5 -0
- package/packages/core/publication/pipeline/stable-entry.js +215 -0
- package/packages/core/publication/pipeline/stable-products.js +317 -0
- package/packages/core/publication/pipeline/stable-wait.js +170 -0
- package/packages/core/publication/pipeline/stable.js +82 -0
- package/packages/core/publication/pipeline/version-actions.js +93 -0
- package/packages/core/publication/pipeline/version-build.js +130 -0
- package/packages/core/publication/pipeline/version-context.js +143 -0
- package/packages/core/publication/pipeline/version-material.js +36 -0
- package/packages/core/publication/pipeline/version-preparation.js +69 -0
- package/packages/core/publication/pipeline/version-regeneration.js +162 -0
- package/packages/core/publication/pipeline/version-source-guard.js +69 -0
- package/packages/core/publication/pipeline/worker.js +72 -0
- package/packages/core/release/stable-release-gate.js +11 -0
- package/packages/core/workflow/attempt/identity.js +7 -2
- package/packages/core/workflow/pipeline/actions.js +14 -1
- package/packages/core/workflow/pipeline/build-result.js +8 -0
- package/packages/core/workflow/pipeline/build.js +49 -2
- package/packages/core/workflow/pipeline/channel-control.js +10 -1
- package/packages/core/workflow/pipeline/channel-source.js +53 -0
- package/packages/core/workflow/pipeline/group-control.js +2 -0
- package/packages/core/workflow/pipeline/host.js +24 -16
- package/packages/core/workflow/pipeline/selection.js +9 -4
- package/scripts/check-pipeline-publication-topology.mjs +12 -0
- package/scripts/check-release-topology.mjs +1 -0
- package/scripts/generate-site-bundle.mjs +1 -1
- package/scripts/verify-standalone-product.mjs +42 -0
|
@@ -184,8 +184,19 @@ export function evaluateStableReleaseGate({
|
|
|
184
184
|
"version-bound impact declares a summary and at least one surface",
|
|
185
185
|
{ summary: string(impact?.summary), surfaceIds: surfaceImpacts.map((entry) => string(entry?.id)).filter(Boolean) },
|
|
186
186
|
));
|
|
187
|
+
checks.push(check(
|
|
188
|
+
string(impact?.release?.version) === candidateTag.replace(/^v/, ""),
|
|
189
|
+
"stable.impact_version",
|
|
190
|
+
"product impact belongs to the exact candidate version",
|
|
191
|
+
{ expectedVersion: candidateTag.replace(/^v/, ""), observedVersion: string(impact?.release?.version) },
|
|
192
|
+
));
|
|
187
193
|
|
|
188
194
|
const canaryById = new Map(canaries.map((canary) => [string(canary?.id), canary]));
|
|
195
|
+
checks.push(check(
|
|
196
|
+
canaryById.size === canaries.length,
|
|
197
|
+
"canary.evidence_inventory",
|
|
198
|
+
"each canary has one unambiguous observation",
|
|
199
|
+
));
|
|
189
200
|
const canaryCompletionTimes = [];
|
|
190
201
|
for (const required of policy.requiredCanaries) {
|
|
191
202
|
const evidence = canaryById.get(required.id);
|
|
@@ -67,7 +67,7 @@ export function pipelineIntent({
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
export function
|
|
70
|
+
export function validateConsumerSource(source, repository) {
|
|
71
71
|
object(
|
|
72
72
|
source,
|
|
73
73
|
[
|
|
@@ -86,13 +86,18 @@ export function sourceGeneration(intent, source, baseCommit) {
|
|
|
86
86
|
if (
|
|
87
87
|
source.schema !== "buildchain.consumer-source/v1" ||
|
|
88
88
|
source.contract !== CONSUMER_CONTRACT ||
|
|
89
|
-
source.repository !==
|
|
89
|
+
source.repository !== repository
|
|
90
90
|
)
|
|
91
91
|
throw new Error("Generation requires the admitted consumer source");
|
|
92
92
|
for (const key of ["commit", "tree", "configBlob"])
|
|
93
93
|
text(source[key], `source.${key}`, /^[0-9a-f]{40}$/u);
|
|
94
94
|
relativePath(source.configPath, "source.configPath");
|
|
95
95
|
text(source.configDigest, "source.configDigest", ROOT);
|
|
96
|
+
return source;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function sourceGeneration(intent, source, baseCommit) {
|
|
100
|
+
validateConsumerSource(source, intent.repository);
|
|
96
101
|
text(baseCommit, "baseCommit", /^[0-9a-f]{40}$/u);
|
|
97
102
|
const value = {
|
|
98
103
|
schema: SOURCE_GENERATION,
|
|
@@ -6,6 +6,7 @@ import { pipelineHost } from "./host.js";
|
|
|
6
6
|
import { controlPipeline } from "./controller.js";
|
|
7
7
|
import { buildPipelineSource } from "./build.js";
|
|
8
8
|
import { recordPipelineBuild } from "./build-control.js";
|
|
9
|
+
import { wakePipelineStableWait } from "../../publication/pipeline/stable-wait.js";
|
|
9
10
|
import {
|
|
10
11
|
RECOVERY_BUILD_CONTEXT,
|
|
11
12
|
recordRecoveryBuild,
|
|
@@ -57,6 +58,18 @@ export async function recordPipelineBuildAction(core, env) {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
export async function wakePipelineAction(core, env) {
|
|
60
|
-
const
|
|
61
|
+
const waiting = core.getInput("stable-wait");
|
|
62
|
+
const host = await pipelineHost(
|
|
63
|
+
core,
|
|
64
|
+
env,
|
|
65
|
+
waiting ? "Recheck stable publication" : "Record delivery attempt",
|
|
66
|
+
);
|
|
67
|
+
if (waiting) {
|
|
68
|
+
const wait = JSON.parse(waiting);
|
|
69
|
+
if (wait.attempt !== core.getInput("attempt", { required: true }))
|
|
70
|
+
throw new Error("Stable wake changed its exact business attempt");
|
|
71
|
+
core.info(JSON.stringify(await wakePipelineStableWait(wait, host)));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
61
74
|
await host.wake(core.getInput("attempt", { required: true }));
|
|
62
75
|
}
|
|
@@ -37,6 +37,14 @@ export async function retainPipelineBuildResult(
|
|
|
37
37
|
session,
|
|
38
38
|
host,
|
|
39
39
|
) {
|
|
40
|
+
if (
|
|
41
|
+
readback.outcome === "success" &&
|
|
42
|
+
session.intent.expectedNodes.includes("publish")
|
|
43
|
+
)
|
|
44
|
+
await host.qualifyChannel(
|
|
45
|
+
context.source,
|
|
46
|
+
session.intent.source.targetBranch,
|
|
47
|
+
);
|
|
40
48
|
const reference = await resultReference(context, readback, session, host);
|
|
41
49
|
await session.progress.progress({
|
|
42
50
|
attempt: context.attempt,
|
|
@@ -1,12 +1,57 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
4
5
|
import { bindConsumerSource } from "../../consumer/contract/identity.js";
|
|
5
6
|
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
6
7
|
import { consumerCommandSession } from "../../runtime/consumer-shell.js";
|
|
7
8
|
import { createNativeChildEnvironment } from "../../dev-delivery/native/execution.js";
|
|
8
9
|
|
|
9
|
-
function
|
|
10
|
+
function inspectTrackedBytes(cwd, tree) {
|
|
11
|
+
const root = fs.realpathSync(cwd);
|
|
12
|
+
const entries = execFileSync(
|
|
13
|
+
"git",
|
|
14
|
+
["-C", root, "ls-tree", "-r", "-z", tree],
|
|
15
|
+
{
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
.split("\0")
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const match =
|
|
24
|
+
/^(100644|100755|120000) blob ([0-9a-f]{40})\t([\s\S]+)$/u.exec(entry);
|
|
25
|
+
if (!match) throw new Error("Build source requires resolved tracked files");
|
|
26
|
+
const [, mode, blob, file] = match;
|
|
27
|
+
const absolute = path.join(root, file);
|
|
28
|
+
if (fs.realpathSync(path.dirname(absolute)) !== path.dirname(absolute))
|
|
29
|
+
throw new Error(`Build source parent became a symbolic link: ${file}`);
|
|
30
|
+
const stat = fs.lstatSync(absolute);
|
|
31
|
+
const link = mode === "120000";
|
|
32
|
+
if (link ? !stat.isSymbolicLink() : !stat.isFile())
|
|
33
|
+
throw new Error(`Build tracked source kind changed: ${file}`);
|
|
34
|
+
if (
|
|
35
|
+
!link &&
|
|
36
|
+
process.platform !== "win32" &&
|
|
37
|
+
Boolean(stat.mode & 0o111) !== (mode === "100755")
|
|
38
|
+
)
|
|
39
|
+
throw new Error(`Build tracked source executable mode changed: ${file}`);
|
|
40
|
+
const bytes = link
|
|
41
|
+
? fs.readlinkSync(absolute, { encoding: "buffer" })
|
|
42
|
+
: fs.readFileSync(absolute);
|
|
43
|
+
const actual = createHash("sha1")
|
|
44
|
+
.update(`blob ${bytes.length}\0`)
|
|
45
|
+
.update(bytes)
|
|
46
|
+
.digest("hex");
|
|
47
|
+
if (actual !== blob)
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Build tracked source bytes differ from the admitted tree: ${file}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function inspectPipelineSource(cwd, expected) {
|
|
10
55
|
const git = (...args) =>
|
|
11
56
|
execFileSync("git", ["-C", cwd, ...args], {
|
|
12
57
|
encoding: "utf8",
|
|
@@ -36,6 +81,8 @@ function sourceCheckout(cwd, expected) {
|
|
|
36
81
|
throw new Error(
|
|
37
82
|
"Build source has tracked modifications at its verification boundary",
|
|
38
83
|
);
|
|
84
|
+
// Index flags and local Git filters cannot qualify different product bytes.
|
|
85
|
+
inspectTrackedBytes(cwd, expected.tree);
|
|
39
86
|
return plan;
|
|
40
87
|
}
|
|
41
88
|
|
|
@@ -54,7 +101,7 @@ function productDirectory(cwd, product) {
|
|
|
54
101
|
// jobs must bind provider completion and exact artifacts before granting reuse.
|
|
55
102
|
export async function buildPipelineSource(
|
|
56
103
|
{ cwd, source, platform, environment = process.env },
|
|
57
|
-
{ inspect =
|
|
104
|
+
{ inspect = inspectPipelineSource, session = consumerCommandSession } = {},
|
|
58
105
|
) {
|
|
59
106
|
const plan = inspect(cwd, source);
|
|
60
107
|
const result = await buildPipelineProducts(
|
|
@@ -68,6 +68,10 @@ export async function controlPipelineChannel(session, admission, inputs, host) {
|
|
|
68
68
|
}
|
|
69
69
|
if (host.terminalOnly)
|
|
70
70
|
return { operation: "wait", reason: "terminal-event-cannot-execute" };
|
|
71
|
+
const qualification = await host.qualifyChannel(
|
|
72
|
+
live.source,
|
|
73
|
+
live.targetBranch,
|
|
74
|
+
);
|
|
71
75
|
const build = await pipelineBuildEvidence(session, host);
|
|
72
76
|
if (!build) return { operation: "build", admission };
|
|
73
77
|
if (build.run.status !== "completed" || build.run.conclusion !== "success")
|
|
@@ -97,7 +101,12 @@ export async function controlPipelineChannel(session, admission, inputs, host) {
|
|
|
97
101
|
session,
|
|
98
102
|
"merge",
|
|
99
103
|
"waiting",
|
|
100
|
-
{
|
|
104
|
+
{
|
|
105
|
+
schema: "buildchain.pipeline-channel-enqueue/v1",
|
|
106
|
+
qualification,
|
|
107
|
+
policy,
|
|
108
|
+
queue,
|
|
109
|
+
},
|
|
101
110
|
host,
|
|
102
111
|
);
|
|
103
112
|
const again = await host.source.observeIntent(
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { selectProductPublicationIntent } from "../../release/product-publication.js";
|
|
3
|
+
import { pipelineExpectedProducts } from "../../publication/pipeline/plan.js";
|
|
4
|
+
import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
|
|
5
|
+
|
|
6
|
+
// This read-only qualification must precede both required checks and queue
|
|
7
|
+
// admission. Publication repeats the Rust-owned decision after protected merge.
|
|
8
|
+
export async function qualifyPipelineChannelSource(source, branch, host) {
|
|
9
|
+
if (branch.startsWith("dev/")) return null;
|
|
10
|
+
const admitted = await host.source.source(source.commit, source.configPath);
|
|
11
|
+
if (recordDigest(admitted.identity) !== recordDigest(source))
|
|
12
|
+
throw new Error("Channel qualification source identity drift");
|
|
13
|
+
const { plan } = admitted;
|
|
14
|
+
const routes = plan.channels.filter(
|
|
15
|
+
(route) => route.to === branch && route.operation !== "develop",
|
|
16
|
+
);
|
|
17
|
+
if (routes.length !== 1)
|
|
18
|
+
throw new Error("Channel qualification requires one publication route");
|
|
19
|
+
const route = routes[0];
|
|
20
|
+
const from = `${route.operation === "alpha" ? "dev" : "alpha"}/${branch.split("/").slice(1).join("/")}`;
|
|
21
|
+
if (["alpha", "stable"].includes(route.operation) && route.from !== from)
|
|
22
|
+
throw new Error("Channel source and target version lines must match");
|
|
23
|
+
const version = await githubPipelineVersion(
|
|
24
|
+
host.request,
|
|
25
|
+
host.repository,
|
|
26
|
+
).inspect(source, plan.version);
|
|
27
|
+
const selection = selectProductPublicationIntent({
|
|
28
|
+
channel: route.operation === "alpha" ? "alpha" : "stable",
|
|
29
|
+
targetRef: branch,
|
|
30
|
+
sourceSha: source.commit,
|
|
31
|
+
sourceTimestamp: version.sourceTimestamp,
|
|
32
|
+
repository: source.repository,
|
|
33
|
+
artifactKind: "custom",
|
|
34
|
+
requiredArtifactsRoot: recordDigest(pipelineExpectedProducts(plan)),
|
|
35
|
+
candidateVersion: version.version,
|
|
36
|
+
observedVersions: [],
|
|
37
|
+
});
|
|
38
|
+
if (
|
|
39
|
+
plan.version.strategy === "anchored" &&
|
|
40
|
+
selection.version !== version.version
|
|
41
|
+
)
|
|
42
|
+
throw new Error("Anchored channel source requires a materialized version");
|
|
43
|
+
const body = {
|
|
44
|
+
schema: "buildchain.pipeline-channel-source-qualification/v1",
|
|
45
|
+
source,
|
|
46
|
+
route,
|
|
47
|
+
contractRoot: recordDigest(plan),
|
|
48
|
+
version: version.version,
|
|
49
|
+
versionFilesRoot: recordDigest(version.files),
|
|
50
|
+
selection,
|
|
51
|
+
};
|
|
52
|
+
return { ...body, root: recordDigest(body) };
|
|
53
|
+
}
|
|
@@ -36,6 +36,8 @@ export async function recordPipelineGroup(context, host) {
|
|
|
36
36
|
context.source,
|
|
37
37
|
context.platforms.map((platform) => platform.platform),
|
|
38
38
|
);
|
|
39
|
+
if (readback.outcome === "success")
|
|
40
|
+
await host.qualifyChannel(context.source, context.branch);
|
|
39
41
|
await host.request(`/repos/${host.repository}/check-runs`, {
|
|
40
42
|
method: "POST",
|
|
41
43
|
body: {
|
|
@@ -17,6 +17,7 @@ import { GitHubClient } from "../../dev-delivery/admission/github-client.js";
|
|
|
17
17
|
import { githubPipelineIntegration } from "../../providers/github/pipeline-integration.js";
|
|
18
18
|
import { recordPipelineGroup } from "./group-control.js";
|
|
19
19
|
import { controlPipelineChannel } from "./channel-control.js";
|
|
20
|
+
import { qualifyPipelineChannelSource } from "./channel-source.js";
|
|
20
21
|
import { settlePipeline } from "./settlement.js";
|
|
21
22
|
import { pipelineProjection } from "./projection.js";
|
|
22
23
|
import { resumePipelineNotifications } from "./notifications.js";
|
|
@@ -48,6 +49,24 @@ export async function pipelineHost(core, env, jobName) {
|
|
|
48
49
|
const writer = await runs.writer(runId, runAttempt, jobName);
|
|
49
50
|
const source = githubPipelineSource(request, repository);
|
|
50
51
|
const [owner, repo] = repository.split("/");
|
|
52
|
+
const archives = new Map();
|
|
53
|
+
function archive(session) {
|
|
54
|
+
if (!archives.has(session.intent.id))
|
|
55
|
+
archives.set(
|
|
56
|
+
session.intent.id,
|
|
57
|
+
discussionMaterials({
|
|
58
|
+
octokit: github,
|
|
59
|
+
repository,
|
|
60
|
+
intentId: session.intent.id,
|
|
61
|
+
// Storage is partitioned by verified provider job, not attempt authority.
|
|
62
|
+
// Journal references still select exact immutable asset IDs and digests.
|
|
63
|
+
partition: `${writer.runId}-${writer.runAttempt}-${writer.jobId}`,
|
|
64
|
+
authorityDescription:
|
|
65
|
+
"The canonical Buildchain intent Git journal owns transaction state.",
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
return archives.get(session.intent.id);
|
|
69
|
+
}
|
|
51
70
|
const host = {
|
|
52
71
|
repository,
|
|
53
72
|
token,
|
|
@@ -70,6 +89,8 @@ export async function pipelineHost(core, env, jobName) {
|
|
|
70
89
|
groupRecord: (context) => recordPipelineGroup(context, host),
|
|
71
90
|
channel: (session, admission, inputs) =>
|
|
72
91
|
controlPipelineChannel(session, admission, inputs, host),
|
|
92
|
+
qualifyChannel: (source, branch) =>
|
|
93
|
+
qualifyPipelineChannelSource(source, branch, host),
|
|
73
94
|
settle: (session, fresh, delivery) =>
|
|
74
95
|
settlePipeline(session, fresh, delivery, host),
|
|
75
96
|
project: (session) =>
|
|
@@ -81,24 +102,11 @@ export async function pipelineHost(core, env, jobName) {
|
|
|
81
102
|
recoverPublication: (session, admitted) =>
|
|
82
103
|
inspectRecoveryPublication(session, admitted, host),
|
|
83
104
|
materialStore: (session) =>
|
|
84
|
-
pipelineMaterials(
|
|
85
|
-
discussionMaterials({
|
|
86
|
-
octokit: github,
|
|
87
|
-
repository,
|
|
88
|
-
intentId: session.intent.id,
|
|
89
|
-
authorityDescription:
|
|
90
|
-
"The canonical Buildchain intent Git journal owns transaction state.",
|
|
91
|
-
}),
|
|
92
|
-
{ repository, attempt: session.observed.history.at(-1).identity },
|
|
93
|
-
),
|
|
94
|
-
productArchive: (session) =>
|
|
95
|
-
discussionMaterials({
|
|
96
|
-
octokit: github,
|
|
105
|
+
pipelineMaterials(archive(session), {
|
|
97
106
|
repository,
|
|
98
|
-
|
|
99
|
-
authorityDescription:
|
|
100
|
-
"The canonical Buildchain intent Git journal owns transaction state.",
|
|
107
|
+
attempt: session.observed.history.at(-1).identity,
|
|
101
108
|
}),
|
|
109
|
+
productArchive: archive,
|
|
102
110
|
wake: async (attempt) => {
|
|
103
111
|
await index.resolve(attempt);
|
|
104
112
|
await request(`/repos/${repository}/dispatches`, {
|
|
@@ -10,8 +10,12 @@ async function successorSelection(
|
|
|
10
10
|
inputs,
|
|
11
11
|
host,
|
|
12
12
|
) {
|
|
13
|
+
const failedBuild =
|
|
14
|
+
session.observed.status === "failure" &&
|
|
15
|
+
session.observed.phases.build?.payload.state === "failure";
|
|
13
16
|
if (
|
|
14
|
-
!
|
|
17
|
+
(!failedBuild &&
|
|
18
|
+
!["superseded", "cancelled"].includes(session.observed.status)) ||
|
|
15
19
|
event.terminalOnly ||
|
|
16
20
|
admission.live.state !== "open" ||
|
|
17
21
|
admission.live.targetBranch !== session.intent.source.targetBranch
|
|
@@ -27,9 +31,10 @@ async function successorSelection(
|
|
|
27
31
|
.id !== session.observed.generation;
|
|
28
32
|
if (
|
|
29
33
|
!changed &&
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
(failedBuild ||
|
|
35
|
+
!["reopened", "enqueued", "ready_for_review", "labeled"].includes(
|
|
36
|
+
payload.action,
|
|
37
|
+
))
|
|
33
38
|
)
|
|
34
39
|
return { session, admission };
|
|
35
40
|
await host.notifyTerminal?.(session);
|
|
@@ -12,6 +12,7 @@ export function assertPipelinePublicationTopology(
|
|
|
12
12
|
".github/workflows/public-ops-pipeline.yml",
|
|
13
13
|
".github/workflows/public-ops-recover.yml",
|
|
14
14
|
".github/workflows/.ops-pipeline-execute.yml",
|
|
15
|
+
".github/workflows/.release-pipeline-version.yml",
|
|
15
16
|
]);
|
|
16
17
|
const pipelineTopology = discoverReleaseTopology(
|
|
17
18
|
pipeline.workflowPaths,
|
|
@@ -33,4 +34,15 @@ export function assertPipelinePublicationTopology(
|
|
|
33
34
|
module,
|
|
34
35
|
),
|
|
35
36
|
);
|
|
37
|
+
const version = ".github/workflows/.release-pipeline-version.yml";
|
|
38
|
+
assert.ok(
|
|
39
|
+
inspectWorkflowJob(version, "build", root).modules.has(
|
|
40
|
+
"packages/core/publication/pipeline/version-build.js",
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
assert.ok(
|
|
44
|
+
inspectWorkflowJob(version, "materialize", root).modules.has(
|
|
45
|
+
"packages/core/publication/pipeline/version-context.js",
|
|
46
|
+
),
|
|
47
|
+
);
|
|
36
48
|
}
|
|
@@ -30,6 +30,7 @@ const PRIVILEGED_ENTRYPOINTS = [
|
|
|
30
30
|
"packages/core/publication/oci/preview-actions.js",
|
|
31
31
|
"packages/core/publication/settlement/actions.js",
|
|
32
32
|
"packages/core/publication/pipeline/actions.js",
|
|
33
|
+
"packages/core/publication/pipeline/version-actions.js",
|
|
33
34
|
];
|
|
34
35
|
|
|
35
36
|
export function localModuleSpecifiers(source) {
|
|
@@ -1343,7 +1343,7 @@ function buildSiteBundle() {
|
|
|
1343
1343
|
"badge-endpoint-registry.json": badgeEndpointRegistry,
|
|
1344
1344
|
"publication-registry.json": publicationRegistry,
|
|
1345
1345
|
"buildchain-contract.json": createBuildchainContractWorld({ root, controllerRegistry }),
|
|
1346
|
-
"kfd-upstream-aggregate.json": collectKfdUpstreamFacts({ cwd: root, includeOwn: false }),
|
|
1346
|
+
"kfd-upstream-aggregate.json": collectKfdUpstreamFacts({ cwd: root, components: readJson("architecture/product-upstreams.json"), includeOwn: false }),
|
|
1347
1347
|
"kfd-claims.json": createBuildchainKfdClaimRegistry({ root }),
|
|
1348
1348
|
"product-mechanism.json": productMechanism,
|
|
1349
1349
|
"release-provenance.json": releaseProvenance,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import {
|
|
7
|
+
platformTriple,
|
|
8
|
+
packageVersion,
|
|
9
|
+
sourceSha,
|
|
10
|
+
} from "../packages/core/build/standalone/identity.js";
|
|
11
|
+
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const manifest = JSON.parse(
|
|
14
|
+
fs.readFileSync(
|
|
15
|
+
path.join(cwd, "dist/binary", `buildchain-${platformTriple()}.json`),
|
|
16
|
+
"utf8",
|
|
17
|
+
),
|
|
18
|
+
);
|
|
19
|
+
const version = packageVersion(cwd);
|
|
20
|
+
assert.equal(manifest.version, version);
|
|
21
|
+
assert.equal(manifest.sourceSha, sourceSha(cwd));
|
|
22
|
+
assert.equal(manifest.platform, platformTriple());
|
|
23
|
+
const binary = path.join(
|
|
24
|
+
cwd,
|
|
25
|
+
"dist/binary",
|
|
26
|
+
process.platform === "win32" ? "buildchain.exe" : "buildchain",
|
|
27
|
+
);
|
|
28
|
+
assert.equal(
|
|
29
|
+
createHash("sha256").update(fs.readFileSync(binary)).digest("hex"),
|
|
30
|
+
manifest.sha256,
|
|
31
|
+
);
|
|
32
|
+
assert.equal(
|
|
33
|
+
execFileSync(binary, ["--version"], { encoding: "utf8" }).trim(),
|
|
34
|
+
version,
|
|
35
|
+
);
|
|
36
|
+
assert.match(
|
|
37
|
+
execFileSync(binary, ["help"], { encoding: "utf8" }),
|
|
38
|
+
/^Usage:\s+buildchain --help/mu,
|
|
39
|
+
);
|
|
40
|
+
process.stdout.write(
|
|
41
|
+
`Verified standalone product ${platformTriple()} at ${version}\n`,
|
|
42
|
+
);
|