@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
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
import { encodeLog } from './log-index-value'
|
|
2
|
+
import { summaryBoundaryReducer, summaryRequestsReducer } from './pr-summary'
|
|
3
|
+
import { infrastructureFailureResetReducer } from '../processes/infrastructure-failure-budget'
|
|
4
|
+
import {
|
|
5
|
+
frozenPrImageHostReducer,
|
|
6
|
+
currentPrAttachmentsReducer,
|
|
7
|
+
hostedPrAttachmentsReducer,
|
|
8
|
+
pendingPrAttachmentReclaimsReducer,
|
|
9
|
+
} from './pr-attachments'
|
|
10
|
+
import { isRemoteWorkspace } from '../events/workspace-remote'
|
|
11
|
+
/** JSON-safe projections for persisted current-state consumers. */
|
|
12
|
+
import type { AbEvent } from '../events/catalog'
|
|
13
|
+
import { defineReducer } from './incremental'
|
|
14
|
+
import { buildReducer, type BuildAcc } from './reducer'
|
|
15
|
+
import {
|
|
16
|
+
currentDeferralObservationReducer,
|
|
17
|
+
mergeAttentionFromFacts,
|
|
18
|
+
type DeferralLedger,
|
|
19
|
+
} from './auto-merge'
|
|
20
|
+
import { logIndexReducer } from './log-index'
|
|
21
|
+
import { publicationStateReducer } from '../processes/publication-state'
|
|
22
|
+
import { executionNoProgressReducer } from '../processes/execution-no-progress'
|
|
23
|
+
import {
|
|
24
|
+
openExecutionReducer,
|
|
25
|
+
lastExecutionOutcomeReducer,
|
|
26
|
+
} from '../processes/execution-settlement'
|
|
27
|
+
import {
|
|
28
|
+
baseBranchReducer,
|
|
29
|
+
provisionMarkerReducer,
|
|
30
|
+
recoveryCheckpointReducer,
|
|
31
|
+
openBuildWorkspaceReducer,
|
|
32
|
+
} from '../processes/dispatcher-selectors'
|
|
33
|
+
import { dashboardFactsReducer } from '../cli/dashboard/facts'
|
|
34
|
+
|
|
35
|
+
export interface BuildStreamSeed {
|
|
36
|
+
build: BuildAcc
|
|
37
|
+
deferral: DeferralLedger
|
|
38
|
+
mergeAttentionPrs: number[]
|
|
39
|
+
/** Terminal identity can precede later observations; retain it at the seed cursor. */
|
|
40
|
+
terminalEvent?: AbEvent
|
|
41
|
+
}
|
|
42
|
+
// Bump with either constituent's semantics or historical eligibility semantics.
|
|
43
|
+
export const buildStreamSeedReducer = defineReducer<BuildStreamSeed, AbEvent, BuildStreamSeed>({
|
|
44
|
+
version: 1 + buildReducer.version + currentDeferralObservationReducer.version,
|
|
45
|
+
initial: () => ({
|
|
46
|
+
build: buildReducer.initial(),
|
|
47
|
+
deferral: currentDeferralObservationReducer.initial(),
|
|
48
|
+
mergeAttentionPrs: [],
|
|
49
|
+
}),
|
|
50
|
+
fold(acc, events) {
|
|
51
|
+
for (const event of events) {
|
|
52
|
+
if (event.type === 'build.completed' || event.type === 'build.aborted')
|
|
53
|
+
acc.terminalEvent = event
|
|
54
|
+
acc.build = buildReducer.advance(acc.build, [event])
|
|
55
|
+
acc.deferral = currentDeferralObservationReducer.advance(acc.deferral, [event])
|
|
56
|
+
const decision = mergeAttentionFromFacts(buildReducer.finish(acc.build), acc.deferral)
|
|
57
|
+
if (decision && !acc.mergeAttentionPrs.includes(decision.pr.number))
|
|
58
|
+
acc.mergeAttentionPrs.push(decision.pr.number)
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
finish: (acc) => structuredClone(acc),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const parts = {
|
|
65
|
+
infrastructureReset: infrastructureFailureResetReducer,
|
|
66
|
+
summaryRequests: summaryRequestsReducer,
|
|
67
|
+
summaryBoundary: summaryBoundaryReducer,
|
|
68
|
+
hostedImages: hostedPrAttachmentsReducer,
|
|
69
|
+
designations: currentPrAttachmentsReducer,
|
|
70
|
+
imageHost: frozenPrImageHostReducer,
|
|
71
|
+
pendingAttachments: pendingPrAttachmentReclaimsReducer,
|
|
72
|
+
recoveryCheckpoint: recoveryCheckpointReducer,
|
|
73
|
+
provisionMarker: provisionMarkerReducer,
|
|
74
|
+
baseBranch: baseBranchReducer,
|
|
75
|
+
build: buildReducer,
|
|
76
|
+
log: logIndexReducer,
|
|
77
|
+
publication: publicationStateReducer,
|
|
78
|
+
executionNoProgress: executionNoProgressReducer,
|
|
79
|
+
open: openExecutionReducer,
|
|
80
|
+
workspace: openBuildWorkspaceReducer,
|
|
81
|
+
lastExecution: lastExecutionOutcomeReducer,
|
|
82
|
+
dashboard: dashboardFactsReducer,
|
|
83
|
+
deferral: currentDeferralObservationReducer,
|
|
84
|
+
}
|
|
85
|
+
export interface BuildReadAcc {
|
|
86
|
+
summaryRequests: ReturnType<typeof summaryRequestsReducer.initial>
|
|
87
|
+
summaryBoundary: ReturnType<typeof summaryBoundaryReducer.initial>
|
|
88
|
+
hostedImages: ReturnType<typeof hostedPrAttachmentsReducer.initial>
|
|
89
|
+
designations: ReturnType<typeof currentPrAttachmentsReducer.initial>
|
|
90
|
+
imageHost: ReturnType<typeof frozenPrImageHostReducer.initial>
|
|
91
|
+
pendingAttachments: ReturnType<typeof pendingPrAttachmentReclaimsReducer.initial>
|
|
92
|
+
recoveryCheckpoint: ReturnType<typeof recoveryCheckpointReducer.initial>
|
|
93
|
+
provisionMarker: ReturnType<typeof provisionMarkerReducer.initial>
|
|
94
|
+
baseBranch: ReturnType<typeof baseBranchReducer.initial>
|
|
95
|
+
build: ReturnType<typeof buildReducer.initial>
|
|
96
|
+
log: ReturnType<typeof logIndexReducer.initial>
|
|
97
|
+
publication: ReturnType<typeof publicationStateReducer.initial>
|
|
98
|
+
executionNoProgress: ReturnType<typeof executionNoProgressReducer.initial>
|
|
99
|
+
open: ReturnType<typeof openExecutionReducer.initial>
|
|
100
|
+
workspace: ReturnType<typeof openBuildWorkspaceReducer.initial>
|
|
101
|
+
lastExecution: ReturnType<typeof lastExecutionOutcomeReducer.initial>
|
|
102
|
+
dashboard: ReturnType<typeof dashboardFactsReducer.initial>
|
|
103
|
+
deferral: ReturnType<typeof currentDeferralObservationReducer.initial>
|
|
104
|
+
summaryHistory: {
|
|
105
|
+
kind: 'review' | 'verify'
|
|
106
|
+
phase?: string
|
|
107
|
+
round?: number
|
|
108
|
+
verdict?: string
|
|
109
|
+
count?: number
|
|
110
|
+
result?: import('../events/payloads').EventPayload<'verify.completed'>
|
|
111
|
+
}[]
|
|
112
|
+
summaryAttempts: number[]
|
|
113
|
+
summaryRequested: Record<string, Extract<AbEvent, { type: 'pr-summary.requested' }>>
|
|
114
|
+
summaryEligibleAfter?: string
|
|
115
|
+
finalizePublicationRequested: boolean
|
|
116
|
+
latestWorkspace?: Extract<AbEvent, { type: 'workspace.provisioned' }>
|
|
117
|
+
findings: Record<string, import('../ontology').Finding>
|
|
118
|
+
planFeedback: Record<string, import('../ontology').Feedback | null>
|
|
119
|
+
feedback: Record<string, import('../ontology').Feedback | null>
|
|
120
|
+
reconcileBases: Record<string, string>
|
|
121
|
+
firstWorkspace?: Extract<AbEvent, { type: 'workspace.provisioned' }>
|
|
122
|
+
latestAbort?: Extract<AbEvent, { type: 'build.abort-requested' }>
|
|
123
|
+
latestDiscard?: Extract<AbEvent, { type: 'build.discard-requested' }>
|
|
124
|
+
maxPlanRevision: number
|
|
125
|
+
created?: Extract<AbEvent, { type: 'build.created' }>
|
|
126
|
+
lastCreated?: Extract<AbEvent, { type: 'build.created' }>
|
|
127
|
+
latestRelease?: Extract<AbEvent, { type: 'workspace.released' }>
|
|
128
|
+
latestWorkspaceFailureSeq: number
|
|
129
|
+
workspaceGenerations: number
|
|
130
|
+
dispatchFailureCount: number
|
|
131
|
+
commentPosted: boolean
|
|
132
|
+
specRecorded: boolean
|
|
133
|
+
autoMergeEverRequested: boolean
|
|
134
|
+
seenTypes: AbEvent['type'][]
|
|
135
|
+
localWorkspace: boolean
|
|
136
|
+
cleanupCounts: Record<string, number>
|
|
137
|
+
reclaimCounts: Record<string, number>
|
|
138
|
+
infrastructureReset: ReturnType<typeof infrastructureFailureResetReducer.initial>
|
|
139
|
+
infrastructureCount: number
|
|
140
|
+
infrastructureRaisedSeq: number
|
|
141
|
+
remoteWorkspace: boolean
|
|
142
|
+
terminalEvent?: AbEvent
|
|
143
|
+
mergeTimes: string[]
|
|
144
|
+
observationFacts: {
|
|
145
|
+
payload: import('../events/payloads').EventPayload<'observation.recorded'>
|
|
146
|
+
seq: number
|
|
147
|
+
ts: string
|
|
148
|
+
}[]
|
|
149
|
+
observations: Extract<AbEvent, { type: 'observation.recorded' }>['payload'][]
|
|
150
|
+
}
|
|
151
|
+
export const buildReadFactsReducer = defineReducer({
|
|
152
|
+
version: 1 + Object.values(parts).reduce((sum, reducer) => sum + reducer.version, 0),
|
|
153
|
+
initial: (): BuildReadAcc => ({
|
|
154
|
+
summaryRequests: summaryRequestsReducer.initial(),
|
|
155
|
+
summaryBoundary: summaryBoundaryReducer.initial(),
|
|
156
|
+
hostedImages: hostedPrAttachmentsReducer.initial(),
|
|
157
|
+
designations: currentPrAttachmentsReducer.initial(),
|
|
158
|
+
imageHost: frozenPrImageHostReducer.initial(),
|
|
159
|
+
pendingAttachments: pendingPrAttachmentReclaimsReducer.initial(),
|
|
160
|
+
recoveryCheckpoint: recoveryCheckpointReducer.initial(),
|
|
161
|
+
provisionMarker: provisionMarkerReducer.initial(),
|
|
162
|
+
baseBranch: baseBranchReducer.initial(),
|
|
163
|
+
build: buildReducer.initial(),
|
|
164
|
+
log: logIndexReducer.initial(),
|
|
165
|
+
publication: publicationStateReducer.initial(),
|
|
166
|
+
executionNoProgress: executionNoProgressReducer.initial(),
|
|
167
|
+
open: openExecutionReducer.initial(),
|
|
168
|
+
workspace: openBuildWorkspaceReducer.initial(),
|
|
169
|
+
lastExecution: lastExecutionOutcomeReducer.initial(),
|
|
170
|
+
dashboard: dashboardFactsReducer.initial(),
|
|
171
|
+
deferral: currentDeferralObservationReducer.initial(),
|
|
172
|
+
observations: [],
|
|
173
|
+
observationFacts: [],
|
|
174
|
+
mergeTimes: [],
|
|
175
|
+
summaryHistory: [],
|
|
176
|
+
summaryAttempts: [],
|
|
177
|
+
summaryRequested: {},
|
|
178
|
+
finalizePublicationRequested: false,
|
|
179
|
+
findings: {},
|
|
180
|
+
feedback: {},
|
|
181
|
+
planFeedback: {},
|
|
182
|
+
reconcileBases: {},
|
|
183
|
+
maxPlanRevision: -1,
|
|
184
|
+
seenTypes: [],
|
|
185
|
+
localWorkspace: false,
|
|
186
|
+
cleanupCounts: {},
|
|
187
|
+
reclaimCounts: {},
|
|
188
|
+
infrastructureReset: infrastructureFailureResetReducer.initial(),
|
|
189
|
+
infrastructureCount: 0,
|
|
190
|
+
infrastructureRaisedSeq: 0,
|
|
191
|
+
remoteWorkspace: false,
|
|
192
|
+
latestWorkspaceFailureSeq: 0,
|
|
193
|
+
workspaceGenerations: 0,
|
|
194
|
+
dispatchFailureCount: 0,
|
|
195
|
+
commentPosted: false,
|
|
196
|
+
specRecorded: false,
|
|
197
|
+
autoMergeEverRequested: false,
|
|
198
|
+
}),
|
|
199
|
+
fold(acc: BuildReadAcc, events: readonly AbEvent[]) {
|
|
200
|
+
acc.baseBranch = baseBranchReducer.advance(acc.baseBranch, events)
|
|
201
|
+
acc.provisionMarker = provisionMarkerReducer.advance(acc.provisionMarker, events)
|
|
202
|
+
acc.recoveryCheckpoint = recoveryCheckpointReducer.advance(acc.recoveryCheckpoint, events)
|
|
203
|
+
acc.pendingAttachments = pendingPrAttachmentReclaimsReducer.advance(
|
|
204
|
+
acc.pendingAttachments,
|
|
205
|
+
events,
|
|
206
|
+
)
|
|
207
|
+
acc.imageHost = frozenPrImageHostReducer.advance(acc.imageHost, events)
|
|
208
|
+
acc.designations = currentPrAttachmentsReducer.advance(acc.designations, events)
|
|
209
|
+
acc.hostedImages = hostedPrAttachmentsReducer.advance(acc.hostedImages, events)
|
|
210
|
+
acc.summaryBoundary = summaryBoundaryReducer.advance(acc.summaryBoundary, events)
|
|
211
|
+
acc.summaryRequests = summaryRequestsReducer.advance(acc.summaryRequests, events)
|
|
212
|
+
acc.build = buildReducer.advance(acc.build, events)
|
|
213
|
+
acc.log = logIndexReducer.advance(acc.log, events)
|
|
214
|
+
acc.publication = publicationStateReducer.advance(acc.publication, events)
|
|
215
|
+
acc.executionNoProgress = executionNoProgressReducer.advance(acc.executionNoProgress, events)
|
|
216
|
+
acc.open = openExecutionReducer.advance(acc.open, events)
|
|
217
|
+
acc.workspace = openBuildWorkspaceReducer.advance(acc.workspace, events)
|
|
218
|
+
acc.lastExecution = lastExecutionOutcomeReducer.advance(acc.lastExecution, events)
|
|
219
|
+
acc.dashboard = dashboardFactsReducer.advance(acc.dashboard, events)
|
|
220
|
+
acc.deferral = currentDeferralObservationReducer.advance(acc.deferral, events)
|
|
221
|
+
for (const event of events) {
|
|
222
|
+
if (event.type === 'plan-review.verdict' || event.type === 'code-review.verdict')
|
|
223
|
+
acc.summaryHistory.push({
|
|
224
|
+
kind: 'review',
|
|
225
|
+
phase: event.type === 'plan-review.verdict' ? 'plan-review' : 'code-review',
|
|
226
|
+
round: event.payload.round,
|
|
227
|
+
verdict: event.payload.verdict,
|
|
228
|
+
count: event.payload.findings.length,
|
|
229
|
+
})
|
|
230
|
+
if (event.type === 'verify.completed')
|
|
231
|
+
acc.summaryHistory.push({ kind: 'verify', result: event.payload })
|
|
232
|
+
if (
|
|
233
|
+
event.type === 'pr-summary.attempted' &&
|
|
234
|
+
!acc.summaryAttempts.includes(event.payload.occurrence)
|
|
235
|
+
)
|
|
236
|
+
acc.summaryAttempts.push(event.payload.occurrence)
|
|
237
|
+
if (event.type === 'pr-summary.requested')
|
|
238
|
+
acc.summaryRequested[String(event.payload.occurrence)] ??= event
|
|
239
|
+
if (
|
|
240
|
+
event.type === 'pr-summary.result' &&
|
|
241
|
+
event.payload.eligibleAfter &&
|
|
242
|
+
(!acc.summaryEligibleAfter ||
|
|
243
|
+
Date.parse(event.payload.eligibleAfter) > Date.parse(acc.summaryEligibleAfter))
|
|
244
|
+
)
|
|
245
|
+
acc.summaryEligibleAfter = event.payload.eligibleAfter
|
|
246
|
+
if (event.type === 'publication.requested' && event.payload.operation === 'finalize')
|
|
247
|
+
acc.finalizePublicationRequested = true
|
|
248
|
+
if (event.type === 'workspace.provisioned') acc.latestWorkspace = event
|
|
249
|
+
if (event.type === 'plan-review.verdict')
|
|
250
|
+
acc.planFeedback[String(event.payload.round + 1)] =
|
|
251
|
+
event.payload.verdict === 'revise'
|
|
252
|
+
? { findings: event.payload.findings.map((finding) => finding.id) }
|
|
253
|
+
: null
|
|
254
|
+
if (event.type === 'plan-review.verdict' || event.type === 'code-review.verdict')
|
|
255
|
+
for (const finding of event.payload.findings)
|
|
256
|
+
acc.findings[JSON.stringify(finding.id)] = finding
|
|
257
|
+
if (event.type === 'plan.started' || event.type === 'implement.started')
|
|
258
|
+
acc.feedback[
|
|
259
|
+
JSON.stringify([
|
|
260
|
+
event.type === 'plan.started' ? 'plan' : 'implement',
|
|
261
|
+
event.payload.round,
|
|
262
|
+
])
|
|
263
|
+
] = event.payload.feedback ?? null
|
|
264
|
+
if (event.type === 'verify.started')
|
|
265
|
+
acc.feedback[JSON.stringify([`verify:${event.payload.step}`, event.payload.attempt])] =
|
|
266
|
+
event.payload.feedback ?? null
|
|
267
|
+
if (event.type === 'reconcile.started')
|
|
268
|
+
acc.reconcileBases[String(event.payload.attempt)] = event.payload.baseSha
|
|
269
|
+
if (event.type === 'workspace.provisioned') acc.firstWorkspace ??= event
|
|
270
|
+
if (event.type === 'build.abort-requested') acc.latestAbort = event
|
|
271
|
+
if (event.type === 'build.discard-requested') acc.latestDiscard = event
|
|
272
|
+
if (event.type === 'plan.completed')
|
|
273
|
+
acc.maxPlanRevision = Math.max(acc.maxPlanRevision, event.payload.artifact.rev)
|
|
274
|
+
if (!acc.seenTypes.includes(event.type)) acc.seenTypes.push(event.type)
|
|
275
|
+
if (event.type === 'workspace.provisioned' && !isRemoteWorkspace(event.payload))
|
|
276
|
+
acc.localWorkspace = true
|
|
277
|
+
if (event.type === 'infrastructure.cleanup-attempted') {
|
|
278
|
+
const key = JSON.stringify(event.payload.workspaceRef)
|
|
279
|
+
acc.cleanupCounts[key] = (acc.cleanupCounts[key] ?? 0) + 1
|
|
280
|
+
}
|
|
281
|
+
if (event.type === 'pr-attachment.reclaim-failed') {
|
|
282
|
+
const key = String(event.payload.hostedSeq)
|
|
283
|
+
acc.reclaimCounts[key] = (acc.reclaimCounts[key] ?? 0) + 1
|
|
284
|
+
}
|
|
285
|
+
const priorReset = infrastructureFailureResetReducer.finish(acc.infrastructureReset)
|
|
286
|
+
acc.infrastructureReset = infrastructureFailureResetReducer.advance(acc.infrastructureReset, [
|
|
287
|
+
event,
|
|
288
|
+
])
|
|
289
|
+
if (infrastructureFailureResetReducer.finish(acc.infrastructureReset) !== priorReset)
|
|
290
|
+
acc.infrastructureCount = 0
|
|
291
|
+
if (event.type === 'infrastructure.failed') acc.infrastructureCount++
|
|
292
|
+
if (
|
|
293
|
+
event.type === 'escalation.raised' &&
|
|
294
|
+
event.payload.policyCause === 'infrastructure-failure-limit'
|
|
295
|
+
)
|
|
296
|
+
acc.infrastructureRaisedSeq = event.seq
|
|
297
|
+
if (event.type === 'build.created') {
|
|
298
|
+
acc.created ??= event
|
|
299
|
+
acc.lastCreated = event
|
|
300
|
+
}
|
|
301
|
+
if (event.type === 'workspace.provisioned') acc.workspaceGenerations++
|
|
302
|
+
if (event.type === 'workspace.released') acc.latestRelease = event
|
|
303
|
+
if (event.type === 'dispatch.failed') {
|
|
304
|
+
acc.dispatchFailureCount++
|
|
305
|
+
if (event.payload.stage === 'workspace') acc.latestWorkspaceFailureSeq = event.seq
|
|
306
|
+
}
|
|
307
|
+
if (event.type === 'dispatch.comment-posted') acc.commentPosted = true
|
|
308
|
+
if (event.type === 'spec.imported' || event.type === 'spec.authored') acc.specRecorded = true
|
|
309
|
+
if (event.type === 'build.auto-merge-requested') acc.autoMergeEverRequested = true
|
|
310
|
+
if (event.type === 'workspace.provisioned' && isRemoteWorkspace(event.payload))
|
|
311
|
+
acc.remoteWorkspace = true
|
|
312
|
+
if (event.type === 'build.completed' || event.type === 'build.aborted')
|
|
313
|
+
acc.terminalEvent = event
|
|
314
|
+
if (event.type === 'observation.recorded') {
|
|
315
|
+
acc.observations.push(event.payload)
|
|
316
|
+
acc.observationFacts.push({ payload: event.payload, seq: event.seq, ts: event.ts })
|
|
317
|
+
}
|
|
318
|
+
if (event.type === 'pr.merged') acc.mergeTimes.push(event.ts)
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
finish: (acc: BuildReadAcc) => ({
|
|
322
|
+
summaryRequests: summaryRequestsReducer.finish(acc.summaryRequests),
|
|
323
|
+
summaryBoundary: summaryBoundaryReducer.finish(acc.summaryBoundary),
|
|
324
|
+
hostedImages: [...hostedPrAttachmentsReducer.finish(acc.hostedImages)],
|
|
325
|
+
designations: currentPrAttachmentsReducer.finish(acc.designations),
|
|
326
|
+
imageHost: frozenPrImageHostReducer.finish(acc.imageHost),
|
|
327
|
+
pendingAttachments: pendingPrAttachmentReclaimsReducer.finish(acc.pendingAttachments),
|
|
328
|
+
recoveryCheckpoint: recoveryCheckpointReducer.finish(acc.recoveryCheckpoint),
|
|
329
|
+
provisionMarker: provisionMarkerReducer.finish(acc.provisionMarker),
|
|
330
|
+
baseBranch: baseBranchReducer.finish(acc.baseBranch),
|
|
331
|
+
state: buildReducer.finish(acc.build),
|
|
332
|
+
log: encodeLog(logIndexReducer.finish(acc.log)),
|
|
333
|
+
publication: structuredClone(acc.publication),
|
|
334
|
+
executionNoProgress: executionNoProgressReducer.finish(acc.executionNoProgress),
|
|
335
|
+
open: openExecutionReducer.finish(acc.open),
|
|
336
|
+
workspace: openBuildWorkspaceReducer.finish(acc.workspace),
|
|
337
|
+
lastExecution: lastExecutionOutcomeReducer.finish(acc.lastExecution),
|
|
338
|
+
dashboard: dashboardFactsReducer.finish(acc.dashboard),
|
|
339
|
+
deferral: currentDeferralObservationReducer.finish(acc.deferral),
|
|
340
|
+
observationFacts: structuredClone(acc.observationFacts),
|
|
341
|
+
mergeTimes: [...acc.mergeTimes],
|
|
342
|
+
observations: structuredClone(acc.observations),
|
|
343
|
+
terminalEvent: acc.terminalEvent,
|
|
344
|
+
summaryHistory: structuredClone(acc.summaryHistory),
|
|
345
|
+
summaryAttempts: [...acc.summaryAttempts],
|
|
346
|
+
summaryRequested: structuredClone(acc.summaryRequested),
|
|
347
|
+
summaryEligibleAfter: acc.summaryEligibleAfter,
|
|
348
|
+
finalizePublicationRequested: acc.finalizePublicationRequested,
|
|
349
|
+
latestWorkspace: acc.latestWorkspace,
|
|
350
|
+
planFeedback: structuredClone(acc.planFeedback),
|
|
351
|
+
findings: structuredClone(acc.findings),
|
|
352
|
+
feedback: structuredClone(acc.feedback),
|
|
353
|
+
reconcileBases: { ...acc.reconcileBases },
|
|
354
|
+
firstWorkspace: acc.firstWorkspace,
|
|
355
|
+
latestAbort: acc.latestAbort,
|
|
356
|
+
latestDiscard: acc.latestDiscard,
|
|
357
|
+
maxPlanRevision: acc.maxPlanRevision,
|
|
358
|
+
seenTypes: [...acc.seenTypes],
|
|
359
|
+
localWorkspace: acc.localWorkspace,
|
|
360
|
+
cleanupCounts: { ...acc.cleanupCounts },
|
|
361
|
+
reclaimCounts: { ...acc.reclaimCounts },
|
|
362
|
+
infrastructureReset: infrastructureFailureResetReducer.finish(acc.infrastructureReset),
|
|
363
|
+
infrastructureCount: acc.infrastructureCount,
|
|
364
|
+
infrastructureRaisedSeq: acc.infrastructureRaisedSeq,
|
|
365
|
+
remoteWorkspace: acc.remoteWorkspace,
|
|
366
|
+
created: acc.created,
|
|
367
|
+
lastCreated: acc.lastCreated,
|
|
368
|
+
latestRelease: acc.latestRelease,
|
|
369
|
+
latestWorkspaceFailureSeq: acc.latestWorkspaceFailureSeq,
|
|
370
|
+
workspaceGenerations: acc.workspaceGenerations,
|
|
371
|
+
dispatchFailureCount: acc.dispatchFailureCount,
|
|
372
|
+
commentPosted: acc.commentPosted,
|
|
373
|
+
specRecorded: acc.specRecorded,
|
|
374
|
+
autoMergeEverRequested: acc.autoMergeEverRequested,
|
|
375
|
+
}),
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
import type { RepositoryEvent } from '../events/repository'
|
|
379
|
+
import { repositoryAttentionReducer } from './repository-attention'
|
|
380
|
+
import { dispatchSettingsReducer } from './dispatch-settings'
|
|
381
|
+
import { harvestReducer } from './harvest'
|
|
382
|
+
import { ticketSourceHealthReducer } from './ticket-source-health'
|
|
383
|
+
import { autoMergeDefaultReducer } from './auto-merge-default'
|
|
384
|
+
import { effectiveConfigReducer } from './service-facts'
|
|
385
|
+
import { sandboxStatesReducer } from '../processes/sandbox-state'
|
|
386
|
+
import { openHarvestExecutionsReducer } from '../processes/harvest-execution-state'
|
|
387
|
+
import { dispatchStatusReducer } from './dispatch-status'
|
|
388
|
+
|
|
389
|
+
/** Shared current reads retain a fixed recent window. Historical holder
|
|
390
|
+
* correlations outside it use the explicitly parameterized cache. */
|
|
391
|
+
export const RECENT_DISPATCH_RUN_LIMIT = 16
|
|
392
|
+
export interface RepositoryReadAcc {
|
|
393
|
+
openHarvest: ReturnType<typeof openHarvestExecutionsReducer.initial>
|
|
394
|
+
attention: ReturnType<typeof repositoryAttentionReducer.initial>
|
|
395
|
+
harvestFailures: Record<
|
|
396
|
+
string,
|
|
397
|
+
{ count: number; latest: Extract<RepositoryEvent, { type: 'harvest.failed' }> }
|
|
398
|
+
>
|
|
399
|
+
harvestVerdicts: Record<
|
|
400
|
+
string,
|
|
401
|
+
{
|
|
402
|
+
first: Extract<RepositoryEvent, { type: 'harvest.review.verdict' }>
|
|
403
|
+
latest: Extract<RepositoryEvent, { type: 'harvest.review.verdict' }>
|
|
404
|
+
}
|
|
405
|
+
>
|
|
406
|
+
harvestAuthorizations: Record<
|
|
407
|
+
string,
|
|
408
|
+
Extract<RepositoryEvent, { type: 'harvest.execution.authorized' }>
|
|
409
|
+
>
|
|
410
|
+
harvestExecutionStarts: Record<string, string>
|
|
411
|
+
latestHarvestResumedSeq: number
|
|
412
|
+
harvestSessionEndings: Record<string, number>
|
|
413
|
+
harvestSessions: (import('../cli/harvest').HarvestSessionStatusView & { run: string })[]
|
|
414
|
+
pendingHumanResume?: number
|
|
415
|
+
dismissedThrough: number
|
|
416
|
+
failedAt: Record<string, string>
|
|
417
|
+
settings: ReturnType<typeof dispatchSettingsReducer.initial>
|
|
418
|
+
harvest: ReturnType<typeof harvestReducer.initial>
|
|
419
|
+
health: ReturnType<typeof ticketSourceHealthReducer.initial>
|
|
420
|
+
autoMerge: ReturnType<typeof autoMergeDefaultReducer.initial>
|
|
421
|
+
config: ReturnType<typeof effectiveConfigReducer.initial>
|
|
422
|
+
sandboxes: ReturnType<typeof sandboxStatesReducer.initial>
|
|
423
|
+
status: ReturnType<ReturnType<typeof dispatchStatusReducer>['initial']>
|
|
424
|
+
run?: string
|
|
425
|
+
scan?: Extract<RepositoryEvent, { type: 'dispatcher.ticket-scan-completed' }>['payload']
|
|
426
|
+
recentRuns: [string, DispatcherRunFacts][]
|
|
427
|
+
latestRun?: string
|
|
428
|
+
latestStoppedRun?: string
|
|
429
|
+
publications: Extract<RepositoryEvent, { type: 'orchestrator.sandbox.published' }>[]
|
|
430
|
+
}
|
|
431
|
+
export const repositoryReadFactsReducer = defineReducer({
|
|
432
|
+
version:
|
|
433
|
+
1 +
|
|
434
|
+
dispatcherRunFactsReducer('version').version +
|
|
435
|
+
openHarvestExecutionsReducer.version +
|
|
436
|
+
repositoryAttentionReducer.version +
|
|
437
|
+
dispatchSettingsReducer.version +
|
|
438
|
+
harvestReducer.version +
|
|
439
|
+
ticketSourceHealthReducer.version +
|
|
440
|
+
autoMergeDefaultReducer.version +
|
|
441
|
+
effectiveConfigReducer.version +
|
|
442
|
+
sandboxStatesReducer.version +
|
|
443
|
+
dispatchStatusReducer('version').version,
|
|
444
|
+
initial: (): RepositoryReadAcc => ({
|
|
445
|
+
recentRuns: [],
|
|
446
|
+
openHarvest: openHarvestExecutionsReducer.initial(),
|
|
447
|
+
attention: repositoryAttentionReducer.initial(),
|
|
448
|
+
dismissedThrough: 0,
|
|
449
|
+
failedAt: {},
|
|
450
|
+
harvestSessions: [],
|
|
451
|
+
harvestSessionEndings: {},
|
|
452
|
+
harvestFailures: {},
|
|
453
|
+
harvestVerdicts: {},
|
|
454
|
+
harvestAuthorizations: {},
|
|
455
|
+
latestHarvestResumedSeq: 0,
|
|
456
|
+
harvestExecutionStarts: {},
|
|
457
|
+
settings: dispatchSettingsReducer.initial(),
|
|
458
|
+
harvest: harvestReducer.initial(),
|
|
459
|
+
health: ticketSourceHealthReducer.initial(),
|
|
460
|
+
autoMerge: autoMergeDefaultReducer.initial(),
|
|
461
|
+
config: effectiveConfigReducer.initial(),
|
|
462
|
+
sandboxes: sandboxStatesReducer.initial(),
|
|
463
|
+
status: dispatchStatusReducer('initial').initial(),
|
|
464
|
+
publications: [],
|
|
465
|
+
}),
|
|
466
|
+
fold(acc: RepositoryReadAcc, events: readonly RepositoryEvent[]) {
|
|
467
|
+
acc.attention = repositoryAttentionReducer.advance(acc.attention, events)
|
|
468
|
+
acc.openHarvest = openHarvestExecutionsReducer.advance(acc.openHarvest, events)
|
|
469
|
+
acc.settings = dispatchSettingsReducer.advance(acc.settings, events)
|
|
470
|
+
acc.harvest = harvestReducer.advance(acc.harvest, events)
|
|
471
|
+
acc.health = ticketSourceHealthReducer.advance(acc.health, events)
|
|
472
|
+
acc.autoMerge = autoMergeDefaultReducer.advance(acc.autoMerge, events)
|
|
473
|
+
acc.config = effectiveConfigReducer.advance(acc.config, events)
|
|
474
|
+
acc.sandboxes = sandboxStatesReducer.advance(acc.sandboxes, events)
|
|
475
|
+
for (const event of events) {
|
|
476
|
+
if (event.type === 'harvest.failed') {
|
|
477
|
+
const key = JSON.stringify([event.payload.run, event.payload.step, event.payload.round])
|
|
478
|
+
acc.harvestFailures[key] = {
|
|
479
|
+
count: (acc.harvestFailures[key]?.count ?? 0) + 1,
|
|
480
|
+
latest: event,
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (event.type === 'harvest.review.verdict') {
|
|
484
|
+
const key = JSON.stringify([event.payload.run, event.payload.round])
|
|
485
|
+
acc.harvestVerdicts[key] = {
|
|
486
|
+
first: acc.harvestVerdicts[key]?.first ?? event,
|
|
487
|
+
latest: event,
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (event.type === 'harvest.execution.started')
|
|
491
|
+
acc.harvestExecutionStarts[String(event.seq)] = event.payload.execution
|
|
492
|
+
if (event.type === 'harvest.execution.authorized')
|
|
493
|
+
acc.harvestAuthorizations[JSON.stringify(event.payload.execution)] ??= event
|
|
494
|
+
if (event.type === 'harvest.resumed') acc.latestHarvestResumedSeq = event.seq
|
|
495
|
+
if (event.type === 'harvest.session.started')
|
|
496
|
+
acc.harvestSessions.push({
|
|
497
|
+
run: event.payload.run,
|
|
498
|
+
session: event.payload.session,
|
|
499
|
+
role: event.payload.role,
|
|
500
|
+
step: event.payload.step,
|
|
501
|
+
round: event.payload.round,
|
|
502
|
+
status: 'open',
|
|
503
|
+
...(event.payload.stream === undefined ? {} : { stream: event.payload.stream }),
|
|
504
|
+
streamStatus: event.payload.stream === undefined ? 'closed' : 'open',
|
|
505
|
+
})
|
|
506
|
+
if (event.type === 'harvest.session.ended') {
|
|
507
|
+
acc.harvestSessionEndings[JSON.stringify(event.payload.session)] ??= event.seq
|
|
508
|
+
const session = acc.harvestSessions.findLast(
|
|
509
|
+
(session) => session.session === event.payload.session && session.status === 'open',
|
|
510
|
+
)
|
|
511
|
+
if (session) {
|
|
512
|
+
session.status = 'ended'
|
|
513
|
+
session.streamStatus = 'closed'
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (event.type === 'harvest.resume-requested' && event.actor.kind === 'human')
|
|
517
|
+
acc.pendingHumanResume = event.seq
|
|
518
|
+
if (event.type === 'harvest.pause-requested') delete acc.pendingHumanResume
|
|
519
|
+
if (event.type === 'harvest.resumed' && acc.pendingHumanResume !== undefined)
|
|
520
|
+
acc.dismissedThrough = Math.max(acc.dismissedThrough, acc.pendingHumanResume)
|
|
521
|
+
if (event.type === 'harvest.failed' && !event.payload.willRetry)
|
|
522
|
+
acc.failedAt[JSON.stringify(event.payload.run)] = event.ts
|
|
523
|
+
if (event.type === 'dispatcher.run-started') {
|
|
524
|
+
acc.run = event.payload.run
|
|
525
|
+
acc.status = dispatchStatusReducer(acc.run).initial()
|
|
526
|
+
}
|
|
527
|
+
if (acc.run) acc.status = dispatchStatusReducer(acc.run).advance(acc.status, [event])
|
|
528
|
+
if (event.type === 'dispatcher.ticket-scan-completed') acc.scan = event.payload
|
|
529
|
+
if (event.type === 'dispatcher.run-started') acc.latestRun = event.payload.run
|
|
530
|
+
if (event.type === 'dispatcher.run-stopped') acc.latestStoppedRun = event.payload.run
|
|
531
|
+
if (
|
|
532
|
+
event.type.startsWith('dispatcher.') &&
|
|
533
|
+
'run' in event.payload &&
|
|
534
|
+
typeof event.payload.run === 'string'
|
|
535
|
+
) {
|
|
536
|
+
const run = event.payload.run
|
|
537
|
+
let entry = acc.recentRuns.find(([id]) => id === run)
|
|
538
|
+
if (!entry) {
|
|
539
|
+
entry = [run, {}]
|
|
540
|
+
acc.recentRuns.push(entry)
|
|
541
|
+
}
|
|
542
|
+
foldDispatcherRunFact(entry[1], event)
|
|
543
|
+
while (acc.recentRuns.length > RECENT_DISPATCH_RUN_LIMIT) {
|
|
544
|
+
const index = acc.recentRuns.findIndex(
|
|
545
|
+
([id]) => id !== acc.latestRun && id !== acc.latestStoppedRun,
|
|
546
|
+
)
|
|
547
|
+
acc.recentRuns.splice(index, 1)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (event.type === 'orchestrator.sandbox.published') {
|
|
551
|
+
acc.publications = acc.publications.filter(
|
|
552
|
+
(prior) =>
|
|
553
|
+
prior.payload.operator !== event.payload.operator ||
|
|
554
|
+
prior.payload.branch !== event.payload.branch,
|
|
555
|
+
)
|
|
556
|
+
acc.publications.push(event)
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
finish: (acc: RepositoryReadAcc) => ({
|
|
561
|
+
harvestFailures: structuredClone(acc.harvestFailures),
|
|
562
|
+
harvestVerdicts: structuredClone(acc.harvestVerdicts),
|
|
563
|
+
harvestAuthorizations: structuredClone(acc.harvestAuthorizations),
|
|
564
|
+
harvestExecutionStarts: { ...acc.harvestExecutionStarts },
|
|
565
|
+
latestHarvestResumedSeq: acc.latestHarvestResumedSeq,
|
|
566
|
+
harvestSessionEndings: { ...acc.harvestSessionEndings },
|
|
567
|
+
harvestSessions: structuredClone(acc.harvestSessions),
|
|
568
|
+
attention: repositoryAttentionReducer.finish(acc.attention),
|
|
569
|
+
dismissedThrough: acc.dismissedThrough,
|
|
570
|
+
failedAt: { ...acc.failedAt },
|
|
571
|
+
openHarvest: openHarvestExecutionsReducer.finish(acc.openHarvest),
|
|
572
|
+
settings: dispatchSettingsReducer.finish(acc.settings),
|
|
573
|
+
harvest: harvestReducer.finish(acc.harvest),
|
|
574
|
+
health: ticketSourceHealthReducer.finish(acc.health),
|
|
575
|
+
autoMerge: autoMergeDefaultReducer.finish(acc.autoMerge),
|
|
576
|
+
config: effectiveConfigReducer.finish(acc.config),
|
|
577
|
+
sandboxes: sandboxStatesReducer.finish(acc.sandboxes),
|
|
578
|
+
status: acc.run ? dispatchStatusReducer(acc.run).finish(acc.status) : undefined,
|
|
579
|
+
scan: acc.scan,
|
|
580
|
+
recentRuns: structuredClone(acc.recentRuns),
|
|
581
|
+
latestRun: acc.latestRun,
|
|
582
|
+
latestStoppedRun: acc.latestStoppedRun,
|
|
583
|
+
publications: structuredClone(acc.publications),
|
|
584
|
+
}),
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
import type { IncrementalReducer } from './incremental'
|
|
588
|
+
/** Companion for an optional finished answer: absence is represented by an
|
|
589
|
+
* omitted object field, never top-level undefined. */
|
|
590
|
+
export function optionalValue<A, E, V>(reducer: IncrementalReducer<A, E, V>) {
|
|
591
|
+
return {
|
|
592
|
+
...reducer,
|
|
593
|
+
finish: (acc: A) => ({ value: reducer.finish(acc) }),
|
|
594
|
+
reduce: (events: readonly E[]) => ({ value: reducer.reduce(events) }),
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** One run's correlation facts. History for unrelated runs never enters this value. */
|
|
599
|
+
export interface DispatcherRunFacts {
|
|
600
|
+
start?: RepositoryEvent
|
|
601
|
+
latestStart?: RepositoryEvent
|
|
602
|
+
stop?: RepositoryEvent
|
|
603
|
+
tick?: RepositoryEvent
|
|
604
|
+
completed?: RepositoryEvent
|
|
605
|
+
boundary?: 'dispatcher.tick-started' | 'dispatcher.tick-completed' | 'dispatcher.tick-failed'
|
|
606
|
+
stopRequest?: Extract<RepositoryEvent, { type: 'dispatcher.stop-requested' }>
|
|
607
|
+
}
|
|
608
|
+
export function dispatcherRunFactsReducer(run: string) {
|
|
609
|
+
return defineReducer<DispatcherRunFacts, RepositoryEvent, DispatcherRunFacts>({
|
|
610
|
+
version: 2,
|
|
611
|
+
initial: () => ({}),
|
|
612
|
+
fold(acc, events) {
|
|
613
|
+
for (const event of events) {
|
|
614
|
+
if (!('run' in event.payload) || event.payload.run !== run) continue
|
|
615
|
+
foldDispatcherRunFact(acc, event)
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
finish: (acc) => structuredClone(acc),
|
|
619
|
+
})
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function foldDispatcherRunFact(acc: DispatcherRunFacts, event: RepositoryEvent): void {
|
|
623
|
+
if (event.type === 'dispatcher.run-started') {
|
|
624
|
+
acc.start ??= event
|
|
625
|
+
acc.latestStart = event
|
|
626
|
+
} else if (event.type === 'dispatcher.run-stopped') acc.stop = event
|
|
627
|
+
else if (event.type === 'dispatcher.stop-requested') acc.stopRequest ??= event
|
|
628
|
+
else if (
|
|
629
|
+
event.type === 'dispatcher.tick-started' ||
|
|
630
|
+
event.type === 'dispatcher.tick-completed' ||
|
|
631
|
+
event.type === 'dispatcher.tick-failed'
|
|
632
|
+
) {
|
|
633
|
+
acc.boundary = event.type
|
|
634
|
+
if (event.type !== 'dispatcher.tick-started') acc.tick = event
|
|
635
|
+
if (event.type === 'dispatcher.tick-completed') acc.completed = event
|
|
636
|
+
}
|
|
637
|
+
}
|