@kungfu-tech/buildchain 4.1.3-alpha.0 → 4.1.3-alpha.10
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 +41 -75
- package/CONTRIBUTING.md +19 -26
- package/architecture/action-taxonomy.json +24 -0
- package/architecture/agent-change-map.md +272 -12
- package/architecture/capability-state-machine-manifest.json +1 -4
- package/architecture/ci-lane-change-budget.json +190 -807
- package/architecture/core-mechanisms.json +3 -6
- 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 +120 -0
- package/architecture/decisions/0009-pipeline-version-preparation.md +186 -0
- package/architecture/internal-capabilities.json +371 -9
- package/architecture/maintainability-debt.json +50 -34
- package/architecture/maintainability-policy.json +8 -8
- package/architecture/minimal-consumer-migration.json +5670 -0
- package/architecture/platform-stage-checkpoints.json +31 -7
- package/architecture/product-upstreams.json +20 -0
- package/architecture/release-topology.json +331 -74
- package/architecture/stage-capsule-qualification.json +3 -1
- package/architecture/universal-workflow-bootstrap.json +15 -1
- package/architecture/universal-workflow-capability-policy.json +9 -9
- package/architecture/universal-workflow-fault-campaign.json +3 -1
- package/architecture/workflow-taxonomy.json +77 -328
- package/contracts/release-discussion-v1.schema.json +3 -0
- package/dist/readers/business-attempt.cjs +809 -0
- package/dist/readers/release-discussion.cjs +4 -0
- package/dist/site/buildchain-contract.json +16 -68
- package/dist/site/buildchain-site.json +47 -27
- package/dist/site/capability-registry.json +4 -4
- package/dist/site/controller-registry.json +1 -47
- package/dist/site/kfd-claims.json +499 -494
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/node-api-registry.json +56 -56
- package/dist/site/page-registry.json +40 -20
- package/dist/site/public-surface-audit.json +286 -349
- package/dist/site/publication-authority-registry.json +203 -523
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +7 -7
- package/dist/site/workflow-registry.json +590 -940
- package/docs/code-organization.md +9 -6
- package/docs/node-api-reference.md +103 -103
- package/docs/release-discussions.md +44 -5
- package/docs/release-governance.md +10 -12
- package/docs/runtime-entry.md +73 -5
- package/docs/runtime-train-validation.md +39 -11
- package/docs/stable-candidate-patrol.md +6 -5
- package/docs/workflow-catalog.md +9 -32
- package/package.json +4 -3
- package/packages/core/adoption/buildchain-kfd-claims.js +7 -7
- package/packages/core/build/standalone/build.js +7 -1
- package/packages/core/consumer/buildchain-config.js +2 -21
- 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 +149 -0
- package/packages/core/consumer/contract/products.js +119 -0
- package/packages/core/consumer/contract/reader.js +47 -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/commands/project-cut-merge-queue-admission.mjs +47 -20
- package/packages/core/dev-delivery/dev-delivery-provider-heartbeat.js +18 -15
- package/packages/core/dev-delivery/native/actions.js +9 -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/native/process-entry.js +19 -0
- package/packages/core/dev-delivery/queue/landing-action.js +53 -0
- package/packages/core/dev-delivery/warrant/failure-settlement.js +8 -6
- package/packages/core/dev-delivery/warrant/service.js +7 -0
- package/packages/core/governance/buildchain-publication-authority.js +8 -23
- package/packages/core/governance/github-governance-authority.js +1 -0
- package/packages/core/observability/controller-evidence.js +0 -8
- 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 +35 -11
- 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-entry-runs.js +210 -0
- package/packages/core/providers/github/pipeline-events.js +131 -0
- package/packages/core/providers/github/pipeline-integration.js +95 -0
- package/packages/core/providers/github/pipeline-policy.js +240 -0
- package/packages/core/providers/github/pipeline-product-payload.js +50 -0
- package/packages/core/providers/github/pipeline-product-release.js +198 -0
- package/packages/core/providers/github/pipeline-publication-artifacts.js +153 -0
- package/packages/core/providers/github/pipeline-release-evidence.js +30 -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 +195 -0
- package/packages/core/providers/github/pipeline-stable-baseline.js +86 -0
- package/packages/core/providers/github/pipeline-stable-source.js +372 -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 +199 -0
- package/packages/core/providers/github/pipeline-worker.js +108 -0
- package/packages/core/providers/github/release-evidence-assets.js +97 -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 +36 -0
- package/packages/core/publication/npm/pipeline-provider.js +137 -0
- package/packages/core/publication/npm/registry.js +12 -9
- package/packages/core/publication/pipeline/actions.js +116 -0
- package/packages/core/publication/pipeline/apply.js +189 -0
- package/packages/core/publication/pipeline/build-segments.js +107 -0
- package/packages/core/publication/pipeline/capsules.js +92 -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 +112 -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 +130 -0
- package/packages/core/publication/pipeline/documents.js +192 -0
- package/packages/core/publication/pipeline/effects.js +192 -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 +122 -0
- package/packages/core/publication/pipeline/next-development.js +117 -0
- package/packages/core/publication/pipeline/pack.js +180 -0
- package/packages/core/publication/pipeline/package-policy.js +22 -0
- package/packages/core/publication/pipeline/plan.js +136 -0
- package/packages/core/publication/pipeline/prepare.js +139 -0
- package/packages/core/publication/pipeline/qualification.js +175 -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 +42 -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/stable-entry.js +219 -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/version.js +81 -0
- package/packages/core/publication/pipeline/worker.js +72 -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/next-development-transition.js +15 -15
- 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/release/stable-release-gate.js +11 -0
- 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 +170 -0
- package/packages/core/workflow/attempt/journal.js +89 -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 +52 -0
- package/packages/core/workflow/pipeline/build-control.js +140 -0
- package/packages/core/workflow/pipeline/build-evidence.js +210 -0
- package/packages/core/workflow/pipeline/build-qualification.js +145 -0
- package/packages/core/workflow/pipeline/build-result.js +78 -0
- package/packages/core/workflow/pipeline/build.js +142 -0
- package/packages/core/workflow/pipeline/cancellation.js +169 -0
- package/packages/core/workflow/pipeline/channel-control.js +157 -0
- package/packages/core/workflow/pipeline/channel-source.js +53 -0
- package/packages/core/workflow/pipeline/controller.js +141 -0
- package/packages/core/workflow/pipeline/delivery-control.js +249 -0
- package/packages/core/workflow/pipeline/delivery-observation.js +124 -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 +59 -0
- package/packages/core/workflow/pipeline/guard-build.js +74 -0
- package/packages/core/workflow/pipeline/guard.js +158 -0
- package/packages/core/workflow/pipeline/host.js +144 -0
- package/packages/core/workflow/pipeline/integration-status.js +113 -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/record-action.js +27 -0
- package/packages/core/workflow/pipeline/recorder-status.js +69 -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 +79 -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 +111 -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/packages/core/workflow/workflow-taxonomy.mjs +67 -47
- package/scripts/build-release-discussion-reader.mjs +11 -7
- package/scripts/check-inventory.mjs +7 -70
- package/scripts/check-pipeline-publication-topology.mjs +50 -0
- package/scripts/check-public-dogfood-contract.mjs +38 -51
- package/scripts/check-release-topology.mjs +15 -8
- package/scripts/check-self-consumer-contract.mjs +133 -0
- package/scripts/check-universal-workflow-bootstrap.mjs +4 -6
- package/scripts/generate-minimal-consumer-contract.mjs +155 -0
- package/scripts/generate-site-bundle.mjs +1 -1
- package/scripts/inventory/binary.mjs +9 -19
- package/scripts/inventory/promotion.mjs +2 -13
- package/scripts/prepare-product-toolchain.mjs +10 -0
- package/scripts/verify-golden-path.mjs +5 -1
- package/scripts/verify-product-platform.mjs +65 -0
- package/scripts/verify-standalone-product.mjs +42 -0
- 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,141 @@
|
|
|
1
|
+
import { pipelineEvent } from "./events.js";
|
|
2
|
+
import { selectPipelineSession } from "./selection.js";
|
|
3
|
+
import { beginPipelineBuild } from "./build-control.js";
|
|
4
|
+
import { controlPipelineDelivery } from "./delivery-control.js";
|
|
5
|
+
import {
|
|
6
|
+
githubPipelineEvents,
|
|
7
|
+
githubPipelineQueueExit,
|
|
8
|
+
} from "../../providers/github/pipeline-events.js";
|
|
9
|
+
import { pipelinePlatforms } from "./platforms.js";
|
|
10
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
11
|
+
|
|
12
|
+
async function groupBuild(event, inputs, host) {
|
|
13
|
+
const queue = await host.queue.getMergeQueueState(event.branch);
|
|
14
|
+
if (
|
|
15
|
+
!queue.enabled ||
|
|
16
|
+
!queue.entries.some(
|
|
17
|
+
(entry) =>
|
|
18
|
+
entry.headSha === event.commit && entry.baseSha === event.baseCommit,
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Merge-group execution is not in the exact protected provider queue",
|
|
23
|
+
);
|
|
24
|
+
const admitted = await host.source.source(
|
|
25
|
+
event.commit,
|
|
26
|
+
inputs["config-path"],
|
|
27
|
+
);
|
|
28
|
+
const context = {
|
|
29
|
+
schema: "buildchain.pipeline-group-build-context/v1",
|
|
30
|
+
source: admitted.identity,
|
|
31
|
+
branch: event.branch,
|
|
32
|
+
baseCommit: event.baseCommit,
|
|
33
|
+
runId: host.runId,
|
|
34
|
+
runAttempt: host.runAttempt,
|
|
35
|
+
platforms: pipelinePlatforms(admitted.plan),
|
|
36
|
+
};
|
|
37
|
+
return { operation: "build", context };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function controlPipeline(name, payload, inputs, host) {
|
|
41
|
+
const event = pipelineEvent(name, payload, host.repository);
|
|
42
|
+
host.terminalOnly = event.terminalOnly;
|
|
43
|
+
host.eventAction = payload.action || "";
|
|
44
|
+
if (event.kind === "ignore")
|
|
45
|
+
return { operation: "wait", reason: event.reason };
|
|
46
|
+
if (event.kind === "branch") {
|
|
47
|
+
const events = githubPipelineEvents(
|
|
48
|
+
host.request,
|
|
49
|
+
host.provider,
|
|
50
|
+
host.repository,
|
|
51
|
+
);
|
|
52
|
+
const attempts = await events.branch(event.branch, event.commit);
|
|
53
|
+
for (const attempt of attempts) await host.wake(attempt);
|
|
54
|
+
return {
|
|
55
|
+
operation: "wait",
|
|
56
|
+
reason: "existing-channel-intents-woken",
|
|
57
|
+
count: attempts.length,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (event.kind === "merge-group") return groupBuild(event, inputs, host);
|
|
61
|
+
const selected = await selectPipelineSession(event, payload, inputs, host);
|
|
62
|
+
if (!selected)
|
|
63
|
+
return { operation: "wait", reason: "no-admitted-pipeline-intent" };
|
|
64
|
+
const { session, admission } = selected;
|
|
65
|
+
const current = session.observed.history.at(-1);
|
|
66
|
+
if (
|
|
67
|
+
current.identity.requestKey.startsWith("recover:") &&
|
|
68
|
+
recordDigest(current.events[0].runtime) !== recordDigest(host.runtime)
|
|
69
|
+
) {
|
|
70
|
+
await host.wake(current.identity.id);
|
|
71
|
+
return {
|
|
72
|
+
operation: "wait",
|
|
73
|
+
reason: "admitted-recovery-runtime-continuation-required",
|
|
74
|
+
attempt: current.identity.id,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
["failure", "cancelled", "superseded", "complete"].includes(
|
|
79
|
+
session.observed.status,
|
|
80
|
+
)
|
|
81
|
+
) {
|
|
82
|
+
await host.notifyTerminal?.(session);
|
|
83
|
+
await host.project(session);
|
|
84
|
+
return {
|
|
85
|
+
operation: "wait",
|
|
86
|
+
reason: `attempt-${session.observed.status}`,
|
|
87
|
+
attempt: session.observed.attempt,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (
|
|
91
|
+
admission.live.source &&
|
|
92
|
+
host.selection.source.sha !== admission.live.source.commit
|
|
93
|
+
) {
|
|
94
|
+
await host.wake(session.observed.attempt);
|
|
95
|
+
return {
|
|
96
|
+
operation: "wait",
|
|
97
|
+
reason: "source-generation-runtime-selection-required",
|
|
98
|
+
attempt: session.observed.attempt,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (session.intent.expectedNodes.includes("warrant"))
|
|
102
|
+
host.queueExit ||= githubPipelineQueueExit(
|
|
103
|
+
host.github.graphql,
|
|
104
|
+
host.repository,
|
|
105
|
+
);
|
|
106
|
+
const operation = session.intent.expectedNodes.includes("warrant")
|
|
107
|
+
? await controlPipelineDelivery(session, inputs, host)
|
|
108
|
+
: await host.channel(session, admission, inputs);
|
|
109
|
+
if (operation.operation === "build") {
|
|
110
|
+
const context = await beginPipelineBuild(
|
|
111
|
+
session,
|
|
112
|
+
operation.admission || admission,
|
|
113
|
+
host,
|
|
114
|
+
);
|
|
115
|
+
await host.project(session);
|
|
116
|
+
if (!context)
|
|
117
|
+
return { operation: "wait", reason: "existing-build-execution-retained" };
|
|
118
|
+
return { operation: "build", context };
|
|
119
|
+
}
|
|
120
|
+
await host.project(session);
|
|
121
|
+
if (operation.operation === "successor") {
|
|
122
|
+
// Closed or retargeted PRs do not authorize a new attempt on the old route.
|
|
123
|
+
// A later normal event re-admits a lawful source after the old candidate ends.
|
|
124
|
+
await host.wake(session.observed.attempt);
|
|
125
|
+
return {
|
|
126
|
+
operation: "wait",
|
|
127
|
+
reason: operation.reason,
|
|
128
|
+
attempt: session.observed.attempt,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (operation.operation === "settle") {
|
|
132
|
+
const settled = await host.settle(
|
|
133
|
+
session,
|
|
134
|
+
operation.fresh,
|
|
135
|
+
operation.delivery,
|
|
136
|
+
);
|
|
137
|
+
await host.project(session);
|
|
138
|
+
return settled;
|
|
139
|
+
}
|
|
140
|
+
return operation;
|
|
141
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { preparePipelineDelivery } from "./delivery-request.js";
|
|
3
|
+
import { pipelineBuildEvidence } from "./build-evidence.js";
|
|
4
|
+
import {
|
|
5
|
+
observePipelineWorker,
|
|
6
|
+
pipelineDeliveryExecution,
|
|
7
|
+
pipelineDeliveryStore,
|
|
8
|
+
} from "./delivery-observation.js";
|
|
9
|
+
import { reconcilePipeline, pipelineCandidate } from "./reconcile.js";
|
|
10
|
+
import {
|
|
11
|
+
cancelPipelineCandidate,
|
|
12
|
+
recoverPipelineCancellation,
|
|
13
|
+
} from "./cancellation.js";
|
|
14
|
+
import { readBusinessAttempt } from "../attempt/reader.js";
|
|
15
|
+
|
|
16
|
+
export async function observePipelineDelivery(session, inputs, host, delivery) {
|
|
17
|
+
const observed = await session.journal.read();
|
|
18
|
+
const current = { ...observed.history.at(-1), intent: session.intent };
|
|
19
|
+
const admission = await host.source.observeIntent(
|
|
20
|
+
session.intent,
|
|
21
|
+
current.generation,
|
|
22
|
+
inputs,
|
|
23
|
+
);
|
|
24
|
+
admission.live.terminalOnly = host.terminalOnly === true;
|
|
25
|
+
const queue = await delivery.read();
|
|
26
|
+
admission.live.queueExit =
|
|
27
|
+
admission.live.state === "open" &&
|
|
28
|
+
admission.live.source?.commit === current.generation.source.commit &&
|
|
29
|
+
admission.live.targetBranch === session.intent.source.targetBranch
|
|
30
|
+
? await host.queueExit(current, pipelineCandidate(queue, current))
|
|
31
|
+
: null;
|
|
32
|
+
admission.live.dequeued = Boolean(admission.live.queueExit);
|
|
33
|
+
const worker = await observePipelineWorker(session, current, queue, host);
|
|
34
|
+
const input = { current, live: admission.live, queue, worker, evidence: {} };
|
|
35
|
+
return {
|
|
36
|
+
...input,
|
|
37
|
+
admission,
|
|
38
|
+
observed,
|
|
39
|
+
attempt: observed.attempt,
|
|
40
|
+
phase: observed.missing[0],
|
|
41
|
+
decision: reconcilePipeline(input),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function cleanup(fresh, session, inputs, host, delivery) {
|
|
46
|
+
const ports = {
|
|
47
|
+
observe: () => observePipelineDelivery(session, inputs, host, delivery),
|
|
48
|
+
progress: session.progress,
|
|
49
|
+
service: delivery.service,
|
|
50
|
+
materials: host.materialStore(session),
|
|
51
|
+
wake: async (successor) => {
|
|
52
|
+
const retained = await host.provider.lookup(
|
|
53
|
+
host.repository,
|
|
54
|
+
successor.pullRequestNumber,
|
|
55
|
+
session.intent.source.targetBranch,
|
|
56
|
+
);
|
|
57
|
+
if (retained)
|
|
58
|
+
await host.wake(readBusinessAttempt(retained.snapshot).attempt);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
if (fresh.decision.operation === "record-cancellation")
|
|
62
|
+
return recoverPipelineCancellation(fresh, ports);
|
|
63
|
+
if (
|
|
64
|
+
["stop-worker", "cancel-queued", "settle-cancelled"].includes(
|
|
65
|
+
fresh.decision.operation,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
return cancelPipelineCandidate(fresh, ports);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function retainDeliveryRequest(fresh, session, build, host) {
|
|
73
|
+
const scheduling = await session.journal.read();
|
|
74
|
+
const schedulingCurrent = {
|
|
75
|
+
...scheduling.history.at(-1),
|
|
76
|
+
intent: session.intent,
|
|
77
|
+
};
|
|
78
|
+
// The provider execution owns the delivery slot before it acquires a
|
|
79
|
+
// Warrant. A later wake must not replace its retained coordinates while its
|
|
80
|
+
// queued or running job is still entitled to perform final admission.
|
|
81
|
+
const executionOwner = await pipelineDeliveryExecution(
|
|
82
|
+
session,
|
|
83
|
+
schedulingCurrent,
|
|
84
|
+
host,
|
|
85
|
+
);
|
|
86
|
+
if (executionOwner) {
|
|
87
|
+
const { run } = await host.runs.read(
|
|
88
|
+
executionOwner.runId,
|
|
89
|
+
executionOwner.runAttempt,
|
|
90
|
+
);
|
|
91
|
+
if (
|
|
92
|
+
run.id !== executionOwner.runId ||
|
|
93
|
+
run.run_attempt !== executionOwner.runAttempt
|
|
94
|
+
)
|
|
95
|
+
throw new Error("Retained delivery execution provider identity drift");
|
|
96
|
+
if (run.status !== "completed")
|
|
97
|
+
return {
|
|
98
|
+
operation: "wait",
|
|
99
|
+
reason: "admitted-delivery-execution-running",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const active = await observePipelineWorker(
|
|
103
|
+
session,
|
|
104
|
+
schedulingCurrent,
|
|
105
|
+
fresh.queue,
|
|
106
|
+
host,
|
|
107
|
+
);
|
|
108
|
+
if (active?.status === "in_progress")
|
|
109
|
+
return { operation: "wait", reason: "native-worker-running" };
|
|
110
|
+
const request = await (host.prepareDelivery || preparePipelineDelivery)({
|
|
111
|
+
current: fresh.current,
|
|
112
|
+
run: build.run,
|
|
113
|
+
build: build.readback,
|
|
114
|
+
runtimeSha: host.runtime.sha,
|
|
115
|
+
token: host.token,
|
|
116
|
+
plan: fresh.admission.plan,
|
|
117
|
+
});
|
|
118
|
+
const execution = {
|
|
119
|
+
schema: "buildchain.pipeline-delivery-execution/v1",
|
|
120
|
+
attempt: fresh.attempt,
|
|
121
|
+
generation: fresh.current.generation.id,
|
|
122
|
+
runId: host.runId,
|
|
123
|
+
runAttempt: host.runAttempt,
|
|
124
|
+
request,
|
|
125
|
+
};
|
|
126
|
+
const reference = await host
|
|
127
|
+
.materialStore(session)
|
|
128
|
+
.retain(`delivery/execution-${host.runId}-${host.runAttempt}`, execution);
|
|
129
|
+
await session.progress.progress({
|
|
130
|
+
attempt: fresh.attempt,
|
|
131
|
+
phase: "warrant",
|
|
132
|
+
state: "running",
|
|
133
|
+
eventKey: `delivery:${host.runId}:${host.runAttempt}`,
|
|
134
|
+
materials: [reference],
|
|
135
|
+
expectedHead: scheduling.head,
|
|
136
|
+
});
|
|
137
|
+
return { operation: "deliver", request, attempt: fresh.attempt };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function controlPipelineDelivery(session, inputs, host) {
|
|
141
|
+
const delivery = host.delivery
|
|
142
|
+
? host.delivery(session)
|
|
143
|
+
: pipelineDeliveryStore(session, host);
|
|
144
|
+
const fresh = await observePipelineDelivery(session, inputs, host, delivery);
|
|
145
|
+
const candidate = pipelineCandidate(fresh.queue, fresh.current);
|
|
146
|
+
if (["cancelled", "dequeued"].includes(candidate?.status))
|
|
147
|
+
fresh.decision = {
|
|
148
|
+
operation: "record-cancellation",
|
|
149
|
+
candidateId: candidate.candidateId,
|
|
150
|
+
};
|
|
151
|
+
if (candidate?.status === "terminal-failure") {
|
|
152
|
+
const material = await host
|
|
153
|
+
.materialStore(session)
|
|
154
|
+
.retain(
|
|
155
|
+
`failure/${fresh.queue.stateRoot.slice(7)}`,
|
|
156
|
+
fresh.queue,
|
|
157
|
+
"provider-readback",
|
|
158
|
+
);
|
|
159
|
+
await session.progress.progress({
|
|
160
|
+
attempt: fresh.attempt,
|
|
161
|
+
phase: fresh.phase,
|
|
162
|
+
state: "failure",
|
|
163
|
+
eventKey: `failure:${fresh.queue.stateRoot}`,
|
|
164
|
+
reason: "native-execution-failed",
|
|
165
|
+
materials: [material],
|
|
166
|
+
});
|
|
167
|
+
return { operation: "wait", reason: "native-execution-failed" };
|
|
168
|
+
}
|
|
169
|
+
const result = await cleanup(fresh, session, inputs, host, delivery);
|
|
170
|
+
if (result) {
|
|
171
|
+
if (result.status === "cancelled") await host.wake(fresh.attempt);
|
|
172
|
+
return { operation: "wait", reason: result.reason || result.status };
|
|
173
|
+
}
|
|
174
|
+
if (fresh.decision.operation === "supersede") {
|
|
175
|
+
if (
|
|
176
|
+
!["cancelled", "failure", "superseded", "complete"].includes(
|
|
177
|
+
fresh.observed.status,
|
|
178
|
+
)
|
|
179
|
+
) {
|
|
180
|
+
try {
|
|
181
|
+
await session.progress.progress({
|
|
182
|
+
attempt: fresh.attempt,
|
|
183
|
+
phase: fresh.phase,
|
|
184
|
+
state: "superseded",
|
|
185
|
+
eventKey: `supersede:${recordDigest(fresh.live)}`,
|
|
186
|
+
reason: fresh.decision.reason,
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
// Concurrent notifications can finish the same attempt after readback.
|
|
190
|
+
// Its immutable terminal result is sufficient; never rewrite that phase.
|
|
191
|
+
const latest = await session.journal.read();
|
|
192
|
+
if (
|
|
193
|
+
latest.attempt !== fresh.attempt ||
|
|
194
|
+
!["cancelled", "failure", "superseded", "complete"].includes(
|
|
195
|
+
latest.status,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { operation: "successor", reason: fresh.decision.reason };
|
|
202
|
+
}
|
|
203
|
+
if (fresh.live.merged) return { operation: "settle", fresh, delivery };
|
|
204
|
+
if (fresh.live.state !== "open" || fresh.live.terminalOnly)
|
|
205
|
+
return { operation: "wait", reason: "no-execution-admission" };
|
|
206
|
+
const build = await pipelineBuildEvidence(session, host);
|
|
207
|
+
if (!build) return { operation: "build", admission: fresh.admission };
|
|
208
|
+
if (build.run.status !== "completed")
|
|
209
|
+
return { operation: "wait", reason: "source-workflow-still-running" };
|
|
210
|
+
const policy = await host.policy.observe(
|
|
211
|
+
fresh.current,
|
|
212
|
+
fresh.admission.protectedPlan.review,
|
|
213
|
+
);
|
|
214
|
+
if (
|
|
215
|
+
!policy.review ||
|
|
216
|
+
!policy.checksPassing ||
|
|
217
|
+
!fresh.live.ready ||
|
|
218
|
+
fresh.live.draft
|
|
219
|
+
)
|
|
220
|
+
return { operation: "wait", reason: "protected-source-gates-pending" };
|
|
221
|
+
if (!fresh.current.phases.review) {
|
|
222
|
+
const material = await host
|
|
223
|
+
.materialStore(session)
|
|
224
|
+
.retain(`review/${policy.root.slice(7)}`, policy, "provider-readback");
|
|
225
|
+
await session.progress.progress({
|
|
226
|
+
attempt: fresh.attempt,
|
|
227
|
+
phase: "review",
|
|
228
|
+
state: "success",
|
|
229
|
+
eventKey: `review:${policy.root}`,
|
|
230
|
+
materials: [material],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const decision = reconcilePipeline({
|
|
234
|
+
...fresh,
|
|
235
|
+
evidence: {
|
|
236
|
+
build,
|
|
237
|
+
review: policy,
|
|
238
|
+
checks: policy,
|
|
239
|
+
qualification: fresh.queue.activeWarrant?.phase === "qualified",
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
if (fresh.worker?.status === "in_progress")
|
|
243
|
+
return { operation: "wait", reason: "native-worker-running" };
|
|
244
|
+
if (
|
|
245
|
+
["reserve", "select", "qualify-native", "land"].includes(decision.operation)
|
|
246
|
+
)
|
|
247
|
+
return retainDeliveryRequest(fresh, session, build, host);
|
|
248
|
+
return decision;
|
|
249
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { GitHubDevDeliveryStore } from "../../providers/dev-delivery/store.js";
|
|
2
|
+
import { defaultDevDeliveryStateRef } from "../../dev-delivery/warrant/values.js";
|
|
3
|
+
import { createDeliveryWarrantService } from "../../dev-delivery/warrant/service.js";
|
|
4
|
+
import { PIPELINE_WORKER } from "../../providers/github/pipeline-worker.js";
|
|
5
|
+
import { pipelineCandidate } from "./reconcile.js";
|
|
6
|
+
|
|
7
|
+
function workerJob(jobs, name) {
|
|
8
|
+
const selected = jobs.filter(
|
|
9
|
+
(job) => job.name === name || job.name.endsWith(` / ${name}`),
|
|
10
|
+
);
|
|
11
|
+
if (selected.length > 1)
|
|
12
|
+
throw new Error("Native worker job identity is ambiguous");
|
|
13
|
+
return selected[0];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function pipelineDeliveryExecution(session, current, host) {
|
|
17
|
+
const phase = [...current.events]
|
|
18
|
+
.reverse()
|
|
19
|
+
.find((event) =>
|
|
20
|
+
event.payload.materials.some((material) =>
|
|
21
|
+
material.id.startsWith("delivery/execution-"),
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
const references =
|
|
25
|
+
phase?.payload.materials.filter((material) =>
|
|
26
|
+
material.id.startsWith("delivery/execution-"),
|
|
27
|
+
) || [];
|
|
28
|
+
if (!references.length) return null;
|
|
29
|
+
if (references.length !== 1)
|
|
30
|
+
throw new Error("Native worker requires one admitted execution");
|
|
31
|
+
const execution = await host.materialStore(session).read(references[0]);
|
|
32
|
+
if (
|
|
33
|
+
execution.attempt !== current.identity.id ||
|
|
34
|
+
execution.generation !== current.generation.id
|
|
35
|
+
)
|
|
36
|
+
throw new Error("Native worker execution belongs to another attempt");
|
|
37
|
+
return execution;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function observePipelineWorker(session, current, queue, host) {
|
|
41
|
+
const warrant = queue.activeWarrant;
|
|
42
|
+
const candidate = pipelineCandidate(queue, current);
|
|
43
|
+
if (
|
|
44
|
+
!warrant ||
|
|
45
|
+
candidate?.candidateId !== warrant.candidateId ||
|
|
46
|
+
warrant.sourceHead !== current.generation.source.commit ||
|
|
47
|
+
warrant.pullRequestNumber !== current.intent.source.pullRequest
|
|
48
|
+
)
|
|
49
|
+
return null;
|
|
50
|
+
const execution = await pipelineDeliveryExecution(session, current, host);
|
|
51
|
+
if (!execution) return null;
|
|
52
|
+
const { run, jobs } = await host.runs.read(
|
|
53
|
+
execution.runId,
|
|
54
|
+
execution.runAttempt,
|
|
55
|
+
);
|
|
56
|
+
if (run.status !== "completed") {
|
|
57
|
+
const terminalJobs = [
|
|
58
|
+
"Credentialless native execution",
|
|
59
|
+
"Credentialless native evidence seal",
|
|
60
|
+
].map((name) => workerJob(jobs, name));
|
|
61
|
+
if (terminalJobs.some((job) => !job || job.status !== "completed"))
|
|
62
|
+
return {
|
|
63
|
+
status: "in_progress",
|
|
64
|
+
reason: "admitted-execution-not-terminal",
|
|
65
|
+
run,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const entries = (run.referenced_workflows || []).filter((entry) =>
|
|
69
|
+
entry.path?.startsWith(
|
|
70
|
+
"kungfu-systems/buildchain/.github/workflows/public-ops-dev-auto-merge.yml@",
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
if (entries.length !== 1) return null;
|
|
74
|
+
const native = workerJob(jobs, "Credentialless native execution"),
|
|
75
|
+
seal = workerJob(jobs, "Credentialless native evidence seal");
|
|
76
|
+
if (!native || !seal) return null;
|
|
77
|
+
if (
|
|
78
|
+
run.status !== "completed" &&
|
|
79
|
+
run.id !== host.runId &&
|
|
80
|
+
native.status === "completed" &&
|
|
81
|
+
seal.status === "completed"
|
|
82
|
+
)
|
|
83
|
+
await host.runs.completed(execution.runId, execution.runAttempt);
|
|
84
|
+
return host.workers.observe(
|
|
85
|
+
{
|
|
86
|
+
schema: PIPELINE_WORKER,
|
|
87
|
+
repository: host.repository,
|
|
88
|
+
attempt: current.identity.id,
|
|
89
|
+
generation: current.generation.id,
|
|
90
|
+
sourceHead: warrant.sourceHead,
|
|
91
|
+
candidateId: warrant.candidateId,
|
|
92
|
+
fencingToken: warrant.fencingToken,
|
|
93
|
+
leaseGeneration: warrant.generation,
|
|
94
|
+
runId: execution.runId,
|
|
95
|
+
runAttempt: execution.runAttempt,
|
|
96
|
+
nativeJobId: native.id,
|
|
97
|
+
sealJobId: seal.id,
|
|
98
|
+
},
|
|
99
|
+
current,
|
|
100
|
+
warrant,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function pipelineDeliveryStore(session, host) {
|
|
105
|
+
const branch = session.intent.source.targetBranch;
|
|
106
|
+
const store = new GitHubDevDeliveryStore({
|
|
107
|
+
repository: host.repository,
|
|
108
|
+
token: host.token,
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
read: async () =>
|
|
112
|
+
(
|
|
113
|
+
await store.read({
|
|
114
|
+
stateRef: defaultDevDeliveryStateRef(branch),
|
|
115
|
+
protectedBase: branch,
|
|
116
|
+
now: new Date().toISOString(),
|
|
117
|
+
})
|
|
118
|
+
).queue,
|
|
119
|
+
service: createDeliveryWarrantService(
|
|
120
|
+
{ repository: host.repository, token: host.token, branch },
|
|
121
|
+
store,
|
|
122
|
+
),
|
|
123
|
+
};
|
|
124
|
+
}
|