@defrex/autobuild 0.13.0 → 0.14.0
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/README.md +1 -1
- package/SPEC.md +19 -1
- package/bin/ab-dispatch-kernel.ts +4 -6
- package/docs/architecture.md +96 -70
- package/docs/hosted-service.md +2 -2
- package/docs/operator-api.md +1 -1
- package/docs/remote-store-protocol.md +84 -2
- package/package.json +4 -3
- package/packages/core/src/cli/attention.ts +18 -164
- package/packages/core/src/cli/build-control.ts +11 -15
- package/packages/core/src/cli/bulk-control.ts +3 -2
- package/packages/core/src/cli/context.ts +16 -78
- package/packages/core/src/cli/dashboard/model.ts +36 -22
- package/packages/core/src/cli/dispatch-frontend.ts +24 -67
- package/packages/core/src/cli/dispatch-process.ts +14 -7
- package/packages/core/src/cli/dispatch-repository-feed.ts +196 -0
- package/packages/core/src/cli/dispatch.ts +148 -79
- package/packages/core/src/cli/dispatcher-liveness.ts +26 -19
- package/packages/core/src/cli/harvest.ts +71 -39
- package/packages/core/src/cli/historical-build-state.ts +27 -0
- package/packages/core/src/cli/main.ts +3 -3
- package/packages/core/src/cli/pr-attachments.ts +10 -13
- package/packages/core/src/cli/pr-summary-publication.ts +27 -60
- package/packages/core/src/cli/pr-summary.ts +43 -13
- package/packages/core/src/cli/publication-settlement.ts +10 -20
- package/packages/core/src/cli/repository-status.ts +38 -2
- package/packages/core/src/cli/status.ts +62 -32
- package/packages/core/src/cli/terminals.ts +25 -142
- package/packages/core/src/cli/ticket.ts +2 -2
- package/packages/core/src/cli/wait.ts +91 -84
- package/packages/core/src/cli/watch.ts +60 -97
- package/packages/core/src/config/pipeline-source.ts +4 -3
- package/packages/core/src/events/repository.ts +7 -3
- package/packages/core/src/integration/harness.ts +1 -2
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +2429 -9
- package/packages/core/src/kernel/auto-merge.ts +19 -3
- package/packages/core/src/kernel/dispatcher-liveness.ts +10 -4
- package/packages/core/src/kernel/generators/repository-journals.ts +14 -3
- package/packages/core/src/kernel/harvest.ts +25 -23
- package/packages/core/src/kernel/log-index-value.ts +38 -0
- package/packages/core/src/kernel/read-facts.ts +637 -0
- package/packages/core/src/kernel/reducer-registry.ts +137 -3
- package/packages/core/src/kernel/repository-attention.ts +179 -0
- package/packages/core/src/kernel/service-facts.ts +135 -0
- package/packages/core/src/kernel/terminal-guard.ts +133 -0
- package/packages/core/src/operator/control.ts +5 -6
- package/packages/core/src/operator/index.ts +2 -0
- package/packages/core/src/operator/query.ts +37 -20
- package/packages/core/src/operator/read-view.ts +4 -13
- package/packages/core/src/operator/requests.ts +2 -14
- package/packages/core/src/operator/sandbox.ts +10 -9
- package/packages/core/src/operator/tickets.ts +2 -2
- package/packages/core/src/plugin-sdk/index.ts +13 -0
- package/packages/core/src/processes/build-child.ts +6 -6
- package/packages/core/src/processes/build-runner.ts +62 -70
- package/packages/core/src/processes/dispatch-failure-budget.ts +5 -4
- package/packages/core/src/processes/dispatcher.ts +113 -173
- package/packages/core/src/processes/harvest-execution-state.ts +15 -9
- package/packages/core/src/processes/harvest-runner.ts +34 -70
- package/packages/core/src/processes/harvest.ts +38 -15
- package/packages/core/src/processes/infrastructure-failure-budget.ts +13 -15
- package/packages/core/src/processes/orchestrator-tick.ts +2 -2
- package/packages/core/src/processes/publication-loss.ts +35 -21
- package/packages/core/src/processes/repo-view.ts +229 -1134
- package/packages/core/src/processes/tick-publication.ts +110 -0
- package/packages/core/src/store/adapter.ts +23 -0
- package/packages/core/src/store/build-scope.ts +17 -0
- package/packages/core/src/store/contract.ts +251 -0
- package/packages/core/src/store/local/schema.ts +1 -0
- package/packages/core/src/store/local/store.ts +179 -54
- package/packages/core/src/store/memory.ts +119 -29
- package/packages/core/src/store/phase-session.ts +17 -0
- package/packages/core/src/store/reduced-reads.ts +239 -0
- package/packages/core/src/store/reducer-value-shapes.json +14056 -0
- package/packages/core/src/store/reducer-value-validation.ts +96 -0
- package/packages/core/src/store/reducer-values.ts +145 -0
- package/packages/core/src/store/remote/client.ts +41 -0
- package/packages/core/src/store/remote/compatibility.ts +2 -0
- package/packages/core/src/store/remote/index.ts +19 -0
- package/packages/core/src/store/remote/protocol.ts +16 -0
- package/packages/core/src/store/remote/version.ts +3 -1
- package/packages/core/src/store/repo-state-events.ts +1 -1
- package/packages/core/src/store/repo-tick.ts +72 -0
- package/packages/core/src/store/session-handle.ts +19 -0
- package/packages/core/src/store/snapshots.ts +10 -0
- package/packages/core/src/store/ticket-assets.ts +4 -4
- package/packages/core/src/store/types.ts +39 -2
- package/packages/core/src/testing/dispatch-read-budget.ts +215 -0
- package/packages/core/src/testing/index.ts +8 -0
- package/packages/core/src/testing/reduced-read-budget.ts +462 -0
- package/skills/guide/SKILL.md +6 -1
- package/skills/guide/references/operator-api.md +1 -1
- package/skills/guide/references/remote-store-protocol.md +84 -2
- package/templates/hosted-service/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readBuildFacts, readBuildSelector, readPhaseFailures } from '../store/reduced-reads'
|
|
1
2
|
import { delegateCredential } from '../store/remote/credentials'
|
|
2
3
|
import { refreshSummaryBoundary } from '../cli/pr-summary-publication'
|
|
3
4
|
/**
|
|
@@ -50,12 +51,11 @@ import type { Config } from '../config/schema'
|
|
|
50
51
|
import type { AbEvent, EventWrite } from '../events/catalog'
|
|
51
52
|
import type { EventPayload } from '../events/payloads'
|
|
52
53
|
import { KERNEL, agentActor, type Actor } from '../events/envelope'
|
|
53
|
-
import { isRemoteWorkspace } from '../events/workspace-remote'
|
|
54
54
|
import type { IdSource } from '../ids'
|
|
55
|
-
import {
|
|
55
|
+
import { decideNextFromFacts, type Decision } from '../kernel/engine'
|
|
56
56
|
import { defineReducer } from '../kernel/incremental'
|
|
57
57
|
import { PHASE_SPECS } from '../kernel/phases'
|
|
58
|
-
import {
|
|
58
|
+
import type { BuildState } from '../kernel/reducer'
|
|
59
59
|
import { evaluateVerifyApplicability } from '../kernel/verify-applicability'
|
|
60
60
|
import {
|
|
61
61
|
verifyPhase,
|
|
@@ -89,7 +89,6 @@ import { phaseScratchRejection, scratchPathsTouchedInRange } from '../ports/work
|
|
|
89
89
|
import type { Exec } from '../ports/workspace/git-worktree'
|
|
90
90
|
import type { BuildScopedStore, Clock } from '../store/types'
|
|
91
91
|
import { resetsPhaseFailureBudget, type PhaseFailureResetEscalation } from './phase-failure-budget'
|
|
92
|
-
import { publicationPending } from './publication-state'
|
|
93
92
|
|
|
94
93
|
export { publicationPending } from './publication-state'
|
|
95
94
|
|
|
@@ -639,9 +638,6 @@ export class BuildRunner {
|
|
|
639
638
|
/** Consecutive durable setup failures since the latest successful attachment
|
|
640
639
|
* or explicit human answer. The answer re-arms the budget but deliberately
|
|
641
640
|
* does not erase the current diagnostic while recovery is still running. */
|
|
642
|
-
private setupStreak(events: readonly AbEvent[]): number {
|
|
643
|
-
return setupStreakReducer.reduce(events)
|
|
644
|
-
}
|
|
645
641
|
|
|
646
642
|
private setupQuestion(failure: EventPayload<'runner.setup-failed'>): string {
|
|
647
643
|
const status =
|
|
@@ -671,10 +667,10 @@ export class BuildRunner {
|
|
|
671
667
|
* announce takeover against that same boundary. If a write is interrupted,
|
|
672
668
|
* no attachment lands; the next runner reduces the newer prefix and closes
|
|
673
669
|
* only sessions that remain open. */
|
|
674
|
-
private async reclaimSessionsAndAttach(
|
|
670
|
+
private async reclaimSessionsAndAttach(state: BuildState): Promise<void> {
|
|
675
671
|
const { store, slug, instance, host } = this.deps
|
|
676
|
-
const resumedFromSeq =
|
|
677
|
-
for (const session of
|
|
672
|
+
const resumedFromSeq = state.lastSeq
|
|
673
|
+
for (const session of state.sessions.open) {
|
|
678
674
|
await store.append(slug, {
|
|
679
675
|
actor: KERNEL,
|
|
680
676
|
type: 'session.ended',
|
|
@@ -709,8 +705,8 @@ export class BuildRunner {
|
|
|
709
705
|
const claimed = await store.claimLease(slug, instance, this.leaseTtlMs)
|
|
710
706
|
if (!claimed) throw new LeaseHeldError(slug, instance)
|
|
711
707
|
|
|
712
|
-
let
|
|
713
|
-
const decision =
|
|
708
|
+
let facts = (await readBuildFacts(store, slug)).value
|
|
709
|
+
const decision = decideNextFromFacts(facts.state, facts.log, config)
|
|
714
710
|
if (
|
|
715
711
|
decision.kind === 'acknowledge' &&
|
|
716
712
|
(decision.command === 'pause' || decision.command === 'abort')
|
|
@@ -719,12 +715,12 @@ export class BuildRunner {
|
|
|
719
715
|
return { kind: 'control-acknowledged', command: decision.command }
|
|
720
716
|
}
|
|
721
717
|
|
|
722
|
-
const state =
|
|
718
|
+
const state = facts.state
|
|
723
719
|
const currentFailure = state.setupFailure
|
|
724
720
|
const openSetupEscalation = state.openEscalations.some(
|
|
725
721
|
(escalation) => escalation.phase === 'setup' && escalation.source === 'policy',
|
|
726
722
|
)
|
|
727
|
-
const streak =
|
|
723
|
+
const streak = await readBuildSelector(store, slug, 'setupStreak')
|
|
728
724
|
|
|
729
725
|
// Crash-safe exhaustion: the final failure and escalation are separate
|
|
730
726
|
// appends. If only the failure landed, raise the blocker before another
|
|
@@ -738,7 +734,7 @@ export class BuildRunner {
|
|
|
738
734
|
}
|
|
739
735
|
|
|
740
736
|
const recovering = currentFailure !== undefined
|
|
741
|
-
if (!recovering) await this.reclaimSessionsAndAttach(
|
|
737
|
+
if (!recovering) await this.reclaimSessionsAndAttach(facts.state)
|
|
742
738
|
|
|
743
739
|
// First beat immediately (liveness visible without waiting an interval),
|
|
744
740
|
// then keep-alive. Setup runs under this heartbeat so a slow install cannot
|
|
@@ -800,8 +796,8 @@ export class BuildRunner {
|
|
|
800
796
|
|
|
801
797
|
if (recovering) {
|
|
802
798
|
try {
|
|
803
|
-
|
|
804
|
-
await this.reclaimSessionsAndAttach(
|
|
799
|
+
facts = (await readBuildFacts(store, slug)).value
|
|
800
|
+
await this.reclaimSessionsAndAttach(facts.state)
|
|
805
801
|
} catch (error) {
|
|
806
802
|
this.stopHeartbeat()
|
|
807
803
|
this.attached = false
|
|
@@ -819,8 +815,8 @@ export class BuildRunner {
|
|
|
819
815
|
async step(): Promise<Decision> {
|
|
820
816
|
const config = await this.captureConfig()
|
|
821
817
|
const { store, slug } = this.deps
|
|
822
|
-
const
|
|
823
|
-
const decision =
|
|
818
|
+
const facts = (await readBuildFacts(store, slug)).value
|
|
819
|
+
const decision = decideNextFromFacts(facts.state, facts.log, config)
|
|
824
820
|
switch (decision.kind) {
|
|
825
821
|
case 'capture-reconcile-policy':
|
|
826
822
|
await store.append(slug, {
|
|
@@ -851,25 +847,25 @@ export class BuildRunner {
|
|
|
851
847
|
await this.raiseEscalation(decision)
|
|
852
848
|
return decision
|
|
853
849
|
case 'run-phase':
|
|
854
|
-
await this.runPhase(decision
|
|
850
|
+
await this.runPhase(decision)
|
|
855
851
|
return decision
|
|
856
852
|
case 'run-check':
|
|
857
853
|
await this.runCheck(decision)
|
|
858
854
|
return decision
|
|
859
855
|
case 'run-agent-verify':
|
|
860
|
-
await this.runAgentVerify(decision
|
|
856
|
+
await this.runAgentVerify(decision)
|
|
861
857
|
return decision
|
|
862
858
|
case 'evaluate-verify':
|
|
863
|
-
await this.evaluateVerify(decision
|
|
859
|
+
await this.evaluateVerify(decision)
|
|
864
860
|
return decision
|
|
865
861
|
case 'skip-verify':
|
|
866
862
|
await this.skipVerify(decision)
|
|
867
863
|
return decision
|
|
868
864
|
case 'run-finalize-step':
|
|
869
|
-
await this.runFinalizeStep(decision
|
|
865
|
+
await this.runFinalizeStep(decision)
|
|
870
866
|
return decision
|
|
871
867
|
case 'check-reconcile-progress':
|
|
872
|
-
await this.checkReconcileProgress(decision
|
|
868
|
+
await this.checkReconcileProgress(decision)
|
|
873
869
|
return decision
|
|
874
870
|
}
|
|
875
871
|
}
|
|
@@ -885,23 +881,24 @@ export class BuildRunner {
|
|
|
885
881
|
if (!this.attached) {
|
|
886
882
|
const attachment = await this.attach()
|
|
887
883
|
if (attachment.kind === 'control-acknowledged') {
|
|
888
|
-
return
|
|
884
|
+
return (await readBuildFacts(this.deps.store, this.deps.slug)).value.state
|
|
889
885
|
}
|
|
890
886
|
}
|
|
891
887
|
try {
|
|
892
888
|
if (await this.refreshSummary())
|
|
893
|
-
return
|
|
894
|
-
if (
|
|
895
|
-
return
|
|
889
|
+
return (await readBuildFacts(this.deps.store, this.deps.slug)).value.state
|
|
890
|
+
if ((await readBuildFacts(this.deps.store, this.deps.slug)).value.publication.pending()) {
|
|
891
|
+
return (await readBuildFacts(this.deps.store, this.deps.slug)).value.state
|
|
896
892
|
}
|
|
897
893
|
for (;;) {
|
|
898
894
|
await this.ensureLease()
|
|
899
895
|
const decision = await this.step()
|
|
900
896
|
if (await this.refreshSummary()) break
|
|
901
897
|
if (decision.kind === 'wait') break
|
|
902
|
-
if (
|
|
898
|
+
if ((await readBuildFacts(this.deps.store, this.deps.slug)).value.publication.pending())
|
|
899
|
+
break
|
|
903
900
|
}
|
|
904
|
-
return
|
|
901
|
+
return (await readBuildFacts(this.deps.store, this.deps.slug)).value.state
|
|
905
902
|
} finally {
|
|
906
903
|
this.stopHeartbeat()
|
|
907
904
|
await this.closeProducerSessions()
|
|
@@ -982,12 +979,14 @@ export class BuildRunner {
|
|
|
982
979
|
* reuse the snapshot as the merge target: actual phase startup refreshes
|
|
983
980
|
* again, preserving the execution-boundary freshness guarantee.
|
|
984
981
|
*/
|
|
985
|
-
private async checkReconcileProgress(
|
|
986
|
-
decision: CheckReconcileProgressDecision,
|
|
987
|
-
events: AbEvent[],
|
|
988
|
-
): Promise<void> {
|
|
982
|
+
private async checkReconcileProgress(decision: CheckReconcileProgressDecision): Promise<void> {
|
|
989
983
|
const { nextAttempt } = decision
|
|
990
|
-
const failures =
|
|
984
|
+
const failures = await readPhaseFailures(
|
|
985
|
+
this.deps.store,
|
|
986
|
+
this.deps.slug,
|
|
987
|
+
'reconcile',
|
|
988
|
+
nextAttempt,
|
|
989
|
+
)
|
|
991
990
|
if (failures.count >= this.maxPhaseAttempts) {
|
|
992
991
|
await this.raisePolicyExhausted('reconcile', nextAttempt, failures)
|
|
993
992
|
return
|
|
@@ -999,7 +998,7 @@ export class BuildRunner {
|
|
|
999
998
|
|
|
1000
999
|
let baseSha: string
|
|
1001
1000
|
try {
|
|
1002
|
-
baseSha = await this.refreshReconcileBase(
|
|
1001
|
+
baseSha = await this.refreshReconcileBase()
|
|
1003
1002
|
} catch (error) {
|
|
1004
1003
|
await this.failPhase(
|
|
1005
1004
|
'reconcile',
|
|
@@ -1022,13 +1021,13 @@ export class BuildRunner {
|
|
|
1022
1021
|
}
|
|
1023
1022
|
|
|
1024
1023
|
/** A core-phase run: retry guard (D5), started event (§15.3), session. */
|
|
1025
|
-
private async runPhase(decision: RunPhaseDecision
|
|
1024
|
+
private async runPhase(decision: RunPhaseDecision): Promise<void> {
|
|
1026
1025
|
const { store, slug } = this.deps
|
|
1027
1026
|
const { phase, round } = decision
|
|
1028
1027
|
|
|
1029
1028
|
// D5 RETRY GUARD first: at the cap, escalate instead of running — a
|
|
1030
1029
|
// failing phase must never loop forever.
|
|
1031
|
-
const failures = this.
|
|
1030
|
+
const failures = await readPhaseFailures(this.deps.store, this.deps.slug, phase, round)
|
|
1032
1031
|
if (failures.count >= this.maxPhaseAttempts) {
|
|
1033
1032
|
await this.raisePolicyExhausted(phase, round, failures)
|
|
1034
1033
|
return
|
|
@@ -1045,7 +1044,7 @@ export class BuildRunner {
|
|
|
1045
1044
|
let reconcileBaseSha: string | undefined
|
|
1046
1045
|
if (phase === 'reconcile') {
|
|
1047
1046
|
try {
|
|
1048
|
-
reconcileBaseSha = await this.refreshReconcileBase(
|
|
1047
|
+
reconcileBaseSha = await this.refreshReconcileBase()
|
|
1049
1048
|
} catch (error) {
|
|
1050
1049
|
await this.failPhase(
|
|
1051
1050
|
phase,
|
|
@@ -1081,20 +1080,20 @@ export class BuildRunner {
|
|
|
1081
1080
|
* would let another build overwrite the object this attempt resolves. The
|
|
1082
1081
|
* explicit ref also makes the fetched remote-only commit available locally.
|
|
1083
1082
|
*/
|
|
1084
|
-
private async refreshReconcileBase(
|
|
1085
|
-
const
|
|
1086
|
-
if (
|
|
1083
|
+
private async refreshReconcileBase(): Promise<string> {
|
|
1084
|
+
const baseBranch = await readBuildSelector(this.deps.store, this.deps.slug, 'baseBranch')
|
|
1085
|
+
if (baseBranch === undefined) {
|
|
1087
1086
|
throw new Error(
|
|
1088
1087
|
'this build has no build.created event — cannot resolve its frozen baseBranch (§15.3)',
|
|
1089
1088
|
)
|
|
1090
1089
|
}
|
|
1091
1090
|
|
|
1092
|
-
const sourceRef = `refs/heads/${
|
|
1091
|
+
const sourceRef = `refs/heads/${baseBranch}`
|
|
1093
1092
|
const targetRef = `refs/autobuild/reconcile/${this.deps.slug}/base`
|
|
1094
1093
|
if (this.deps.forge.snapshotBase !== undefined) {
|
|
1095
1094
|
const sha = await this.deps.forge.snapshotBase({
|
|
1096
1095
|
workspacePath: this.deps.workspacePath,
|
|
1097
|
-
base:
|
|
1096
|
+
base: baseBranch,
|
|
1098
1097
|
destinationRef: targetRef,
|
|
1099
1098
|
})
|
|
1100
1099
|
if (!GIT_OBJECT_ID.test(sha)) {
|
|
@@ -1138,9 +1137,13 @@ export class BuildRunner {
|
|
|
1138
1137
|
* a canonical kernel-authored skip and launches no command or session.
|
|
1139
1138
|
* Inclusion delegates to the existing verifier path unchanged.
|
|
1140
1139
|
*/
|
|
1141
|
-
private async evaluateVerify(decision: EvaluateVerifyDecision
|
|
1140
|
+
private async evaluateVerify(decision: EvaluateVerifyDecision): Promise<void> {
|
|
1142
1141
|
const { exec, workspacePath } = this.deps
|
|
1143
|
-
const baseSha =
|
|
1142
|
+
const baseSha = await readBuildSelector(this.deps.store, this.deps.slug, 'verifyDiffBase')
|
|
1143
|
+
if (baseSha === undefined)
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
'conditional verify requires a workspace.provisioned base SHA, but this build has none',
|
|
1146
|
+
)
|
|
1144
1147
|
const args = ['git', 'diff', '--no-renames', '--name-only', '-z', baseSha, 'HEAD', '--']
|
|
1145
1148
|
const result = await exec(args, { cwd: workspacePath })
|
|
1146
1149
|
if (result.exitCode !== 0) {
|
|
@@ -1154,7 +1157,7 @@ export class BuildRunner {
|
|
|
1154
1157
|
{ kind: 'paths', step: decision.step, paths: decision.paths },
|
|
1155
1158
|
changedPaths,
|
|
1156
1159
|
)
|
|
1157
|
-
if (
|
|
1160
|
+
if (applicability.applies === false) {
|
|
1158
1161
|
await this.skipVerify({
|
|
1159
1162
|
kind: 'skip-verify',
|
|
1160
1163
|
step: decision.step,
|
|
@@ -1167,7 +1170,7 @@ export class BuildRunner {
|
|
|
1167
1170
|
if (decision.action.kind === 'run-check') {
|
|
1168
1171
|
await this.runCheck(decision.action)
|
|
1169
1172
|
} else {
|
|
1170
|
-
await this.runAgentVerify(decision.action
|
|
1173
|
+
await this.runAgentVerify(decision.action)
|
|
1171
1174
|
}
|
|
1172
1175
|
}
|
|
1173
1176
|
|
|
@@ -1245,14 +1248,14 @@ export class BuildRunner {
|
|
|
1245
1248
|
}
|
|
1246
1249
|
|
|
1247
1250
|
/** Agent-verify step (§5): a session with a pass/fail/skip verdict. */
|
|
1248
|
-
private async runAgentVerify(decision: RunAgentVerifyDecision
|
|
1251
|
+
private async runAgentVerify(decision: RunAgentVerifyDecision): Promise<void> {
|
|
1249
1252
|
const { store, slug } = this.deps
|
|
1250
1253
|
const { step, skill, attempt, feedback } = decision
|
|
1251
1254
|
const phase = verifyPhase(step)
|
|
1252
1255
|
|
|
1253
1256
|
// D5 guard, extended to agent-verify (see module doc): keyed by
|
|
1254
1257
|
// verify:<step> with the verify attempt as the round.
|
|
1255
|
-
const failures = this.
|
|
1258
|
+
const failures = await readPhaseFailures(this.deps.store, this.deps.slug, phase, attempt)
|
|
1256
1259
|
if (failures.count >= this.maxPhaseAttempts) {
|
|
1257
1260
|
await this.raisePolicyExhausted(phase, attempt, failures)
|
|
1258
1261
|
return
|
|
@@ -1292,10 +1295,7 @@ export class BuildRunner {
|
|
|
1292
1295
|
* Step, Git, and Forge failures become the ordinary failed outcome plus an
|
|
1293
1296
|
* observation and never route a green build backward.
|
|
1294
1297
|
*/
|
|
1295
|
-
private async runFinalizeStep(
|
|
1296
|
-
decision: RunFinalizeStepDecision,
|
|
1297
|
-
events: AbEvent[],
|
|
1298
|
-
): Promise<void> {
|
|
1298
|
+
private async runFinalizeStep(decision: RunFinalizeStepDecision): Promise<void> {
|
|
1299
1299
|
const { step, action } = decision
|
|
1300
1300
|
await this.deps.store.append(this.deps.slug, {
|
|
1301
1301
|
actor: KERNEL,
|
|
@@ -1308,7 +1308,12 @@ export class BuildRunner {
|
|
|
1308
1308
|
|
|
1309
1309
|
try {
|
|
1310
1310
|
await this.requireFinalizeWorktreeClean('before the post-step starts')
|
|
1311
|
-
durableHead =
|
|
1311
|
+
durableHead = await readBuildSelector(this.deps.store, this.deps.slug, 'publishedBranchHead')
|
|
1312
|
+
if (durableHead === undefined)
|
|
1313
|
+
throw new Error(
|
|
1314
|
+
'finalize publication requires a prior implement.completed, reconcile.completed, ' +
|
|
1315
|
+
'or successful finalize publication head, but this build has none',
|
|
1316
|
+
)
|
|
1312
1317
|
} catch (error) {
|
|
1313
1318
|
failureNote = `finalize publication preflight failed: ${errorMessage(error)}`
|
|
1314
1319
|
}
|
|
@@ -1338,9 +1343,8 @@ export class BuildRunner {
|
|
|
1338
1343
|
head,
|
|
1339
1344
|
)
|
|
1340
1345
|
if (scratchPaths.length > 0) throw phaseScratchRejection(scratchPaths)
|
|
1341
|
-
const remote =
|
|
1342
|
-
|
|
1343
|
-
)
|
|
1346
|
+
const remote = (await readBuildFacts(this.deps.store, this.deps.slug)).value
|
|
1347
|
+
.remoteWorkspace
|
|
1344
1348
|
if (remote) {
|
|
1345
1349
|
await this.deps.store.append(this.deps.slug, {
|
|
1346
1350
|
actor,
|
|
@@ -2642,18 +2646,6 @@ export class BuildRunner {
|
|
|
2642
2646
|
* an applicable answer count again, so a still-failing phase re-escalates on
|
|
2643
2647
|
* new evidence instead of deadlocking before its recovery session starts.
|
|
2644
2648
|
*/
|
|
2645
|
-
private phaseFailures(
|
|
2646
|
-
events: AbEvent[],
|
|
2647
|
-
phase: Phase,
|
|
2648
|
-
round: number,
|
|
2649
|
-
): {
|
|
2650
|
-
count: number
|
|
2651
|
-
lastError?: string
|
|
2652
|
-
lastWillRetry?: boolean
|
|
2653
|
-
lastProviderAttempts?: readonly ProviderAttempt[]
|
|
2654
|
-
} {
|
|
2655
|
-
return phaseFailuresReducer(phase, round).reduce(events)
|
|
2656
|
-
}
|
|
2657
2649
|
|
|
2658
2650
|
private async failPhase(
|
|
2659
2651
|
phase: Phase,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readDispatchBudget } from '../store/reduced-reads'
|
|
1
2
|
import type { AbEvent } from '../events/catalog'
|
|
2
3
|
import { DISPATCHER } from '../events/envelope'
|
|
3
4
|
import type { IdSource } from '../ids'
|
|
@@ -54,19 +55,19 @@ export function dispatchFailureBudget(
|
|
|
54
55
|
|
|
55
56
|
/** Repair the failure/raise crash gap before allowing another provider call. */
|
|
56
57
|
export async function ensureDispatchBudget(
|
|
57
|
-
store: Pick<BuildStore, '
|
|
58
|
+
store: Pick<BuildStore, 'getReducerValue' | 'appendIfCurrent'>,
|
|
58
59
|
ids: IdSource,
|
|
59
60
|
slug: string,
|
|
60
61
|
maxAttempts: number,
|
|
61
62
|
infrastructureOwnsWorkspace: boolean,
|
|
62
63
|
): Promise<boolean> {
|
|
63
64
|
for (;;) {
|
|
64
|
-
const
|
|
65
|
-
const budget =
|
|
65
|
+
const current = await readDispatchBudget(store, slug, infrastructureOwnsWorkspace)
|
|
66
|
+
const budget = current.value
|
|
66
67
|
if (budget.parked) return false
|
|
67
68
|
if (budget.count < maxAttempts || budget.latest === undefined) return true
|
|
68
69
|
const failure = budget.latest.payload
|
|
69
|
-
const raised = await store.appendIfCurrent(slug,
|
|
70
|
+
const raised = await store.appendIfCurrent(slug, current.cursor, {
|
|
70
71
|
actor: DISPATCHER,
|
|
71
72
|
type: 'escalation.raised',
|
|
72
73
|
payload: {
|