@kungfu-tech/buildchain 4.0.1-alpha.2 → 4.0.1-alpha.21
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/actions/release-tail/README.md +8 -0
- package/architecture/ci-lane-change-budget.json +187 -0
- package/architecture/decisions/0002-next-development-transition.md +101 -0
- package/architecture/decisions/0003-two-phase-delivery-warrant.md +152 -0
- package/architecture/internal-capabilities.json +211 -8
- package/architecture/maintainability-policy.json +113 -28
- package/architecture/release-tail-contract-inventory.json +5 -2
- package/architecture/v3-core-mechanism-inventory.json +91 -0
- package/architecture/v4-adopter-delivery-parity.json +32 -5
- package/architecture/v4-capability-state-machine-manifest.json +73 -0
- package/architecture/v4-compatibility-facts-parity.json +77 -0
- package/architecture/v4-delivery-authority-parity.json +250 -0
- package/architecture/v4-delivery-warrant-shadow-fixtures.json +41 -9
- package/architecture/v4-floating-consumer-policy.json +6 -0
- package/architecture/v4-next-development-parity.json +192 -0
- package/architecture/v4-publication-rehearsal-parity.json +84 -0
- package/architecture/v4-release-train-parity.json +1 -0
- package/architecture/v4-tail-reseal-parity.json +150 -0
- package/bin/buildchain.mjs +32 -3
- package/bin/internal/adopter-delivery-cli.mjs +70 -0
- package/bin/internal/command-registry.mjs +3 -0
- package/bin/internal/compatibility-facts-cli.mjs +101 -0
- package/contracts/dev-delivery-authority-v2.schema.json +662 -0
- package/contracts/fixtures/next-development-transition-v1/anchored-manual-waiting.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/semver-auto-planned.json +47 -0
- package/contracts/fixtures/next-development-transition-v1/version-model-cases.json +40 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/archive-template.json +19 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/bootstrap-positive.json +93 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-positive.json +29 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/gate-unknown-selector.json +26 -0
- package/contracts/fixtures/v4-adopter-delivery-v1/offline-vectors.json +50 -0
- package/contracts/fixtures/v4-compatibility-facts-v1/shared.json +1129 -0
- package/contracts/fixtures/v4-floating-consumer-policy-v1/cases.json +7 -1
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/artifacts/product.bin +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/config/buildchain.toml +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/release-activation.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/documents/signed-channel.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/evidence/qualification.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/candidate.json +5 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/candidate/manifests/release-passport.json +1 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/capsule.json +707 -0
- package/contracts/fixtures/v4-publication-rehearsal-v1/offline-vectors.json +27 -0
- package/contracts/fixtures/v4-tail-reseal-v1/valid.json +229 -0
- package/contracts/next-development-request-v1.schema.json +66 -0
- package/contracts/next-development-transition-v1.schema.json +292 -0
- package/contracts/v4-adopter-delivery-v1.schema.json +81 -0
- package/contracts/v4-compatibility-facts-v1.schema.json +230 -0
- package/contracts/v4-floating-consumer-policy-receipt-v1.schema.json +4 -2
- package/contracts/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/contracts/v4-tail-reseal-v1.schema.json +276 -0
- package/dist/site/agent-index.json +5 -0
- package/dist/site/artifact-schemas.json +10 -0
- package/dist/site/buildchain-contract.json +1475 -27
- package/dist/site/buildchain-site.json +627 -31
- package/dist/site/capability-registry.json +17 -14
- package/dist/site/cli-registry.json +205 -3
- package/dist/site/kfd-claims.json +490 -16
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +52 -6
- package/dist/site/node-api-registry.json +14736 -8851
- package/dist/site/page-registry.json +593 -21
- package/dist/site/public-surface-audit.json +837 -48
- package/dist/site/publication-authority-registry.json +44 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-provenance.json +24 -0
- package/dist/site/schemas/dev-delivery-authority-v2.schema.json +662 -0
- package/dist/site/schemas/release-tail-capabilities-v1.schema.json +199 -0
- package/dist/site/schemas/v4-adopter-delivery-v1.schema.json +81 -0
- package/dist/site/schemas/v4-compatibility-facts-v1.schema.json +230 -0
- package/dist/site/schemas/v4-publication-rehearsal-capsule-v1.schema.json +233 -0
- package/dist/site/site-manifest.json +34 -10
- package/dist/site/workflow-registry.json +160 -10
- package/docs/MAP.md +8 -2
- package/docs/cli-reference.md +404 -13
- package/docs/cli.md +8 -0
- package/docs/dev-alpha-candidate-patrol.md +8 -0
- package/docs/dev-delivery-qualification-landing-adr.md +251 -0
- package/docs/dev-delivery-warrant.md +351 -38
- package/docs/next-development-transition.md +118 -0
- package/docs/node-api-reference.md +646 -293
- package/docs/release-tail-provider-plane.md +30 -0
- package/docs/v4-adopter-delivery.md +133 -0
- package/docs/v4-compatibility-facts.md +91 -0
- package/docs/v4-publication-rehearsal.md +130 -0
- package/docs/v4-tail-reseal.md +73 -0
- package/docs/versioning.md +4 -3
- package/package.json +34 -6
- package/packages/core/adopter-delivery-passport.js +259 -0
- package/packages/core/adopter-delivery-vectors.js +158 -0
- package/packages/core/adopter-delivery-vectors.json +77 -0
- package/packages/core/buildchain-agent-manuals.js +3 -0
- package/packages/core/buildchain-compatibility-authority.js +336 -0
- package/packages/core/buildchain-compatibility-fact.js +394 -0
- package/packages/core/buildchain-compatibility-facts.json +217 -0
- package/packages/core/buildchain-compatibility-proof.js +566 -0
- package/packages/core/buildchain-config.js +84 -0
- package/packages/core/buildchain-contract.js +96 -143
- package/packages/core/buildchain-delivery-bootstrap.js +240 -0
- package/packages/core/buildchain-delivery-infrastructure.js +164 -0
- package/packages/core/buildchain-delivery-self-dogfood.js +395 -0
- package/packages/core/buildchain-publication-authority.js +1 -0
- package/packages/core/channel-candidate.js +8 -0
- package/packages/core/channel-promotion-baseline.js +55 -0
- package/packages/core/ci-lane-change-budget.js +247 -0
- package/packages/core/dev-alpha-candidate-selection.js +10 -2
- package/packages/core/dev-delivery-authority-candidate.js +270 -0
- package/packages/core/dev-delivery-authority-evidence.js +146 -0
- package/packages/core/dev-delivery-authority-landing.js +461 -0
- package/packages/core/dev-delivery-authority-observation.js +48 -0
- package/packages/core/dev-delivery-authority-qualification.js +591 -0
- package/packages/core/dev-delivery-authority-settlement.js +213 -0
- package/packages/core/dev-delivery-authority-state.js +583 -0
- package/packages/core/dev-delivery-candidate-identity.js +13 -0
- package/packages/core/dev-delivery-contract-surface.js +76 -0
- package/packages/core/dev-delivery-execution-failure.js +133 -0
- package/packages/core/dev-delivery-execution-transfer.js +592 -0
- package/packages/core/dev-delivery-landing-admission-core.js +119 -0
- package/packages/core/dev-delivery-landing-readback.js +598 -0
- package/packages/core/dev-delivery-landing-terminal-evidence.js +271 -0
- package/packages/core/dev-delivery-landing-testing-port.js +6 -0
- package/packages/core/dev-delivery-native-execution.js +110 -0
- package/packages/core/dev-delivery-native-proof.js +562 -0
- package/packages/core/dev-delivery-process-boundary.js +563 -0
- package/packages/core/dev-delivery-provider-attempt.js +127 -0
- package/packages/core/dev-delivery-provider-heartbeat.js +382 -0
- package/packages/core/dev-delivery-warrant-cancellation.js +1 -0
- package/packages/core/dev-delivery-warrant-qualification.js +145 -0
- package/packages/core/dev-delivery-warrant-settlement.js +237 -36
- package/packages/core/dev-delivery-warrant-state.js +587 -0
- package/packages/core/dev-delivery-warrant.js +328 -370
- package/packages/core/github-governance-authority.js +1 -0
- package/packages/core/index.js +5 -0
- package/packages/core/kungfu-temporal-fact.js +557 -0
- package/packages/core/legacy-kfd-adopter-driver.js +167 -0
- package/packages/core/next-development-candidate-reservation.js +186 -0
- package/packages/core/next-development-controller.js +728 -0
- package/packages/core/next-development-projection.js +287 -0
- package/packages/core/next-development-transition.js +738 -0
- package/packages/core/paper-agent-entry.js +11 -5
- package/packages/core/paper.js +16 -5
- package/packages/core/published-delivery-authority.js +266 -0
- package/packages/core/release-candidate-recovery.js +70 -15
- package/packages/core/release-candidate.js +2 -2
- package/packages/core/release-passport.js +36 -9
- package/packages/core/v4-adopter-delivery-parity.js +3 -3
- package/packages/core/v4-adopter-delivery.js +359 -0
- package/packages/core/v4-canonical-contracts.js +5 -0
- package/packages/core/v4-delivery-warrant-read-candidate.js +10 -3
- package/packages/core/v4-floating-consumer-evidence.js +18 -4
- package/packages/core/v4-floating-consumer-policy.js +143 -20
- package/packages/core/v4-floating-consumer-release-passport.js +32 -5
- package/packages/core/v4-publication-rehearsal-capsule.js +468 -0
- package/packages/core/v4-publication-rehearsal-provider-bindings.js +212 -0
- package/packages/core/v4-publication-rehearsal.js +481 -0
- package/packages/core/v4-tail-reseal-contract.js +26 -0
- package/packages/core/v4-tail-reseal-github.js +178 -0
- package/packages/core/v4-tail-reseal-receipt.js +225 -0
- package/packages/core/v4-tail-reseal.js +594 -0
- package/packages/core/workflow-yaml-contract.js +24 -2
- package/scripts/buildchain-cli-help.mjs +41 -2
- package/scripts/check-ci-lane-change-budget.mjs +100 -0
- package/scripts/check-inventory.mjs +14 -3
- package/scripts/check-v4-floating-consumer-policy-contract.mjs +23 -4
- package/scripts/dev-alpha-candidate-patrol.mjs +22 -1
- package/scripts/dev-delivery-authority-command-adapters.mjs +206 -0
- package/scripts/dev-delivery-authority-provider.mjs +28 -0
- package/scripts/dev-delivery-authority.mjs +490 -0
- package/scripts/dev-delivery-native-run.mjs +177 -0
- package/scripts/dev-delivery-process-boundary.mjs +260 -0
- package/scripts/dev-delivery-proof.mjs +67 -2
- package/scripts/dev-delivery-provider-heartbeat.mjs +215 -0
- package/scripts/dev-delivery-source-proof-replay.mjs +153 -0
- package/scripts/dev-delivery-source-proof-reuse.mjs +13 -97
- package/scripts/dev-delivery-two-phase-resume.mjs +345 -0
- package/scripts/dev-delivery-two-phase.mjs +573 -0
- package/scripts/dev-delivery-warrant-options.mjs +266 -0
- package/scripts/dev-delivery-warrant-store.mjs +231 -0
- package/scripts/dev-delivery-warrant.mjs +232 -194
- package/scripts/dev-pr-auto-merge.mjs +1 -1
- package/scripts/dev-pr-delivery-warrant.mjs +11 -0
- package/scripts/dev-qualification-patrol.mjs +9 -9
- package/scripts/dispatch-artifact-signing-authority.mjs +1 -1
- package/scripts/generate-channel-promotion-workflow.mjs +2 -2
- package/scripts/generate-next-development-guidance.mjs +49 -0
- package/scripts/generate-release-candidate-passport.mjs +152 -0
- package/scripts/generate-site-bundle.mjs +69 -4
- package/scripts/init-repo.mjs +22 -4
- package/scripts/next-development-self-dogfood-harness.mjs +460 -0
- package/scripts/next-development-self-dogfood.mjs +597 -0
- package/scripts/next-development-transition.mjs +47 -0
- package/scripts/npm-publish-transaction.mjs +2 -2
- package/scripts/promotion-channel-router.mjs +4 -4
- package/scripts/release-candidate-anchor-provenance.mjs +99 -0
- package/scripts/release-tail.mjs +51 -3
- package/scripts/release-train-self-dogfood.mjs +94 -92
- package/scripts/resume-from-candidate-run.mjs +557 -30
- package/scripts/site-capability-metadata.mjs +41 -0
- package/scripts/stable-candidate-qualification.mjs +9 -10
- package/scripts/v4-consumer-policy.mjs +5 -5
- package/scripts/v4-publication-rehearsal-fixture.mjs +144 -0
- package/scripts/v4-tail-reseal-macos-rehearsal.mjs +134 -0
- package/scripts/v4-tail-reseal.mjs +416 -0
- package/templates/native-dev-delivery.yml +143 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
advanceNextDevelopmentTransition,
|
|
5
|
+
bindNextDevelopmentAnchor,
|
|
6
|
+
nextDevelopmentRoot,
|
|
7
|
+
recordNextDevelopmentMaterialization,
|
|
8
|
+
validateNextDevelopmentTransition,
|
|
9
|
+
} from "./next-development-transition.js";
|
|
10
|
+
|
|
11
|
+
export const NEXT_DEVELOPMENT_CONTROLLER_CONTRACT =
|
|
12
|
+
"kungfu-buildchain-next-development-controller/v1";
|
|
13
|
+
|
|
14
|
+
const ROOT = /^sha256:[0-9a-f]{64}$/u;
|
|
15
|
+
const SHA = /^[0-9a-f]{40}$/u;
|
|
16
|
+
|
|
17
|
+
function exactRoot(value, label) {
|
|
18
|
+
const normalized = String(value || "")
|
|
19
|
+
.trim()
|
|
20
|
+
.toLowerCase();
|
|
21
|
+
if (!ROOT.test(normalized)) {
|
|
22
|
+
throw new Error(`${label} must be a sha256 content root`);
|
|
23
|
+
}
|
|
24
|
+
return normalized;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function exactSha(value, label) {
|
|
28
|
+
const normalized = String(value || "")
|
|
29
|
+
.trim()
|
|
30
|
+
.toLowerCase();
|
|
31
|
+
if (!SHA.test(normalized)) {
|
|
32
|
+
throw new Error(`${label} must be an exact 40-character Git SHA`);
|
|
33
|
+
}
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function requiredString(value, label) {
|
|
38
|
+
const normalized = String(value || "").trim();
|
|
39
|
+
if (!normalized) throw new Error(`${label} must be a non-empty string`);
|
|
40
|
+
return normalized;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function protectedBranch(value) {
|
|
44
|
+
const normalized = requiredString(value, "protectedDev.branch");
|
|
45
|
+
if (
|
|
46
|
+
normalized.startsWith("refs/") ||
|
|
47
|
+
normalized.startsWith("/") ||
|
|
48
|
+
normalized.endsWith("/") ||
|
|
49
|
+
normalized.includes("..") ||
|
|
50
|
+
/\s/u.test(normalized)
|
|
51
|
+
) {
|
|
52
|
+
throw new Error("protectedDev.branch must be an unambiguous branch name");
|
|
53
|
+
}
|
|
54
|
+
return normalized;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sortedUnique(values, label) {
|
|
58
|
+
if (!Array.isArray(values)) throw new Error(`${label} must be an array`);
|
|
59
|
+
const normalized = values.map((value, index) =>
|
|
60
|
+
requiredString(value, `${label}[${index}]`),
|
|
61
|
+
);
|
|
62
|
+
const expected = [...new Set(normalized)].sort();
|
|
63
|
+
if (
|
|
64
|
+
expected.length !== normalized.length ||
|
|
65
|
+
expected.some((value, index) => value !== normalized[index])
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(`${label} must be sorted and duplicate-free`);
|
|
68
|
+
}
|
|
69
|
+
return normalized;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function rootedPaths(values, expectedPaths, label, { version = false } = {}) {
|
|
73
|
+
if (!Array.isArray(values)) throw new Error(`${label} must be an array`);
|
|
74
|
+
const normalized = values.map((entry, index) => ({
|
|
75
|
+
path: requiredString(entry?.path, `${label}[${index}].path`),
|
|
76
|
+
root: exactRoot(entry?.root, `${label}[${index}].root`),
|
|
77
|
+
...(version
|
|
78
|
+
? {
|
|
79
|
+
version: requiredString(entry?.version, `${label}[${index}].version`),
|
|
80
|
+
}
|
|
81
|
+
: {}),
|
|
82
|
+
}));
|
|
83
|
+
normalized.sort((left, right) => left.path.localeCompare(right.path));
|
|
84
|
+
if (
|
|
85
|
+
JSON.stringify(normalized.map((entry) => entry.path)) !==
|
|
86
|
+
JSON.stringify(expectedPaths)
|
|
87
|
+
) {
|
|
88
|
+
throw new Error(`${label} must exactly cover its declared paths`);
|
|
89
|
+
}
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function controllerBody(record) {
|
|
94
|
+
const body = structuredClone(record);
|
|
95
|
+
delete body.controllerRoot;
|
|
96
|
+
return body;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function nextDevelopmentControllerRoot(record) {
|
|
100
|
+
return nextDevelopmentRoot(controllerBody(record));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function nextDevelopmentChildKey(transition) {
|
|
104
|
+
const current = validateNextDevelopmentTransition(transition);
|
|
105
|
+
return nextDevelopmentRoot({
|
|
106
|
+
contract: NEXT_DEVELOPMENT_CONTROLLER_CONTRACT,
|
|
107
|
+
repository: current.repository,
|
|
108
|
+
completedAlphaRoot: current.completedAlphaRoot,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function refreshController(record) {
|
|
113
|
+
const next = structuredClone(record);
|
|
114
|
+
next.controllerRoot = nextDevelopmentControllerRoot(next);
|
|
115
|
+
return next;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createNextDevelopmentController({
|
|
119
|
+
transition,
|
|
120
|
+
protectedDevBranch,
|
|
121
|
+
} = {}) {
|
|
122
|
+
const child = validateNextDevelopmentTransition(transition);
|
|
123
|
+
if (!["planned", "waiting-anchor"].includes(child.state.status)) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
"new next-development controller requires an initial child transition",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return refreshController({
|
|
129
|
+
schemaVersion: 1,
|
|
130
|
+
contract: NEXT_DEVELOPMENT_CONTROLLER_CONTRACT,
|
|
131
|
+
childKey: nextDevelopmentChildKey(child),
|
|
132
|
+
idempotencyKey: child.idempotencyKey,
|
|
133
|
+
repository: child.repository,
|
|
134
|
+
protectedDev: { branch: protectedBranch(protectedDevBranch) },
|
|
135
|
+
completedAlphaRoot: child.completedAlphaRoot,
|
|
136
|
+
alphaOutcome: "preserved-success",
|
|
137
|
+
revision: 1,
|
|
138
|
+
transition: child,
|
|
139
|
+
attempts: [],
|
|
140
|
+
activeAttempt: null,
|
|
141
|
+
pullRequest: null,
|
|
142
|
+
readback: null,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function normalizeSourceRoots(values, expectedPaths, targetVersion) {
|
|
147
|
+
if (!Array.isArray(values)) {
|
|
148
|
+
throw new Error("materialization.sourceRoots must be an array");
|
|
149
|
+
}
|
|
150
|
+
const normalized = values
|
|
151
|
+
.map((entry, index) => ({
|
|
152
|
+
path: requiredString(
|
|
153
|
+
entry?.path,
|
|
154
|
+
`materialization.sourceRoots[${index}].path`,
|
|
155
|
+
),
|
|
156
|
+
beforeRoot: exactRoot(
|
|
157
|
+
entry?.beforeRoot,
|
|
158
|
+
`materialization.sourceRoots[${index}].beforeRoot`,
|
|
159
|
+
),
|
|
160
|
+
afterRoot: exactRoot(
|
|
161
|
+
entry?.afterRoot,
|
|
162
|
+
`materialization.sourceRoots[${index}].afterRoot`,
|
|
163
|
+
),
|
|
164
|
+
version: requiredString(
|
|
165
|
+
entry?.version,
|
|
166
|
+
`materialization.sourceRoots[${index}].version`,
|
|
167
|
+
),
|
|
168
|
+
}))
|
|
169
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
170
|
+
if (
|
|
171
|
+
JSON.stringify(normalized.map((entry) => entry.path)) !==
|
|
172
|
+
JSON.stringify(expectedPaths)
|
|
173
|
+
) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"materialization.sourceRoots must exactly cover declared source paths",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (normalized.some((entry) => entry.version !== targetVersion)) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"materialization source versions must equal the declared successor",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return normalized;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeMaterialization(record, value, baseDevSha, operationKey) {
|
|
187
|
+
const transition = record.transition;
|
|
188
|
+
const sourceRoots = normalizeSourceRoots(
|
|
189
|
+
value?.sourceRoots,
|
|
190
|
+
transition.adapter.sourcePaths,
|
|
191
|
+
transition.target.version,
|
|
192
|
+
);
|
|
193
|
+
const derivedRoots = rootedPaths(
|
|
194
|
+
value?.derivedRoots || [],
|
|
195
|
+
transition.adapter.derivedPaths,
|
|
196
|
+
"materialization.derivedRoots",
|
|
197
|
+
);
|
|
198
|
+
const changedPaths = sortedUnique(
|
|
199
|
+
value?.changedPaths || [],
|
|
200
|
+
"materialization.changedPaths",
|
|
201
|
+
);
|
|
202
|
+
const unexpected = changedPaths.filter(
|
|
203
|
+
(changedPath) =>
|
|
204
|
+
!transition.adapter.allowedChangePaths.includes(changedPath),
|
|
205
|
+
);
|
|
206
|
+
if (unexpected.length > 0) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`materialization changed paths outside declared version state: ${unexpected.join(", ")}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const normalized = {
|
|
212
|
+
operationKey,
|
|
213
|
+
generation: record.attempts.length + 1,
|
|
214
|
+
status: "prepared",
|
|
215
|
+
baseDevSha: exactSha(value?.baseDevSha, "materialization.baseDevSha"),
|
|
216
|
+
preparedCommitSha: exactSha(value?.commitSha, "materialization.commitSha"),
|
|
217
|
+
preparedTreeSha: exactSha(value?.treeSha, "materialization.treeSha"),
|
|
218
|
+
targetVersion: transition.target.version,
|
|
219
|
+
sourceRoots,
|
|
220
|
+
derivedRoots,
|
|
221
|
+
changedPaths,
|
|
222
|
+
lifecycleEvidenceRoot: exactRoot(
|
|
223
|
+
value?.lifecycleEvidenceRoot,
|
|
224
|
+
"materialization.lifecycleEvidenceRoot",
|
|
225
|
+
),
|
|
226
|
+
};
|
|
227
|
+
if (normalized.baseDevSha !== baseDevSha) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
"materialization must be regenerated from the exact observed protected Dev SHA",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
normalized.materializationRoot = nextDevelopmentRoot({
|
|
233
|
+
baseDevSha: normalized.baseDevSha,
|
|
234
|
+
preparedCommitSha: normalized.preparedCommitSha,
|
|
235
|
+
preparedTreeSha: normalized.preparedTreeSha,
|
|
236
|
+
targetVersion: normalized.targetVersion,
|
|
237
|
+
sourceRoots: normalized.sourceRoots,
|
|
238
|
+
derivedRoots: normalized.derivedRoots,
|
|
239
|
+
changedPaths: normalized.changedPaths,
|
|
240
|
+
lifecycleEvidenceRoot: normalized.lifecycleEvidenceRoot,
|
|
241
|
+
});
|
|
242
|
+
return normalized;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function normalizePullRequest(record, value, attempt) {
|
|
246
|
+
const number = Number(value?.number);
|
|
247
|
+
if (!Number.isSafeInteger(number) || number < 1) {
|
|
248
|
+
throw new Error("pullRequest.number must be a positive integer");
|
|
249
|
+
}
|
|
250
|
+
const status = requiredString(value?.status, "pullRequest.status");
|
|
251
|
+
if (!["open", "merged"].includes(status)) {
|
|
252
|
+
throw new Error("pullRequest.status must be open or merged");
|
|
253
|
+
}
|
|
254
|
+
const normalized = {
|
|
255
|
+
number,
|
|
256
|
+
url: requiredString(value?.url, "pullRequest.url"),
|
|
257
|
+
baseBranch: protectedBranch(value?.baseBranch),
|
|
258
|
+
headSha: exactSha(value?.headSha, "pullRequest.headSha"),
|
|
259
|
+
status,
|
|
260
|
+
evidenceRoot: exactRoot(value?.evidenceRoot, "pullRequest.evidenceRoot"),
|
|
261
|
+
};
|
|
262
|
+
if (
|
|
263
|
+
normalized.baseBranch !== record.protectedDev.branch ||
|
|
264
|
+
normalized.headSha !== attempt.preparedCommitSha
|
|
265
|
+
) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"pull request must bind the protected Dev branch and prepared commit",
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return normalized;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizeReadback(record, value, attempt) {
|
|
274
|
+
const normalized = {
|
|
275
|
+
devSha: exactSha(value?.devSha, "readback.devSha"),
|
|
276
|
+
treeSha: exactSha(value?.treeSha, "readback.treeSha"),
|
|
277
|
+
version: requiredString(value?.version, "readback.version"),
|
|
278
|
+
containsPreparedCommit: value?.containsPreparedCommit === true,
|
|
279
|
+
versionRoots: rootedPaths(
|
|
280
|
+
value?.versionRoots,
|
|
281
|
+
record.transition.adapter.sourcePaths,
|
|
282
|
+
"readback.versionRoots",
|
|
283
|
+
),
|
|
284
|
+
derivedRoots: rootedPaths(
|
|
285
|
+
value?.derivedRoots || [],
|
|
286
|
+
record.transition.adapter.derivedPaths,
|
|
287
|
+
"readback.derivedRoots",
|
|
288
|
+
),
|
|
289
|
+
evidenceRoot: exactRoot(value?.evidenceRoot, "readback.evidenceRoot"),
|
|
290
|
+
};
|
|
291
|
+
const expectedVersionRoots = attempt.sourceRoots.map(
|
|
292
|
+
({ path, afterRoot }) => ({
|
|
293
|
+
path,
|
|
294
|
+
root: afterRoot,
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
297
|
+
normalized.agrees =
|
|
298
|
+
normalized.containsPreparedCommit &&
|
|
299
|
+
normalized.version === record.transition.target.version &&
|
|
300
|
+
JSON.stringify(normalized.versionRoots) ===
|
|
301
|
+
JSON.stringify(expectedVersionRoots) &&
|
|
302
|
+
JSON.stringify(normalized.derivedRoots) ===
|
|
303
|
+
JSON.stringify(attempt.derivedRoots);
|
|
304
|
+
return normalized;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function normalizedAttempt(record, attempt, index) {
|
|
308
|
+
const normalized = normalizeMaterialization(
|
|
309
|
+
{ ...record, attempts: Array(index).fill(null) },
|
|
310
|
+
{
|
|
311
|
+
baseDevSha: attempt?.baseDevSha,
|
|
312
|
+
commitSha: attempt?.preparedCommitSha,
|
|
313
|
+
treeSha: attempt?.preparedTreeSha,
|
|
314
|
+
sourceRoots: attempt?.sourceRoots,
|
|
315
|
+
derivedRoots: attempt?.derivedRoots,
|
|
316
|
+
changedPaths: attempt?.changedPaths,
|
|
317
|
+
lifecycleEvidenceRoot: attempt?.lifecycleEvidenceRoot,
|
|
318
|
+
},
|
|
319
|
+
attempt?.baseDevSha,
|
|
320
|
+
exactRoot(attempt?.operationKey, `attempts[${index}].operationKey`),
|
|
321
|
+
);
|
|
322
|
+
if (
|
|
323
|
+
!["prepared", "pr-pending", "merged", "superseded"].includes(
|
|
324
|
+
attempt?.status,
|
|
325
|
+
)
|
|
326
|
+
) {
|
|
327
|
+
throw new Error(`attempts[${index}].status is invalid`);
|
|
328
|
+
}
|
|
329
|
+
normalized.status = attempt.status;
|
|
330
|
+
if (attempt.materializationRoot !== normalized.materializationRoot) {
|
|
331
|
+
throw new Error(`attempts[${index}].materializationRoot drifted`);
|
|
332
|
+
}
|
|
333
|
+
return normalized;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function validateControllerObservations(record, attempts) {
|
|
337
|
+
let activeAttempt = null;
|
|
338
|
+
if (record.activeAttempt !== null) {
|
|
339
|
+
activeAttempt = attempts.find(
|
|
340
|
+
(attempt) => attempt.operationKey === record.activeAttempt.operationKey,
|
|
341
|
+
);
|
|
342
|
+
if (
|
|
343
|
+
!activeAttempt ||
|
|
344
|
+
JSON.stringify(activeAttempt) !== JSON.stringify(record.activeAttempt)
|
|
345
|
+
) {
|
|
346
|
+
throw new Error("next-development controller active attempt drifted");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (record.pullRequest !== null) {
|
|
350
|
+
if (!activeAttempt) {
|
|
351
|
+
throw new Error(
|
|
352
|
+
"next-development controller pull request requires an active attempt",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const pullRequest = normalizePullRequest(
|
|
356
|
+
record,
|
|
357
|
+
record.pullRequest,
|
|
358
|
+
activeAttempt,
|
|
359
|
+
);
|
|
360
|
+
if (JSON.stringify(pullRequest) !== JSON.stringify(record.pullRequest)) {
|
|
361
|
+
throw new Error("next-development controller pull request drifted");
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (record.readback !== null) {
|
|
365
|
+
if (!activeAttempt) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
"next-development controller readback requires an active attempt",
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const readback = normalizeReadback(record, record.readback, activeAttempt);
|
|
371
|
+
if (JSON.stringify(readback) !== JSON.stringify(record.readback)) {
|
|
372
|
+
throw new Error("next-development controller readback drifted");
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (
|
|
376
|
+
record.transition.state.status === "verified" &&
|
|
377
|
+
(!record.readback?.agrees || !activeAttempt)
|
|
378
|
+
) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
"verified next-development controller requires agreeing protected Dev readback",
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function validateNextDevelopmentController(record) {
|
|
386
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
387
|
+
throw new Error("next-development controller must be an object");
|
|
388
|
+
}
|
|
389
|
+
if (record.contract !== NEXT_DEVELOPMENT_CONTROLLER_CONTRACT) {
|
|
390
|
+
throw new Error("next-development controller contract mismatch");
|
|
391
|
+
}
|
|
392
|
+
if (record.schemaVersion !== 1) {
|
|
393
|
+
throw new Error("next-development controller schemaVersion must be 1");
|
|
394
|
+
}
|
|
395
|
+
const transition = validateNextDevelopmentTransition(record.transition);
|
|
396
|
+
const expectedChildKey = nextDevelopmentChildKey(transition);
|
|
397
|
+
if (
|
|
398
|
+
record.childKey !== expectedChildKey ||
|
|
399
|
+
record.idempotencyKey !== transition.idempotencyKey ||
|
|
400
|
+
record.repository !== transition.repository ||
|
|
401
|
+
record.completedAlphaRoot !== transition.completedAlphaRoot
|
|
402
|
+
) {
|
|
403
|
+
throw new Error("next-development controller child identity drifted");
|
|
404
|
+
}
|
|
405
|
+
if (
|
|
406
|
+
protectedBranch(record.protectedDev?.branch) !== record.protectedDev.branch
|
|
407
|
+
) {
|
|
408
|
+
throw new Error("next-development controller protected Dev branch drifted");
|
|
409
|
+
}
|
|
410
|
+
if (
|
|
411
|
+
record.alphaOutcome !== "preserved-success" ||
|
|
412
|
+
transition.alphaOutcome !== "preserved-success" ||
|
|
413
|
+
transition.completedAlpha.outcome !== "succeeded"
|
|
414
|
+
) {
|
|
415
|
+
throw new Error("next-development controller invalidated completed Alpha");
|
|
416
|
+
}
|
|
417
|
+
if (!Number.isSafeInteger(record.revision) || record.revision < 1) {
|
|
418
|
+
throw new Error("next-development controller revision must be positive");
|
|
419
|
+
}
|
|
420
|
+
if (!Array.isArray(record.attempts)) {
|
|
421
|
+
throw new Error("next-development controller attempts must be an array");
|
|
422
|
+
}
|
|
423
|
+
const attempts = record.attempts.map((attempt, index) =>
|
|
424
|
+
normalizedAttempt(record, attempt, index),
|
|
425
|
+
);
|
|
426
|
+
if (
|
|
427
|
+
new Set(attempts.map((attempt) => attempt.operationKey)).size !==
|
|
428
|
+
attempts.length
|
|
429
|
+
) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
"next-development controller attempt operation keys must be unique",
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
validateControllerObservations(record, attempts);
|
|
435
|
+
if (record.controllerRoot !== nextDevelopmentControllerRoot(record)) {
|
|
436
|
+
throw new Error("next-development controller root drifted");
|
|
437
|
+
}
|
|
438
|
+
return structuredClone(record);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function assertStore(store) {
|
|
442
|
+
for (const method of ["readChild", "createChild", "compareAndSwapChild"]) {
|
|
443
|
+
if (typeof store?.[method] !== "function") {
|
|
444
|
+
throw new Error(`next-development durable store requires ${method}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function assertExecutor(executor) {
|
|
450
|
+
for (const method of [
|
|
451
|
+
"readProtectedDev",
|
|
452
|
+
"materialize",
|
|
453
|
+
"ensurePullRequest",
|
|
454
|
+
"readPullRequest",
|
|
455
|
+
"readProtectedVersionState",
|
|
456
|
+
]) {
|
|
457
|
+
if (typeof executor?.[method] !== "function") {
|
|
458
|
+
throw new Error(`next-development executor requires ${method}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function assertSameScheduledChild(existing, requested) {
|
|
464
|
+
const current = validateNextDevelopmentController(existing);
|
|
465
|
+
if (
|
|
466
|
+
current.childKey !== requested.childKey ||
|
|
467
|
+
current.idempotencyKey !== requested.idempotencyKey ||
|
|
468
|
+
current.protectedDev.branch !== requested.protectedDev.branch
|
|
469
|
+
) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
"durable next-development child conflicts with the completed Alpha schedule",
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
return current;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export async function scheduleNextDevelopmentController(
|
|
478
|
+
{ transition, protectedDevBranch } = {},
|
|
479
|
+
store,
|
|
480
|
+
) {
|
|
481
|
+
assertStore(store);
|
|
482
|
+
const requested = createNextDevelopmentController({
|
|
483
|
+
transition,
|
|
484
|
+
protectedDevBranch,
|
|
485
|
+
});
|
|
486
|
+
const existing = await store.readChild(requested.childKey);
|
|
487
|
+
if (existing) return assertSameScheduledChild(existing, requested);
|
|
488
|
+
const created = await store.createChild(requested.childKey, requested);
|
|
489
|
+
if (!created?.record) {
|
|
490
|
+
throw new Error("durable store createChild must return the stored record");
|
|
491
|
+
}
|
|
492
|
+
return assertSameScheduledChild(created.record, requested);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function persist(record, store) {
|
|
496
|
+
const expectedRoot = exactRoot(record?.controllerRoot, "controllerRoot");
|
|
497
|
+
const current = structuredClone(record);
|
|
498
|
+
current.controllerRoot = nextDevelopmentControllerRoot(current);
|
|
499
|
+
validateNextDevelopmentController(current);
|
|
500
|
+
const next = structuredClone(current);
|
|
501
|
+
next.revision += 1;
|
|
502
|
+
next.controllerRoot = nextDevelopmentControllerRoot(next);
|
|
503
|
+
const stored = await store.compareAndSwapChild(
|
|
504
|
+
next.childKey,
|
|
505
|
+
expectedRoot,
|
|
506
|
+
next,
|
|
507
|
+
);
|
|
508
|
+
return validateNextDevelopmentController(stored?.record || stored);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function replaceActiveAttempt(record, attempt) {
|
|
512
|
+
const next = structuredClone(record);
|
|
513
|
+
const index = next.attempts.findIndex(
|
|
514
|
+
(candidate) => candidate.operationKey === attempt.operationKey,
|
|
515
|
+
);
|
|
516
|
+
if (index < 0) next.attempts.push(attempt);
|
|
517
|
+
else next.attempts[index] = attempt;
|
|
518
|
+
next.activeAttempt = attempt.status === "superseded" ? null : attempt;
|
|
519
|
+
return next;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function checkpoint(fault, name, record) {
|
|
523
|
+
if (typeof fault === "function") await fault(name, structuredClone(record));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function transitionMaterialization(attempt, transition) {
|
|
527
|
+
const body = {
|
|
528
|
+
targetVersion: transition.target.version,
|
|
529
|
+
anchorRoot: transition.target.anchor?.manifestRoot || null,
|
|
530
|
+
paths: attempt.sourceRoots.map((entry) => ({
|
|
531
|
+
path: entry.path,
|
|
532
|
+
beforeRoot: entry.beforeRoot,
|
|
533
|
+
afterRoot: entry.afterRoot,
|
|
534
|
+
changed: entry.beforeRoot !== entry.afterRoot,
|
|
535
|
+
})),
|
|
536
|
+
};
|
|
537
|
+
return {
|
|
538
|
+
...body,
|
|
539
|
+
materializationRoot: nextDevelopmentRoot(body),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function transitionEvidence(attempt) {
|
|
544
|
+
return nextDevelopmentRoot({
|
|
545
|
+
materializationRoot: attempt.materializationRoot,
|
|
546
|
+
lifecycleEvidenceRoot: attempt.lifecycleEvidenceRoot,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export async function reconcileNextDevelopmentController(
|
|
551
|
+
record,
|
|
552
|
+
{ store, executor, reviewedInput, recordedAt, fault } = {},
|
|
553
|
+
) {
|
|
554
|
+
assertStore(store);
|
|
555
|
+
assertExecutor(executor);
|
|
556
|
+
let current = validateNextDevelopmentController(record);
|
|
557
|
+
if (current.transition.state.status === "verified") return current;
|
|
558
|
+
|
|
559
|
+
if (
|
|
560
|
+
current.transition.state.status === "waiting-anchor" &&
|
|
561
|
+
!current.transition.target.version
|
|
562
|
+
) {
|
|
563
|
+
if (!reviewedInput) return current;
|
|
564
|
+
current.transition = bindNextDevelopmentAnchor(
|
|
565
|
+
current.transition,
|
|
566
|
+
reviewedInput,
|
|
567
|
+
);
|
|
568
|
+
current = await persist(current, store);
|
|
569
|
+
await checkpoint(fault, "anchor-bound", current);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (!current.activeAttempt) {
|
|
573
|
+
const observed = await executor.readProtectedDev({
|
|
574
|
+
repository: current.repository,
|
|
575
|
+
branch: current.protectedDev.branch,
|
|
576
|
+
});
|
|
577
|
+
const baseDevSha = exactSha(observed?.sha, "protectedDev.sha");
|
|
578
|
+
const operationKey = nextDevelopmentRoot({
|
|
579
|
+
childKey: current.childKey,
|
|
580
|
+
baseDevSha,
|
|
581
|
+
target: current.transition.target,
|
|
582
|
+
});
|
|
583
|
+
const materialized = await executor.materialize({
|
|
584
|
+
operationKey,
|
|
585
|
+
repository: current.repository,
|
|
586
|
+
branch: current.protectedDev.branch,
|
|
587
|
+
baseDevSha,
|
|
588
|
+
targetVersion: current.transition.target.version,
|
|
589
|
+
anchor: current.transition.target.anchor,
|
|
590
|
+
adapter: structuredClone(current.transition.adapter),
|
|
591
|
+
});
|
|
592
|
+
const attempt = normalizeMaterialization(
|
|
593
|
+
current,
|
|
594
|
+
materialized,
|
|
595
|
+
baseDevSha,
|
|
596
|
+
operationKey,
|
|
597
|
+
);
|
|
598
|
+
current = replaceActiveAttempt(current, attempt);
|
|
599
|
+
current = await persist(current, store);
|
|
600
|
+
await checkpoint(fault, "materialized", current);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
let attempt = current.activeAttempt;
|
|
604
|
+
const latest = await executor.readProtectedDev({
|
|
605
|
+
repository: current.repository,
|
|
606
|
+
branch: current.protectedDev.branch,
|
|
607
|
+
});
|
|
608
|
+
const latestDevSha = exactSha(latest?.sha, "protectedDev.sha");
|
|
609
|
+
if (latestDevSha !== attempt.baseDevSha && !current.pullRequest) {
|
|
610
|
+
attempt = { ...attempt, status: "superseded" };
|
|
611
|
+
current = replaceActiveAttempt(current, attempt);
|
|
612
|
+
current = await persist(current, store);
|
|
613
|
+
await checkpoint(fault, "dev-advanced", current);
|
|
614
|
+
return current;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (["planned", "waiting-anchor"].includes(current.transition.state.status)) {
|
|
618
|
+
current.transition = recordNextDevelopmentMaterialization(
|
|
619
|
+
current.transition,
|
|
620
|
+
{
|
|
621
|
+
materialization: transitionMaterialization(attempt, current.transition),
|
|
622
|
+
evidenceRoot: transitionEvidence(attempt),
|
|
623
|
+
recordedAt,
|
|
624
|
+
},
|
|
625
|
+
);
|
|
626
|
+
current = await persist(current, store);
|
|
627
|
+
await checkpoint(fault, "materialization-recorded", current);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (!current.pullRequest) {
|
|
631
|
+
const ensured = await executor.ensurePullRequest({
|
|
632
|
+
operationKey: current.childKey,
|
|
633
|
+
repository: current.repository,
|
|
634
|
+
baseBranch: current.protectedDev.branch,
|
|
635
|
+
headSha: attempt.preparedCommitSha,
|
|
636
|
+
targetVersion: current.transition.target.version,
|
|
637
|
+
completedAlphaRoot: current.completedAlphaRoot,
|
|
638
|
+
});
|
|
639
|
+
current.pullRequest = normalizePullRequest(current, ensured, attempt);
|
|
640
|
+
attempt = { ...attempt, status: "pr-pending" };
|
|
641
|
+
current = replaceActiveAttempt(current, attempt);
|
|
642
|
+
if (current.transition.state.status === "materialized") {
|
|
643
|
+
current.transition = advanceNextDevelopmentTransition(
|
|
644
|
+
current.transition,
|
|
645
|
+
{
|
|
646
|
+
to: "pr-pending",
|
|
647
|
+
event: "protected-version-pr-opened",
|
|
648
|
+
expectedStateRoot: current.transition.state.stateRoot,
|
|
649
|
+
evidenceRoot: current.pullRequest.evidenceRoot,
|
|
650
|
+
recordedAt,
|
|
651
|
+
},
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
current = await persist(current, store);
|
|
655
|
+
await checkpoint(fault, "pull-request-recorded", current);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const pullRequest = normalizePullRequest(
|
|
659
|
+
current,
|
|
660
|
+
await executor.readPullRequest({
|
|
661
|
+
repository: current.repository,
|
|
662
|
+
number: current.pullRequest.number,
|
|
663
|
+
}),
|
|
664
|
+
current.activeAttempt,
|
|
665
|
+
);
|
|
666
|
+
current.pullRequest = pullRequest;
|
|
667
|
+
if (pullRequest.status !== "merged") return current;
|
|
668
|
+
if (current.transition.state.status === "pr-pending") {
|
|
669
|
+
current.transition = advanceNextDevelopmentTransition(current.transition, {
|
|
670
|
+
to: "merged",
|
|
671
|
+
event: "protected-version-pr-merged",
|
|
672
|
+
expectedStateRoot: current.transition.state.stateRoot,
|
|
673
|
+
evidenceRoot: pullRequest.evidenceRoot,
|
|
674
|
+
recordedAt,
|
|
675
|
+
});
|
|
676
|
+
attempt = { ...current.activeAttempt, status: "merged" };
|
|
677
|
+
current = replaceActiveAttempt(current, attempt);
|
|
678
|
+
current = await persist(current, store);
|
|
679
|
+
await checkpoint(fault, "merge-recorded", current);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const readback = normalizeReadback(
|
|
683
|
+
current,
|
|
684
|
+
await executor.readProtectedVersionState({
|
|
685
|
+
repository: current.repository,
|
|
686
|
+
branch: current.protectedDev.branch,
|
|
687
|
+
sourcePaths: current.transition.adapter.sourcePaths,
|
|
688
|
+
derivedPaths: current.transition.adapter.derivedPaths,
|
|
689
|
+
targetVersion: current.transition.target.version,
|
|
690
|
+
preparedCommitSha: current.activeAttempt.preparedCommitSha,
|
|
691
|
+
}),
|
|
692
|
+
current.activeAttempt,
|
|
693
|
+
);
|
|
694
|
+
current.readback = readback;
|
|
695
|
+
if (!readback.agrees) {
|
|
696
|
+
current = await persist(current, store);
|
|
697
|
+
await checkpoint(fault, "readback-mismatch", current);
|
|
698
|
+
return current;
|
|
699
|
+
}
|
|
700
|
+
current.transition = advanceNextDevelopmentTransition(current.transition, {
|
|
701
|
+
to: "verified",
|
|
702
|
+
event: "protected-dev-version-state-readback-verified",
|
|
703
|
+
expectedStateRoot: current.transition.state.stateRoot,
|
|
704
|
+
evidenceRoot: readback.evidenceRoot,
|
|
705
|
+
recordedAt,
|
|
706
|
+
});
|
|
707
|
+
current = await persist(current, store);
|
|
708
|
+
await checkpoint(fault, "verified", current);
|
|
709
|
+
return current;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export async function runNextDevelopmentController(
|
|
713
|
+
{ transition, protectedDevBranch, reviewedInput, recordedAt, fault } = {},
|
|
714
|
+
{ store, executor } = {},
|
|
715
|
+
) {
|
|
716
|
+
const scheduled = await scheduleNextDevelopmentController(
|
|
717
|
+
{ transition, protectedDevBranch },
|
|
718
|
+
store,
|
|
719
|
+
);
|
|
720
|
+
await checkpoint(fault, "scheduled", scheduled);
|
|
721
|
+
return reconcileNextDevelopmentController(scheduled, {
|
|
722
|
+
store,
|
|
723
|
+
executor,
|
|
724
|
+
reviewedInput,
|
|
725
|
+
recordedAt,
|
|
726
|
+
fault,
|
|
727
|
+
});
|
|
728
|
+
}
|