@kungfu-tech/buildchain 4.1.3-alpha.0 → 4.1.3-alpha.2
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 +16 -0
- package/architecture/agent-change-map.md +245 -4
- package/architecture/ci-lane-change-budget.json +289 -0
- package/architecture/decisions/0005-minimal-consumer-contract.md +122 -0
- package/architecture/decisions/0006-business-attempt-journal.md +89 -0
- package/architecture/decisions/0007-hosted-pipeline-controller.md +106 -0
- package/architecture/decisions/0008-pipeline-product-publication.md +99 -0
- package/architecture/internal-capabilities.json +340 -4
- package/architecture/maintainability-debt.json +3 -1
- package/architecture/maintainability-policy.json +8 -8
- package/architecture/minimal-consumer-migration.json +7558 -0
- package/architecture/release-topology.json +248 -11
- package/architecture/universal-workflow-bootstrap.json +10 -0
- package/architecture/universal-workflow-capability-policy.json +6 -6
- package/architecture/workflow-taxonomy.json +55 -0
- package/contracts/release-discussion-v1.schema.json +3 -0
- package/dist/readers/business-attempt.cjs +805 -0
- package/dist/readers/release-discussion.cjs +4 -0
- package/dist/site/buildchain-contract.json +7 -5
- package/dist/site/buildchain-site.json +25 -15
- package/dist/site/capability-registry.json +3 -3
- package/dist/site/kfd-claims.json +311 -10
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/node-api-registry.json +9 -9
- package/dist/site/page-registry.json +20 -10
- package/dist/site/public-surface-audit.json +201 -7
- package/dist/site/publication-authority-registry.json +121 -1
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +416 -7
- package/docs/node-api-reference.md +12 -12
- package/docs/release-discussions.md +44 -5
- package/docs/runtime-entry.md +45 -5
- package/docs/workflow-catalog.md +5 -0
- package/package.json +4 -3
- package/packages/core/consumer/contract/entries.js +85 -0
- package/packages/core/consumer/contract/examples.js +96 -0
- package/packages/core/consumer/contract/identity.js +65 -0
- package/packages/core/consumer/contract/inspection.js +45 -0
- package/packages/core/consumer/contract/plan.js +99 -0
- package/packages/core/consumer/contract/products.js +101 -0
- package/packages/core/consumer/contract/shape.js +46 -0
- package/packages/core/dev-delivery/candidate/admission.js +9 -5
- package/packages/core/dev-delivery/candidate/reservation.js +11 -0
- package/packages/core/dev-delivery/candidate/source-paths.js +12 -4
- package/packages/core/dev-delivery/native/actions.js +7 -1
- package/packages/core/dev-delivery/native/heartbeat-action.js +16 -0
- package/packages/core/dev-delivery/native/heartbeat.js +6 -4
- package/packages/core/dev-delivery/queue/landing-action.js +5 -0
- package/packages/core/governance/buildchain-publication-authority.js +5 -0
- package/packages/core/paper/operations/bootstrap.js +2 -0
- package/packages/core/paper/paper-npm-bootstrap.js +2 -2
- package/packages/core/providers/github/attempt-index.js +60 -0
- package/packages/core/providers/github/attempt-journal.js +154 -0
- package/packages/core/providers/github/discussions/materials.js +23 -7
- package/packages/core/providers/github/discussions/transport.js +16 -5
- package/packages/core/providers/github/pipeline-checkout.js +49 -0
- package/packages/core/providers/github/pipeline-events.js +52 -0
- package/packages/core/providers/github/pipeline-integration.js +95 -0
- package/packages/core/providers/github/pipeline-policy.js +213 -0
- package/packages/core/providers/github/pipeline-product-payload.js +50 -0
- package/packages/core/providers/github/pipeline-product-release.js +191 -0
- package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
- package/packages/core/providers/github/pipeline-run-entry.js +86 -0
- package/packages/core/providers/github/pipeline-runs.js +132 -0
- package/packages/core/providers/github/pipeline-source.js +178 -0
- package/packages/core/providers/github/pipeline-version.js +201 -0
- package/packages/core/providers/github/pipeline-worker.js +108 -0
- package/packages/core/publication/candidate/registry-hydration.js +2 -2
- package/packages/core/publication/npm/pack-preview.js +2 -2
- package/packages/core/publication/npm/pack-result.js +31 -0
- package/packages/core/publication/npm/package.js +3 -6
- package/packages/core/publication/npm/pipeline-channel.js +87 -0
- package/packages/core/publication/npm/pipeline-provider.js +109 -0
- package/packages/core/publication/npm/registry.js +12 -9
- package/packages/core/publication/pipeline/actions.js +100 -0
- package/packages/core/publication/pipeline/apply.js +182 -0
- package/packages/core/publication/pipeline/build-segments.js +107 -0
- package/packages/core/publication/pipeline/capsules.js +89 -0
- package/packages/core/publication/pipeline/context.js +39 -0
- package/packages/core/publication/pipeline/development-anchor.js +91 -0
- package/packages/core/publication/pipeline/development-current.js +63 -0
- package/packages/core/publication/pipeline/development-pr.js +85 -0
- package/packages/core/publication/pipeline/development-proof.js +76 -0
- package/packages/core/publication/pipeline/development-transition.js +112 -0
- package/packages/core/publication/pipeline/distribution.js +124 -0
- package/packages/core/publication/pipeline/documents.js +156 -0
- package/packages/core/publication/pipeline/effects.js +162 -0
- package/packages/core/publication/pipeline/files.js +66 -0
- package/packages/core/publication/pipeline/imported-materials.js +32 -0
- package/packages/core/publication/pipeline/journal.js +110 -0
- package/packages/core/publication/pipeline/next-development.js +116 -0
- package/packages/core/publication/pipeline/pack.js +151 -0
- package/packages/core/publication/pipeline/package-policy.js +22 -0
- package/packages/core/publication/pipeline/plan.js +114 -0
- package/packages/core/publication/pipeline/prepare.js +151 -0
- package/packages/core/publication/pipeline/qualification.js +170 -0
- package/packages/core/publication/pipeline/qualify.js +104 -0
- package/packages/core/publication/pipeline/recovery-admission.js +122 -0
- package/packages/core/publication/pipeline/recovery-build-download.js +80 -0
- package/packages/core/publication/pipeline/recovery-build-plan.js +122 -0
- package/packages/core/publication/pipeline/recovery-build-readback.js +68 -0
- package/packages/core/publication/pipeline/recovery-capsules.js +54 -0
- package/packages/core/publication/pipeline/recovery-import.js +80 -0
- package/packages/core/publication/pipeline/recovery-inspection.js +218 -0
- package/packages/core/publication/pipeline/recovery-materials.js +104 -0
- package/packages/core/publication/pipeline/recovery-plan.js +46 -0
- package/packages/core/publication/pipeline/recovery-prepare.js +73 -0
- package/packages/core/publication/pipeline/recovery-qualification.js +128 -0
- package/packages/core/publication/pipeline/recovery-readback.js +56 -0
- package/packages/core/publication/pipeline/recovery-signing.js +58 -0
- package/packages/core/publication/pipeline/sealed-products.js +79 -0
- package/packages/core/publication/pipeline/settle.js +43 -0
- package/packages/core/publication/pipeline/signing.js +140 -0
- package/packages/core/publication/pipeline/source-plan.js +83 -0
- package/packages/core/publication/pipeline/version.js +81 -0
- package/packages/core/publication/publication-reproducibility.js +2 -2
- package/packages/core/release/discussion/binary.js +11 -0
- package/packages/core/release/discussion/checkpoints.js +45 -4
- package/packages/core/release/discussion/envelope.js +1 -0
- package/packages/core/release/discussion/evidence.js +51 -0
- package/packages/core/release/discussion/presentation.js +92 -0
- package/packages/core/release/discussion/publication.js +3 -1
- package/packages/core/release/discussion/qualification.js +1 -0
- package/packages/core/release/discussion/reader.js +9 -0
- package/packages/core/release/discussion/session.js +2 -0
- package/packages/core/release/discussion/store.js +28 -31
- package/packages/core/release/discussion/threads.js +83 -0
- package/packages/core/release/promote-candidate/product-provider-adapters.js +4 -3
- package/packages/core/release/promote-ref/internal/npm-existing-evidence.js +2 -1
- package/packages/core/release/promote-ref/internal/publish-command.js +16 -5
- package/packages/core/runtime/buildchain-domain.wasm +0 -0
- package/packages/core/runtime/domain-wasm-artifact.js +2 -2
- package/packages/core/runtime/entry/actions.js +27 -25
- package/packages/core/runtime/entry/attempt.js +25 -0
- package/packages/core/runtime/entry/selection.js +14 -0
- package/packages/core/runtime/entry/source.js +62 -0
- package/packages/core/workflow/attempt/identity.js +165 -0
- package/packages/core/workflow/attempt/journal.js +87 -0
- package/packages/core/workflow/attempt/materials.js +72 -0
- package/packages/core/workflow/attempt/reader-entry.js +12 -0
- package/packages/core/workflow/attempt/reader.js +149 -0
- package/packages/core/workflow/attempt/records.js +153 -0
- package/packages/core/workflow/attempt/store.js +75 -0
- package/packages/core/workflow/commands/pipeline-build.mjs +23 -0
- package/packages/core/workflow/pipeline/action-output.js +11 -0
- package/packages/core/workflow/pipeline/actions.js +62 -0
- package/packages/core/workflow/pipeline/build-control.js +140 -0
- package/packages/core/workflow/pipeline/build-evidence.js +92 -0
- package/packages/core/workflow/pipeline/build-qualification.js +145 -0
- package/packages/core/workflow/pipeline/build-result.js +69 -0
- package/packages/core/workflow/pipeline/build.js +95 -0
- package/packages/core/workflow/pipeline/cancellation.js +169 -0
- package/packages/core/workflow/pipeline/channel-control.js +127 -0
- package/packages/core/workflow/pipeline/controller.js +133 -0
- package/packages/core/workflow/pipeline/delivery-control.js +204 -0
- package/packages/core/workflow/pipeline/delivery-observation.js +118 -0
- package/packages/core/workflow/pipeline/delivery-request.js +198 -0
- package/packages/core/workflow/pipeline/events.js +84 -0
- package/packages/core/workflow/pipeline/fence.js +42 -0
- package/packages/core/workflow/pipeline/group-control.js +56 -0
- package/packages/core/workflow/pipeline/guard-build.js +37 -0
- package/packages/core/workflow/pipeline/guard.js +156 -0
- package/packages/core/workflow/pipeline/host.js +114 -0
- package/packages/core/workflow/pipeline/materials.js +58 -0
- package/packages/core/workflow/pipeline/notifications.js +36 -0
- package/packages/core/workflow/pipeline/parent-notification.js +27 -0
- package/packages/core/workflow/pipeline/platforms.js +21 -0
- package/packages/core/workflow/pipeline/progress.js +108 -0
- package/packages/core/workflow/pipeline/projection.js +20 -0
- package/packages/core/workflow/pipeline/reconcile.js +194 -0
- package/packages/core/workflow/pipeline/recovery-action.js +30 -0
- package/packages/core/workflow/pipeline/recovery-admission.js +122 -0
- package/packages/core/workflow/pipeline/recovery-build-control.js +95 -0
- package/packages/core/workflow/pipeline/recovery-build-evidence.js +71 -0
- package/packages/core/workflow/pipeline/recovery-build.js +121 -0
- package/packages/core/workflow/pipeline/recovery-controller.js +91 -0
- package/packages/core/workflow/pipeline/recovery-integration.js +66 -0
- package/packages/core/workflow/pipeline/recovery-merge-proof.js +90 -0
- package/packages/core/workflow/pipeline/recovery-ownership-settlement.js +150 -0
- package/packages/core/workflow/pipeline/recovery-ownership.js +91 -0
- package/packages/core/workflow/pipeline/recovery-plan.js +126 -0
- package/packages/core/workflow/pipeline/recovery-runtime.js +68 -0
- package/packages/core/workflow/pipeline/recovery-session.js +129 -0
- package/packages/core/workflow/pipeline/recovery-transition.js +135 -0
- package/packages/core/workflow/pipeline/recovery-unrecorded-build.js +74 -0
- package/packages/core/workflow/pipeline/runtime-source.js +111 -0
- package/packages/core/workflow/pipeline/selection.js +106 -0
- package/packages/core/workflow/pipeline/session.js +72 -0
- package/packages/core/workflow/pipeline/settlement.js +137 -0
- package/packages/core/workflow/pipeline/wake.js +38 -0
- package/packages/core/workflow/pipeline/web-status.js +108 -0
- package/scripts/build-release-discussion-reader.mjs +11 -7
- package/scripts/check-pipeline-publication-topology.mjs +36 -0
- package/scripts/check-release-topology.mjs +6 -1
- package/scripts/generate-minimal-consumer-contract.mjs +155 -0
- package/scripts/verify-golden-path.mjs +5 -1
- package/templates/minimal-consumer/binary/.buildchain/buildchain.toml +45 -0
- package/templates/minimal-consumer/binary/.github/workflows/buildchain-recover.yml +30 -0
- package/templates/minimal-consumer/binary/.github/workflows/buildchain.yml +28 -0
- package/templates/minimal-consumer/binary/package.json +6 -0
- package/templates/minimal-consumer/binary/src/build.mjs +5 -0
- package/templates/minimal-consumer/binary/src/hello.c +2 -0
- package/templates/minimal-consumer/binary/src/verify.mjs +3 -0
- package/templates/minimal-consumer/npm/.buildchain/buildchain.toml +46 -0
- package/templates/minimal-consumer/npm/.github/workflows/buildchain-recover.yml +30 -0
- package/templates/minimal-consumer/npm/.github/workflows/buildchain.yml +28 -0
- package/templates/minimal-consumer/npm/package.json +6 -0
- package/templates/minimal-consumer/npm/src/build.mjs +5 -0
- package/templates/minimal-consumer/npm/src/verify.mjs +3 -0
- package/templates/minimal-consumer/paper/.buildchain/buildchain.toml +45 -0
- package/templates/minimal-consumer/paper/.github/workflows/buildchain-recover.yml +30 -0
- package/templates/minimal-consumer/paper/.github/workflows/buildchain.yml +28 -0
- package/templates/minimal-consumer/paper/package.json +6 -0
- package/templates/minimal-consumer/paper/src/build.mjs +12 -0
- package/templates/minimal-consumer/paper/src/verify.mjs +3 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
3
|
+
import { bindPipelineDevelopmentAnchor } from "./development-transition.js";
|
|
4
|
+
import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
|
|
5
|
+
import { developmentTransitionReadback } from "./development-proof.js";
|
|
6
|
+
|
|
7
|
+
export async function observePipelineVersionAnchor(context, host, transition) {
|
|
8
|
+
const { plan } = context;
|
|
9
|
+
const commit = await host.source.branchHead(plan.developmentBranch);
|
|
10
|
+
const source = await host.source.source(commit, plan.source.configPath);
|
|
11
|
+
const ancestry =
|
|
12
|
+
plan.channel === "stable"
|
|
13
|
+
? null
|
|
14
|
+
: await host.request(
|
|
15
|
+
`/repos/${host.repository}/compare/${plan.intentSource.commit}...${commit}`,
|
|
16
|
+
);
|
|
17
|
+
if (
|
|
18
|
+
ancestry &&
|
|
19
|
+
(!["identical", "ahead"].includes(ancestry.status) ||
|
|
20
|
+
ancestry.merge_base_commit?.sha !== plan.intentSource.commit)
|
|
21
|
+
)
|
|
22
|
+
throw new Error(
|
|
23
|
+
"Version anchor is not descended from the released development source",
|
|
24
|
+
);
|
|
25
|
+
if (recordDigest(source.plan) !== plan.contractRoot)
|
|
26
|
+
throw new Error("Protected anchor changed the product contract");
|
|
27
|
+
const version = await githubPipelineVersion(
|
|
28
|
+
host.request,
|
|
29
|
+
host.repository,
|
|
30
|
+
).inspect(source.identity, plan.versionPolicy);
|
|
31
|
+
if (version.version === plan.version)
|
|
32
|
+
return {
|
|
33
|
+
state: "waiting",
|
|
34
|
+
reason: "waiting-for-protected-version-anchor",
|
|
35
|
+
transitionRoot: transition.idempotencyKey,
|
|
36
|
+
};
|
|
37
|
+
const pulls = await host.request(
|
|
38
|
+
`/repos/${host.repository}/commits/${commit}/pulls?per_page=100`,
|
|
39
|
+
);
|
|
40
|
+
const candidates = pulls.filter(
|
|
41
|
+
(pr) =>
|
|
42
|
+
pr.base?.ref === plan.developmentBranch && pr.merge_commit_sha === commit,
|
|
43
|
+
);
|
|
44
|
+
if (candidates.length !== 1)
|
|
45
|
+
throw new Error("Version anchor requires one exact protected merged PR");
|
|
46
|
+
const pr = await host.request(
|
|
47
|
+
`/repos/${host.repository}/pulls/${candidates[0].number}`,
|
|
48
|
+
);
|
|
49
|
+
const integration = await host.integration.observe({
|
|
50
|
+
intent: {
|
|
51
|
+
source: { pullRequest: pr.number, targetBranch: plan.developmentBranch },
|
|
52
|
+
},
|
|
53
|
+
generation: {
|
|
54
|
+
source: { commit: pr.head.sha, configPath: plan.source.configPath },
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
if (
|
|
58
|
+
plan.channel === "stable" &&
|
|
59
|
+
Date.parse(pr.merged_at) <=
|
|
60
|
+
Date.parse(transition.completedStable.completedAt)
|
|
61
|
+
)
|
|
62
|
+
return {
|
|
63
|
+
state: "waiting",
|
|
64
|
+
reason: "waiting-for-protected-version-anchor",
|
|
65
|
+
transitionRoot: transition.idempotencyKey,
|
|
66
|
+
};
|
|
67
|
+
const file = plan.versionPolicy.files[0].path;
|
|
68
|
+
const anchor = {
|
|
69
|
+
manifestPath: file,
|
|
70
|
+
manifestRoot: `sha256:${createHash("sha256").update(version.files[file]).digest("hex")}`,
|
|
71
|
+
};
|
|
72
|
+
const bound = bindPipelineDevelopmentAnchor(transition, {
|
|
73
|
+
targetVersion: version.version,
|
|
74
|
+
anchor,
|
|
75
|
+
});
|
|
76
|
+
const verified = developmentTransitionReadback(bound, {
|
|
77
|
+
before: version,
|
|
78
|
+
after: version,
|
|
79
|
+
evidence: integration,
|
|
80
|
+
createdAt: pr.created_at,
|
|
81
|
+
mergedAt: pr.merged_at,
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
state: "success",
|
|
85
|
+
reason: "protected-version-anchor-verified",
|
|
86
|
+
transition: verified,
|
|
87
|
+
source: source.identity,
|
|
88
|
+
integration,
|
|
89
|
+
anchor,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { compareDevelopmentVersions } from "../publication-development.js";
|
|
2
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
3
|
+
|
|
4
|
+
export async function observeAdvancedPipelineDevelopment(
|
|
5
|
+
context,
|
|
6
|
+
host,
|
|
7
|
+
transition,
|
|
8
|
+
adapter,
|
|
9
|
+
) {
|
|
10
|
+
const { plan } = context;
|
|
11
|
+
const commit = await host.source.branchHead(plan.developmentBranch);
|
|
12
|
+
const source = await host.source.source(commit, plan.source.configPath);
|
|
13
|
+
if (recordDigest(source.plan) !== plan.contractRoot)
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Protected development contract changed before next-version readback",
|
|
16
|
+
);
|
|
17
|
+
const observed = await adapter.inspect(source.identity, plan.versionPolicy);
|
|
18
|
+
const order = compareDevelopmentVersions(
|
|
19
|
+
observed.version,
|
|
20
|
+
transition.target.version,
|
|
21
|
+
);
|
|
22
|
+
if (order < 0) return null;
|
|
23
|
+
const pulls = await host.request(
|
|
24
|
+
`/repos/${host.repository}/commits/${commit}/pulls?per_page=100`,
|
|
25
|
+
);
|
|
26
|
+
const matches = pulls.filter(
|
|
27
|
+
(pr) =>
|
|
28
|
+
pr.base?.ref === plan.developmentBranch && pr.merge_commit_sha === commit,
|
|
29
|
+
);
|
|
30
|
+
if (matches.length !== 1)
|
|
31
|
+
throw new Error(
|
|
32
|
+
"Advanced development requires one exact protected merged PR",
|
|
33
|
+
);
|
|
34
|
+
const pr = await host.request(
|
|
35
|
+
`/repos/${host.repository}/pulls/${matches[0].number}`,
|
|
36
|
+
);
|
|
37
|
+
const integration = await host.integration.observe({
|
|
38
|
+
intent: {
|
|
39
|
+
source: { pullRequest: pr.number, targetBranch: plan.developmentBranch },
|
|
40
|
+
},
|
|
41
|
+
generation: {
|
|
42
|
+
source: { commit: pr.head.sha, configPath: plan.source.configPath },
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
if (integration.mergeCommit !== commit)
|
|
46
|
+
throw new Error(
|
|
47
|
+
"Advanced development proof differs from the observed protected commit",
|
|
48
|
+
);
|
|
49
|
+
return {
|
|
50
|
+
state: "success",
|
|
51
|
+
reason:
|
|
52
|
+
order === 0
|
|
53
|
+
? "protected-development-already-current"
|
|
54
|
+
: "protected-development-already-advanced",
|
|
55
|
+
transitionRoot: transition.idempotencyKey,
|
|
56
|
+
targetVersion: transition.target.version,
|
|
57
|
+
observedVersion: observed.version,
|
|
58
|
+
source: source.identity,
|
|
59
|
+
integration,
|
|
60
|
+
filesRoot: recordDigest(observed.files),
|
|
61
|
+
publicationOutcome: "preserved-success",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
|
|
3
|
+
export async function prepareNextDevelopmentPullRequest(
|
|
4
|
+
context,
|
|
5
|
+
host,
|
|
6
|
+
journal,
|
|
7
|
+
transition,
|
|
8
|
+
adapter,
|
|
9
|
+
) {
|
|
10
|
+
const { plan } = context;
|
|
11
|
+
const retained = await journal.materials(
|
|
12
|
+
"publication/next-development-source/",
|
|
13
|
+
);
|
|
14
|
+
if (retained.length > 1)
|
|
15
|
+
throw new Error("Next-development source reservation is ambiguous");
|
|
16
|
+
let request = retained[0];
|
|
17
|
+
if (!request) {
|
|
18
|
+
const baseCommit = await host.source.branchHead(plan.developmentBranch);
|
|
19
|
+
const source = await host.source.source(baseCommit, plan.source.configPath);
|
|
20
|
+
if (recordDigest(source.plan) !== plan.contractRoot)
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Protected development contract changed before version preparation",
|
|
23
|
+
);
|
|
24
|
+
const input = await adapter.inspect(source.identity, plan.versionPolicy);
|
|
25
|
+
if (
|
|
26
|
+
plan.channel === "stable"
|
|
27
|
+
? input.version.split("-alpha.")[0] !== plan.version
|
|
28
|
+
: input.version !== plan.version
|
|
29
|
+
)
|
|
30
|
+
throw new Error(
|
|
31
|
+
"Development version already changed; reconcile its protected provenance before preparing another PR",
|
|
32
|
+
);
|
|
33
|
+
request = {
|
|
34
|
+
source: source.identity,
|
|
35
|
+
versionPolicy: plan.versionPolicy,
|
|
36
|
+
version: transition.target.version,
|
|
37
|
+
root: recordDigest({
|
|
38
|
+
transitionRoot: transition.idempotencyKey,
|
|
39
|
+
source: source.identity,
|
|
40
|
+
}),
|
|
41
|
+
sourceTimestamp: input.sourceTimestamp,
|
|
42
|
+
};
|
|
43
|
+
await journal.record("publication/next-development-source", request, {
|
|
44
|
+
phase: "next-development",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
await journal.fence();
|
|
48
|
+
const materialization = await adapter.materializeDevelopment(request);
|
|
49
|
+
const branch = materialization.branch;
|
|
50
|
+
const pulls = await host.request(
|
|
51
|
+
`/repos/${host.repository}/pulls?state=all&head=${encodeURIComponent(`${host.repository.split("/")[0]}:${branch}`)}&base=${encodeURIComponent(plan.developmentBranch)}&per_page=100`,
|
|
52
|
+
);
|
|
53
|
+
if (!Array.isArray(pulls) || pulls.length > 1)
|
|
54
|
+
throw new Error("Next-development PR identity is ambiguous");
|
|
55
|
+
let pr = pulls[0];
|
|
56
|
+
if (!pr) {
|
|
57
|
+
await journal.fence();
|
|
58
|
+
if (!host.pullRequests)
|
|
59
|
+
throw new Error(
|
|
60
|
+
"Next-development requires the repository-authorized automation App or token to trigger ordinary PR checks",
|
|
61
|
+
);
|
|
62
|
+
pr = await host.pullRequests(`/repos/${host.repository}/pulls`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
body: {
|
|
65
|
+
title: `chore(version): prepare ${transition.target.version}`,
|
|
66
|
+
head: branch,
|
|
67
|
+
base: plan.developmentBranch,
|
|
68
|
+
body: `Prepare the declared next development version after ${plan.tag}.\n\nBuildchain-Parent-Attempt: ${context.attempt}\nBuildchain-Transition: ${transition.idempotencyKey}\n\nThe normal protected pipeline verifies the declared products before merge.`,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const selected = {
|
|
73
|
+
schema: "buildchain.pipeline-next-development-pr/v1",
|
|
74
|
+
number: pr.number,
|
|
75
|
+
branch,
|
|
76
|
+
base: plan.developmentBranch,
|
|
77
|
+
source: materialization.source,
|
|
78
|
+
materialization,
|
|
79
|
+
transitionRoot: transition.idempotencyKey,
|
|
80
|
+
};
|
|
81
|
+
await journal.record("publication/next-development-pr", selected, {
|
|
82
|
+
phase: "next-development",
|
|
83
|
+
});
|
|
84
|
+
return selected;
|
|
85
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { stablePipelineDevelopmentReadback } from "./development-transition.js";
|
|
3
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
4
|
+
import {
|
|
5
|
+
nextDevelopmentRoot,
|
|
6
|
+
recordNextDevelopmentMaterialization,
|
|
7
|
+
advanceNextDevelopmentTransition,
|
|
8
|
+
} from "../../release/next-development-transition.js";
|
|
9
|
+
|
|
10
|
+
export function developmentTransitionReadback(
|
|
11
|
+
transition,
|
|
12
|
+
{ before, after, evidence, createdAt, mergedAt },
|
|
13
|
+
) {
|
|
14
|
+
const hash = (bytes) =>
|
|
15
|
+
`sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
16
|
+
const paths = [
|
|
17
|
+
...transition.adapter.sourcePaths,
|
|
18
|
+
...transition.adapter.derivedPaths,
|
|
19
|
+
]
|
|
20
|
+
.sort()
|
|
21
|
+
.map((file) => {
|
|
22
|
+
if (
|
|
23
|
+
typeof before.files[file] !== "string" ||
|
|
24
|
+
typeof after.files[file] !== "string"
|
|
25
|
+
)
|
|
26
|
+
throw new Error("Next-development lacks exact declared file bytes");
|
|
27
|
+
return {
|
|
28
|
+
path: file,
|
|
29
|
+
beforeRoot: hash(before.files[file]),
|
|
30
|
+
afterRoot: hash(after.files[file]),
|
|
31
|
+
changed: before.files[file] !== after.files[file],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
if (after.version !== transition.target.version)
|
|
35
|
+
throw new Error(
|
|
36
|
+
"Next-development materialization does not match its authorized target",
|
|
37
|
+
);
|
|
38
|
+
const body = {
|
|
39
|
+
targetVersion: transition.target.version,
|
|
40
|
+
anchorRoot: transition.target.anchor?.manifestRoot || null,
|
|
41
|
+
paths,
|
|
42
|
+
};
|
|
43
|
+
if (!transition.completedAlpha)
|
|
44
|
+
return stablePipelineDevelopmentReadback(
|
|
45
|
+
transition,
|
|
46
|
+
body,
|
|
47
|
+
evidence,
|
|
48
|
+
createdAt,
|
|
49
|
+
mergedAt,
|
|
50
|
+
);
|
|
51
|
+
let state = recordNextDevelopmentMaterialization(transition, {
|
|
52
|
+
materialization: {
|
|
53
|
+
...body,
|
|
54
|
+
materializationRoot: nextDevelopmentRoot(body),
|
|
55
|
+
},
|
|
56
|
+
evidenceRoot: recordDigest(evidence),
|
|
57
|
+
recordedAt: new Date(createdAt).toISOString(),
|
|
58
|
+
});
|
|
59
|
+
state = advanceNextDevelopmentTransition(state, {
|
|
60
|
+
to: "pr-pending",
|
|
61
|
+
event: "protected-version-pr-observed",
|
|
62
|
+
evidenceRoot: recordDigest(evidence),
|
|
63
|
+
expectedStateRoot: state.state.stateRoot,
|
|
64
|
+
recordedAt: new Date(createdAt).toISOString(),
|
|
65
|
+
});
|
|
66
|
+
if (mergedAt)
|
|
67
|
+
for (const phase of ["merged", "verified"])
|
|
68
|
+
state = advanceNextDevelopmentTransition(state, {
|
|
69
|
+
to: phase,
|
|
70
|
+
event: `protected-version-${phase}`,
|
|
71
|
+
evidenceRoot: recordDigest(evidence),
|
|
72
|
+
expectedStateRoot: state.state.stateRoot,
|
|
73
|
+
recordedAt: new Date(mergedAt).toISOString(),
|
|
74
|
+
});
|
|
75
|
+
return state;
|
|
76
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createNextDevelopmentTransition,
|
|
3
|
+
bindNextDevelopmentAnchor,
|
|
4
|
+
nextDevelopmentRoot,
|
|
5
|
+
} from "../../release/next-development-transition.js";
|
|
6
|
+
import { createStableDevelopmentTransition } from "../publication-development.js";
|
|
7
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
8
|
+
|
|
9
|
+
export function createPipelineDevelopmentTransition(context, publication) {
|
|
10
|
+
const { plan, materialization } = context;
|
|
11
|
+
const input = {
|
|
12
|
+
repository: plan.source.repository,
|
|
13
|
+
model: {
|
|
14
|
+
strategy: plan.versionPolicy.strategy,
|
|
15
|
+
next: plan.versionPolicy.strategy === "semver" ? "auto" : "manual",
|
|
16
|
+
},
|
|
17
|
+
sourcePaths: [
|
|
18
|
+
...new Set(plan.versionPolicy.files.map(({ path }) => path)),
|
|
19
|
+
].sort(),
|
|
20
|
+
derivedPaths: [...(plan.versionPolicy.derived_files || [])].sort(),
|
|
21
|
+
};
|
|
22
|
+
const completed = {
|
|
23
|
+
outcome: "succeeded",
|
|
24
|
+
version: plan.version,
|
|
25
|
+
exactTag: plan.tag,
|
|
26
|
+
releaseSha: materialization.source.commit,
|
|
27
|
+
treeSha: materialization.source.tree,
|
|
28
|
+
publicationRoot: publication.release.receiptRoot,
|
|
29
|
+
completedAt: publication.completedAt,
|
|
30
|
+
};
|
|
31
|
+
if (plan.channel === "alpha")
|
|
32
|
+
return createNextDevelopmentTransition({
|
|
33
|
+
...input,
|
|
34
|
+
completedAlpha: completed,
|
|
35
|
+
});
|
|
36
|
+
const adapter = {
|
|
37
|
+
sourcePaths: input.sourcePaths,
|
|
38
|
+
derivedPaths: input.derivedPaths,
|
|
39
|
+
};
|
|
40
|
+
if (input.model.strategy === "semver")
|
|
41
|
+
return {
|
|
42
|
+
...createStableDevelopmentTransition({
|
|
43
|
+
...input,
|
|
44
|
+
completedStable: completed,
|
|
45
|
+
}),
|
|
46
|
+
adapter,
|
|
47
|
+
};
|
|
48
|
+
const identity = {
|
|
49
|
+
contract: "buildchain.pipeline-stable-anchor-transition/v1",
|
|
50
|
+
...input,
|
|
51
|
+
completedStable: completed,
|
|
52
|
+
};
|
|
53
|
+
return {
|
|
54
|
+
...identity,
|
|
55
|
+
idempotencyKey: nextDevelopmentRoot(identity),
|
|
56
|
+
adapter,
|
|
57
|
+
target: { version: null, anchor: null },
|
|
58
|
+
state: { status: "waiting-anchor" },
|
|
59
|
+
publicationOutcome: "preserved-success",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function bindPipelineDevelopmentAnchor(transition, input) {
|
|
64
|
+
if (transition.completedAlpha)
|
|
65
|
+
return bindNextDevelopmentAnchor(transition, input);
|
|
66
|
+
if (
|
|
67
|
+
transition.contract !== "buildchain.pipeline-stable-anchor-transition/v1" ||
|
|
68
|
+
transition.state.status !== "waiting-anchor" ||
|
|
69
|
+
!input.targetVersion ||
|
|
70
|
+
!input.anchor?.manifestRoot
|
|
71
|
+
)
|
|
72
|
+
throw new Error(
|
|
73
|
+
"Stable manual development requires its explicit protected anchor",
|
|
74
|
+
);
|
|
75
|
+
return {
|
|
76
|
+
...transition,
|
|
77
|
+
target: { version: input.targetVersion, anchor: input.anchor },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function stablePipelineDevelopmentReadback(
|
|
82
|
+
transition,
|
|
83
|
+
materialization,
|
|
84
|
+
evidence,
|
|
85
|
+
createdAt,
|
|
86
|
+
mergedAt,
|
|
87
|
+
) {
|
|
88
|
+
if (
|
|
89
|
+
![
|
|
90
|
+
"buildchain.stable-development-transition/v1",
|
|
91
|
+
"buildchain.pipeline-stable-anchor-transition/v1",
|
|
92
|
+
].includes(transition.contract)
|
|
93
|
+
)
|
|
94
|
+
throw new Error("Unknown stable development transition");
|
|
95
|
+
const state = {
|
|
96
|
+
status: mergedAt ? "verified" : "pr-pending",
|
|
97
|
+
evidenceRoot: recordDigest(evidence),
|
|
98
|
+
recordedAt: new Date(mergedAt || createdAt).toISOString(),
|
|
99
|
+
materializationRoot: nextDevelopmentRoot(materialization),
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
...transition,
|
|
103
|
+
materialization,
|
|
104
|
+
state: {
|
|
105
|
+
...state,
|
|
106
|
+
stateRoot: nextDevelopmentRoot({
|
|
107
|
+
transitionRoot: transition.idempotencyKey,
|
|
108
|
+
...state,
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { pipelineNpmChannel } from "../npm/pipeline-channel.js";
|
|
3
|
+
import { applyPipelineEffects } from "./effects.js";
|
|
4
|
+
import { compareDevelopmentVersions } from "../publication-development.js";
|
|
5
|
+
|
|
6
|
+
export async function distributePipelineProducts(
|
|
7
|
+
context,
|
|
8
|
+
host,
|
|
9
|
+
journal,
|
|
10
|
+
retained,
|
|
11
|
+
environment,
|
|
12
|
+
directory,
|
|
13
|
+
) {
|
|
14
|
+
const { plan, materialization } = context;
|
|
15
|
+
const channel = `v${plan.version.split(".")[0]}${plan.channel === "alpha" ? "-alpha" : ""}`;
|
|
16
|
+
const refPath = `/repos/${host.repository}/git/ref/tags/${channel}`;
|
|
17
|
+
const npmTag = plan.channel === "alpha" ? "alpha" : "latest";
|
|
18
|
+
const npm = pipelineNpmChannel({ environment });
|
|
19
|
+
async function observe(effect) {
|
|
20
|
+
if (effect.kind === "git-channel") {
|
|
21
|
+
const value = await host.request(refPath, { allow404: true });
|
|
22
|
+
if (value && value.object?.type !== "commit")
|
|
23
|
+
throw new Error("Floating channel requires a lightweight commit ref");
|
|
24
|
+
return value
|
|
25
|
+
? { state: "present", commit: value.object.sha }
|
|
26
|
+
: { state: "absent", commit: null };
|
|
27
|
+
}
|
|
28
|
+
return npm.observe(effect);
|
|
29
|
+
}
|
|
30
|
+
const plans = await journal.materials("publication/distribution-plan/");
|
|
31
|
+
if (plans.length > 1)
|
|
32
|
+
throw new Error("Distribution has conflicting retained plans");
|
|
33
|
+
let effects = plans[0]?.effects;
|
|
34
|
+
if (!effects) {
|
|
35
|
+
const raw = [
|
|
36
|
+
{
|
|
37
|
+
id: "git-channel",
|
|
38
|
+
kind: "git-channel",
|
|
39
|
+
channel,
|
|
40
|
+
commit: materialization.source.commit,
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
for (const artifact of retained.qualified.artifacts.filter((entry) =>
|
|
44
|
+
entry.targets.some(({ provider }) => provider === "npm"),
|
|
45
|
+
))
|
|
46
|
+
raw.push({
|
|
47
|
+
id: `npm-channel:${artifact.id}`,
|
|
48
|
+
kind: "npm-channel",
|
|
49
|
+
name: artifact.package.name,
|
|
50
|
+
access: artifact.targets.find(({ provider }) => provider === "npm")
|
|
51
|
+
.access,
|
|
52
|
+
tag: npmTag,
|
|
53
|
+
version: plan.version,
|
|
54
|
+
});
|
|
55
|
+
effects = [];
|
|
56
|
+
for (const effect of raw) {
|
|
57
|
+
const before = await observe(effect);
|
|
58
|
+
const body = { ...effect, expected: before };
|
|
59
|
+
effects.push({ ...body, root: recordDigest(body) });
|
|
60
|
+
}
|
|
61
|
+
await journal.record(
|
|
62
|
+
"publication/distribution-plan",
|
|
63
|
+
{ planRoot: plan.root, effects },
|
|
64
|
+
{ phase: "distribution" },
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const matches = (effect, value) =>
|
|
68
|
+
value.state === "present" &&
|
|
69
|
+
(effect.kind === "git-channel"
|
|
70
|
+
? value.commit === effect.commit
|
|
71
|
+
: value.version === effect.version);
|
|
72
|
+
const provider = {
|
|
73
|
+
observe,
|
|
74
|
+
matches,
|
|
75
|
+
canApply: (effect, value) =>
|
|
76
|
+
recordDigest(value) === recordDigest(effect.expected),
|
|
77
|
+
async apply(effect) {
|
|
78
|
+
const current = await observe(effect);
|
|
79
|
+
if (matches(effect, current)) return;
|
|
80
|
+
if (recordDigest(current) !== recordDigest(effect.expected))
|
|
81
|
+
throw new Error(
|
|
82
|
+
"Floating channel changed since retained distribution admission",
|
|
83
|
+
);
|
|
84
|
+
if (
|
|
85
|
+
effect.kind === "npm-channel" &&
|
|
86
|
+
current.version &&
|
|
87
|
+
compareDevelopmentVersions(current.version, effect.version) > 0
|
|
88
|
+
)
|
|
89
|
+
throw new Error("npm channel cannot regress a newer published version");
|
|
90
|
+
await journal.fence();
|
|
91
|
+
if (effect.kind === "git-channel") {
|
|
92
|
+
if (current.state === "absent")
|
|
93
|
+
return host.request(`/repos/${host.repository}/git/refs`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
body: { ref: `refs/tags/${channel}`, sha: effect.commit },
|
|
96
|
+
});
|
|
97
|
+
// Provider-enforced fast-forward preserves published history. A
|
|
98
|
+
// divergent channel requires a separately proved source reconciliation.
|
|
99
|
+
return host.request(
|
|
100
|
+
`/repos/${host.repository}/git/refs/tags/${channel}`,
|
|
101
|
+
{ method: "PATCH", body: { sha: effect.commit, force: false } },
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
await npm.apply(effect);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const receipts = await applyPipelineEffects({
|
|
108
|
+
effects,
|
|
109
|
+
transactionRoot: retained.documents.transaction.transactionRoot,
|
|
110
|
+
receipts: await journal.materials("publication/distribution-effect/"),
|
|
111
|
+
provider,
|
|
112
|
+
fence: journal.fence,
|
|
113
|
+
retain: (receipt) =>
|
|
114
|
+
journal.record("publication/distribution-effect", receipt, {
|
|
115
|
+
phase: "distribution",
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
schema: "buildchain.pipeline-distribution-complete/v1",
|
|
120
|
+
planRoot: plan.root,
|
|
121
|
+
effects,
|
|
122
|
+
receipts,
|
|
123
|
+
};
|
|
124
|
+
}
|