@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
|
@@ -1,310 +1,108 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import {
|
|
2
|
+
devDeliveryClone as clone,
|
|
3
|
+
devDeliveryContentRoot,
|
|
4
|
+
devDeliveryExactRoot as exactRoot,
|
|
5
|
+
devDeliveryPositiveInteger as positiveInteger,
|
|
6
|
+
devDeliveryText as text,
|
|
7
|
+
devDeliveryTimestamp as timestamp,
|
|
8
|
+
} from "./dev-delivery-common.js";
|
|
9
|
+
import {
|
|
10
|
+
DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA,
|
|
11
|
+
createCancelQueuedDevDeliveryCandidate,
|
|
12
|
+
} from "./dev-delivery-warrant-cancellation.js";
|
|
13
|
+
import {
|
|
14
|
+
DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA,
|
|
15
|
+
createDevDeliveryTerminalSettler,
|
|
16
|
+
normalizeProviderFailureAuthorityBinding,
|
|
17
|
+
} from "./dev-delivery-warrant-settlement.js";
|
|
18
|
+
import {
|
|
19
|
+
normalizeNativeCommandContract,
|
|
20
|
+
validateActiveDevDeliveryWarrant,
|
|
21
|
+
} from "./dev-delivery-native-proof.js";
|
|
22
|
+
import { createDevDeliveryWarrantQualifier } from "./dev-delivery-warrant-qualification.js";
|
|
23
|
+
import {
|
|
24
|
+
DEV_DELIVERY_CLASSES,
|
|
25
|
+
DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
26
|
+
DEV_DELIVERY_PRIORITIES,
|
|
27
|
+
DEV_DELIVERY_QUEUE_CONTRACT,
|
|
28
|
+
DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
29
|
+
DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA,
|
|
30
|
+
DEV_DELIVERY_WARRANT_SCHEMA,
|
|
31
|
+
TERMINAL_STATES,
|
|
32
|
+
createDevDeliveryQueue,
|
|
33
|
+
issueDevDeliveryWarrant,
|
|
34
|
+
normalizeDevDeliveryQueue,
|
|
35
|
+
rankDevDeliveryCandidates,
|
|
36
|
+
submitDevDeliveryCandidate,
|
|
37
|
+
transitionDevDeliveryQueue as transition,
|
|
38
|
+
} from "./dev-delivery-warrant-state.js";
|
|
39
|
+
|
|
6
40
|
export { devDeliveryContentRoot } from "./dev-delivery-common.js";
|
|
7
|
-
export {
|
|
8
|
-
|
|
41
|
+
export {
|
|
42
|
+
RELEASE_BLOCKER_PRIORITY_CLAIM_SCHEMA,
|
|
43
|
+
createReleaseBlockerPriorityClaim,
|
|
44
|
+
} from "./release-blocker-priority.js";
|
|
45
|
+
export {
|
|
46
|
+
SOURCE_QUALIFICATION_PROOF_SCHEMA,
|
|
47
|
+
SOURCE_QUALIFICATION_PROOF_V2_SCHEMA,
|
|
48
|
+
PROJECT_CUT_REPLAY_PROOF_SCHEMA,
|
|
49
|
+
INTEGRATION_DELIVERY_PROOF_SCHEMA,
|
|
50
|
+
classifyDevDeliveryDelta,
|
|
51
|
+
createIntegrationDeliveryProof,
|
|
52
|
+
createProjectCutReplayPlan,
|
|
53
|
+
createProjectCutReplayProof,
|
|
54
|
+
createSourceQualificationProof,
|
|
55
|
+
createSourceQualificationProofV2,
|
|
56
|
+
verifyIntegrationDeliveryProof,
|
|
57
|
+
verifyProjectCutReplayProof,
|
|
58
|
+
verifySourceQualificationProof,
|
|
59
|
+
} from "./dev-delivery-proof.js";
|
|
60
|
+
export {
|
|
61
|
+
DEV_DELIVERY_QUALIFICATION_RECEIPT_SCHEMA,
|
|
62
|
+
NATIVE_COMMAND_CONTRACT_SCHEMA,
|
|
63
|
+
NATIVE_EXECUTION_BINDING_SCHEMA,
|
|
64
|
+
NATIVE_EXECUTION_RECEIPT_SCHEMA,
|
|
65
|
+
NATIVE_PROOF_BASE_DELTA_SCHEMA,
|
|
66
|
+
NATIVE_PROOF_REUSE_DECISION_SCHEMA,
|
|
67
|
+
NATIVE_QUALIFICATION_PROOF_SCHEMA,
|
|
68
|
+
createNativeCommandContract,
|
|
69
|
+
createNativeExecutionBinding,
|
|
70
|
+
createNativeExecutionReceipt,
|
|
71
|
+
createNativeProofBaseDelta,
|
|
72
|
+
createNativeProofReuseDecision,
|
|
73
|
+
createNativeQualificationProof,
|
|
74
|
+
normalizeNativeCommandContract,
|
|
75
|
+
verifyNativeExecutionReceipt,
|
|
76
|
+
verifyNativeProofReuseDecision,
|
|
77
|
+
verifyNativeQualificationProof,
|
|
78
|
+
} from "./dev-delivery-native-proof.js";
|
|
79
|
+
export {
|
|
80
|
+
DEV_DELIVERY_CLASSES,
|
|
81
|
+
DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
82
|
+
DEV_DELIVERY_PRIORITIES,
|
|
83
|
+
DEV_DELIVERY_QUEUE_CONTRACT,
|
|
84
|
+
DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
85
|
+
DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA,
|
|
86
|
+
DEV_DELIVERY_WARRANT_SCHEMA,
|
|
87
|
+
createDevDeliveryQueue,
|
|
88
|
+
normalizeDevDeliveryQueue,
|
|
89
|
+
rankDevDeliveryCandidates,
|
|
90
|
+
submitDevDeliveryCandidate,
|
|
91
|
+
};
|
|
9
92
|
export { DEV_DELIVERY_CANCELLATION_RECEIPT_SCHEMA };
|
|
10
|
-
export const DEV_DELIVERY_QUEUE_CONTRACT = "kungfu-buildchain-dev-delivery-warrant-queue";
|
|
11
|
-
export const DEV_DELIVERY_WARRANT_SCHEMA = "kungfu.buildchain.dev-delivery-warrant/v1";
|
|
12
|
-
export const DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-submission-receipt/v1";
|
|
13
|
-
export const DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-selection-receipt/v1";
|
|
14
|
-
export const DEV_DELIVERY_LEASE_RECEIPT_SCHEMA = "kungfu.buildchain.dev-delivery-lease-receipt/v1";
|
|
15
93
|
export { DEV_DELIVERY_SETTLEMENT_RECEIPT_SCHEMA };
|
|
16
|
-
export const DEV_DELIVERY_PRIORITIES = Object.freeze({
|
|
17
|
-
ordinary: 0,
|
|
18
|
-
expedited: 1,
|
|
19
|
-
emergency: 2,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export const DEV_DELIVERY_CLASSES = Object.freeze(["non-native-fast", "native-proof-required", "cross-platform", "release"]);
|
|
23
|
-
|
|
24
|
-
const TERMINAL_STATES = new Set(["merged", "terminal-failure", "dequeued", "cancelled"]);
|
|
25
|
-
function nonNegativeInteger(value, label, fallback = 0) {
|
|
26
|
-
const parsed = Number(value ?? fallback);
|
|
27
|
-
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${label} must be a non-negative integer`);
|
|
28
|
-
return parsed;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function priority(value = "ordinary") {
|
|
32
|
-
const normalized = text(value || "ordinary").toLowerCase();
|
|
33
|
-
if (!(normalized in DEV_DELIVERY_PRIORITIES)) {
|
|
34
|
-
throw new Error(`priority must be one of ${Object.keys(DEV_DELIVERY_PRIORITIES).join(", ")}`);
|
|
35
|
-
}
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function deliveryClass(value) {
|
|
40
|
-
const normalized = text(value);
|
|
41
|
-
if (!DEV_DELIVERY_CLASSES.includes(normalized)) {
|
|
42
|
-
throw new Error(`deliveryClass must be one of ${DEV_DELIVERY_CLASSES.join(", ")}`);
|
|
43
|
-
}
|
|
44
|
-
return normalized;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function normalizePolicy(policy = {}) {
|
|
48
|
-
return {
|
|
49
|
-
agingSeconds: positiveInteger(policy.agingSeconds, "policy agingSeconds", 900),
|
|
50
|
-
maxPriority: positiveInteger(policy.maxPriority, "policy maxPriority", 2),
|
|
51
|
-
leaseSeconds: positiveInteger(policy.leaseSeconds, "policy leaseSeconds", 3600),
|
|
52
|
-
emergencyPolicy: text(policy.emergencyPolicy || "reviewed-explicit-only"),
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function normalizeCandidate(input, expected) {
|
|
57
|
-
const identity = createDevDeliveryCandidateIdentity(input, expected, deliveryClass);
|
|
58
|
-
if (input.candidateId && input.candidateId !== identity.candidateId) {
|
|
59
|
-
throw new Error(`candidateId mismatch for PR #${identity.pullRequestNumber}`);
|
|
60
|
-
}
|
|
61
|
-
const status = text(input.status || "queued");
|
|
62
|
-
if (!["queued", "selected", "proving", "waiting", "blocked", ...TERMINAL_STATES].includes(status)) {
|
|
63
|
-
throw new Error(`unsupported candidate status ${status || "<empty>"}`);
|
|
64
|
-
}
|
|
65
|
-
const candidate = {
|
|
66
|
-
...identity,
|
|
67
|
-
sourceHead: exactSha(input.sourceHead, "sourceHead"),
|
|
68
|
-
sourcePatchRoot: exactRoot(input.sourcePatchRoot, "sourcePatchRoot"),
|
|
69
|
-
sourceProofRoot: exactRoot(input.sourceProofRoot, "sourceProofRoot"),
|
|
70
|
-
planRoot: exactRoot(input.planRoot, "planRoot"),
|
|
71
|
-
closureRoot: exactRoot(input.closureRoot, "closureRoot"),
|
|
72
|
-
dependencyRoot: exactRoot(input.dependencyRoot, "dependencyRoot"),
|
|
73
|
-
toolchainRoot: exactRoot(input.toolchainRoot, "toolchainRoot"), ...(input.environmentRoot ? { environmentRoot: exactRoot(input.environmentRoot, "environmentRoot") } : {}), ...(Object.hasOwn(input, "sourceWorkflowRunId") ? { sourceWorkflowRunId: nonNegativeInteger(input.sourceWorkflowRunId, "candidate sourceWorkflowRunId", 0) } : {}),
|
|
74
|
-
priority: priority(input.priority),
|
|
75
|
-
enqueuedAt: timestamp(input.enqueuedAt, "candidate enqueuedAt"),
|
|
76
|
-
updatedAt: timestamp(input.updatedAt || input.enqueuedAt, "candidate updatedAt"),
|
|
77
|
-
attempts: positiveInteger(input.attempts, "candidate attempts", 1),
|
|
78
|
-
recoveries: nonNegativeInteger(input.recoveries, "candidate recoveries", 0),
|
|
79
|
-
status,
|
|
80
|
-
terminal: input.terminal || null,
|
|
81
|
-
};
|
|
82
|
-
if (Object.hasOwn(input, "affectedPaths")) { if (!Array.isArray(input.affectedPaths)) throw new Error("candidate affectedPaths must be an array"); candidate.affectedPaths = [...new Set(input.affectedPaths.map(text).filter(Boolean))].sort(); } if (Object.hasOwn(input, "releaseBlockerPriority")) candidate.releaseBlockerPriority = normalizeReleaseBlockerPriorityClaim(input.releaseBlockerPriority, candidate, expected);
|
|
83
|
-
return candidate;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function queueBody(queue) {
|
|
87
|
-
const body = clone(queue);
|
|
88
|
-
delete body.stateRoot;
|
|
89
|
-
return body;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function withQueueRoot(queue) {
|
|
93
|
-
return {
|
|
94
|
-
...queueBody(queue),
|
|
95
|
-
stateRoot: devDeliveryContentRoot(queueBody(queue)),
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function createDevDeliveryQueue({ repository: repositoryInput, protectedBase: protectedBaseInput, policy = {}, now = new Date().toISOString() } = {}) {
|
|
100
|
-
return withQueueRoot({
|
|
101
|
-
schemaVersion: 1,
|
|
102
|
-
contract: DEV_DELIVERY_QUEUE_CONTRACT,
|
|
103
|
-
repository: repository(repositoryInput),
|
|
104
|
-
protectedBase: protectedBase(protectedBaseInput),
|
|
105
|
-
generation: 0,
|
|
106
|
-
fencingCounter: 0,
|
|
107
|
-
policy: normalizePolicy(policy),
|
|
108
|
-
activeWarrant: null,
|
|
109
|
-
candidates: [],
|
|
110
|
-
updatedAt: timestamp(now, "now"),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function normalizeDevDeliveryQueue(input, expected = {}) {
|
|
115
|
-
const queue = clone(input || {});
|
|
116
|
-
if (queue.contract !== DEV_DELIVERY_QUEUE_CONTRACT || Number(queue.schemaVersion) !== 1) {
|
|
117
|
-
throw new Error(`dev delivery queue must use ${DEV_DELIVERY_QUEUE_CONTRACT} schemaVersion 1`);
|
|
118
|
-
}
|
|
119
|
-
queue.repository = repository(queue.repository);
|
|
120
|
-
queue.protectedBase = protectedBase(queue.protectedBase);
|
|
121
|
-
if (expected.repository && queue.repository !== expected.repository) {
|
|
122
|
-
throw new Error(`dev delivery queue repository mismatch: ${queue.repository} != ${expected.repository}`);
|
|
123
|
-
}
|
|
124
|
-
if (expected.protectedBase && queue.protectedBase !== expected.protectedBase) {
|
|
125
|
-
throw new Error(`dev delivery queue protectedBase mismatch: ${queue.protectedBase} != ${expected.protectedBase}`);
|
|
126
|
-
}
|
|
127
|
-
queue.generation = nonNegativeInteger(queue.generation, "queue generation");
|
|
128
|
-
queue.fencingCounter = nonNegativeInteger(queue.fencingCounter, "queue fencingCounter");
|
|
129
|
-
queue.policy = normalizePolicy(queue.policy);
|
|
130
|
-
queue.candidates = (queue.candidates || []).map((candidate) => normalizeCandidate(candidate, queue));
|
|
131
|
-
validateDevDeliveryCandidateChain(queue.candidates, TERMINAL_STATES);
|
|
132
|
-
queue.updatedAt = timestamp(queue.updatedAt, "queue updatedAt");
|
|
133
|
-
if (queue.activeWarrant) {
|
|
134
|
-
const warrant = queue.activeWarrant;
|
|
135
|
-
if (warrant.schema !== DEV_DELIVERY_WARRANT_SCHEMA) throw new Error("active Warrant schema is unsupported");
|
|
136
|
-
warrant.candidateId = exactRoot(warrant.candidateId, "Warrant candidateId");
|
|
137
|
-
warrant.fencingToken = exactRoot(warrant.fencingToken, "Warrant fencingToken");
|
|
138
|
-
warrant.expectedOldStateRoot = exactRoot(warrant.expectedOldStateRoot, "Warrant expectedOldStateRoot");
|
|
139
|
-
warrant.generation = positiveInteger(warrant.generation, "Warrant generation");
|
|
140
|
-
warrant.issuedAt = timestamp(warrant.issuedAt, "Warrant issuedAt");
|
|
141
|
-
warrant.expiresAt = timestamp(warrant.expiresAt, "Warrant expiresAt");
|
|
142
|
-
const activeCandidates = queue.candidates.filter((candidate) => ["selected", "proving", "waiting", "blocked"].includes(candidate.status));
|
|
143
|
-
if (activeCandidates.length !== 1 || activeCandidates[0].candidateId !== warrant.candidateId) {
|
|
144
|
-
throw new Error("exactly one active candidate must match the active Warrant");
|
|
145
|
-
}
|
|
146
|
-
if (warrant.releaseBlockerPriority?.claimRoot !== activeCandidates[0].releaseBlockerPriority?.claimRoot) throw new Error("active Warrant release-blocker priority drift");
|
|
147
|
-
} else if (queue.candidates.some((candidate) => ["selected", "proving", "waiting", "blocked"].includes(candidate.status))) {
|
|
148
|
-
throw new Error("active candidate exists without an active Warrant");
|
|
149
|
-
}
|
|
150
|
-
const rooted = withQueueRoot(queue);
|
|
151
|
-
if (input.stateRoot && input.stateRoot !== rooted.stateRoot) throw new Error("dev delivery queue stateRoot drift");
|
|
152
|
-
return rooted;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function transition(queueInput, mutate, nowInput) {
|
|
156
|
-
const before = normalizeDevDeliveryQueue(queueInput);
|
|
157
|
-
const expectedOldStateRoot = before.stateRoot;
|
|
158
|
-
const queue = clone(before);
|
|
159
|
-
delete queue.stateRoot;
|
|
160
|
-
const now = timestamp(nowInput, "now");
|
|
161
|
-
const result = mutate(queue, before, now);
|
|
162
|
-
queue.generation += 1;
|
|
163
|
-
queue.updatedAt = now;
|
|
164
|
-
const after = withQueueRoot(queue);
|
|
165
|
-
return { before, after, expectedOldStateRoot, result };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function submissionReceipt({ before, after, candidate, action, now }) {
|
|
169
|
-
const queueAgeSeconds = Math.max(0, Math.floor((Date.parse(now) - Date.parse(candidate.enqueuedAt)) / 1000));
|
|
170
|
-
return {
|
|
171
|
-
schema: DEV_DELIVERY_SUBMISSION_RECEIPT_SCHEMA,
|
|
172
|
-
repository: after.repository,
|
|
173
|
-
protectedBase: after.protectedBase,
|
|
174
|
-
candidateId: candidate.candidateId,
|
|
175
|
-
pullRequestNumber: candidate.pullRequestNumber,
|
|
176
|
-
sourceHead: candidate.sourceHead,
|
|
177
|
-
assignmentRoot: candidate.assignmentRoot,
|
|
178
|
-
initiativeRoot: candidate.initiativeRoot,
|
|
179
|
-
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
180
|
-
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
181
|
-
sourceProofRoot: candidate.sourceProofRoot,
|
|
182
|
-
planRoot: candidate.planRoot,
|
|
183
|
-
closureRoot: candidate.closureRoot,
|
|
184
|
-
dependencyRoot: candidate.dependencyRoot,
|
|
185
|
-
toolchainRoot: candidate.toolchainRoot,
|
|
186
|
-
sourceWorkflowRunId: candidate.sourceWorkflowRunId,
|
|
187
|
-
...(Object.hasOwn(candidate, "releaseBlockerPriority") ? { releaseBlockerPriority: candidate.releaseBlockerPriority } : {}),
|
|
188
|
-
deliveryClass: candidate.deliveryClass,
|
|
189
|
-
priority: candidate.priority,
|
|
190
|
-
retainedEnqueuedAt: candidate.enqueuedAt,
|
|
191
|
-
queueAgeSeconds,
|
|
192
|
-
attempts: candidate.attempts,
|
|
193
|
-
action,
|
|
194
|
-
expectedOldStateRoot: before.stateRoot,
|
|
195
|
-
nextStateRoot: after.stateRoot,
|
|
196
|
-
nextAction: "Wait for deterministic Warrant selection or observe the active predecessor.",
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function submitDevDeliveryCandidate(queueInput, input, { now = new Date().toISOString() } = {}) {
|
|
201
|
-
const transaction = transition(
|
|
202
|
-
queueInput,
|
|
203
|
-
(queue, before, currentTime) => {
|
|
204
|
-
const candidate = normalizeCandidate(
|
|
205
|
-
{
|
|
206
|
-
...input,
|
|
207
|
-
enqueuedAt: input.enqueuedAt || currentTime,
|
|
208
|
-
updatedAt: currentTime,
|
|
209
|
-
attempts: input.attempts || 1,
|
|
210
|
-
recoveries: input.recoveries || 0,
|
|
211
|
-
status: "queued",
|
|
212
|
-
},
|
|
213
|
-
queue,
|
|
214
|
-
);
|
|
215
|
-
const chainedInput = chainedDevDeliveryAttemptInput({ queue, candidate, input, currentTime, terminalStates: TERMINAL_STATES });
|
|
216
|
-
const attemptedCandidate = chainedInput ? normalizeCandidate(chainedInput, queue) : candidate;
|
|
217
|
-
const conflicting = queue.candidates.find((entry) => entry.pullRequestNumber === attemptedCandidate.pullRequestNumber && entry.candidateId !== attemptedCandidate.candidateId && !TERMINAL_STATES.has(entry.status));
|
|
218
|
-
if (conflicting) throw new Error(`PR #${candidate.pullRequestNumber} already has a different active semantic source`);
|
|
219
|
-
const existing = queue.candidates.find((entry) => entry.candidateId === attemptedCandidate.candidateId);
|
|
220
|
-
let action = "submitted";
|
|
221
|
-
let selected = attemptedCandidate;
|
|
222
|
-
if (existing) {
|
|
223
|
-
if (TERMINAL_STATES.has(existing.status)) {
|
|
224
|
-
throw new Error(`candidate ${attemptedCandidate.candidateId} is terminal and cannot be resubmitted`);
|
|
225
|
-
}
|
|
226
|
-
const exactProofFields = ["sourcePatchRoot", "sourceProofRoot", "planRoot", "closureRoot", "dependencyRoot", "toolchainRoot"];
|
|
227
|
-
const exactProofMatches = exactProofFields.every((field) => existing[field] === attemptedCandidate[field]) && existing.releaseBlockerPriority?.claimRoot === attemptedCandidate.releaseBlockerPriority?.claimRoot;
|
|
228
|
-
if (existing.sourceHead === attemptedCandidate.sourceHead && exactProofMatches) {
|
|
229
|
-
action = "duplicate-noop";
|
|
230
|
-
selected = existing;
|
|
231
|
-
} else {
|
|
232
|
-
if (before.activeWarrant?.candidateId === existing.candidateId) {
|
|
233
|
-
throw new Error("selected candidate sourceHead cannot change before terminal Warrant closeout");
|
|
234
|
-
}
|
|
235
|
-
const headChanged = existing.sourceHead !== attemptedCandidate.sourceHead;
|
|
236
|
-
existing.sourceHead = attemptedCandidate.sourceHead;
|
|
237
|
-
for (const field of exactProofFields) existing[field] = attemptedCandidate[field];
|
|
238
|
-
if (Object.hasOwn(attemptedCandidate, "releaseBlockerPriority")) existing.releaseBlockerPriority = attemptedCandidate.releaseBlockerPriority;
|
|
239
|
-
else delete existing.releaseBlockerPriority;
|
|
240
|
-
existing.updatedAt = currentTime;
|
|
241
|
-
existing.attempts += 1;
|
|
242
|
-
existing.status = "queued";
|
|
243
|
-
action = headChanged ? "safe-head-repair-retained-age" : "safe-proof-refresh-retained-age";
|
|
244
|
-
selected = existing;
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
queue.candidates.push(attemptedCandidate);
|
|
248
|
-
}
|
|
249
|
-
return { candidate: selected, action };
|
|
250
|
-
},
|
|
251
|
-
now,
|
|
252
|
-
);
|
|
253
|
-
const receipt = submissionReceipt({
|
|
254
|
-
before: transaction.before,
|
|
255
|
-
after: transaction.after,
|
|
256
|
-
candidate: transaction.result.candidate,
|
|
257
|
-
action: transaction.result.action,
|
|
258
|
-
now: timestamp(now, "now"),
|
|
259
|
-
});
|
|
260
|
-
return {
|
|
261
|
-
queue: transaction.after,
|
|
262
|
-
receipt,
|
|
263
|
-
receiptRoot: devDeliveryContentRoot(receipt),
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function effectivePriority(candidate, policy, now) {
|
|
268
|
-
const ageSeconds = Math.max(0, Math.floor((Date.parse(now) - Date.parse(candidate.enqueuedAt)) / 1000));
|
|
269
|
-
const agingBoost = Math.floor(ageSeconds / policy.agingSeconds);
|
|
270
|
-
return {
|
|
271
|
-
ageSeconds,
|
|
272
|
-
basePriority: DEV_DELIVERY_PRIORITIES[candidate.priority],
|
|
273
|
-
agingBoost,
|
|
274
|
-
score: Math.min(policy.maxPriority, DEV_DELIVERY_PRIORITIES[candidate.priority] + agingBoost),
|
|
275
|
-
releaseBlocker: Boolean(candidate.releaseBlockerPriority),
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
94
|
|
|
279
|
-
export function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
.filter((candidate) => candidate.status === "queued")
|
|
284
|
-
.map((candidate) => ({
|
|
285
|
-
candidate,
|
|
286
|
-
priority: effectivePriority(candidate, queue.policy, currentTime),
|
|
287
|
-
}))
|
|
288
|
-
.sort((left, right) => compareReleaseBlockerPriority(left, right) || right.priority.score - left.priority.score || left.candidate.enqueuedAt.localeCompare(right.candidate.enqueuedAt) || left.candidate.candidateId.localeCompare(right.candidate.candidateId));
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function warrantToken(input) {
|
|
292
|
-
return devDeliveryContentRoot({
|
|
293
|
-
schema: DEV_DELIVERY_WARRANT_SCHEMA,
|
|
294
|
-
repository: input.repository,
|
|
295
|
-
protectedBase: input.protectedBase,
|
|
296
|
-
candidateId: input.candidateId,
|
|
297
|
-
generation: input.generation,
|
|
298
|
-
expectedOldStateRoot: input.expectedOldStateRoot,
|
|
299
|
-
issuedAt: input.issuedAt,
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOString(), leaseSeconds } = {}) {
|
|
95
|
+
export function selectDevDeliveryWarrant(
|
|
96
|
+
queueInput,
|
|
97
|
+
{ now = new Date().toISOString(), leaseSeconds } = {},
|
|
98
|
+
) {
|
|
304
99
|
const currentTime = timestamp(now, "now");
|
|
305
100
|
let queue = normalizeDevDeliveryQueue(queueInput);
|
|
306
101
|
let recoveryReceipt = null;
|
|
307
|
-
if (
|
|
102
|
+
if (
|
|
103
|
+
queue.activeWarrant &&
|
|
104
|
+
Date.parse(queue.activeWarrant.expiresAt) <= Date.parse(currentTime)
|
|
105
|
+
) {
|
|
308
106
|
const recovered = recoverExpiredDevDeliveryWarrant(queue, {
|
|
309
107
|
now: currentTime,
|
|
310
108
|
});
|
|
@@ -312,16 +110,22 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
312
110
|
recoveryReceipt = recovered.receipt;
|
|
313
111
|
}
|
|
314
112
|
if (queue.activeWarrant) {
|
|
113
|
+
const stopRequired =
|
|
114
|
+
recoveryReceipt?.action === "expired-lease-fenced-stop-required";
|
|
315
115
|
const receipt = {
|
|
316
116
|
schema: DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
317
|
-
selected:
|
|
318
|
-
reason:
|
|
117
|
+
selected: !stopRequired,
|
|
118
|
+
reason: stopRequired
|
|
119
|
+
? "expired-provisional-warrant-stop-required"
|
|
120
|
+
: "non-preemptive-active-warrant",
|
|
319
121
|
candidateId: queue.activeWarrant.candidateId,
|
|
320
122
|
fencingToken: queue.activeWarrant.fencingToken,
|
|
321
123
|
leaseGeneration: queue.activeWarrant.generation,
|
|
322
124
|
expectedOldStateRoot: queue.stateRoot,
|
|
323
125
|
nextStateRoot: queue.stateRoot,
|
|
324
|
-
nextAction:
|
|
126
|
+
nextAction: stopRequired
|
|
127
|
+
? recoveryReceipt.nextAction
|
|
128
|
+
: "Continue the active delivery attempt; later candidates remain visibly queued.",
|
|
325
129
|
};
|
|
326
130
|
return {
|
|
327
131
|
queue,
|
|
@@ -353,37 +157,16 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
353
157
|
const transaction = transition(
|
|
354
158
|
queue,
|
|
355
159
|
(next, before) => {
|
|
356
|
-
const candidate = next.candidates.find(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
sourceHead: candidate.sourceHead,
|
|
367
|
-
assignmentRoot: candidate.assignmentRoot,
|
|
368
|
-
initiativeRoot: candidate.initiativeRoot,
|
|
369
|
-
sourceIdentityRoot: candidate.sourceIdentityRoot,
|
|
370
|
-
sourcePatchRoot: candidate.sourcePatchRoot,
|
|
371
|
-
sourceProofRoot: candidate.sourceProofRoot,
|
|
372
|
-
planRoot: candidate.planRoot,
|
|
373
|
-
closureRoot: candidate.closureRoot,
|
|
374
|
-
dependencyRoot: candidate.dependencyRoot,
|
|
375
|
-
toolchainRoot: candidate.toolchainRoot,
|
|
376
|
-
sourceWorkflowRunId: candidate.sourceWorkflowRunId,
|
|
377
|
-
...(Object.hasOwn(candidate, "releaseBlockerPriority") ? { releaseBlockerPriority: candidate.releaseBlockerPriority } : {}),
|
|
378
|
-
deliveryClass: candidate.deliveryClass,
|
|
379
|
-
generation: next.fencingCounter,
|
|
380
|
-
expectedOldStateRoot: before.stateRoot,
|
|
381
|
-
issuedAt,
|
|
382
|
-
expiresAt: new Date(Date.parse(issuedAt) + effectiveLeaseSeconds * 1000).toISOString(),
|
|
383
|
-
fencingToken: "",
|
|
384
|
-
nextAction: "Replay on the exact current dev base, prove required checks, then enqueue the unchanged PR head.",
|
|
385
|
-
};
|
|
386
|
-
warrant.fencingToken = warrantToken(warrant);
|
|
160
|
+
const candidate = next.candidates.find(
|
|
161
|
+
(entry) => entry.candidateId === selected.candidate.candidateId,
|
|
162
|
+
);
|
|
163
|
+
const warrant = issueDevDeliveryWarrant(
|
|
164
|
+
next,
|
|
165
|
+
before,
|
|
166
|
+
candidate,
|
|
167
|
+
currentTime,
|
|
168
|
+
leaseSeconds,
|
|
169
|
+
);
|
|
387
170
|
next.activeWarrant = warrant;
|
|
388
171
|
candidate.status = "selected";
|
|
389
172
|
candidate.updatedAt = currentTime;
|
|
@@ -394,7 +177,11 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
394
177
|
const receipt = {
|
|
395
178
|
schema: DEV_DELIVERY_SELECTION_RECEIPT_SCHEMA,
|
|
396
179
|
selected: true,
|
|
397
|
-
reason: selected.priority.releaseBlocker
|
|
180
|
+
reason: selected.priority.releaseBlocker
|
|
181
|
+
? "release-blocker-bounded-priority"
|
|
182
|
+
: selected.priority.agingBoost > 0
|
|
183
|
+
? "fifo-aging-bounded-priority"
|
|
184
|
+
: "fifo-bounded-priority",
|
|
398
185
|
repository: transaction.after.repository,
|
|
399
186
|
protectedBase: transaction.after.protectedBase,
|
|
400
187
|
candidateId: transaction.result.candidate.candidateId,
|
|
@@ -410,7 +197,23 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
410
197
|
dependencyRoot: transaction.result.candidate.dependencyRoot,
|
|
411
198
|
toolchainRoot: transaction.result.candidate.toolchainRoot,
|
|
412
199
|
sourceWorkflowRunId: transaction.result.candidate.sourceWorkflowRunId,
|
|
413
|
-
...(
|
|
200
|
+
...(transaction.result.candidate.nativeCommandContract
|
|
201
|
+
? {
|
|
202
|
+
nativeCommandContract:
|
|
203
|
+
transaction.result.candidate.nativeCommandContract,
|
|
204
|
+
}
|
|
205
|
+
: {}),
|
|
206
|
+
...(transaction.result.candidate.shardEvidenceRoots
|
|
207
|
+
? {
|
|
208
|
+
shardEvidenceRoots: transaction.result.candidate.shardEvidenceRoots,
|
|
209
|
+
}
|
|
210
|
+
: {}),
|
|
211
|
+
...(Object.hasOwn(transaction.result.candidate, "releaseBlockerPriority")
|
|
212
|
+
? {
|
|
213
|
+
releaseBlockerPriority:
|
|
214
|
+
transaction.result.candidate.releaseBlockerPriority,
|
|
215
|
+
}
|
|
216
|
+
: {}),
|
|
414
217
|
deliveryClass: transaction.result.candidate.deliveryClass,
|
|
415
218
|
queueAgeSeconds: selected.priority.ageSeconds,
|
|
416
219
|
basePriority: selected.priority.basePriority,
|
|
@@ -430,25 +233,49 @@ export function selectDevDeliveryWarrant(queueInput, { now = new Date().toISOStr
|
|
|
430
233
|
recoveryReceipt,
|
|
431
234
|
};
|
|
432
235
|
}
|
|
433
|
-
|
|
434
|
-
|
|
236
|
+
function assertWarrantMutation(
|
|
237
|
+
queue,
|
|
238
|
+
warrant,
|
|
239
|
+
now,
|
|
240
|
+
{ allowExpired = false } = {},
|
|
241
|
+
) {
|
|
435
242
|
if (!queue.activeWarrant) throw new Error("no active Delivery Warrant");
|
|
436
|
-
if (text(warrant?.fencingToken) !== queue.activeWarrant.fencingToken)
|
|
437
|
-
|
|
438
|
-
if (
|
|
439
|
-
|
|
243
|
+
if (text(warrant?.fencingToken) !== queue.activeWarrant.fencingToken)
|
|
244
|
+
throw new Error("stale fencing token");
|
|
245
|
+
if (Number(warrant?.generation) !== queue.activeWarrant.generation)
|
|
246
|
+
throw new Error("stale lease generation");
|
|
247
|
+
if (text(warrant?.candidateId) !== queue.activeWarrant.candidateId)
|
|
248
|
+
throw new Error("Warrant candidate mismatch");
|
|
249
|
+
if (
|
|
250
|
+
!allowExpired &&
|
|
251
|
+
Date.parse(queue.activeWarrant.expiresAt) <= Date.parse(now)
|
|
252
|
+
)
|
|
253
|
+
throw new Error("Delivery Warrant lease expired");
|
|
440
254
|
}
|
|
441
|
-
|
|
442
|
-
|
|
255
|
+
export function heartbeatDevDeliveryWarrant(
|
|
256
|
+
queueInput,
|
|
257
|
+
warrant,
|
|
258
|
+
{ now = new Date().toISOString(), leaseSeconds } = {},
|
|
259
|
+
) {
|
|
443
260
|
const currentTime = timestamp(now, "now");
|
|
444
261
|
const transaction = transition(
|
|
445
262
|
queueInput,
|
|
446
263
|
(queue, before) => {
|
|
447
264
|
assertWarrantMutation(before, warrant, currentTime);
|
|
448
|
-
const effectiveLeaseSeconds = positiveInteger(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
265
|
+
const effectiveLeaseSeconds = positiveInteger(
|
|
266
|
+
leaseSeconds,
|
|
267
|
+
"leaseSeconds",
|
|
268
|
+
queue.policy.leaseSeconds,
|
|
269
|
+
);
|
|
270
|
+
queue.activeWarrant.heartbeatAt = currentTime;
|
|
271
|
+
queue.activeWarrant.expiresAt = new Date(
|
|
272
|
+
Date.parse(currentTime) + effectiveLeaseSeconds * 1000,
|
|
273
|
+
).toISOString();
|
|
274
|
+
const candidate = queue.candidates.find(
|
|
275
|
+
(entry) => entry.candidateId === queue.activeWarrant.candidateId,
|
|
276
|
+
);
|
|
277
|
+
candidate.status =
|
|
278
|
+
queue.activeWarrant.phase === "qualified" ? "qualified" : "proving";
|
|
452
279
|
candidate.updatedAt = currentTime;
|
|
453
280
|
return { candidate, warrant: queue.activeWarrant };
|
|
454
281
|
},
|
|
@@ -471,18 +298,44 @@ export function heartbeatDevDeliveryWarrant(queueInput, warrant, { now = new Dat
|
|
|
471
298
|
receiptRoot: devDeliveryContentRoot(receipt),
|
|
472
299
|
};
|
|
473
300
|
}
|
|
474
|
-
|
|
475
|
-
|
|
301
|
+
export function recoverExpiredDevDeliveryWarrant(
|
|
302
|
+
queueInput,
|
|
303
|
+
{ now = new Date().toISOString() } = {},
|
|
304
|
+
) {
|
|
476
305
|
const currentTime = timestamp(now, "now");
|
|
477
306
|
const before = normalizeDevDeliveryQueue(queueInput);
|
|
478
|
-
if (
|
|
307
|
+
if (
|
|
308
|
+
!before.activeWarrant ||
|
|
309
|
+
Date.parse(before.activeWarrant.expiresAt) > Date.parse(currentTime)
|
|
310
|
+
) {
|
|
479
311
|
const receipt = {
|
|
480
312
|
schema: DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
481
313
|
action: "recovery-noop",
|
|
482
314
|
reason: before.activeWarrant ? "lease-active" : "no-active-warrant",
|
|
483
315
|
expectedOldStateRoot: before.stateRoot,
|
|
484
316
|
nextStateRoot: before.stateRoot,
|
|
485
|
-
nextAction: before.activeWarrant
|
|
317
|
+
nextAction: before.activeWarrant
|
|
318
|
+
? "Continue the active delivery attempt."
|
|
319
|
+
: "Select the next queued candidate.",
|
|
320
|
+
};
|
|
321
|
+
return {
|
|
322
|
+
queue: before,
|
|
323
|
+
receipt,
|
|
324
|
+
receiptRoot: devDeliveryContentRoot(receipt),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (before.activeWarrant.phase === "provisional") {
|
|
328
|
+
const receipt = {
|
|
329
|
+
schema: DEV_DELIVERY_LEASE_RECEIPT_SCHEMA,
|
|
330
|
+
action: "expired-lease-fenced-stop-required",
|
|
331
|
+
reason: "provisional-native-worker-stop-unproven",
|
|
332
|
+
candidateId: before.activeWarrant.candidateId,
|
|
333
|
+
rejectedFencingToken: before.activeWarrant.fencingToken,
|
|
334
|
+
expiredLeaseGeneration: before.activeWarrant.generation,
|
|
335
|
+
expectedOldStateRoot: before.stateRoot,
|
|
336
|
+
nextStateRoot: before.stateRoot,
|
|
337
|
+
nextAction:
|
|
338
|
+
"Prove the fenced native worker stopped, then close this exact generation with rooted terminal evidence.",
|
|
486
339
|
};
|
|
487
340
|
return {
|
|
488
341
|
queue: before,
|
|
@@ -494,7 +347,9 @@ export function recoverExpiredDevDeliveryWarrant(queueInput, { now = new Date().
|
|
|
494
347
|
before,
|
|
495
348
|
(queue) => {
|
|
496
349
|
const expired = clone(queue.activeWarrant);
|
|
497
|
-
const candidate = queue.candidates.find(
|
|
350
|
+
const candidate = queue.candidates.find(
|
|
351
|
+
(entry) => entry.candidateId === expired.candidateId,
|
|
352
|
+
);
|
|
498
353
|
candidate.status = "queued";
|
|
499
354
|
candidate.recoveries += 1;
|
|
500
355
|
candidate.attempts += 1;
|
|
@@ -513,7 +368,8 @@ export function recoverExpiredDevDeliveryWarrant(queueInput, { now = new Date().
|
|
|
513
368
|
retainedEnqueuedAt: transaction.result.candidate.enqueuedAt,
|
|
514
369
|
expectedOldStateRoot: transaction.expectedOldStateRoot,
|
|
515
370
|
nextStateRoot: transaction.after.stateRoot,
|
|
516
|
-
nextAction:
|
|
371
|
+
nextAction:
|
|
372
|
+
"Re-select through the queue to mint a new lease generation and fencing token.",
|
|
517
373
|
};
|
|
518
374
|
return {
|
|
519
375
|
queue: transaction.after,
|
|
@@ -521,19 +377,54 @@ export function recoverExpiredDevDeliveryWarrant(queueInput, { now = new Date().
|
|
|
521
377
|
receiptRoot: devDeliveryContentRoot(receipt),
|
|
522
378
|
};
|
|
523
379
|
}
|
|
524
|
-
|
|
525
|
-
|
|
380
|
+
export const qualifyDevDeliveryWarrant = createDevDeliveryWarrantQualifier({
|
|
381
|
+
transition,
|
|
382
|
+
assertWarrantMutation,
|
|
383
|
+
});
|
|
384
|
+
export function closeDevDeliveryWarrant(
|
|
385
|
+
queueInput,
|
|
386
|
+
warrant,
|
|
387
|
+
{
|
|
388
|
+
outcome,
|
|
389
|
+
evidenceRoot,
|
|
390
|
+
reason = "",
|
|
391
|
+
providerFailureAuthority = null,
|
|
392
|
+
now = new Date().toISOString(),
|
|
393
|
+
} = {},
|
|
394
|
+
) {
|
|
526
395
|
const currentTime = timestamp(now, "now");
|
|
527
396
|
const normalizedOutcome = text(outcome);
|
|
528
397
|
if (!TERMINAL_STATES.has(normalizedOutcome)) {
|
|
529
|
-
throw new Error(
|
|
398
|
+
throw new Error(
|
|
399
|
+
`outcome must be one of ${[...TERMINAL_STATES].join(", ")}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const failureAuthority = normalizeProviderFailureAuthorityBinding(
|
|
403
|
+
providerFailureAuthority || {},
|
|
404
|
+
);
|
|
405
|
+
if (failureAuthority && normalizedOutcome !== "terminal-failure") {
|
|
406
|
+
throw new Error(
|
|
407
|
+
"provider failure authority is valid only for terminal-failure",
|
|
408
|
+
);
|
|
530
409
|
}
|
|
531
410
|
const transaction = transition(
|
|
532
411
|
queueInput,
|
|
533
412
|
(queue, before) => {
|
|
534
|
-
assertWarrantMutation(before, warrant, currentTime
|
|
413
|
+
assertWarrantMutation(before, warrant, currentTime, {
|
|
414
|
+
allowExpired: true,
|
|
415
|
+
});
|
|
416
|
+
if (
|
|
417
|
+
(before.activeWarrant.phase || "qualified") !== "qualified" &&
|
|
418
|
+
normalizedOutcome === "merged"
|
|
419
|
+
) {
|
|
420
|
+
throw new Error(
|
|
421
|
+
"provisional Warrant cannot settle as merged before native qualification",
|
|
422
|
+
);
|
|
423
|
+
}
|
|
535
424
|
const active = clone(queue.activeWarrant);
|
|
536
|
-
const candidate = queue.candidates.find(
|
|
425
|
+
const candidate = queue.candidates.find(
|
|
426
|
+
(entry) => entry.candidateId === active.candidateId,
|
|
427
|
+
);
|
|
537
428
|
candidate.status = normalizedOutcome;
|
|
538
429
|
candidate.updatedAt = currentTime;
|
|
539
430
|
candidate.terminal = {
|
|
@@ -543,6 +434,7 @@ export function closeDevDeliveryWarrant(queueInput, warrant, { outcome, evidence
|
|
|
543
434
|
closedAt: currentTime,
|
|
544
435
|
fencingToken: active.fencingToken,
|
|
545
436
|
leaseGeneration: active.generation,
|
|
437
|
+
...(failureAuthority || {}),
|
|
546
438
|
};
|
|
547
439
|
queue.activeWarrant = null;
|
|
548
440
|
return { candidate, active };
|
|
@@ -558,8 +450,13 @@ export function closeDevDeliveryWarrant(queueInput, warrant, { outcome, evidence
|
|
|
558
450
|
fencingToken: transaction.result.active.fencingToken,
|
|
559
451
|
leaseGeneration: transaction.result.active.generation,
|
|
560
452
|
evidenceRoot: transaction.result.candidate.terminal.evidenceRoot,
|
|
453
|
+
...(failureAuthority || {}),
|
|
561
454
|
expectedOldStateRoot: transaction.expectedOldStateRoot,
|
|
562
455
|
nextStateRoot: transaction.after.stateRoot,
|
|
456
|
+
successorWake: createDevDeliverySuccessorWake(
|
|
457
|
+
transaction.after,
|
|
458
|
+
currentTime,
|
|
459
|
+
),
|
|
563
460
|
nextAction: "Select the next queued candidate, if any.",
|
|
564
461
|
};
|
|
565
462
|
return {
|
|
@@ -568,17 +465,70 @@ export function closeDevDeliveryWarrant(queueInput, warrant, { outcome, evidence
|
|
|
568
465
|
receiptRoot: devDeliveryContentRoot(receipt),
|
|
569
466
|
};
|
|
570
467
|
}
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
468
|
+
function createDevDeliverySuccessorWake(queue, now) {
|
|
469
|
+
const next = rankDevDeliveryCandidates(queue, { now })[0]?.candidate;
|
|
470
|
+
if (!next) return null;
|
|
471
|
+
return {
|
|
472
|
+
schema: "kungfu.buildchain.dev-delivery-wake/v1",
|
|
473
|
+
targetBranch: queue.protectedBase,
|
|
474
|
+
...Object.fromEntries(
|
|
475
|
+
[
|
|
476
|
+
"candidateId",
|
|
477
|
+
"pullRequestNumber",
|
|
478
|
+
"sourceHead",
|
|
479
|
+
"assignmentRoot",
|
|
480
|
+
"initiativeRoot",
|
|
481
|
+
"sourceIdentityRoot",
|
|
482
|
+
"sourcePatchRoot",
|
|
483
|
+
"sourceProofRoot",
|
|
484
|
+
"planRoot",
|
|
485
|
+
"closureRoot",
|
|
486
|
+
"dependencyRoot",
|
|
487
|
+
"toolchainRoot",
|
|
488
|
+
"deliveryClass",
|
|
489
|
+
"priority",
|
|
490
|
+
"nativeCommandContract",
|
|
491
|
+
"shardEvidenceRoots",
|
|
492
|
+
]
|
|
493
|
+
.filter((field) => next[field] !== undefined)
|
|
494
|
+
.map((field) => [field, next[field]]),
|
|
495
|
+
),
|
|
496
|
+
...(next.environmentRoot ? { environmentRoot: next.environmentRoot } : {}),
|
|
497
|
+
...(next.affectedPaths ? { affectedPaths: next.affectedPaths } : {}),
|
|
498
|
+
...(Object.hasOwn(next, "sourceWorkflowRunId")
|
|
499
|
+
? { sourceWorkflowRunId: next.sourceWorkflowRunId }
|
|
500
|
+
: {}),
|
|
501
|
+
...(next.releaseBlockerPriority
|
|
502
|
+
? { releaseBlockerPriority: next.releaseBlockerPriority }
|
|
503
|
+
: {}),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const cancelQueuedDevDeliveryCandidateTransition =
|
|
507
|
+
createCancelQueuedDevDeliveryCandidate(normalizeDevDeliveryQueue);
|
|
508
|
+
export function cancelQueuedDevDeliveryCandidate(queueInput, input, options) {
|
|
509
|
+
return cancelQueuedDevDeliveryCandidateTransition(queueInput, input, options);
|
|
510
|
+
}
|
|
511
|
+
export const settleDevDeliveryTerminalEvent = createDevDeliveryTerminalSettler({
|
|
512
|
+
normalizeQueue: normalizeDevDeliveryQueue,
|
|
513
|
+
closeWarrant: closeDevDeliveryWarrant,
|
|
514
|
+
cancelQueued: cancelQueuedDevDeliveryCandidate,
|
|
515
|
+
terminalStates: TERMINAL_STATES,
|
|
516
|
+
});
|
|
517
|
+
export function observeDevDeliveryQueue(
|
|
518
|
+
queueInput,
|
|
519
|
+
{ now = new Date().toISOString(), allowLegacyV3Readback = false } = {},
|
|
520
|
+
) {
|
|
521
|
+
const queue = normalizeDevDeliveryQueue(queueInput, {
|
|
522
|
+
allowLegacyV3Readback,
|
|
523
|
+
});
|
|
578
524
|
const currentTime = timestamp(now, "now");
|
|
579
525
|
const states = {};
|
|
580
|
-
for (const candidate of queue.candidates)
|
|
581
|
-
|
|
526
|
+
for (const candidate of queue.candidates)
|
|
527
|
+
states[candidate.status] = (states[candidate.status] || 0) + 1;
|
|
528
|
+
const queued = rankDevDeliveryCandidates(queue, {
|
|
529
|
+
now: currentTime,
|
|
530
|
+
allowLegacyV3Readback,
|
|
531
|
+
});
|
|
582
532
|
return {
|
|
583
533
|
schema: "kungfu.buildchain.dev-delivery-queue-observation/v1",
|
|
584
534
|
repository: queue.repository,
|
|
@@ -586,7 +536,12 @@ export function observeDevDeliveryQueue(queueInput, { now = new Date().toISOStri
|
|
|
586
536
|
stateRoot: queue.stateRoot,
|
|
587
537
|
generation: queue.generation,
|
|
588
538
|
activeWarrant: queue.activeWarrant,
|
|
589
|
-
activeCandidate: queue.activeWarrant
|
|
539
|
+
activeCandidate: queue.activeWarrant
|
|
540
|
+
? queue.candidates.find(
|
|
541
|
+
(candidate) =>
|
|
542
|
+
candidate.candidateId === queue.activeWarrant.candidateId,
|
|
543
|
+
) || null
|
|
544
|
+
: null,
|
|
590
545
|
states,
|
|
591
546
|
queued: queued.map((entry, index) => ({
|
|
592
547
|
position: index + 1,
|
|
@@ -598,7 +553,10 @@ export function observeDevDeliveryQueue(queueInput, { now = new Date().toISOStri
|
|
|
598
553
|
queueAgeSeconds: entry.priority.ageSeconds,
|
|
599
554
|
attempts: entry.candidate.attempts,
|
|
600
555
|
recoveries: entry.candidate.recoveries,
|
|
601
|
-
nextAction:
|
|
556
|
+
nextAction:
|
|
557
|
+
index === 0 && !queue.activeWarrant
|
|
558
|
+
? "eligible-for-selection"
|
|
559
|
+
: "wait-for-active-warrant",
|
|
602
560
|
})),
|
|
603
561
|
observedAt: currentTime,
|
|
604
562
|
};
|