@kungfu-tech/buildchain 4.1.3 → 4.1.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/AGENTS.md +11 -13
- package/CONTRIBUTING.md +32 -9
- package/README.md +56 -140
- package/actions/release/promotion/ref/README.md +1 -1
- package/architecture/action-taxonomy.json +1 -1
- package/architecture/agent-change-map.md +32 -45
- package/architecture/build-orchestration.json +1 -1
- package/architecture/capability-state-machine-manifest.json +5 -5
- package/architecture/ci-lane-change-budget.json +92 -92
- package/architecture/code-layout.json +2 -12
- package/architecture/core-mechanisms.json +38 -39
- package/architecture/cross-platform-adopter-qualification.json +1 -1
- package/architecture/dev-delivery-orchestration.json +2 -2
- package/architecture/floating-consumer-policy.json +3 -3
- package/architecture/internal-capabilities.json +27 -28
- package/architecture/maintainability-debt.json +60 -94
- package/architecture/maintainability-policy.json +7 -13
- package/architecture/minimal-consumer-migration.json +43 -5014
- package/architecture/platform-stage-checkpoints.json +1 -1
- package/architecture/release-tail-contract.json +2 -2
- package/architecture/release-topology.json +31 -25
- package/architecture/stage-capsule-qualification.json +1 -1
- package/architecture/universal-workflow-bootstrap.json +60 -60
- package/architecture/universal-workflow-capability-policy.json +3 -3
- package/architecture/universal-workflow-fault-campaign.json +7 -7
- package/architecture/workflow-taxonomy.json +59 -60
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +1 -1
- package/dist/site/buildchain-contract.json +138 -66
- package/dist/site/buildchain-site.json +257 -331
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/cli-registry.json +6 -142
- package/dist/site/controller-registry.json +19 -19
- package/dist/site/kfd-claims.json +481 -529
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +22 -22
- package/dist/site/node-api-registry.json +82 -339
- package/dist/site/page-registry.json +229 -304
- package/dist/site/public-surface-audit.json +544 -754
- package/dist/site/publication-authority-registry.json +316 -316
- package/dist/site/publication-registry.json +1 -1
- package/dist/site/site-manifest.json +23 -23
- package/dist/site/workflow-registry.json +1071 -1027
- package/docs/MAP.md +28 -20
- package/docs/auditable-demo.md +17 -49
- package/docs/bootstrap-recovery.md +46 -37
- package/docs/cli-reference.md +2 -46
- package/docs/cli.md +97 -186
- package/docs/code-organization.md +7 -5
- package/docs/dev-alpha-candidate-patrol.md +19 -42
- package/docs/dev-delivery-orchestration.md +15 -11
- package/docs/dev-delivery-qualification-landing-adr.md +10 -9
- package/docs/dev-delivery-warrant.md +17 -16
- package/docs/dev-qualification-patrol.md +25 -20
- package/docs/getting-started.md +80 -139
- package/docs/github-artifact-attestation.md +31 -81
- package/docs/install.md +66 -30
- package/docs/lifecycle-protocol.md +19 -57
- package/docs/migration-inventory.md +17 -45
- package/docs/next-development-transition.md +13 -13
- package/docs/node-api-reference.md +122 -138
- package/docs/oci-publication.md +13 -10
- package/docs/publication-artifacts.md +128 -486
- package/docs/publication-authority.md +15 -45
- package/docs/publish-transaction.md +13 -11
- package/docs/release-candidate.md +126 -355
- package/docs/release-discussions.md +36 -67
- package/docs/release-flow.md +80 -339
- package/docs/release-governance.md +114 -962
- package/docs/release-promotion-request.md +45 -43
- package/docs/release-propagation.md +13 -27
- package/docs/release-tail-provider-plane.md +38 -65
- package/docs/reusable-build-surface.md +78 -196
- package/docs/runtime-train-validation.md +8 -6
- package/docs/stable-candidate-patrol.md +13 -66
- package/docs/v4-adopter-delivery.md +15 -15
- package/docs/v4-declarative-release-tail-migration.md +19 -17
- package/docs/v4-production-release.md +11 -6
- package/docs/v4-publication-rehearsal.md +12 -27
- package/docs/v4-stage-capsule.md +14 -20
- package/docs/v4-tail-reseal.md +34 -20
- package/docs/web-surface-deployments.md +26 -264
- package/docs/workflow-catalog.md +31 -31
- package/docs/workflow-path-migration.md +10 -4
- package/package.json +5 -4
- package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
- package/packages/core/adoption/cli/output.mjs +1 -9
- package/packages/core/adoption/cli/product-gate.mjs +1 -15
- package/packages/core/adoption/cli/project.mjs +9 -10
- package/packages/core/adoption/cli/standards.mjs +1 -14
- package/packages/core/adoption/cli/support.mjs +0 -2
- package/packages/core/adoption/cli/surface-register.mjs +0 -2
- package/packages/core/adoption/commands/init-repo.mjs +77 -485
- package/packages/core/adoption/consumer-init.js +279 -0
- package/packages/core/adoption/qualification/admission.js +1 -1
- package/packages/core/build/cli/cache.mjs +0 -2
- package/packages/core/build/cli/facts.mjs +0 -3
- package/packages/core/build/cli/homebrew.mjs +0 -4
- package/packages/core/build/cli/lifecycle.mjs +0 -3
- package/packages/core/build/github-artifact-attestation.js +2 -2
- package/packages/core/build/platform-stage-checkpoints.js +1 -1
- package/packages/core/build/signing/dispatch.js +1 -1
- package/packages/core/consumer/buildchain-config.js +9 -0
- package/packages/core/consumer/contract/inspection.js +351 -9
- package/packages/core/consumer/contract/javascript-syntax.js +439 -0
- package/packages/core/consumer/contract/javascript-values.js +61 -0
- package/packages/core/consumer/contract/javascript.js +555 -0
- package/packages/core/consumer/contract/local-validation.js +84 -0
- package/packages/core/contracts/buildchain-contract.js +39 -8
- package/packages/core/contracts/cli/introspection.mjs +1 -9
- package/packages/core/contracts/public-surface-cli.js +0 -1
- package/packages/core/dev-delivery/dev-delivery-contract-surface.js +2 -2
- package/packages/core/governance/buildchain-publication-authority.js +24 -24
- package/packages/core/governance/cli/doctor.mjs +58 -42
- package/packages/core/governance/commands/architecture.mjs +6 -1
- package/packages/core/governance/commands/audit-publication-control-plane.mjs +1 -1
- package/packages/core/governance/commands/workflow-friction-report.mjs +1 -1
- package/packages/core/governance/github-governance-authority.js +2 -2
- package/packages/core/governance/publication/collection.js +1 -1
- package/packages/core/index.js +1 -3
- package/packages/core/observability/cli/candidate.mjs +0 -2
- package/packages/core/observability/cli/context.mjs +0 -5
- package/packages/core/observability/cli/diagnostics.mjs +0 -7
- package/packages/core/observability/cli/events.mjs +2 -10
- package/packages/core/paper/commands/paper-work-fleet-cli.mjs +1 -1
- package/packages/core/paper/commands/paper.mjs +33 -422
- package/packages/core/paper/consumer-status.js +72 -0
- package/packages/core/paper/operations/identity.js +0 -3
- package/packages/core/paper/operations/migration.js +460 -0
- package/packages/core/paper/operations/preflight-checks.js +8 -68
- package/packages/core/paper/operations/scaffold.js +16 -326
- package/packages/core/paper/operations/status.js +27 -53
- package/packages/core/paper/paper-agent-entry.js +36 -147
- package/packages/core/paper/paper-fleet.js +24 -39
- package/packages/core/paper/paper-repository.js +16 -0
- package/packages/core/paper/paper-runtime-channels.js +1 -1
- package/packages/core/paper/paper-scaffold-content.js +153 -162
- package/packages/core/paper/paper-work.js +3 -10
- package/packages/core/paper/paper.js +43 -5
- package/packages/core/providers/github/artifact-attestation.js +1 -1
- package/packages/core/providers/github/pipeline-context-artifacts.js +172 -0
- package/packages/core/publication/authority/control-plane.js +2 -2
- package/packages/core/publication/authority/seal.js +1 -1
- package/packages/core/publication/cli/dispatch.mjs +1 -12
- package/packages/core/publication/pipeline/actions.js +16 -9
- package/packages/core/publication/pipeline/apply.js +17 -9
- package/packages/core/publication/pipeline/context.js +29 -0
- package/packages/core/publication/pipeline/prepare.js +15 -6
- package/packages/core/publication/pipeline/recovery-materials.js +63 -1
- package/packages/core/publication/pipeline/recovery-prepare.js +21 -9
- package/packages/core/publication/pipeline/stable-entry.js +12 -7
- package/packages/core/publication/pipeline/stable-products.js +21 -19
- package/packages/core/publication/pipeline/version-actions.js +15 -6
- package/packages/core/publication/publication-rehearsal-projection.js +0 -9
- package/packages/core/release/cli/dispatch.mjs +1 -4
- package/packages/core/release/cli/inspection-handlers.mjs +1 -33
- package/packages/core/release/cli/release-handlers.mjs +2 -19
- package/packages/core/release/cli/verification-handlers.mjs +4 -25
- package/packages/core/release/next-development-projection.js +13 -13
- package/packages/core/release/passport/locations.js +13 -8
- package/packages/core/release/release-passport.js +70 -5
- package/packages/core/web/cli/badges.mjs +0 -4
- package/packages/core/web/publication-admission.js +1 -1
- package/packages/core/web/publication-capability.js +1 -1
- package/packages/core/web/readme-badges.js +1 -1
- package/packages/core/workflow/cli/main.mjs +1 -4
- package/packages/core/workflow/cli/process.mjs +1 -1
- package/packages/core/workflow/commands/buildchain-cli-help.mjs +2 -8
- package/packages/core/workflow/engine/conformance.js +1 -1
- package/packages/core/workflow/pipeline/build.js +21 -1
- package/packages/core/workflow/pipeline/delivery-observation.js +6 -2
- package/packages/core/workflow/workflow-taxonomy.mjs +61 -17
- package/scripts/check-code-layout.mjs +2 -0
- package/scripts/check-core-mechanism-inventory.mjs +29 -13
- package/scripts/check-floating-consumer-policy-contract.mjs +3 -3
- package/scripts/check-inventory.mjs +27 -27
- package/scripts/check-maintainability.mjs +28 -5
- package/scripts/check-public-dogfood-contract.mjs +3 -3
- package/scripts/check-release-topology.mjs +1 -1
- package/scripts/check-self-consumer-contract.mjs +7 -7
- package/scripts/check-universal-workflow-bootstrap.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +1 -1
- package/scripts/generate-minimal-consumer-contract.mjs +21 -1
- package/scripts/generate-site-bundle.mjs +2 -1
- package/scripts/generate-tail-reseal-fixture.mjs +3 -3
- package/scripts/inventory/promotion.mjs +1 -1
- package/scripts/next-development-self-dogfood-harness.mjs +1 -1
- package/scripts/next-development-self-dogfood.mjs +4 -4
- package/scripts/site-capability-metadata.mjs +2 -6
- package/scripts/source-metric-lineage.mjs +62 -7
- package/scripts/verify-golden-path.mjs +31 -35
- package/templates/native-dev-delivery.yml +1 -1
- package/templates/universal-buildchain-bootstrap.yml +1 -1
- package/packages/core/paper/operations/bootstrap.js +0 -75
- package/packages/core/paper/operations/plans.js +0 -213
- package/packages/core/paper/operations/provisioning.js +0 -113
- package/packages/core/paper/operations/scaffold-config.js +0 -77
- package/packages/core/paper/operations/scaffold-workflows.js +0 -116
- package/packages/core/paper/operations/workspace.js +0 -60
- package/packages/core/paper/paper-npm-bootstrap.js +0 -492
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { collectPaperAgentEntry } from "./paper-agent-entry.js";
|
|
3
3
|
import {
|
|
4
|
-
PAPER_PATHS,
|
|
5
4
|
PAPER_WORK_BRANCH_PATTERN,
|
|
6
5
|
gitResult,
|
|
7
6
|
gitValue,
|
|
8
7
|
normalizedWorkBranch,
|
|
9
8
|
paperDevelopmentRef,
|
|
10
9
|
paperWorkSource,
|
|
11
|
-
readJson,
|
|
12
10
|
remoteBranchObservation,
|
|
13
11
|
rootedPlan,
|
|
14
12
|
workCheck,
|
|
@@ -30,12 +28,7 @@ function failedActions(checks) {
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
function agentEntryForWork(cwd, buildchainSha) {
|
|
33
|
-
|
|
34
|
-
return collectPaperAgentEntry({
|
|
35
|
-
cwd,
|
|
36
|
-
buildchainSha: buildchainSha || policy?.runtime?.sourceSha || "",
|
|
37
|
-
mode: "contract",
|
|
38
|
-
});
|
|
31
|
+
return collectPaperAgentEntry({ cwd, buildchainSha, mode: "contract" });
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
export function createPaperWorkStartPlan({
|
|
@@ -74,7 +67,7 @@ export function createPaperWorkStartPlan({
|
|
|
74
67
|
workCheck(
|
|
75
68
|
"agent-entry.current",
|
|
76
69
|
agentEntry.ok,
|
|
77
|
-
"The
|
|
70
|
+
"The Paper product configuration and shared consumer wiring are current.",
|
|
78
71
|
"buildchain paper migrate --write --json",
|
|
79
72
|
),
|
|
80
73
|
workCheck(
|
|
@@ -252,7 +245,7 @@ export function createPaperWorkSubmitPlan({
|
|
|
252
245
|
workCheck(
|
|
253
246
|
"agent-entry.current",
|
|
254
247
|
agentEntry.ok,
|
|
255
|
-
"The
|
|
248
|
+
"The Paper product configuration and shared consumer wiring are current.",
|
|
256
249
|
"buildchain paper migrate --write --json",
|
|
257
250
|
),
|
|
258
251
|
workCheck(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { paperConsumerStatus } from "./consumer-status.js";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import {
|
|
3
4
|
paperConfig,
|
|
@@ -44,15 +45,52 @@ import {
|
|
|
44
45
|
paperPreflightNextActions,
|
|
45
46
|
} from "./operations/preflight-checks.js";
|
|
46
47
|
export function collectPaperSourcePolicy({ cwd = process.cwd() } = {}) {
|
|
48
|
+
const consumer = paperConsumerStatus(cwd);
|
|
49
|
+
if (consumer) return consumer;
|
|
47
50
|
const agentEntry = collectPaperAgentEntry({ cwd, mode: "ci" });
|
|
48
51
|
const provisioning = validatePaperProvisioningAuthority(cwd);
|
|
49
|
-
const checks = [
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const checks = [
|
|
53
|
+
...agentEntry.checks,
|
|
54
|
+
{
|
|
55
|
+
id: "provisioning.authority",
|
|
56
|
+
status: provisioning.valid ? "pass" : "fail",
|
|
57
|
+
message: provisioning.errors.join("; "),
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
try {
|
|
61
|
+
validateBuildchainConfig(cwd, { requireLifecycleStages: ["verify"] });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
checks.push({
|
|
64
|
+
id: "config.publication",
|
|
65
|
+
status: "fail",
|
|
66
|
+
message: error.message,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
ok: checks.every(
|
|
71
|
+
(check) => check.status === "pass" || check.status === "skip",
|
|
72
|
+
),
|
|
73
|
+
checks,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function collectPaperPreflight(options = {}) {
|
|
78
|
+
const consumer = paperConsumerStatus(
|
|
79
|
+
path.resolve(options.cwd || process.cwd()),
|
|
80
|
+
);
|
|
81
|
+
if (!consumer) return collectLegacyPaperPreflight(options);
|
|
82
|
+
const result =
|
|
83
|
+
options.agentEntryMode && options.agentEntryMode !== "contract"
|
|
84
|
+
? collectPaperAgentEntry({
|
|
85
|
+
cwd: consumer.cwd,
|
|
86
|
+
mode: options.agentEntryMode,
|
|
87
|
+
env: options.env,
|
|
88
|
+
})
|
|
89
|
+
: consumer;
|
|
90
|
+
return { ...result, contract: PAPER_PREFLIGHT_CONTRACT };
|
|
53
91
|
}
|
|
54
92
|
|
|
55
|
-
|
|
93
|
+
function collectLegacyPaperPreflight({
|
|
56
94
|
cwd = process.cwd(),
|
|
57
95
|
buildchainRoot = process.cwd(),
|
|
58
96
|
buildchainVersion = "",
|
|
@@ -10,7 +10,7 @@ export function verifyArtifactAttestationSigner(request, execute = command) {
|
|
|
10
10
|
"--repo",
|
|
11
11
|
request.repository,
|
|
12
12
|
"--signer-workflow",
|
|
13
|
-
"kungfu-systems/buildchain/.github/workflows
|
|
13
|
+
"kungfu-systems/buildchain/.github/workflows/.release-artifact-attestation.yml",
|
|
14
14
|
"--signer-digest",
|
|
15
15
|
request.signerDigest,
|
|
16
16
|
"--source-digest",
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import artifact from "@actions/artifact";
|
|
4
|
+
import {
|
|
5
|
+
canonicalJson,
|
|
6
|
+
recordDigest,
|
|
7
|
+
} from "../../release/discussion/envelope.js";
|
|
8
|
+
import { publicationPath } from "../../publication/pipeline/files.js";
|
|
9
|
+
|
|
10
|
+
const INLINE_LIMIT = 32 * 1024;
|
|
11
|
+
const MATERIAL_LIMIT = 16 * 1024 * 1024;
|
|
12
|
+
const REFERENCE = "buildchain.pipeline-context-reference/v1";
|
|
13
|
+
const SCHEMAS = [
|
|
14
|
+
"buildchain.pipeline-publication-context/v1",
|
|
15
|
+
"buildchain.pipeline-version-context/v1",
|
|
16
|
+
];
|
|
17
|
+
const DIGEST = /^sha256:[0-9a-f]{64}$/u;
|
|
18
|
+
|
|
19
|
+
function execution(env) {
|
|
20
|
+
const result = {
|
|
21
|
+
repository: env.GITHUB_REPOSITORY,
|
|
22
|
+
runId: Number(env.GITHUB_RUN_ID),
|
|
23
|
+
runAttempt: Number(env.GITHUB_RUN_ATTEMPT),
|
|
24
|
+
};
|
|
25
|
+
if (
|
|
26
|
+
!/^[\w.-]+\/[\w.-]+$/u.test(result.repository || "") ||
|
|
27
|
+
!Number.isSafeInteger(result.runId) ||
|
|
28
|
+
result.runId < 1 ||
|
|
29
|
+
!Number.isSafeInteger(result.runAttempt) ||
|
|
30
|
+
result.runAttempt < 1
|
|
31
|
+
)
|
|
32
|
+
throw new Error("Context transfer requires its exact provider execution");
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function projection(value, env) {
|
|
37
|
+
const current = execution(env);
|
|
38
|
+
if (
|
|
39
|
+
!SCHEMAS.includes(value.schema) ||
|
|
40
|
+
value.runId !== current.runId ||
|
|
41
|
+
value.runAttempt !== current.runAttempt
|
|
42
|
+
)
|
|
43
|
+
throw new Error("Context transfer belongs to another provider execution");
|
|
44
|
+
const source =
|
|
45
|
+
value.schema === SCHEMAS[0]
|
|
46
|
+
? value.materialization?.source
|
|
47
|
+
: value.preparation?.source;
|
|
48
|
+
if (
|
|
49
|
+
source?.repository !== current.repository ||
|
|
50
|
+
!/^[0-9a-f]{40}$/u.test(source?.commit || "")
|
|
51
|
+
)
|
|
52
|
+
throw new Error("Context transfer requires an exact repository source");
|
|
53
|
+
return {
|
|
54
|
+
schema: REFERENCE,
|
|
55
|
+
contextSchema: value.schema,
|
|
56
|
+
...current,
|
|
57
|
+
contextRoot: recordDigest(value),
|
|
58
|
+
...(value.schema === SCHEMAS[0]
|
|
59
|
+
? { materialization: { source } }
|
|
60
|
+
: { preparation: { source } }),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function workspace(env) {
|
|
65
|
+
const directory = path.join(
|
|
66
|
+
env.GITHUB_WORKSPACE,
|
|
67
|
+
".buildchain/context-material",
|
|
68
|
+
);
|
|
69
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
70
|
+
return fs.mkdtempSync(path.join(directory, "transfer-"));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Only an immutable reference and the checkout projection cross job outputs.
|
|
74
|
+
// The original context remains unchanged in the authoritative attempt journal.
|
|
75
|
+
export async function writePipelineContext(value, env, client = artifact) {
|
|
76
|
+
const serialized = canonicalJson(value);
|
|
77
|
+
const bytes = Buffer.byteLength(serialized);
|
|
78
|
+
if (bytes <= INLINE_LIMIT) return serialized;
|
|
79
|
+
if (bytes > MATERIAL_LIMIT)
|
|
80
|
+
throw new Error("Pipeline context exceeds its retained material bound");
|
|
81
|
+
const selected = projection(value, env);
|
|
82
|
+
const directory = workspace(env);
|
|
83
|
+
try {
|
|
84
|
+
const file = path.join(directory, "context.json");
|
|
85
|
+
fs.writeFileSync(file, serialized, { flag: "wx" });
|
|
86
|
+
const uploaded = await client.uploadArtifact(
|
|
87
|
+
`buildchain-context-${selected.runAttempt}-${selected.contextRoot.slice(7)}`,
|
|
88
|
+
[file],
|
|
89
|
+
directory,
|
|
90
|
+
{ retentionDays: 30, compressionLevel: 0 },
|
|
91
|
+
);
|
|
92
|
+
const digest = `sha256:${String(uploaded.digest || "").replace(/^sha256:/u, "")}`;
|
|
93
|
+
if (
|
|
94
|
+
!Number.isSafeInteger(uploaded.id) ||
|
|
95
|
+
uploaded.id < 1 ||
|
|
96
|
+
!DIGEST.test(digest)
|
|
97
|
+
)
|
|
98
|
+
throw new Error("Context upload omitted immutable provider coordinates");
|
|
99
|
+
const encoded = canonicalJson({
|
|
100
|
+
...selected,
|
|
101
|
+
bytes,
|
|
102
|
+
artifact: { id: uploaded.id, digest },
|
|
103
|
+
});
|
|
104
|
+
if (Buffer.byteLength(encoded) > INLINE_LIMIT)
|
|
105
|
+
throw new Error(
|
|
106
|
+
"Context checkout projection exceeds the job output bound",
|
|
107
|
+
);
|
|
108
|
+
return encoded;
|
|
109
|
+
} finally {
|
|
110
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validateReference(value, env) {
|
|
115
|
+
const current = execution(env);
|
|
116
|
+
if (
|
|
117
|
+
!SCHEMAS.includes(value.contextSchema) ||
|
|
118
|
+
value.repository !== current.repository ||
|
|
119
|
+
value.runId !== current.runId ||
|
|
120
|
+
value.runAttempt !== current.runAttempt ||
|
|
121
|
+
!DIGEST.test(value.contextRoot || "") ||
|
|
122
|
+
!Number.isSafeInteger(value.bytes) ||
|
|
123
|
+
value.bytes <= INLINE_LIMIT ||
|
|
124
|
+
value.bytes > MATERIAL_LIMIT ||
|
|
125
|
+
!Number.isSafeInteger(value.artifact?.id) ||
|
|
126
|
+
value.artifact.id < 1 ||
|
|
127
|
+
!DIGEST.test(value.artifact?.digest || "")
|
|
128
|
+
)
|
|
129
|
+
throw new Error(
|
|
130
|
+
"Context reference changed its execution or material boundary",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function readPipelineContext(encoded, env, client = artifact) {
|
|
135
|
+
if (Buffer.byteLength(encoded) > INLINE_LIMIT)
|
|
136
|
+
throw new Error("Pipeline context must use bounded job output transport");
|
|
137
|
+
const reference = JSON.parse(encoded);
|
|
138
|
+
if (reference.schema !== REFERENCE) return reference;
|
|
139
|
+
validateReference(reference, env);
|
|
140
|
+
const directory = workspace(env);
|
|
141
|
+
try {
|
|
142
|
+
// No findBy or repository token: the artifact SDK scopes this read to the
|
|
143
|
+
// current workflow execution, including credentialless product build jobs.
|
|
144
|
+
const downloaded = await client.downloadArtifact(reference.artifact.id, {
|
|
145
|
+
path: directory,
|
|
146
|
+
expectedHash: reference.artifact.digest,
|
|
147
|
+
});
|
|
148
|
+
if (
|
|
149
|
+
downloaded.digestMismatch ||
|
|
150
|
+
canonicalJson(fs.readdirSync(directory)) !== '["context.json"]'
|
|
151
|
+
)
|
|
152
|
+
throw new Error("Context artifact changed its archive or file inventory");
|
|
153
|
+
const file = publicationPath(directory, "context.json");
|
|
154
|
+
if (fs.statSync(file).size !== reference.bytes)
|
|
155
|
+
throw new Error("Context artifact changed its retained byte length");
|
|
156
|
+
const value = JSON.parse(
|
|
157
|
+
new TextDecoder("utf-8", { fatal: true }).decode(fs.readFileSync(file)),
|
|
158
|
+
);
|
|
159
|
+
const expected = {
|
|
160
|
+
...projection(value, env),
|
|
161
|
+
bytes: reference.bytes,
|
|
162
|
+
artifact: reference.artifact,
|
|
163
|
+
};
|
|
164
|
+
if (recordDigest(expected) !== recordDigest(reference))
|
|
165
|
+
throw new Error(
|
|
166
|
+
"Context artifact differs from its exact checkout projection or root",
|
|
167
|
+
);
|
|
168
|
+
return value;
|
|
169
|
+
} finally {
|
|
170
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export function publicationControlPlaneRequest(request) {
|
|
2
2
|
const kind = request.autoAdmissionKind;
|
|
3
3
|
const defaults = {
|
|
4
|
-
"release-candidate": ".github/workflows
|
|
5
|
-
"publication-artifact": ".github/workflows
|
|
4
|
+
"release-candidate": ".github/workflows/.release-candidate-promote.yml",
|
|
5
|
+
"publication-artifact": ".github/workflows/.release-paper.yml",
|
|
6
6
|
"binary-release-assets": ".github/workflows/.release-binary-assets.yml",
|
|
7
7
|
};
|
|
8
8
|
if (!defaults[kind])
|
|
@@ -26,7 +26,7 @@ export function sealManagedPublicationAdmission({
|
|
|
26
26
|
);
|
|
27
27
|
const suffix = binary ? ":binary-release-assets" : paper ? ":paper" : "";
|
|
28
28
|
const defaults = {
|
|
29
|
-
"release-candidate": ".github/workflows
|
|
29
|
+
"release-candidate": ".github/workflows/.release-candidate-promote.yml",
|
|
30
30
|
"publication-artifact": ".github/workflows/public-release-paper-sealed.yml",
|
|
31
31
|
"binary-release-assets": ".github/workflows/.release-binary-assets.yml",
|
|
32
32
|
};
|
|
@@ -2,22 +2,11 @@ import { npmPublishDryRun } from "../npm/preview.js";
|
|
|
2
2
|
import { runPublicationArtifactCli } from "../commands/publication-artifact.mjs";
|
|
3
3
|
import { runPublicationPackageCli } from "../commands/publication-package.mjs";
|
|
4
4
|
import { runPublicationReproducibilityCli } from "../commands/publication-reproducibility.mjs";
|
|
5
|
-
import {
|
|
6
|
-
BUILDCHAIN_PROCESS_SAMPLE_REPORT_CONTRACT,
|
|
7
|
-
formatDiagnosticsSummaryTable,
|
|
8
|
-
startProcessSampler,
|
|
9
|
-
summarizeDiagnosticsArtifacts,
|
|
10
|
-
summarizeProcessSamples,
|
|
11
|
-
validateAnchoredPackageRelease,
|
|
12
|
-
} from "../../observability/diagnostics.js";
|
|
5
|
+
import { validateAnchoredPackageRelease } from "../../observability/diagnostics.js";
|
|
13
6
|
import {
|
|
14
7
|
printJson,
|
|
15
8
|
readBooleanFlag,
|
|
16
9
|
readFlag,
|
|
17
|
-
readJsonInput,
|
|
18
|
-
readRepeatedFlag,
|
|
19
|
-
readRepeatedJsonInputs,
|
|
20
|
-
writeJsonFile,
|
|
21
10
|
} from "../../contracts/cli/options.mjs";
|
|
22
11
|
import { runScript } from "../../workflow/cli/process.mjs";
|
|
23
12
|
|
|
@@ -11,9 +11,13 @@ import {
|
|
|
11
11
|
import { applyPipelinePublication } from "./apply.js";
|
|
12
12
|
import { settlePipelinePublication } from "./settle.js";
|
|
13
13
|
import { githubJsonClient } from "../../providers/github/json-client.js";
|
|
14
|
+
import {
|
|
15
|
+
readPipelineContext,
|
|
16
|
+
writePipelineContext,
|
|
17
|
+
} from "../../providers/github/pipeline-context-artifacts.js";
|
|
14
18
|
|
|
15
|
-
const contextInput = (core) =>
|
|
16
|
-
|
|
19
|
+
const contextInput = (core, env) =>
|
|
20
|
+
readPipelineContext(core.getInput("context", { required: true }), env);
|
|
17
21
|
const directory = (env) =>
|
|
18
22
|
path.join(env.GITHUB_WORKSPACE, ".buildchain/publication");
|
|
19
23
|
|
|
@@ -26,7 +30,10 @@ export async function preparePipelinePublicationAction(core, env) {
|
|
|
26
30
|
);
|
|
27
31
|
core.setOutput("operation", result.operation);
|
|
28
32
|
core.setOutput("stable-wait", result.wait ? JSON.stringify(result.wait) : "");
|
|
29
|
-
core.setOutput(
|
|
33
|
+
core.setOutput(
|
|
34
|
+
"context",
|
|
35
|
+
await writePipelineContext(result.context || {}, env),
|
|
36
|
+
);
|
|
30
37
|
core.setOutput(
|
|
31
38
|
"matrix",
|
|
32
39
|
JSON.stringify({ include: result.context?.platforms || [] }),
|
|
@@ -34,7 +41,7 @@ export async function preparePipelinePublicationAction(core, env) {
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
export async function buildPipelinePublicationAction(core, env) {
|
|
37
|
-
const context = contextInput(core);
|
|
44
|
+
const context = await contextInput(core, env);
|
|
38
45
|
const platform = core.getInput("platform", { required: true });
|
|
39
46
|
const cwd = path.join(env.GITHUB_WORKSPACE, ".buildchain/product");
|
|
40
47
|
await buildPipelineSource({
|
|
@@ -59,7 +66,7 @@ export async function buildPipelinePublicationAction(core, env) {
|
|
|
59
66
|
export async function preparePipelineQualificationAction(core, env) {
|
|
60
67
|
const host = await pipelineHost(core, env, "Qualify and sign products");
|
|
61
68
|
const prepared = await preparePipelineQualification(
|
|
62
|
-
contextInput(core),
|
|
69
|
+
await contextInput(core, env),
|
|
63
70
|
host,
|
|
64
71
|
directory(env),
|
|
65
72
|
);
|
|
@@ -71,7 +78,7 @@ export async function preparePipelineQualificationAction(core, env) {
|
|
|
71
78
|
export async function sealPipelineQualificationAction(core, env) {
|
|
72
79
|
const host = await pipelineHost(core, env, "Qualify and sign products");
|
|
73
80
|
await sealPipelineQualification(
|
|
74
|
-
contextInput(core),
|
|
81
|
+
await contextInput(core, env),
|
|
75
82
|
host,
|
|
76
83
|
directory(env),
|
|
77
84
|
core.getInput("bundle-path", { required: true }),
|
|
@@ -81,7 +88,7 @@ export async function sealPipelineQualificationAction(core, env) {
|
|
|
81
88
|
export async function applyPipelinePublicationAction(core, env) {
|
|
82
89
|
const host = await pipelineHost(core, env, "Apply qualified publication");
|
|
83
90
|
const result = await applyPipelinePublication(
|
|
84
|
-
contextInput(core),
|
|
91
|
+
await contextInput(core, env),
|
|
85
92
|
host,
|
|
86
93
|
directory(env),
|
|
87
94
|
env,
|
|
@@ -98,7 +105,7 @@ export async function settlePipelinePublicationAction(core, env) {
|
|
|
98
105
|
userAgent: "buildchain-next-development",
|
|
99
106
|
});
|
|
100
107
|
const result = await settlePipelinePublication(
|
|
101
|
-
contextInput(core),
|
|
108
|
+
await contextInput(core, env),
|
|
102
109
|
host,
|
|
103
110
|
directory(env),
|
|
104
111
|
env,
|
|
@@ -106,7 +113,7 @@ export async function settlePipelinePublicationAction(core, env) {
|
|
|
106
113
|
core.info(result.reason);
|
|
107
114
|
core.setOutput(
|
|
108
115
|
"version-context",
|
|
109
|
-
|
|
116
|
+
await writePipelineContext(result.versionContext || {}, env),
|
|
110
117
|
);
|
|
111
118
|
core.setOutput("operation", result.versionContext ? "regenerate" : "wait");
|
|
112
119
|
core.setOutput(
|
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
} from "../../release/release-invocation.js";
|
|
9
9
|
import { githubPipelineProductRelease } from "../../providers/github/pipeline-product-release.js";
|
|
10
10
|
import { pipelineNpmProvider } from "../npm/pipeline-provider.js";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
publicationContext,
|
|
13
|
+
uniquePublicationMaterial,
|
|
14
|
+
completedPublicationRecovery,
|
|
15
|
+
} from "./context.js";
|
|
12
16
|
import { restorePipelineProducts } from "./sealed-products.js";
|
|
13
17
|
import {
|
|
14
18
|
pipelineReleaseDocuments,
|
|
@@ -34,16 +38,14 @@ export async function applyPipelinePublication(
|
|
|
34
38
|
} = {},
|
|
35
39
|
) {
|
|
36
40
|
const { journal, archive } = await publicationContext(context, host);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
journal,
|
|
41
|
-
{
|
|
41
|
+
const completed = await completedPublicationRecovery(context, journal);
|
|
42
|
+
const wait =
|
|
43
|
+
!completed &&
|
|
44
|
+
(await preparePipelineStableQualification(context.plan, host, journal, {
|
|
42
45
|
attempt: context.attempt,
|
|
43
46
|
generation: context.generation,
|
|
44
47
|
phase: "publish",
|
|
45
|
-
}
|
|
46
|
-
);
|
|
48
|
+
}));
|
|
47
49
|
if (wait) return { operation: "wait", wait };
|
|
48
50
|
const retained = await uniquePublicationMaterial(
|
|
49
51
|
journal,
|
|
@@ -113,7 +115,13 @@ export async function applyPipelinePublication(
|
|
|
113
115
|
const provider = {
|
|
114
116
|
observe: (effect) => providerFor(effect).observe(effect),
|
|
115
117
|
matches: (effect, value) => providerFor(effect).matches(effect, value),
|
|
116
|
-
apply: (effect) =>
|
|
118
|
+
apply: (effect) => {
|
|
119
|
+
if (completed)
|
|
120
|
+
throw new Error(
|
|
121
|
+
"Completed publication recovery cannot perform new provider effects",
|
|
122
|
+
);
|
|
123
|
+
return providerFor(effect).apply(effect);
|
|
124
|
+
},
|
|
117
125
|
};
|
|
118
126
|
const receipts = await journal.materials("publication/effect/");
|
|
119
127
|
const effects = pipelinePublicationEffects({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
2
|
import { resumePipelineSession } from "../../workflow/pipeline/session.js";
|
|
3
3
|
import { pipelinePublicationJournal } from "./journal.js";
|
|
4
|
+
import { createReleaseReceipt } from "../../release/release-invocation.js";
|
|
4
5
|
|
|
5
6
|
export async function publicationContext(context, host) {
|
|
6
7
|
if (
|
|
@@ -37,3 +38,31 @@ export async function uniquePublicationMaterial(journal, prefix) {
|
|
|
37
38
|
);
|
|
38
39
|
return values[0];
|
|
39
40
|
}
|
|
41
|
+
|
|
42
|
+
export async function completedPublicationRecovery(context, journal) {
|
|
43
|
+
if (!context.recovery?.preserveTransaction) return null;
|
|
44
|
+
const values = await journal.materials("publication/predecessor-complete/");
|
|
45
|
+
if (!values.length) return null;
|
|
46
|
+
const complete = values[0];
|
|
47
|
+
const retained = await uniquePublicationMaterial(
|
|
48
|
+
journal,
|
|
49
|
+
"publication/qualified/",
|
|
50
|
+
);
|
|
51
|
+
const receipt = complete.release?.receipt;
|
|
52
|
+
if (
|
|
53
|
+
values.length !== 1 ||
|
|
54
|
+
complete.schema !== "buildchain.pipeline-publication-complete/v1" ||
|
|
55
|
+
!Number.isFinite(Date.parse(complete.completedAt)) ||
|
|
56
|
+
receipt?.outcome !== "complete" ||
|
|
57
|
+
retained.qualified.planRoot !== context.plan.root ||
|
|
58
|
+
receipt.transactionRoot !==
|
|
59
|
+
retained.documents.transaction.transactionRoot ||
|
|
60
|
+
receipt.releasePassportRoot !== retained.documents.passport.passportRoot ||
|
|
61
|
+
recordDigest(createReleaseReceipt(receipt)) !==
|
|
62
|
+
recordDigest(complete.release)
|
|
63
|
+
)
|
|
64
|
+
throw new Error(
|
|
65
|
+
"Completed publication recovery changed its retained signed transaction",
|
|
66
|
+
);
|
|
67
|
+
return complete;
|
|
68
|
+
}
|
|
@@ -5,6 +5,7 @@ import { githubPipelineVersion } from "../../providers/github/pipeline-version.j
|
|
|
5
5
|
import { createPipelinePublicationPlan } from "./source-plan.js";
|
|
6
6
|
import { preparePipelineStableQualification } from "./stable-wait.js";
|
|
7
7
|
import { prepareRecoveredPublication } from "./recovery-prepare.js";
|
|
8
|
+
import { completedPublicationRecovery } from "./context.js";
|
|
8
9
|
import {
|
|
9
10
|
preparePipelineVersionContext,
|
|
10
11
|
retainedPipelineVersionRegeneration,
|
|
@@ -48,12 +49,20 @@ export async function preparePipelinePublication(attempt, publisherSha, host) {
|
|
|
48
49
|
);
|
|
49
50
|
await journal.record("publication/plan", plan);
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
// Native phase order admits follow-ups only after publication succeeded.
|
|
53
|
+
// Later PR events must not reopen the completed publication's Alpha gate.
|
|
54
|
+
const completed = await completedPublicationRecovery(
|
|
55
|
+
{ plan, recovery },
|
|
56
|
+
journal,
|
|
57
|
+
);
|
|
58
|
+
if (phase === "publish" && !completed) {
|
|
59
|
+
const wait = await preparePipelineStableQualification(plan, host, journal, {
|
|
60
|
+
attempt,
|
|
61
|
+
generation: observed.generation,
|
|
62
|
+
phase,
|
|
63
|
+
});
|
|
64
|
+
if (wait) return { operation: "wait", wait };
|
|
65
|
+
}
|
|
57
66
|
if (
|
|
58
67
|
!recovery &&
|
|
59
68
|
(plan.publisher.workflowSha !== publisherSha ||
|
|
@@ -68,7 +68,69 @@ export async function readRecoveryPublicationMaterials(session, host) {
|
|
|
68
68
|
throw new Error(
|
|
69
69
|
"Expanded publication recovery inventory exceeds its bound",
|
|
70
70
|
);
|
|
71
|
-
return result;
|
|
71
|
+
return signedRecoveryMaterials(result);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function signedRecoveryMaterials(materials) {
|
|
75
|
+
const plans = materials.filter((item) =>
|
|
76
|
+
item.id.startsWith("publication/plan/"),
|
|
77
|
+
);
|
|
78
|
+
if (new Set(plans.map((item) => item.value.root)).size < 2) return materials;
|
|
79
|
+
const retained = recoveryPublicationMaterial(
|
|
80
|
+
materials,
|
|
81
|
+
"publication/qualified/",
|
|
82
|
+
);
|
|
83
|
+
if (!retained?.signing) return materials;
|
|
84
|
+
const plan = publicationLineageMaterial(
|
|
85
|
+
materials,
|
|
86
|
+
"buildchain.pipeline-publication-plan/v1",
|
|
87
|
+
(value) => value.root === retained.qualified.planRoot,
|
|
88
|
+
);
|
|
89
|
+
const materialization = publicationLineageMaterial(
|
|
90
|
+
materials,
|
|
91
|
+
"buildchain.pipeline-version-materialization/v1",
|
|
92
|
+
(value) => value.root === retained.signing.materializationRoot,
|
|
93
|
+
);
|
|
94
|
+
const planIdentity = ({
|
|
95
|
+
root,
|
|
96
|
+
publisher: { workflowSha, ...publisher },
|
|
97
|
+
...body
|
|
98
|
+
}) => recordDigest({ ...body, publisher });
|
|
99
|
+
const sourceIdentity = ({ root, planRoot, ...body }) => recordDigest(body);
|
|
100
|
+
for (const item of plans)
|
|
101
|
+
if (planIdentity(item.value) !== planIdentity(plan))
|
|
102
|
+
throw new Error(
|
|
103
|
+
"Conflicting recovery plans changed more than the publisher entry",
|
|
104
|
+
);
|
|
105
|
+
// Preserve every original reference and member ID. Only a signed lineage
|
|
106
|
+
// can classify the otherwise identical, unsigned rederivation as history.
|
|
107
|
+
return materials.map((item) => {
|
|
108
|
+
let name;
|
|
109
|
+
if (
|
|
110
|
+
item.id.startsWith("publication/plan/") &&
|
|
111
|
+
item.value.root !== plan.root
|
|
112
|
+
)
|
|
113
|
+
name = "plan";
|
|
114
|
+
if (
|
|
115
|
+
item.id.startsWith("publication/materialization/") &&
|
|
116
|
+
item.value.root !== materialization.root
|
|
117
|
+
) {
|
|
118
|
+
if (sourceIdentity(item.value) !== sourceIdentity(materialization))
|
|
119
|
+
throw new Error(
|
|
120
|
+
"Conflicting recovery materializations changed retained source",
|
|
121
|
+
);
|
|
122
|
+
name = "materialization";
|
|
123
|
+
}
|
|
124
|
+
return name
|
|
125
|
+
? {
|
|
126
|
+
...item,
|
|
127
|
+
id: item.id.replace(
|
|
128
|
+
`publication/${name}/`,
|
|
129
|
+
`publication/predecessor-${name}/`,
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
: item;
|
|
133
|
+
});
|
|
72
134
|
}
|
|
73
135
|
|
|
74
136
|
export function recoveryPublicationMaterial(
|
|
@@ -54,20 +54,32 @@ export async function prepareRecoveredPublication(
|
|
|
54
54
|
tree: runtime.tree.sha,
|
|
55
55
|
},
|
|
56
56
|
};
|
|
57
|
-
await
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
journal
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
const qualified = await journal.materials("publication/qualified/");
|
|
58
|
+
if (qualified.length > 1)
|
|
59
|
+
throw new Error("Recovered publication has conflicting qualifications");
|
|
60
|
+
if (qualified.length) {
|
|
61
|
+
const plans = await journal.materials("publication/plan/");
|
|
62
|
+
if (plans.length !== 1 || plans[0].root !== qualified[0].qualified.planRoot)
|
|
63
|
+
throw new Error("Recovered qualification lacks its one retained plan");
|
|
64
|
+
}
|
|
65
|
+
// A prepared recovery may already have signed and published its derived
|
|
66
|
+
// plan. Later workers retain that plan even when the floating entry moves.
|
|
67
|
+
if (!qualified.length)
|
|
68
|
+
await importRecoveredPublication(
|
|
69
|
+
publication,
|
|
70
|
+
execution,
|
|
71
|
+
recoveryPlan.root,
|
|
72
|
+
journal,
|
|
73
|
+
phase,
|
|
74
|
+
);
|
|
75
|
+
const mode = qualified.length ? "qualified" : publication.mode;
|
|
64
76
|
return {
|
|
65
77
|
planRoot: recoveryPlan.root,
|
|
66
78
|
predecessor: recoveryPlan.predecessor,
|
|
67
79
|
originalPlanRoot: plan.root,
|
|
68
|
-
mode
|
|
80
|
+
mode,
|
|
69
81
|
build: publication.build || null,
|
|
70
|
-
preserveTransaction:
|
|
82
|
+
preserveTransaction: mode === "qualified",
|
|
71
83
|
execution,
|
|
72
84
|
};
|
|
73
85
|
}
|