@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,122 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { assertPipelineExecution } from "../../workflow/pipeline/fence.js";
|
|
3
|
+
import {
|
|
4
|
+
PUBLICATION_IMPORT,
|
|
5
|
+
publicationImportedValues,
|
|
6
|
+
} from "./imported-materials.js";
|
|
7
|
+
|
|
8
|
+
export function pipelinePublicationJournal(session, host) {
|
|
9
|
+
const initial = session.observed;
|
|
10
|
+
const expected = {
|
|
11
|
+
attempt: initial.attempt,
|
|
12
|
+
generation: initial.generation,
|
|
13
|
+
intent: session.intent.id,
|
|
14
|
+
sourceRoot: recordDigest(initial.history.at(-1).generation.source),
|
|
15
|
+
};
|
|
16
|
+
const store = host.materialStore(session);
|
|
17
|
+
async function fence() {
|
|
18
|
+
const observed = await session.journal.read();
|
|
19
|
+
assertPipelineExecution(observed, expected);
|
|
20
|
+
const writer = observed.history
|
|
21
|
+
.at(-1)
|
|
22
|
+
.events.filter((event) =>
|
|
23
|
+
event.payload.materials.some((material) =>
|
|
24
|
+
material.id.startsWith("publication/worker/"),
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
.at(-1)?.payload.writer;
|
|
28
|
+
if (
|
|
29
|
+
writer &&
|
|
30
|
+
(writer.runId !== String(host.runId) ||
|
|
31
|
+
writer.runAttempt !== String(host.runAttempt))
|
|
32
|
+
)
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Publication worker lost its exact provider execution reservation",
|
|
35
|
+
);
|
|
36
|
+
return observed;
|
|
37
|
+
}
|
|
38
|
+
async function materials(prefix) {
|
|
39
|
+
const observed = await session.journal.read();
|
|
40
|
+
if (observed.attempt !== initial.attempt)
|
|
41
|
+
throw new Error("Publication journal is no longer the current attempt");
|
|
42
|
+
const references = observed.history
|
|
43
|
+
.at(-1)
|
|
44
|
+
.events.flatMap((event) => event.payload.materials)
|
|
45
|
+
.filter(
|
|
46
|
+
(reference) =>
|
|
47
|
+
reference.id.startsWith(prefix) ||
|
|
48
|
+
reference.id.startsWith("publication/recovery-import/"),
|
|
49
|
+
);
|
|
50
|
+
const unique = [
|
|
51
|
+
...new Map(
|
|
52
|
+
references.map((reference) => [reference.digest, reference]),
|
|
53
|
+
).values(),
|
|
54
|
+
];
|
|
55
|
+
const values = [];
|
|
56
|
+
for (const reference of unique) {
|
|
57
|
+
const value = await store.read(reference);
|
|
58
|
+
if (value.schema === PUBLICATION_IMPORT)
|
|
59
|
+
values.push(
|
|
60
|
+
...publicationImportedValues(value)
|
|
61
|
+
.filter((item) => item.id.startsWith(prefix))
|
|
62
|
+
.map((item) => item.value),
|
|
63
|
+
);
|
|
64
|
+
else if (reference.id.startsWith(prefix)) values.push(value);
|
|
65
|
+
}
|
|
66
|
+
return [
|
|
67
|
+
...new Map(values.map((value) => [recordDigest(value), value])).values(),
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
async function record(
|
|
71
|
+
id,
|
|
72
|
+
value,
|
|
73
|
+
{ phase = "publish", state = "running", expectedHead } = {},
|
|
74
|
+
) {
|
|
75
|
+
const root = recordDigest(value);
|
|
76
|
+
const observed = await session.journal.read();
|
|
77
|
+
const eventKey = `publication:${id}:${root}`;
|
|
78
|
+
const prior = observed.history
|
|
79
|
+
.at(-1)
|
|
80
|
+
.events.find((event) => event.payload.eventKey === eventKey);
|
|
81
|
+
if (prior) {
|
|
82
|
+
if (
|
|
83
|
+
observed.attempt !== initial.attempt ||
|
|
84
|
+
prior.node !== phase ||
|
|
85
|
+
prior.payload.state !== state ||
|
|
86
|
+
prior.payload.materials.length !== 1 ||
|
|
87
|
+
recordDigest(await store.read(prior.payload.materials[0])) !== root
|
|
88
|
+
)
|
|
89
|
+
throw new Error(
|
|
90
|
+
"Publication retry changed its immutable recorded result",
|
|
91
|
+
);
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
const reference = await store.retain(
|
|
95
|
+
`${id}/${root.slice(7)}`,
|
|
96
|
+
value,
|
|
97
|
+
"receipt",
|
|
98
|
+
);
|
|
99
|
+
await session.progress.progress({
|
|
100
|
+
attempt: initial.attempt,
|
|
101
|
+
phase,
|
|
102
|
+
state,
|
|
103
|
+
eventKey,
|
|
104
|
+
materials: [reference],
|
|
105
|
+
...(expectedHead !== undefined ? { expectedHead } : {}),
|
|
106
|
+
});
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
async function recordImport(value, phase) {
|
|
110
|
+
publicationImportedValues(value);
|
|
111
|
+
const observed = await fence();
|
|
112
|
+
const id = "publication/recovery-import";
|
|
113
|
+
const eventKey = `publication:${id}:${recordDigest(value)}`;
|
|
114
|
+
const prior = observed.history
|
|
115
|
+
.at(-1)
|
|
116
|
+
.events.find((event) => event.payload.eventKey === eventKey);
|
|
117
|
+
// Import is one atomic append, not a new result for each resumed phase.
|
|
118
|
+
// Replay still verifies the original event, state and retained bytes.
|
|
119
|
+
return record(id, value, { phase: prior?.node || phase });
|
|
120
|
+
}
|
|
121
|
+
return { fence, materials, record, recordImport };
|
|
122
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { observeAdvancedPipelineDevelopment } from "./development-current.js";
|
|
2
|
+
import { createPipelineDevelopmentTransition } from "./development-transition.js";
|
|
3
|
+
import { githubPipelineVersion } from "../../providers/github/pipeline-version.js";
|
|
4
|
+
import { uniquePublicationMaterial } from "./context.js";
|
|
5
|
+
import { observePipelineVersionAnchor } from "./development-anchor.js";
|
|
6
|
+
import { developmentTransitionReadback } from "./development-proof.js";
|
|
7
|
+
import { prepareNextDevelopmentPullRequest } from "./development-pr.js";
|
|
8
|
+
|
|
9
|
+
export async function nextPipelineDevelopment(context, host, journal) {
|
|
10
|
+
const { plan } = context;
|
|
11
|
+
const publication = await uniquePublicationMaterial(
|
|
12
|
+
journal,
|
|
13
|
+
"publication/complete/",
|
|
14
|
+
);
|
|
15
|
+
const transitions = await journal.materials(
|
|
16
|
+
"publication/next-development-plan/",
|
|
17
|
+
);
|
|
18
|
+
if (transitions.length > 1)
|
|
19
|
+
throw new Error("Next-development has conflicting retained declarations");
|
|
20
|
+
let transition = transitions[0];
|
|
21
|
+
if (!transition) {
|
|
22
|
+
transition = createPipelineDevelopmentTransition(context, publication);
|
|
23
|
+
await journal.record("publication/next-development-plan", transition, {
|
|
24
|
+
phase: "next-development",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (transition.model.strategy === "anchored")
|
|
28
|
+
return observePipelineVersionAnchor(context, host, transition);
|
|
29
|
+
const retained = await journal.materials("publication/next-development-pr/");
|
|
30
|
+
if (retained.length > 1)
|
|
31
|
+
throw new Error("Next-development has conflicting retained PRs");
|
|
32
|
+
let selected = retained[0];
|
|
33
|
+
const adapter = githubPipelineVersion(host.request, host.repository);
|
|
34
|
+
if (!selected) {
|
|
35
|
+
const advanced = await observeAdvancedPipelineDevelopment(
|
|
36
|
+
context,
|
|
37
|
+
host,
|
|
38
|
+
transition,
|
|
39
|
+
adapter,
|
|
40
|
+
);
|
|
41
|
+
if (advanced) return advanced;
|
|
42
|
+
selected = await prepareNextDevelopmentPullRequest(
|
|
43
|
+
context,
|
|
44
|
+
host,
|
|
45
|
+
journal,
|
|
46
|
+
transition,
|
|
47
|
+
adapter,
|
|
48
|
+
);
|
|
49
|
+
if (selected.versionContext) return selected;
|
|
50
|
+
}
|
|
51
|
+
const pr = await host.request(
|
|
52
|
+
`/repos/${host.repository}/pulls/${selected.number}`,
|
|
53
|
+
);
|
|
54
|
+
if (
|
|
55
|
+
pr.head?.sha !== selected.source.commit ||
|
|
56
|
+
pr.head?.ref !== selected.branch ||
|
|
57
|
+
pr.base?.ref !== selected.base ||
|
|
58
|
+
pr.head?.repo?.full_name !== host.repository
|
|
59
|
+
)
|
|
60
|
+
throw new Error(
|
|
61
|
+
"Next-development PR changed its retained source or target",
|
|
62
|
+
);
|
|
63
|
+
const before = await adapter.inspect(
|
|
64
|
+
selected.materialization.protectedSource,
|
|
65
|
+
plan.versionPolicy,
|
|
66
|
+
);
|
|
67
|
+
const after = await adapter.inspect(selected.source, plan.versionPolicy);
|
|
68
|
+
const pending = developmentTransitionReadback(transition, {
|
|
69
|
+
before,
|
|
70
|
+
after,
|
|
71
|
+
evidence: selected,
|
|
72
|
+
createdAt: pr.created_at,
|
|
73
|
+
});
|
|
74
|
+
if (!pr.merged)
|
|
75
|
+
return {
|
|
76
|
+
state: "waiting",
|
|
77
|
+
reason:
|
|
78
|
+
pr.state === "open"
|
|
79
|
+
? "next-development-protected-pr-pending"
|
|
80
|
+
: "next-development-pr-closed",
|
|
81
|
+
pullRequest: selected.number,
|
|
82
|
+
source: selected.source,
|
|
83
|
+
transition: pending,
|
|
84
|
+
};
|
|
85
|
+
const integration = await host.integration.observe({
|
|
86
|
+
intent: {
|
|
87
|
+
source: { pullRequest: selected.number, targetBranch: selected.base },
|
|
88
|
+
},
|
|
89
|
+
generation: {
|
|
90
|
+
source: { ...selected.source, configPath: plan.source.configPath },
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
const source = await host.source.source(
|
|
94
|
+
integration.mergeCommit,
|
|
95
|
+
plan.source.configPath,
|
|
96
|
+
);
|
|
97
|
+
const readback = await adapter.inspect(source.identity, plan.versionPolicy);
|
|
98
|
+
if (readback.version !== transition.target.version)
|
|
99
|
+
throw new Error(
|
|
100
|
+
"Protected next-development version readback differs from its declaration",
|
|
101
|
+
);
|
|
102
|
+
return {
|
|
103
|
+
state: "success",
|
|
104
|
+
reason: "protected-next-development-verified",
|
|
105
|
+
transitionRoot: transition.idempotencyKey,
|
|
106
|
+
integration,
|
|
107
|
+
version: readback.version,
|
|
108
|
+
publicationRoot: publication.release.receiptRoot,
|
|
109
|
+
transition: developmentTransitionReadback(transition, {
|
|
110
|
+
before,
|
|
111
|
+
after: readback,
|
|
112
|
+
evidence: integration,
|
|
113
|
+
createdAt: pr.created_at,
|
|
114
|
+
mergedAt: pr.merged_at,
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { packNpmArtifact, sealedPackResult } from "../npm/package.js";
|
|
5
|
+
import { readNpmPackageJsonFromTarball } from "../../release/candidate/payloads.js";
|
|
6
|
+
import { createNativeChildEnvironment } from "../../dev-delivery/native/execution.js";
|
|
7
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
8
|
+
import { verifyPipelinePublicationPlan } from "./plan.js";
|
|
9
|
+
import { assertPipelinePackagePolicy } from "./package-policy.js";
|
|
10
|
+
import {
|
|
11
|
+
publicationPath,
|
|
12
|
+
publicationFile,
|
|
13
|
+
writeImmutablePublicationFile,
|
|
14
|
+
} from "./files.js";
|
|
15
|
+
|
|
16
|
+
function packageArtifact(directory, output, expected, version, environment) {
|
|
17
|
+
const product = publicationPath(directory, expected.path, "directory");
|
|
18
|
+
const pack = packNpmArtifact({
|
|
19
|
+
cwd: product,
|
|
20
|
+
env: createNativeChildEnvironment(environment),
|
|
21
|
+
outputDirectory: output,
|
|
22
|
+
ignoreScripts: true,
|
|
23
|
+
registry: "https://registry.npmjs.org/",
|
|
24
|
+
});
|
|
25
|
+
const sealed = sealedPackResult({
|
|
26
|
+
tarballPath: pack.tarballPath,
|
|
27
|
+
integrity: pack.integrity,
|
|
28
|
+
});
|
|
29
|
+
const pkg = readNpmPackageJsonFromTarball(pack.tarballPath);
|
|
30
|
+
assertPipelinePackagePolicy(pkg);
|
|
31
|
+
if (
|
|
32
|
+
pkg.name !== pack.name ||
|
|
33
|
+
pkg.version !== version ||
|
|
34
|
+
pack.version !== version ||
|
|
35
|
+
pkg.private === true
|
|
36
|
+
)
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Sealed npm package does not match the planned public package version",
|
|
39
|
+
);
|
|
40
|
+
return {
|
|
41
|
+
file: pack.tarballPath,
|
|
42
|
+
suffix: ".tgz",
|
|
43
|
+
package: { name: pkg.name, version, integrity: sealed.integrity },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function archiveNames(file, suffix) {
|
|
48
|
+
const options = {
|
|
49
|
+
cwd: path.dirname(file),
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
52
|
+
timeout: 30000,
|
|
53
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
54
|
+
};
|
|
55
|
+
if (suffix === ".zip") {
|
|
56
|
+
const script = [
|
|
57
|
+
"import json,sys,zipfile",
|
|
58
|
+
"with zipfile.ZipFile(sys.argv[1]) as archive:",
|
|
59
|
+
" print(json.dumps(archive.namelist()))",
|
|
60
|
+
].join("\n");
|
|
61
|
+
return JSON.parse(
|
|
62
|
+
execFileSync(
|
|
63
|
+
process.platform === "win32" ? "python" : "python3",
|
|
64
|
+
["-c", script, path.basename(file)],
|
|
65
|
+
options,
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return execFileSync("tar", ["-tf", path.basename(file)], options)
|
|
70
|
+
.trim()
|
|
71
|
+
.split(/\r?\n/u);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function inspectPipelineFileArtifact(directory, expected) {
|
|
75
|
+
const file = publicationPath(directory, expected.path);
|
|
76
|
+
if (expected.kind === "pdf") {
|
|
77
|
+
const bytes = fs.readFileSync(file);
|
|
78
|
+
if (
|
|
79
|
+
!bytes.subarray(0, 8).toString().startsWith("%PDF-") ||
|
|
80
|
+
!bytes.subarray(-1024).toString().includes("%%EOF")
|
|
81
|
+
)
|
|
82
|
+
throw new Error("Declared Paper output is not a complete PDF document");
|
|
83
|
+
return { file, suffix: ".pdf" };
|
|
84
|
+
}
|
|
85
|
+
const suffix = [".tar.gz", ".tar.xz", ".tgz", ".zip", ".tar"].find((value) =>
|
|
86
|
+
file.endsWith(value),
|
|
87
|
+
);
|
|
88
|
+
if (!suffix)
|
|
89
|
+
throw new Error("Declared binary output must be a standard archive");
|
|
90
|
+
const names = archiveNames(file, suffix);
|
|
91
|
+
if (
|
|
92
|
+
!names[0] ||
|
|
93
|
+
names.some(
|
|
94
|
+
(name) =>
|
|
95
|
+
!name.trim() ||
|
|
96
|
+
/[\u0000-\u001f\u007f]/u.test(name) ||
|
|
97
|
+
name.replaceAll("\\", "/").startsWith("/") ||
|
|
98
|
+
/^[A-Za-z]:/u.test(name) ||
|
|
99
|
+
name.replaceAll("\\", "/").split("/").includes(".."),
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
throw new Error("Binary archive contains unsafe or empty paths");
|
|
103
|
+
return { file, suffix };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function packPipelineProducts({
|
|
107
|
+
cwd,
|
|
108
|
+
output,
|
|
109
|
+
plan,
|
|
110
|
+
platform,
|
|
111
|
+
source,
|
|
112
|
+
environment = process.env,
|
|
113
|
+
}) {
|
|
114
|
+
verifyPipelinePublicationPlan(plan);
|
|
115
|
+
const expected = plan.outputs.filter((item) => item.platform === platform);
|
|
116
|
+
if (!expected.length)
|
|
117
|
+
throw new Error("Publication platform was not declared");
|
|
118
|
+
fs.mkdirSync(output, { recursive: true });
|
|
119
|
+
const artifacts = expected.map((item) => {
|
|
120
|
+
const directory = publicationPath(cwd, item.directory, "directory");
|
|
121
|
+
const staging = path.join(output, "npm-pack", item.product, item.artifact);
|
|
122
|
+
const packed =
|
|
123
|
+
item.kind === "npm-package"
|
|
124
|
+
? packageArtifact(directory, staging, item, plan.version, environment)
|
|
125
|
+
: inspectPipelineFileArtifact(directory, item);
|
|
126
|
+
const observed = publicationFile(packed.file);
|
|
127
|
+
const name =
|
|
128
|
+
item.filename ||
|
|
129
|
+
`${item.product}-${platform}-${item.artifact}${packed.suffix}`;
|
|
130
|
+
if (!name.endsWith(packed.suffix))
|
|
131
|
+
throw new Error(
|
|
132
|
+
"Declared publication filename does not match the actual artifact format",
|
|
133
|
+
);
|
|
134
|
+
writeImmutablePublicationFile(
|
|
135
|
+
path.join(output, "payloads", name),
|
|
136
|
+
observed.bytes,
|
|
137
|
+
);
|
|
138
|
+
return {
|
|
139
|
+
...item,
|
|
140
|
+
file: `payloads/${name}`,
|
|
141
|
+
size: observed.size,
|
|
142
|
+
digest: observed.digest,
|
|
143
|
+
...(packed.package ? { package: packed.package } : {}),
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
const body = {
|
|
147
|
+
schema: "buildchain.pipeline-publication-products/v1",
|
|
148
|
+
planRoot: plan.root,
|
|
149
|
+
source,
|
|
150
|
+
platform,
|
|
151
|
+
artifacts,
|
|
152
|
+
};
|
|
153
|
+
const manifest = { ...body, root: recordDigest(body) };
|
|
154
|
+
writeImmutablePublicationFile(
|
|
155
|
+
path.join(output, "manifest.json"),
|
|
156
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
157
|
+
);
|
|
158
|
+
return manifest;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function verifyPipelineProductFiles(directory, manifest) {
|
|
162
|
+
const { root, ...body } = manifest;
|
|
163
|
+
if (
|
|
164
|
+
body.schema !== "buildchain.pipeline-publication-products/v1" ||
|
|
165
|
+
root !== recordDigest(body)
|
|
166
|
+
)
|
|
167
|
+
throw new Error("Publication manifest root does not match retained bytes");
|
|
168
|
+
for (const artifact of manifest.artifacts) {
|
|
169
|
+
const observed = publicationFile(publicationPath(directory, artifact.file));
|
|
170
|
+
if (
|
|
171
|
+
observed.size !== artifact.size ||
|
|
172
|
+
observed.digest !== artifact.digest ||
|
|
173
|
+
(artifact.package && observed.integrity !== artifact.package.integrity)
|
|
174
|
+
)
|
|
175
|
+
throw new Error(
|
|
176
|
+
"Publication artifact changed after its manifest was sealed",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return manifest;
|
|
180
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function assertPipelinePackagePolicy(pkg) {
|
|
2
|
+
const config = pkg.publishConfig;
|
|
3
|
+
if (config === undefined) return;
|
|
4
|
+
if (
|
|
5
|
+
!config ||
|
|
6
|
+
Array.isArray(config) ||
|
|
7
|
+
typeof config !== "object" ||
|
|
8
|
+
Object.keys(config).some(
|
|
9
|
+
(key) => !["access", "registry", "tag", "provenance"].includes(key),
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
throw new Error(
|
|
13
|
+
"npm publishConfig cannot add provider, credential or execution configuration to the product contract",
|
|
14
|
+
);
|
|
15
|
+
if (
|
|
16
|
+
config.registry !== undefined &&
|
|
17
|
+
!["https://registry.npmjs.org", "https://registry.npmjs.org/"].includes(
|
|
18
|
+
config.registry,
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
throw new Error("npm publication requires the declared npmjs provider");
|
|
22
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { recordDigest } from "../../release/discussion/envelope.js";
|
|
2
|
+
import { selectProductPublicationIntent } from "../../release/product-publication.js";
|
|
3
|
+
|
|
4
|
+
export function pipelineExpectedProducts(plan) {
|
|
5
|
+
const outputs = plan.products
|
|
6
|
+
.flatMap((product) =>
|
|
7
|
+
product.platforms.flatMap((platform) =>
|
|
8
|
+
product.artifacts.map((artifact) => ({
|
|
9
|
+
id: `${product.id}/${platform}/${artifact.id}`,
|
|
10
|
+
product: product.id,
|
|
11
|
+
platform,
|
|
12
|
+
artifact: artifact.id,
|
|
13
|
+
kind: artifact.kind,
|
|
14
|
+
directory: product.directory || ".",
|
|
15
|
+
path: artifact.path,
|
|
16
|
+
...(artifact.filename ? { filename: artifact.filename } : {}),
|
|
17
|
+
targets: product.targets
|
|
18
|
+
.filter((target) => target.artifacts.includes(artifact.id))
|
|
19
|
+
.map(({ provider, access }) => ({
|
|
20
|
+
provider,
|
|
21
|
+
...(access ? { access } : {}),
|
|
22
|
+
})),
|
|
23
|
+
})),
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
27
|
+
if (
|
|
28
|
+
!outputs.length ||
|
|
29
|
+
outputs.length > 256 ||
|
|
30
|
+
new Set(outputs.map(({ id }) => id)).size !== outputs.length
|
|
31
|
+
)
|
|
32
|
+
throw new Error(
|
|
33
|
+
"Publication requires a bounded unique product/platform/artifact inventory",
|
|
34
|
+
);
|
|
35
|
+
const filenames = outputs.map(
|
|
36
|
+
(output) =>
|
|
37
|
+
output.filename ||
|
|
38
|
+
`${output.product}-${output.platform}-${output.artifact}${
|
|
39
|
+
output.kind === "npm-package"
|
|
40
|
+
? ".tgz"
|
|
41
|
+
: output.kind === "pdf"
|
|
42
|
+
? ".pdf"
|
|
43
|
+
: [".tar.gz", ".tar.xz", ".tgz", ".zip", ".tar"].find((suffix) =>
|
|
44
|
+
output.path.endsWith(suffix),
|
|
45
|
+
)
|
|
46
|
+
}`,
|
|
47
|
+
);
|
|
48
|
+
if (new Set(filenames).size !== filenames.length)
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Publication artifact filenames must be unique across every declared platform",
|
|
51
|
+
);
|
|
52
|
+
return outputs;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function planPipelinePublication({
|
|
56
|
+
attempt,
|
|
57
|
+
generation,
|
|
58
|
+
source,
|
|
59
|
+
intentSource = source,
|
|
60
|
+
runtime,
|
|
61
|
+
publisher,
|
|
62
|
+
contract,
|
|
63
|
+
route,
|
|
64
|
+
version,
|
|
65
|
+
sourceTimestamp,
|
|
66
|
+
expectedTagSha = null,
|
|
67
|
+
previousChannelCommit = null,
|
|
68
|
+
}) {
|
|
69
|
+
if (route.operation === "develop")
|
|
70
|
+
throw new Error("Development delivery cannot publish products");
|
|
71
|
+
const developmentRoutes = contract.channels.filter(
|
|
72
|
+
(entry) =>
|
|
73
|
+
entry.operation === "alpha" &&
|
|
74
|
+
entry.to === (route.operation === "alpha" ? route.to : route.from),
|
|
75
|
+
);
|
|
76
|
+
if (developmentRoutes.length !== 1)
|
|
77
|
+
throw new Error(
|
|
78
|
+
"Publication requires one declared protected development channel",
|
|
79
|
+
);
|
|
80
|
+
const outputs = pipelineExpectedProducts(contract);
|
|
81
|
+
// The Rust-owned version/route decision operates on a declaration here.
|
|
82
|
+
// Actual sealed artifact roots are admitted independently after the build.
|
|
83
|
+
const selection = selectProductPublicationIntent({
|
|
84
|
+
channel: route.operation === "alpha" ? "alpha" : "stable",
|
|
85
|
+
targetRef: route.to,
|
|
86
|
+
sourceSha: source.commit,
|
|
87
|
+
sourceTimestamp,
|
|
88
|
+
repository: source.repository,
|
|
89
|
+
artifactKind: "custom",
|
|
90
|
+
requiredArtifactsRoot: recordDigest(outputs),
|
|
91
|
+
candidateVersion: version,
|
|
92
|
+
observedVersions: [],
|
|
93
|
+
});
|
|
94
|
+
if (contract.version.strategy === "anchored" && selection.version !== version)
|
|
95
|
+
throw new Error(
|
|
96
|
+
"Anchored publication requires an explicitly materialized version; inference is forbidden",
|
|
97
|
+
);
|
|
98
|
+
const body = {
|
|
99
|
+
schema: "buildchain.pipeline-publication-plan/v1",
|
|
100
|
+
evidenceVersion: 1,
|
|
101
|
+
attempt,
|
|
102
|
+
generation,
|
|
103
|
+
source,
|
|
104
|
+
intentSource,
|
|
105
|
+
runtime,
|
|
106
|
+
publisher,
|
|
107
|
+
contractRoot: recordDigest(contract),
|
|
108
|
+
version: selection.version,
|
|
109
|
+
candidateVersion: version,
|
|
110
|
+
tag: selection.exactTag,
|
|
111
|
+
channel: selection.channel,
|
|
112
|
+
route,
|
|
113
|
+
sourceTimestamp,
|
|
114
|
+
versionPolicy: contract.version,
|
|
115
|
+
...(contract.stable
|
|
116
|
+
? { stablePolicy: structuredClone(contract.stable) }
|
|
117
|
+
: {}),
|
|
118
|
+
developmentBranch: developmentRoutes[0].from,
|
|
119
|
+
expectedTagSha,
|
|
120
|
+
previousChannelCommit,
|
|
121
|
+
outputs,
|
|
122
|
+
versionSelection: selection,
|
|
123
|
+
phases: ["QUALIFY", "APPLY", "SETTLE"],
|
|
124
|
+
};
|
|
125
|
+
return { ...body, root: recordDigest(body) };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function verifyPipelinePublicationPlan(plan) {
|
|
129
|
+
const { root, ...body } = plan || {};
|
|
130
|
+
if (
|
|
131
|
+
body.schema !== "buildchain.pipeline-publication-plan/v1" ||
|
|
132
|
+
root !== recordDigest(body)
|
|
133
|
+
)
|
|
134
|
+
throw new Error("Publication plan bytes do not match their retained root");
|
|
135
|
+
return plan;
|
|
136
|
+
}
|