@kungfu-tech/buildchain 4.1.3-alpha.2 → 4.1.3-alpha.3
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 +17 -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 +3 -3
- 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 +62 -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 +48 -1
- 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/pipeline-entry-runs.js +203 -0
- 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.js +49 -2
- 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
|
}
|
|
@@ -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(
|
|
@@ -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
|
+
);
|