@defrex/autobuild 0.9.0 → 0.10.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 +10 -0
- package/docs/architecture.md +109 -0
- package/docs/configuration.md +24 -3
- package/docs/remote-store-protocol.md +35 -0
- package/docs/website.md +4 -0
- package/package.json +6 -4
- package/packages/core/src/cli/dashboard/facts.ts +215 -0
- package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
- package/packages/core/src/cli/dashboard/model.ts +85 -147
- package/packages/core/src/cli/dashboard/poll.ts +7 -2
- package/packages/core/src/cli/dispatch-process.ts +1 -13
- package/packages/core/src/cli/dispatch.ts +80 -37
- package/packages/core/src/cli/help.ts +15 -1
- package/packages/core/src/cli/init-validation.ts +18 -0
- package/packages/core/src/cli/installation.ts +50 -8
- package/packages/core/src/cli/self-update.ts +77 -2
- package/packages/core/src/cli/status.ts +1 -1
- package/packages/core/src/cli/ticket.ts +146 -1
- package/packages/core/src/cli/wait.ts +10 -2
- package/packages/core/src/cli/watch.ts +10 -2
- package/packages/core/src/config/live.ts +1 -0
- package/packages/core/src/config/pipeline-source.ts +17 -5
- package/packages/core/src/config/schema.ts +21 -0
- package/packages/core/src/integration/harness.ts +113 -1
- package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
- package/packages/core/src/kernel/auto-merge-default.ts +43 -9
- package/packages/core/src/kernel/auto-merge.ts +99 -22
- package/packages/core/src/kernel/dispatch-settings.ts +42 -17
- package/packages/core/src/kernel/dispatch-status.ts +23 -0
- package/packages/core/src/kernel/engine.ts +9 -438
- package/packages/core/src/kernel/generators/build-log.ts +259 -0
- package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
- package/packages/core/src/kernel/generators/session-log.ts +72 -0
- package/packages/core/src/kernel/harvest.ts +62 -18
- package/packages/core/src/kernel/incremental-contract.ts +75 -0
- package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
- package/packages/core/src/kernel/incremental.ts +53 -0
- package/packages/core/src/kernel/log-index.ts +620 -0
- package/packages/core/src/kernel/pr-attachments.ts +164 -46
- package/packages/core/src/kernel/reducer-registry.ts +164 -0
- package/packages/core/src/kernel/reducer.ts +216 -74
- package/packages/core/src/operator/query.ts +68 -28
- package/packages/core/src/operator/read-view.ts +98 -0
- package/packages/core/src/ports/tickets/fake.ts +5 -1
- package/packages/core/src/ports/tickets/file.ts +169 -48
- package/packages/core/src/ports/tickets/linear.ts +33 -1
- package/packages/core/src/ports/tickets/operations.ts +1 -1
- package/packages/core/src/ports/tickets/remote.ts +8 -1
- package/packages/core/src/ports/types.ts +32 -1
- package/packages/core/src/processes/build-execution-state.ts +31 -15
- package/packages/core/src/processes/build-runner.ts +184 -90
- package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
- package/packages/core/src/processes/dispatcher.ts +270 -176
- package/packages/core/src/processes/execution-settlement.ts +72 -39
- package/packages/core/src/processes/harvest-execution-state.ts +41 -29
- package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
- package/packages/core/src/processes/orchestrator-tick.ts +44 -3
- package/packages/core/src/processes/publication-state.ts +164 -52
- package/packages/core/src/processes/repo-view.ts +1436 -0
- package/packages/core/src/processes/sandbox-state.ts +26 -2
- package/packages/core/src/processes/tick-state.ts +33 -0
- package/packages/core/src/store/adapter.ts +10 -1
- package/packages/core/src/store/build-scope.ts +26 -0
- package/packages/core/src/store/contract.ts +337 -0
- package/packages/core/src/store/digest.ts +71 -18
- package/packages/core/src/store/local/schema.ts +17 -0
- package/packages/core/src/store/local/store.ts +111 -4
- package/packages/core/src/store/memory.ts +68 -1
- package/packages/core/src/store/phase-session.ts +23 -0
- package/packages/core/src/store/remote/client.ts +50 -1
- package/packages/core/src/store/remote/index.ts +2 -0
- package/packages/core/src/store/remote/protocol.ts +15 -0
- package/packages/core/src/store/repo-state-events.ts +72 -7
- package/packages/core/src/store/session-handle.ts +30 -0
- package/packages/core/src/store/session-reducer.ts +52 -11
- package/packages/core/src/store/snapshots.ts +88 -0
- package/packages/core/src/store/ticket-assets.ts +41 -8
- package/packages/core/src/store/types.ts +38 -0
- package/skills/guide/SKILL.md +26 -7
- package/skills/guide/references/remote-store-protocol.md +35 -0
- package/skills/tickets/SKILL.md +57 -5
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* precedent `bulk-control.ts` set for the pause/resume walk.
|
|
27
27
|
*/
|
|
28
28
|
import type { RepositoryEvent } from '../events/repository'
|
|
29
|
+
import { defineReducer } from './incremental'
|
|
29
30
|
import { discardInFlight, type BuildState } from './reducer'
|
|
30
31
|
|
|
31
32
|
/** One `dispatcher.auto-merge-default-set` fact, resolved. `actor` is the
|
|
@@ -49,16 +50,49 @@ export function latestAutoMergeDefault(
|
|
|
49
50
|
events: RepositoryEvent[],
|
|
50
51
|
enabled?: boolean,
|
|
51
52
|
): AutoMergeDefaultFact | undefined {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
return pickAutoMergeDefault(autoMergeDefaultReducer.reduce(events), enabled)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const AUTO_MERGE_DEFAULT_REDUCER_VERSION = 1
|
|
57
|
+
|
|
58
|
+
/** The newest `dispatcher.auto-merge-default-set` fact in array order, overall
|
|
59
|
+
* and per value — `latestAutoMergeDefault(events, enabled)` is a lookup in
|
|
60
|
+
* this state, exact for any array order. */
|
|
61
|
+
export interface AutoMergeDefaultFacts {
|
|
62
|
+
latest?: AutoMergeDefaultFact
|
|
63
|
+
latestEnabled?: AutoMergeDefaultFact
|
|
64
|
+
latestDisabled?: AutoMergeDefaultFact
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const autoMergeDefaultReducer = defineReducer<
|
|
68
|
+
AutoMergeDefaultFacts,
|
|
69
|
+
RepositoryEvent,
|
|
70
|
+
AutoMergeDefaultFacts
|
|
71
|
+
>({
|
|
72
|
+
version: AUTO_MERGE_DEFAULT_REDUCER_VERSION,
|
|
73
|
+
initial: () => ({}),
|
|
74
|
+
fold(acc, events) {
|
|
75
|
+
for (const event of events) {
|
|
76
|
+
if (event.type !== 'dispatcher.auto-merge-default-set') continue
|
|
77
|
+
const fact: AutoMergeDefaultFact = {
|
|
78
|
+
enabled: event.payload.enabled,
|
|
79
|
+
seq: event.seq,
|
|
80
|
+
actor: event.actor,
|
|
81
|
+
}
|
|
82
|
+
acc.latest = fact
|
|
83
|
+
if (fact.enabled) acc.latestEnabled = fact
|
|
84
|
+
else acc.latestDisabled = fact
|
|
59
85
|
}
|
|
60
|
-
}
|
|
61
|
-
|
|
86
|
+
},
|
|
87
|
+
finish: (acc) => ({ ...acc }),
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
export function pickAutoMergeDefault(
|
|
91
|
+
facts: AutoMergeDefaultFacts,
|
|
92
|
+
enabled?: boolean,
|
|
93
|
+
): AutoMergeDefaultFact | undefined {
|
|
94
|
+
if (enabled === undefined) return facts.latest
|
|
95
|
+
return enabled ? facts.latestEnabled : facts.latestDisabled
|
|
62
96
|
}
|
|
63
97
|
|
|
64
98
|
/**
|
|
@@ -2,6 +2,7 @@ import type { AbEvent, EventEnvelope, EventWrite } from '../events/catalog'
|
|
|
2
2
|
import { KERNEL } from '../events/envelope'
|
|
3
3
|
import { autoMergeDeferralClasses, type AutoMergeDeferralReason } from '../ports/types'
|
|
4
4
|
import type { BuildStore } from '../store/types'
|
|
5
|
+
import { defineReducer } from './incremental'
|
|
5
6
|
import type { BuildState } from './reducer'
|
|
6
7
|
|
|
7
8
|
/** The complete GitHub `mergeStateStatus` enum. Keeping this list closed is a
|
|
@@ -133,36 +134,104 @@ export function currentDeferralObservation(
|
|
|
133
134
|
events: AbEvent[],
|
|
134
135
|
prNumber: number,
|
|
135
136
|
commandSeq: number,
|
|
136
|
-
):
|
|
137
|
+
): DeferralObservation | undefined {
|
|
138
|
+
return deferralObservationFrom(
|
|
139
|
+
currentDeferralObservationReducer.reduce(events),
|
|
140
|
+
prNumber,
|
|
141
|
+
commandSeq,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type DeferralObservation = Extract<AbEvent, { type: 'observation.recorded' }>
|
|
146
|
+
|
|
147
|
+
const DEFERRAL_REF_PREFIX = 'auto-merge-gate:pr:'
|
|
148
|
+
|
|
149
|
+
/** One array-order ledger entry. Only the facts the currency predicate reads
|
|
150
|
+
* are kept, with every maximal run of other events collapsed to a barrier
|
|
151
|
+
* carrying the run's max seq: the head scan stops at the first event (of ANY
|
|
152
|
+
* type) with `seq >= observation.seq`, so an unrelated event must still be able
|
|
153
|
+
* to end it. */
|
|
154
|
+
type DeferralLedgerEntry =
|
|
155
|
+
| { t: 'barrier'; seq: number }
|
|
156
|
+
| { t: 'observation'; event: DeferralObservation }
|
|
157
|
+
| { t: 'finalized'; seq: number; headSha: string }
|
|
158
|
+
| { t: 'terminal'; seq: number }
|
|
159
|
+
| { t: 'reconciled'; seq: number }
|
|
160
|
+
|
|
161
|
+
export const CURRENT_DEFERRAL_OBSERVATION_REDUCER_VERSION = 1
|
|
162
|
+
|
|
163
|
+
export interface DeferralLedger {
|
|
164
|
+
entries: DeferralLedgerEntry[]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const currentDeferralObservationReducer = defineReducer<
|
|
168
|
+
DeferralLedger,
|
|
169
|
+
AbEvent,
|
|
170
|
+
DeferralLedger
|
|
171
|
+
>({
|
|
172
|
+
version: CURRENT_DEFERRAL_OBSERVATION_REDUCER_VERSION,
|
|
173
|
+
initial: () => ({ entries: [] }),
|
|
174
|
+
fold(acc, events) {
|
|
175
|
+
for (const event of events) {
|
|
176
|
+
let entry: DeferralLedgerEntry | undefined
|
|
177
|
+
if (
|
|
178
|
+
event.type === 'observation.recorded' &&
|
|
179
|
+
event.payload.refs?.some((ref) => ref.startsWith(DEFERRAL_REF_PREFIX)) === true
|
|
180
|
+
) {
|
|
181
|
+
entry = { t: 'observation', event }
|
|
182
|
+
} else if (event.type === 'finalize.completed') {
|
|
183
|
+
entry = { t: 'finalized', seq: event.seq, headSha: event.payload.pr.headSha }
|
|
184
|
+
} else if (event.type === 'pr.merged' || event.type === 'pr.closed') {
|
|
185
|
+
entry = { t: 'terminal', seq: event.seq }
|
|
186
|
+
} else if (event.type === 'reconcile.completed') {
|
|
187
|
+
entry = { t: 'reconciled', seq: event.seq }
|
|
188
|
+
}
|
|
189
|
+
if (entry !== undefined) {
|
|
190
|
+
acc.entries.push(entry)
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
const last = acc.entries.at(-1)
|
|
194
|
+
if (last?.t === 'barrier') last.seq = Math.max(last.seq, event.seq)
|
|
195
|
+
else acc.entries.push({ t: 'barrier', seq: event.seq })
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
finish: (acc) => ({ entries: acc.entries.slice() }),
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
function deferralEntrySeq(entry: DeferralLedgerEntry): number {
|
|
202
|
+
return entry.t === 'observation' ? entry.event.seq : entry.seq
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** The current deferral observation for a PR/command over a reduced ledger. */
|
|
206
|
+
export function deferralObservationFrom(
|
|
207
|
+
ledger: DeferralLedger,
|
|
208
|
+
prNumber: number,
|
|
209
|
+
commandSeq: number,
|
|
210
|
+
): DeferralObservation | undefined {
|
|
137
211
|
const marker = autoMergeDeferralRef(prNumber, commandSeq)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
event.payload.refs?.includes(marker) === true
|
|
145
|
-
) {
|
|
146
|
-
observation = event
|
|
212
|
+
const { entries } = ledger
|
|
213
|
+
let observation: DeferralObservation | undefined
|
|
214
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
215
|
+
const entry = entries[index]
|
|
216
|
+
if (entry?.t === 'observation' && entry.event.payload.refs?.includes(marker) === true) {
|
|
217
|
+
observation = entry.event
|
|
147
218
|
break
|
|
148
219
|
}
|
|
149
220
|
}
|
|
150
221
|
if (observation === undefined) return undefined
|
|
151
222
|
|
|
152
223
|
let headAtObservation: string | undefined
|
|
153
|
-
for (const
|
|
154
|
-
if (
|
|
155
|
-
if (
|
|
224
|
+
for (const entry of entries) {
|
|
225
|
+
if (deferralEntrySeq(entry) >= observation.seq) break
|
|
226
|
+
if (entry.t === 'finalized') headAtObservation = entry.headSha
|
|
156
227
|
}
|
|
157
228
|
|
|
158
|
-
for (const
|
|
159
|
-
if (
|
|
160
|
-
if (
|
|
161
|
-
if (
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
else if (event.payload.pr.headSha !== headAtObservation) return undefined
|
|
165
|
-
}
|
|
229
|
+
for (const entry of entries) {
|
|
230
|
+
if (entry.t === 'observation' || entry.t === 'barrier') continue
|
|
231
|
+
if (entry.seq <= observation.seq) continue
|
|
232
|
+
if (entry.t === 'terminal' || entry.t === 'reconciled') return undefined
|
|
233
|
+
if (headAtObservation === undefined) headAtObservation = entry.headSha
|
|
234
|
+
else if (entry.headSha !== headAtObservation) return undefined
|
|
166
235
|
}
|
|
167
236
|
return observation
|
|
168
237
|
}
|
|
@@ -186,10 +255,18 @@ export function hasAutoMergeDeferralObservation(
|
|
|
186
255
|
export function currentAutoMergeDeferral(
|
|
187
256
|
events: AbEvent[],
|
|
188
257
|
state: Pick<BuildState, 'autoMerge' | 'pr'>,
|
|
258
|
+
): string | undefined {
|
|
259
|
+
return currentAutoMergeDeferralFrom(currentDeferralObservationReducer.reduce(events), state)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** `currentAutoMergeDeferral` over an already-reduced ledger. */
|
|
263
|
+
export function currentAutoMergeDeferralFrom(
|
|
264
|
+
ledger: DeferralLedger,
|
|
265
|
+
state: Pick<BuildState, 'autoMerge' | 'pr'>,
|
|
189
266
|
): string | undefined {
|
|
190
267
|
const pending = pendingAutoMerge(state)
|
|
191
268
|
if (pending?.enabled !== true || state.pr === undefined) return undefined
|
|
192
|
-
return
|
|
269
|
+
return deferralObservationFrom(ledger, state.pr.number, pending.commandSeq)?.payload.summary
|
|
193
270
|
}
|
|
194
271
|
|
|
195
272
|
const DEFERRAL_SUMMARIES = {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* is authoritative; fresh repositories retain the historical process defaults.
|
|
5
5
|
*/
|
|
6
6
|
import type { RepositoryEvent } from '../events/repository'
|
|
7
|
+
import { defineReducer, type IncrementalReducer } from './incremental'
|
|
7
8
|
|
|
8
9
|
export const DEFAULT_DISPATCH_INTAKE = true
|
|
9
10
|
export const DEFAULT_DISPATCH_PAUSED = false
|
|
@@ -21,32 +22,36 @@ export interface DispatchSettings {
|
|
|
21
22
|
defaultAutoMerge: boolean
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
/** The carried accumulator: the settings plus the seq of the fact that set
|
|
26
|
+
* each, which decides last-writer-wins independent of array order. */
|
|
27
|
+
export interface DispatchSettingsAcc extends DispatchSettings {
|
|
28
|
+
intakeSeq: number
|
|
29
|
+
pausedSeq: number
|
|
30
|
+
autoMergeSeq: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Bump when `DispatchSettingsAcc`'s shape or fold semantics change. */
|
|
34
|
+
export const DISPATCH_SETTINGS_REDUCER_VERSION = 1
|
|
31
35
|
|
|
36
|
+
function foldDispatchSettings(acc: DispatchSettingsAcc, events: readonly RepositoryEvent[]): void {
|
|
32
37
|
for (const event of events) {
|
|
33
38
|
switch (event.type) {
|
|
34
39
|
case 'dispatcher.intake-set':
|
|
35
|
-
if (event.seq > intakeSeq) {
|
|
36
|
-
intake = event.payload.enabled
|
|
37
|
-
intakeSeq = event.seq
|
|
40
|
+
if (event.seq > acc.intakeSeq) {
|
|
41
|
+
acc.intake = event.payload.enabled
|
|
42
|
+
acc.intakeSeq = event.seq
|
|
38
43
|
}
|
|
39
44
|
break
|
|
40
45
|
case 'dispatcher.pause-set':
|
|
41
|
-
if (event.seq > pausedSeq) {
|
|
42
|
-
paused = event.payload.enabled
|
|
43
|
-
pausedSeq = event.seq
|
|
46
|
+
if (event.seq > acc.pausedSeq) {
|
|
47
|
+
acc.paused = event.payload.enabled
|
|
48
|
+
acc.pausedSeq = event.seq
|
|
44
49
|
}
|
|
45
50
|
break
|
|
46
51
|
case 'dispatcher.auto-merge-default-set':
|
|
47
|
-
if (event.seq > autoMergeSeq) {
|
|
48
|
-
defaultAutoMerge = event.payload.enabled
|
|
49
|
-
autoMergeSeq = event.seq
|
|
52
|
+
if (event.seq > acc.autoMergeSeq) {
|
|
53
|
+
acc.defaultAutoMerge = event.payload.enabled
|
|
54
|
+
acc.autoMergeSeq = event.seq
|
|
50
55
|
}
|
|
51
56
|
break
|
|
52
57
|
default:
|
|
@@ -55,6 +60,26 @@ export function reduceDispatchSettings(events: RepositoryEvent[]): DispatchSetti
|
|
|
55
60
|
break
|
|
56
61
|
}
|
|
57
62
|
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const dispatchSettingsReducer: IncrementalReducer<
|
|
66
|
+
DispatchSettingsAcc,
|
|
67
|
+
RepositoryEvent,
|
|
68
|
+
DispatchSettings
|
|
69
|
+
> = defineReducer({
|
|
70
|
+
version: DISPATCH_SETTINGS_REDUCER_VERSION,
|
|
71
|
+
initial: () => ({
|
|
72
|
+
intake: DEFAULT_DISPATCH_INTAKE,
|
|
73
|
+
paused: DEFAULT_DISPATCH_PAUSED,
|
|
74
|
+
defaultAutoMerge: DEFAULT_DISPATCH_AUTO_MERGE,
|
|
75
|
+
intakeSeq: 0,
|
|
76
|
+
pausedSeq: 0,
|
|
77
|
+
autoMergeSeq: 0,
|
|
78
|
+
}),
|
|
79
|
+
fold: foldDispatchSettings,
|
|
80
|
+
finish: ({ intake, paused, defaultAutoMerge }) => ({ intake, paused, defaultAutoMerge }),
|
|
81
|
+
})
|
|
58
82
|
|
|
59
|
-
|
|
83
|
+
export function reduceDispatchSettings(events: RepositoryEvent[]): DispatchSettings {
|
|
84
|
+
return dispatchSettingsReducer.reduce(events)
|
|
60
85
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RepositoryEvent } from '../events/repository'
|
|
2
2
|
import type { ArtifactRef } from '../ontology'
|
|
3
|
+
import type { IncrementalReducer } from './incremental'
|
|
3
4
|
|
|
4
5
|
export type DispatchHealth = 'starting' | 'running' | 'stopped' | 'failed'
|
|
5
6
|
|
|
@@ -139,3 +140,25 @@ export function reduceDispatchStatus(
|
|
|
139
140
|
}
|
|
140
141
|
return state
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
/** Bump when `DispatchStatus`'s shape or fold semantics change (it is its own
|
|
145
|
+
* accumulator: `lastSeq` makes the fold skip already-seen events). */
|
|
146
|
+
export const DISPATCH_STATUS_REDUCER_VERSION = 1
|
|
147
|
+
|
|
148
|
+
/** The incremental form of `reduceDispatchStatus` for one run. The run is a
|
|
149
|
+
* parameter, so this is a factory. */
|
|
150
|
+
export function dispatchStatusReducer(
|
|
151
|
+
run: string,
|
|
152
|
+
): IncrementalReducer<DispatchStatus, RepositoryEvent, DispatchStatus> {
|
|
153
|
+
return {
|
|
154
|
+
version: DISPATCH_STATUS_REDUCER_VERSION,
|
|
155
|
+
initial: () => ({ run, health: 'starting', roleWarnings: [], diagnostics: [], lastSeq: 0 }),
|
|
156
|
+
advance: (acc, events) => reduceDispatchStatus(events, run, acc),
|
|
157
|
+
finish: (acc) => ({
|
|
158
|
+
...acc,
|
|
159
|
+
roleWarnings: [...acc.roleWarnings],
|
|
160
|
+
diagnostics: [...acc.diagnostics],
|
|
161
|
+
}),
|
|
162
|
+
reduce: (events) => reduceDispatchStatus(events, run),
|
|
163
|
+
}
|
|
164
|
+
}
|