@kungfu-tech/buildchain 2.3.2-alpha.0 → 2.4.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/buildchain.mjs +35 -1
- package/dist/site/artifact-schemas.json +5 -0
- package/dist/site/buildchain-site.json +7 -0
- package/dist/site/cli-registry.json +11 -1
- package/dist/site/site-manifest.json +7 -0
- package/docs/MAP.md +3 -0
- package/docs/cli.md +24 -2
- package/docs/infra-contract.md +208 -0
- package/docs/lifecycle-protocol.md +12 -0
- package/docs/versioning.md +1 -0
- package/fixtures/infra-contract-aws-cdk-shaped/README.md +8 -0
- package/fixtures/infra-contract-aws-cli-shaped/README.md +7 -0
- package/fixtures/infra-contract-cloudformation-shaped/README.md +7 -0
- package/fixtures/infra-contract-opentofu-shaped/README.md +7 -0
- package/fixtures/infra-contract-pulumi-shaped/README.md +7 -0
- package/fixtures/infra-contract-shaped/README.md +8 -0
- package/fixtures/infra-contract-terraform-shaped/README.md +5 -0
- package/package.json +2 -1
- package/packages/core/buildchain-config.js +139 -2
- package/scripts/generate-site-bundle.mjs +9 -1
- package/scripts/infra-contract-core.mjs +1032 -0
- package/scripts/infra-contract.mjs +345 -0
- package/scripts/init-repo.mjs +85 -4
|
@@ -9,7 +9,7 @@ const RESERVED_LIFECYCLE_KEYS = new Set(["env", "shell"]);
|
|
|
9
9
|
const SUPPORTED_VERSION_FILE_TYPES = new Set(["json", "toml", "regex"]);
|
|
10
10
|
const SUPPORTED_VERSION_STRATEGIES = new Set(["semver", "anchored"]);
|
|
11
11
|
const SUPPORTED_VERSION_NEXT = new Set(["auto", "manual"]);
|
|
12
|
-
const SUPPORTED_PROJECT_TYPES = new Set(["package", "web-surface"]);
|
|
12
|
+
const SUPPORTED_PROJECT_TYPES = new Set(["package", "web-surface", "infra-contract"]);
|
|
13
13
|
const SUPPORTED_PUBLISH_MODES = new Set(["publish-final-version", "promote-existing-version"]);
|
|
14
14
|
const SUPPORTED_PUBLISH_AUTH = new Set(["trusted-publishing", "npm-token"]);
|
|
15
15
|
const SUPPORTED_PACKAGE_SET_ORDER = new Set(["as-provided", "platforms-first-main-last"]);
|
|
@@ -23,6 +23,25 @@ const SUPPORTED_DEPLOY_ADAPTERS = new Set([
|
|
|
23
23
|
"aws-elastic-beanstalk",
|
|
24
24
|
"aws-ecs-service",
|
|
25
25
|
]);
|
|
26
|
+
const SUPPORTED_INFRA_ADAPTERS = new Set([
|
|
27
|
+
"manual-observed",
|
|
28
|
+
"aws-cloudformation",
|
|
29
|
+
"terraform",
|
|
30
|
+
"opentofu",
|
|
31
|
+
"pulumi",
|
|
32
|
+
"aws-cdk",
|
|
33
|
+
"aws-cli",
|
|
34
|
+
"custom-command",
|
|
35
|
+
]);
|
|
36
|
+
const SUPPORTED_INFRA_ADOPTION_MODES = new Set([
|
|
37
|
+
"validate-only",
|
|
38
|
+
"plan-only",
|
|
39
|
+
"observe-only",
|
|
40
|
+
"manual-observed",
|
|
41
|
+
"import-planned",
|
|
42
|
+
"managed-apply",
|
|
43
|
+
]);
|
|
44
|
+
const SUPPORTED_INFRA_APPLY_MODES = new Set(["disabled", "manual-approval", "environment-approval"]);
|
|
26
45
|
|
|
27
46
|
function posixPath(value) {
|
|
28
47
|
return String(value || "").split(path.sep).join("/");
|
|
@@ -117,6 +136,12 @@ export function normalizeBuildchainConfig(config) {
|
|
|
117
136
|
if (normalized.deploy !== undefined) {
|
|
118
137
|
normalized.deploy = normalizeDeploySection(normalized.deploy, normalized.project);
|
|
119
138
|
}
|
|
139
|
+
if (normalized.infra !== undefined) {
|
|
140
|
+
normalized.infra = normalizeInfraSection(normalized.infra);
|
|
141
|
+
}
|
|
142
|
+
if (normalized.consumers !== undefined) {
|
|
143
|
+
normalized.consumers = normalizeConsumersSection(normalized.consumers);
|
|
144
|
+
}
|
|
120
145
|
if (normalized.surfaces !== undefined) {
|
|
121
146
|
normalized.surfaces = normalizeSurfacesSection(normalized.surfaces);
|
|
122
147
|
}
|
|
@@ -130,6 +155,7 @@ export function normalizeBuildchainConfig(config) {
|
|
|
130
155
|
normalized.diagnostics = normalizeDiagnosticsSection(normalized.diagnostics);
|
|
131
156
|
}
|
|
132
157
|
validateWebSurfaceConfig(normalized);
|
|
158
|
+
validateInfraContractConfig(normalized);
|
|
133
159
|
return normalized;
|
|
134
160
|
}
|
|
135
161
|
|
|
@@ -211,7 +237,7 @@ function normalizeProjectSection(project) {
|
|
|
211
237
|
assertPlainObject(project, "project");
|
|
212
238
|
const type = assertString(project.type, "project.type");
|
|
213
239
|
if (!SUPPORTED_PROJECT_TYPES.has(type)) {
|
|
214
|
-
throw new Error("project.type must be one of package or
|
|
240
|
+
throw new Error("project.type must be one of package, web-surface, or infra-contract");
|
|
215
241
|
}
|
|
216
242
|
const normalized = { type };
|
|
217
243
|
for (const key of ["name", "site"]) {
|
|
@@ -222,6 +248,73 @@ function normalizeProjectSection(project) {
|
|
|
222
248
|
return normalized;
|
|
223
249
|
}
|
|
224
250
|
|
|
251
|
+
function normalizeInfraSection(infra) {
|
|
252
|
+
assertPlainObject(infra, "infra");
|
|
253
|
+
const adapter = assertString(infra.adapter, "infra.adapter");
|
|
254
|
+
if (!SUPPORTED_INFRA_ADAPTERS.has(adapter)) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
"infra.adapter must be one of manual-observed, aws-cloudformation, terraform, opentofu, pulumi, aws-cdk, aws-cli, or custom-command",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const adoptionMode = infra.adoption_mode === undefined
|
|
260
|
+
? (adapter === "manual-observed" ? "manual-observed" : "validate-only")
|
|
261
|
+
: assertString(infra.adoption_mode, "infra.adoption_mode");
|
|
262
|
+
if (!SUPPORTED_INFRA_ADOPTION_MODES.has(adoptionMode)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
"infra.adoption_mode must be one of validate-only, plan-only, observe-only, manual-observed, import-planned, or managed-apply",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const applyMode = infra.apply === undefined
|
|
268
|
+
? "disabled"
|
|
269
|
+
: assertString(infra.apply, "infra.apply");
|
|
270
|
+
if (!SUPPORTED_INFRA_APPLY_MODES.has(applyMode)) {
|
|
271
|
+
throw new Error("infra.apply must be one of disabled, manual-approval, or environment-approval");
|
|
272
|
+
}
|
|
273
|
+
const normalized = {
|
|
274
|
+
adapter,
|
|
275
|
+
adoptionMode,
|
|
276
|
+
applyMode,
|
|
277
|
+
environment: infra.environment === undefined ? "" : assertString(infra.environment, "infra.environment"),
|
|
278
|
+
identityRef: infra.identity_ref === undefined ? "" : assertString(infra.identity_ref, "infra.identity_ref"),
|
|
279
|
+
desired: normalizeStringArray(infra.desired, "infra.desired").map(posixPath),
|
|
280
|
+
contract: normalizeStringArray(infra.contract, "infra.contract").map(posixPath),
|
|
281
|
+
secretRefs: normalizeStringArray(infra.secret_refs, "infra.secret_refs"),
|
|
282
|
+
};
|
|
283
|
+
if (infra.commands !== undefined) {
|
|
284
|
+
normalized.commands = normalizeInfraCommands(infra.commands);
|
|
285
|
+
}
|
|
286
|
+
assertNoInlineSecretValues(infra, "infra", new Set(["secret_refs", "commands"]));
|
|
287
|
+
return normalized;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeInfraCommands(commands) {
|
|
291
|
+
assertPlainObject(commands, "infra.commands");
|
|
292
|
+
const normalized = {};
|
|
293
|
+
for (const stage of ["validate", "plan", "apply", "observe"]) {
|
|
294
|
+
if (commands[stage] !== undefined) {
|
|
295
|
+
normalized[stage] = assertString(commands[stage], `infra.commands.${stage}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return normalized;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function normalizeConsumersSection(consumers) {
|
|
302
|
+
if (!Array.isArray(consumers)) {
|
|
303
|
+
throw new Error("consumers must be an array of tables");
|
|
304
|
+
}
|
|
305
|
+
return consumers.map((consumer, index) => normalizeConsumerConfig(consumer, index));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function normalizeConsumerConfig(consumer, index) {
|
|
309
|
+
assertPlainObject(consumer, `consumers[${index}]`);
|
|
310
|
+
return {
|
|
311
|
+
repo: assertString(consumer.repo, `consumers[${index}].repo`),
|
|
312
|
+
path: posixPath(assertString(consumer.path, `consumers[${index}].path`)),
|
|
313
|
+
source: posixPath(assertString(consumer.source, `consumers[${index}].source`)),
|
|
314
|
+
branch: consumer.branch === undefined ? "" : assertString(consumer.branch, `consumers[${index}].branch`),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
225
318
|
function normalizeChannelsSection(channels) {
|
|
226
319
|
assertPlainObject(channels, "channels");
|
|
227
320
|
return Object.fromEntries(
|
|
@@ -531,6 +624,36 @@ function validateWebSurfaceConfig(config) {
|
|
|
531
624
|
validateWebSurfaceSurfaces(config);
|
|
532
625
|
}
|
|
533
626
|
|
|
627
|
+
function validateInfraContractConfig(config) {
|
|
628
|
+
if (config.project?.type !== "infra-contract") {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (!config.infra) {
|
|
632
|
+
throw new Error('project.type = "infra-contract" requires [infra]');
|
|
633
|
+
}
|
|
634
|
+
if (config.infra.desired.length === 0) {
|
|
635
|
+
throw new Error('project.type = "infra-contract" requires infra.desired');
|
|
636
|
+
}
|
|
637
|
+
if (config.infra.contract.length === 0) {
|
|
638
|
+
throw new Error('project.type = "infra-contract" requires infra.contract');
|
|
639
|
+
}
|
|
640
|
+
if (!Array.isArray(config.consumers) || config.consumers.length === 0) {
|
|
641
|
+
throw new Error('project.type = "infra-contract" requires at least one [[consumers]] entry');
|
|
642
|
+
}
|
|
643
|
+
if (config.infra.applyMode !== "disabled" && config.infra.adoptionMode !== "managed-apply") {
|
|
644
|
+
throw new Error("infra.apply requires infra.adoption_mode = managed-apply");
|
|
645
|
+
}
|
|
646
|
+
if (config.infra.applyMode !== "disabled" && !config.infra.environment) {
|
|
647
|
+
throw new Error("infra.apply requires infra.environment");
|
|
648
|
+
}
|
|
649
|
+
if (config.infra.applyMode !== "disabled" && !config.infra.identityRef) {
|
|
650
|
+
throw new Error("infra.apply requires infra.identity_ref");
|
|
651
|
+
}
|
|
652
|
+
if (config.infra.adapter === "manual-observed" && config.infra.applyMode !== "disabled") {
|
|
653
|
+
throw new Error('infra.adapter = "manual-observed" requires infra.apply = "disabled"');
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
534
657
|
function validateWebSurfaceSurfaces(config) {
|
|
535
658
|
const surfaces = config.surfaces || {};
|
|
536
659
|
for (const [name, surface] of Object.entries(surfaces)) {
|
|
@@ -891,6 +1014,20 @@ export function validateBuildchainConfig(
|
|
|
891
1014
|
]),
|
|
892
1015
|
)
|
|
893
1016
|
: undefined,
|
|
1017
|
+
infra: loadedConfig.config.infra
|
|
1018
|
+
? {
|
|
1019
|
+
adapter: loadedConfig.config.infra.adapter,
|
|
1020
|
+
adoptionMode: loadedConfig.config.infra.adoptionMode,
|
|
1021
|
+
applyMode: loadedConfig.config.infra.applyMode,
|
|
1022
|
+
environment: loadedConfig.config.infra.environment,
|
|
1023
|
+
identityRef: loadedConfig.config.infra.identityRef,
|
|
1024
|
+
desired: loadedConfig.config.infra.desired,
|
|
1025
|
+
contract: loadedConfig.config.infra.contract,
|
|
1026
|
+
secretRefs: loadedConfig.config.infra.secretRefs,
|
|
1027
|
+
commands: loadedConfig.config.infra.commands,
|
|
1028
|
+
}
|
|
1029
|
+
: undefined,
|
|
1030
|
+
consumers: loadedConfig.config.consumers,
|
|
894
1031
|
surfaces: loadedConfig.config.surfaces
|
|
895
1032
|
? Object.fromEntries(
|
|
896
1033
|
Object.entries(loadedConfig.config.surfaces).map(([name, surface]) => [
|
|
@@ -52,6 +52,7 @@ function buildSiteBundle() {
|
|
|
52
52
|
docEntry("release-flow", "Release flow", "docs/release-flow.md", "verify"),
|
|
53
53
|
docEntry("versioning", "Versioning", "docs/versioning.md", "why"),
|
|
54
54
|
docEntry("web-surface-deployments", "Web surface deployments", "docs/web-surface-deployments.md", "use"),
|
|
55
|
+
docEntry("infra-contract", "Infra Contract", "docs/infra-contract.md", "use"),
|
|
55
56
|
];
|
|
56
57
|
|
|
57
58
|
const cliRegistry = {
|
|
@@ -61,16 +62,18 @@ function buildSiteBundle() {
|
|
|
61
62
|
npmPackage: packageJson.name,
|
|
62
63
|
commands: [
|
|
63
64
|
{ id: "version", usage: "buildchain version", purpose: "Print the package or embedded binary version." },
|
|
64
|
-
{ id: "init", usage: "buildchain init [--type package|native|web-surface|anchored-package]", purpose: "Bootstrap a repository with Buildchain configuration and caller workflow files." },
|
|
65
|
+
{ id: "init", usage: "buildchain init [--type package|native|web-surface|infra-contract|anchored-package]", purpose: "Bootstrap a repository with Buildchain configuration and caller workflow files." },
|
|
65
66
|
{ id: "validate", usage: "buildchain validate [--require-version-state]", purpose: "Validate buildchain.toml and declared lifecycle surfaces." },
|
|
66
67
|
{ id: "doctor", usage: "buildchain doctor [--json]", purpose: "Report local integration readiness." },
|
|
67
68
|
{ id: "lifecycle", usage: "buildchain lifecycle run <stage>", purpose: "Run configured lifecycle commands and write deterministic artifact manifests." },
|
|
68
69
|
{ id: "release-dry-run", usage: "buildchain release --dry-run --target-ref <ref>", purpose: "Explain what a channel merge would publish before the PR is merged." },
|
|
69
70
|
{ id: "collect-github-release", usage: "buildchain collect github-release --tag <tag>", purpose: "Collect release assets into a release passport." },
|
|
70
71
|
{ id: "verify-release-passport", usage: "buildchain verify release-passport <file-or-url>", purpose: "Fail closed unless a release passport and its evidence are complete." },
|
|
72
|
+
{ id: "verify-infra-contract-evidence-bundle", usage: "buildchain verify infra-contract-evidence-bundle <file>", purpose: "Fail closed unless an infra-contract lifecycle evidence bundle is complete, hash-bound, and validation-consistent." },
|
|
71
73
|
{ id: "logging", usage: "buildchain log|mark|span|verify observability-log", purpose: "Emit timestamped build events, summarize logs, and enforce required phases." },
|
|
72
74
|
{ id: "diagnostics-summary", usage: "buildchain diagnostics summary <diagnostics.json>...", purpose: "Summarize small diagnostics artifacts into JSON and a cross-platform lifecycle timing table." },
|
|
73
75
|
{ id: "npm-dry-run", usage: "buildchain npm dry-run --json", purpose: "Verify npm publish shape before a release transaction." },
|
|
76
|
+
{ id: "infra-contract", usage: "buildchain infra-contract --mode validate|ci|plan|contract|propagation-plan|propagation-apply|apply|evidence-bundle", purpose: "Validate and publish provider-neutral infrastructure contract evidence with a mutation-free CI evidence chain, provider command plans, configured provider command execution, saved-plan apply gates, dry-run-first propagation, and lifecycle evidence bundles." },
|
|
74
77
|
],
|
|
75
78
|
};
|
|
76
79
|
|
|
@@ -135,6 +138,11 @@ function buildSiteBundle() {
|
|
|
135
138
|
"release-provenance.json",
|
|
136
139
|
"agent-index.json",
|
|
137
140
|
],
|
|
141
|
+
infraContract: [
|
|
142
|
+
"infra-contract-plan.json",
|
|
143
|
+
"buildchain.infra-contract.json",
|
|
144
|
+
"infra-contract-propagation.json",
|
|
145
|
+
],
|
|
138
146
|
};
|
|
139
147
|
|
|
140
148
|
const productMechanism = {
|