@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.
Files changed (82) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +10 -0
  3. package/docs/architecture.md +109 -0
  4. package/docs/configuration.md +24 -3
  5. package/docs/remote-store-protocol.md +35 -0
  6. package/docs/website.md +4 -0
  7. package/package.json +6 -4
  8. package/packages/core/src/cli/dashboard/facts.ts +215 -0
  9. package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
  10. package/packages/core/src/cli/dashboard/model.ts +85 -147
  11. package/packages/core/src/cli/dashboard/poll.ts +7 -2
  12. package/packages/core/src/cli/dispatch-process.ts +1 -13
  13. package/packages/core/src/cli/dispatch.ts +80 -37
  14. package/packages/core/src/cli/help.ts +15 -1
  15. package/packages/core/src/cli/init-validation.ts +18 -0
  16. package/packages/core/src/cli/installation.ts +50 -8
  17. package/packages/core/src/cli/self-update.ts +77 -2
  18. package/packages/core/src/cli/status.ts +1 -1
  19. package/packages/core/src/cli/ticket.ts +146 -1
  20. package/packages/core/src/cli/wait.ts +10 -2
  21. package/packages/core/src/cli/watch.ts +10 -2
  22. package/packages/core/src/config/live.ts +1 -0
  23. package/packages/core/src/config/pipeline-source.ts +17 -5
  24. package/packages/core/src/config/schema.ts +21 -0
  25. package/packages/core/src/integration/harness.ts +113 -1
  26. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
  27. package/packages/core/src/kernel/auto-merge-default.ts +43 -9
  28. package/packages/core/src/kernel/auto-merge.ts +99 -22
  29. package/packages/core/src/kernel/dispatch-settings.ts +42 -17
  30. package/packages/core/src/kernel/dispatch-status.ts +23 -0
  31. package/packages/core/src/kernel/engine.ts +9 -438
  32. package/packages/core/src/kernel/generators/build-log.ts +259 -0
  33. package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
  34. package/packages/core/src/kernel/generators/session-log.ts +72 -0
  35. package/packages/core/src/kernel/harvest.ts +62 -18
  36. package/packages/core/src/kernel/incremental-contract.ts +75 -0
  37. package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
  38. package/packages/core/src/kernel/incremental.ts +53 -0
  39. package/packages/core/src/kernel/log-index.ts +620 -0
  40. package/packages/core/src/kernel/pr-attachments.ts +164 -46
  41. package/packages/core/src/kernel/reducer-registry.ts +164 -0
  42. package/packages/core/src/kernel/reducer.ts +216 -74
  43. package/packages/core/src/operator/query.ts +68 -28
  44. package/packages/core/src/operator/read-view.ts +98 -0
  45. package/packages/core/src/ports/tickets/fake.ts +5 -1
  46. package/packages/core/src/ports/tickets/file.ts +169 -48
  47. package/packages/core/src/ports/tickets/linear.ts +33 -1
  48. package/packages/core/src/ports/tickets/operations.ts +1 -1
  49. package/packages/core/src/ports/tickets/remote.ts +8 -1
  50. package/packages/core/src/ports/types.ts +32 -1
  51. package/packages/core/src/processes/build-execution-state.ts +31 -15
  52. package/packages/core/src/processes/build-runner.ts +184 -90
  53. package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
  54. package/packages/core/src/processes/dispatcher.ts +270 -176
  55. package/packages/core/src/processes/execution-settlement.ts +72 -39
  56. package/packages/core/src/processes/harvest-execution-state.ts +41 -29
  57. package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
  58. package/packages/core/src/processes/orchestrator-tick.ts +44 -3
  59. package/packages/core/src/processes/publication-state.ts +164 -52
  60. package/packages/core/src/processes/repo-view.ts +1436 -0
  61. package/packages/core/src/processes/sandbox-state.ts +26 -2
  62. package/packages/core/src/processes/tick-state.ts +33 -0
  63. package/packages/core/src/store/adapter.ts +10 -1
  64. package/packages/core/src/store/build-scope.ts +26 -0
  65. package/packages/core/src/store/contract.ts +337 -0
  66. package/packages/core/src/store/digest.ts +71 -18
  67. package/packages/core/src/store/local/schema.ts +17 -0
  68. package/packages/core/src/store/local/store.ts +111 -4
  69. package/packages/core/src/store/memory.ts +68 -1
  70. package/packages/core/src/store/phase-session.ts +23 -0
  71. package/packages/core/src/store/remote/client.ts +50 -1
  72. package/packages/core/src/store/remote/index.ts +2 -0
  73. package/packages/core/src/store/remote/protocol.ts +15 -0
  74. package/packages/core/src/store/repo-state-events.ts +72 -7
  75. package/packages/core/src/store/session-handle.ts +30 -0
  76. package/packages/core/src/store/session-reducer.ts +52 -11
  77. package/packages/core/src/store/snapshots.ts +88 -0
  78. package/packages/core/src/store/ticket-assets.ts +41 -8
  79. package/packages/core/src/store/types.ts +38 -0
  80. package/skills/guide/SKILL.md +26 -7
  81. package/skills/guide/references/remote-store-protocol.md +35 -0
  82. package/skills/tickets/SKILL.md +57 -5
@@ -1,42 +1,146 @@
1
1
  import type { AbEvent } from '../events/catalog'
2
2
  import type { PrImageHostTarget } from '../ontology'
3
+ import { defineReducer, type IncrementalReducer } from './incremental'
3
4
 
4
5
  export type PrAttachmentDesignationEvent = Extract<AbEvent, { type: 'pr-attachment.designated' }>
5
6
  export type PrAttachmentHostedEvent = Extract<AbEvent, { type: 'pr-attachment.hosted' }>
6
7
 
8
+ /** The host consent frozen into the build at claim time: the lowest-seq
9
+ * `build.created` seen so far (first in array order on a tie). */
10
+ export interface FrozenPrImageHostAcc {
11
+ created?: { seq: number; imageHost?: PrImageHostTarget }
12
+ }
13
+
14
+ /** Bump when `FrozenPrImageHostAcc` or its fold changes. */
15
+ export const FROZEN_PR_IMAGE_HOST_REDUCER_VERSION = 1
16
+
17
+ export const frozenPrImageHostReducer: IncrementalReducer<
18
+ FrozenPrImageHostAcc,
19
+ AbEvent,
20
+ PrImageHostTarget | undefined
21
+ > = defineReducer<FrozenPrImageHostAcc, AbEvent, PrImageHostTarget | undefined>({
22
+ version: FROZEN_PR_IMAGE_HOST_REDUCER_VERSION,
23
+ initial: () => ({}),
24
+ fold(acc, events) {
25
+ for (const event of events) {
26
+ if (
27
+ event.type === 'build.created' &&
28
+ (acc.created === undefined || event.seq < acc.created.seq)
29
+ ) {
30
+ const imageHost = event.payload.pr?.imageHost
31
+ acc.created = imageHost === undefined ? { seq: event.seq } : { seq: event.seq, imageHost }
32
+ }
33
+ }
34
+ },
35
+ finish: (acc) => acc.created?.imageHost,
36
+ })
37
+
7
38
  /** The host consent frozen into the build at claim time. */
8
39
  export function frozenPrImageHost(events: readonly AbEvent[]): PrImageHostTarget | undefined {
9
- let created: Extract<AbEvent, { type: 'build.created' }> | undefined
10
- for (const event of events) {
11
- if (event.type === 'build.created' && (created === undefined || event.seq < created.seq)) {
12
- created = event
13
- }
40
+ return frozenPrImageHostReducer.reduce(events)
41
+ }
42
+
43
+ /** Designation state shared by the current and hosted projections: the
44
+ * restart fence plus the highest-seq designation per artifact kind (first in
45
+ * array order on a tie). Keeping only the latest per kind is exact: if it is
46
+ * at or below the restart, every earlier one is too. */
47
+ interface DesignationAcc {
48
+ restartSeq: number
49
+ byKind: Record<string, PrAttachmentDesignationEvent>
50
+ }
51
+
52
+ function initialDesignations(): DesignationAcc {
53
+ return { restartSeq: 0, byKind: {} }
54
+ }
55
+
56
+ function foldDesignations(acc: DesignationAcc, event: AbEvent): void {
57
+ if (event.type === 'spec.revised') {
58
+ acc.restartSeq = Math.max(acc.restartSeq, event.seq)
59
+ } else if (event.type === 'pr-attachment.designated') {
60
+ const kind = event.payload.artifact.kind
61
+ const previous = Object.hasOwn(acc.byKind, kind) ? acc.byKind[kind] : undefined
62
+ if (previous === undefined || event.seq > previous.seq) acc.byKind[kind] = event
14
63
  }
15
- return created?.payload.pr?.imageHost
16
64
  }
17
65
 
66
+ function finishDesignations(acc: DesignationAcc): PrAttachmentDesignationEvent[] {
67
+ return Object.values(acc.byKind)
68
+ .filter((event) => event.seq > acc.restartSeq)
69
+ .sort((left, right) => left.seq - right.seq)
70
+ }
71
+
72
+ export type CurrentPrAttachmentsAcc = DesignationAcc
73
+
74
+ /** Bump when `CurrentPrAttachmentsAcc` or its fold changes. */
75
+ export const CURRENT_PR_ATTACHMENTS_REDUCER_VERSION = 1
76
+
77
+ export const currentPrAttachmentsReducer: IncrementalReducer<
78
+ CurrentPrAttachmentsAcc,
79
+ AbEvent,
80
+ PrAttachmentDesignationEvent[]
81
+ > = defineReducer<CurrentPrAttachmentsAcc, AbEvent, PrAttachmentDesignationEvent[]>({
82
+ version: CURRENT_PR_ATTACHMENTS_REDUCER_VERSION,
83
+ initial: initialDesignations,
84
+ fold(acc, events) {
85
+ for (const event of events) foldDesignations(acc, event)
86
+ },
87
+ finish: finishDesignations,
88
+ })
89
+
18
90
  /**
19
91
  * Current PR attachments are explicit designations after the latest spec
20
92
  * restart, with the newest exact revision replacing earlier designations of
21
93
  * the same artifact kind. Distinct kinds remain distinct attachments.
22
94
  */
23
95
  export function currentPrAttachments(events: readonly AbEvent[]): PrAttachmentDesignationEvent[] {
24
- let restartSeq = 0
25
- for (const event of events) {
26
- if (event.type === 'spec.revised') restartSeq = Math.max(restartSeq, event.seq)
96
+ return currentPrAttachmentsReducer.reduce(events)
97
+ }
98
+
99
+ /** The designation state plus the highest-seq hosted copy per designation seq
100
+ * (first in array order on a tie). If the highest fails the `seq >
101
+ * designation.seq` check, every lower one does too. */
102
+ export interface HostedPrAttachmentsAcc extends DesignationAcc {
103
+ hostedByDesignation: Record<string, PrAttachmentHostedEvent>
104
+ }
105
+
106
+ /** Bump when `HostedPrAttachmentsAcc` or its fold changes. */
107
+ export const HOSTED_PR_ATTACHMENTS_REDUCER_VERSION = 1
108
+
109
+ function finishHosted(
110
+ acc: HostedPrAttachmentsAcc,
111
+ designations: readonly PrAttachmentDesignationEvent[],
112
+ ): Map<number, PrAttachmentHostedEvent> {
113
+ const hosted = new Map<number, PrAttachmentHostedEvent>()
114
+ for (const designation of designations) {
115
+ const event = Object.hasOwn(acc.hostedByDesignation, designation.seq)
116
+ ? acc.hostedByDesignation[designation.seq]
117
+ : undefined
118
+ if (event !== undefined && event.seq > designation.seq) hosted.set(designation.seq, event)
27
119
  }
120
+ return hosted
121
+ }
122
+
123
+ export const hostedPrAttachmentsReducer: IncrementalReducer<
124
+ HostedPrAttachmentsAcc,
125
+ AbEvent,
126
+ Map<number, PrAttachmentHostedEvent>
127
+ > = defineReducer<HostedPrAttachmentsAcc, AbEvent, Map<number, PrAttachmentHostedEvent>>({
128
+ version: HOSTED_PR_ATTACHMENTS_REDUCER_VERSION,
129
+ initial: () => ({ ...initialDesignations(), hostedByDesignation: {} }),
130
+ fold: foldHosted,
131
+ finish: (acc) => finishHosted(acc, finishDesignations(acc)),
132
+ })
28
133
 
29
- const byKind = new Map<string, PrAttachmentDesignationEvent>()
134
+ function foldHosted(acc: HostedPrAttachmentsAcc, events: readonly AbEvent[]): void {
30
135
  for (const event of events) {
31
- if (event.type !== 'pr-attachment.designated' || event.seq <= restartSeq) {
32
- continue
33
- }
34
- const previous = byKind.get(event.payload.artifact.kind)
35
- if (previous === undefined || event.seq > previous.seq) {
36
- byKind.set(event.payload.artifact.kind, event)
37
- }
136
+ foldDesignations(acc, event)
137
+ if (event.type !== 'pr-attachment.hosted') continue
138
+ const key = String(event.payload.designationSeq)
139
+ const previous = Object.hasOwn(acc.hostedByDesignation, key)
140
+ ? acc.hostedByDesignation[key]
141
+ : undefined
142
+ if (previous === undefined || event.seq > previous.seq) acc.hostedByDesignation[key] = event
38
143
  }
39
- return [...byKind.values()].sort((left, right) => left.seq - right.seq)
40
144
  }
41
145
 
42
146
  /**
@@ -46,37 +150,51 @@ export function currentPrAttachments(events: readonly AbEvent[]): PrAttachmentDe
46
150
  */
47
151
  export function hostedPrAttachments(
48
152
  events: readonly AbEvent[],
49
- designations = currentPrAttachments(events),
153
+ designations?: readonly PrAttachmentDesignationEvent[],
50
154
  ): Map<number, PrAttachmentHostedEvent> {
51
- const designationBySeq = new Map(designations.map((event) => [event.seq, event]))
52
- const hosted = new Map<number, PrAttachmentHostedEvent>()
53
- for (const event of events) {
54
- if (event.type !== 'pr-attachment.hosted') continue
55
- const designation = designationBySeq.get(event.payload.designationSeq)
56
- if (designation === undefined || event.seq <= designation.seq) continue
57
- const previous = hosted.get(designation.seq)
58
- if (previous === undefined || event.seq > previous.seq) {
59
- hosted.set(designation.seq, event)
60
- }
61
- }
62
- return hosted
155
+ const acc = hostedPrAttachmentsReducer.initial()
156
+ foldHosted(acc, events)
157
+ return finishHosted(acc, designations ?? finishDesignations(acc))
63
158
  }
64
159
 
160
+ /** Every hosted copy plus the highest-seq reclaim fact per hosted seq. A
161
+ * reclaim may precede its hosted event in array order, so both are kept and
162
+ * correlated in `finish`. */
163
+ export interface PendingPrAttachmentReclaimsAcc {
164
+ hosted: PrAttachmentHostedEvent[]
165
+ reclaimedAt: Record<string, number>
166
+ }
167
+
168
+ /** Bump when `PendingPrAttachmentReclaimsAcc` or its fold changes. */
169
+ export const PENDING_PR_ATTACHMENT_RECLAIMS_REDUCER_VERSION = 1
170
+
171
+ export const pendingPrAttachmentReclaimsReducer: IncrementalReducer<
172
+ PendingPrAttachmentReclaimsAcc,
173
+ AbEvent,
174
+ PrAttachmentHostedEvent[]
175
+ > = defineReducer<PendingPrAttachmentReclaimsAcc, AbEvent, PrAttachmentHostedEvent[]>({
176
+ version: PENDING_PR_ATTACHMENT_RECLAIMS_REDUCER_VERSION,
177
+ initial: () => ({ hosted: [], reclaimedAt: {} }),
178
+ fold(acc, events) {
179
+ for (const event of events) {
180
+ if (event.type === 'pr-attachment.hosted') acc.hosted.push(event)
181
+ else if (event.type === 'pr-attachment.reclaimed') {
182
+ const key = String(event.payload.hostedSeq)
183
+ const previous = Object.hasOwn(acc.reclaimedAt, key) ? acc.reclaimedAt[key] : undefined
184
+ if (previous === undefined || event.seq > previous) acc.reclaimedAt[key] = event.seq
185
+ }
186
+ }
187
+ },
188
+ finish(acc) {
189
+ const hosted = [...acc.hosted].sort((left, right) => left.seq - right.seq)
190
+ return hosted.filter((event) => {
191
+ const at = Object.hasOwn(acc.reclaimedAt, event.seq) ? acc.reclaimedAt[event.seq] : undefined
192
+ return !(at !== undefined && at > event.seq)
193
+ })
194
+ },
195
+ })
196
+
65
197
  /** Every durable hosted copy that has not yet received a later reclaim fact. */
66
198
  export function pendingPrAttachmentReclaims(events: readonly AbEvent[]): PrAttachmentHostedEvent[] {
67
- const hosted = events
68
- .filter((event): event is PrAttachmentHostedEvent => event.type === 'pr-attachment.hosted')
69
- .sort((left, right) => left.seq - right.seq)
70
- const hostedSeqs = new Set(hosted.map((event) => event.seq))
71
- const reclaimed = new Set<number>()
72
- for (const event of events) {
73
- if (
74
- event.type === 'pr-attachment.reclaimed' &&
75
- hostedSeqs.has(event.payload.hostedSeq) &&
76
- event.seq > event.payload.hostedSeq
77
- ) {
78
- reclaimed.add(event.payload.hostedSeq)
79
- }
80
- }
81
- return hosted.filter((event) => !reclaimed.has(event.seq))
199
+ return pendingPrAttachmentReclaimsReducer.reduce(events)
82
200
  }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Every incremental reducer, with its published version and the whole-array
3
+ * functions it backs. `reducer-registry.test.ts` snapshots the versions and
4
+ * the accumulator shapes, and scans `kernel/`, `store/` and `processes/` so a
5
+ * new event-array projection must be registered here or excluded with a reason.
6
+ */
7
+ import { recordedBaseShaReducer } from '../config/pipeline-source'
8
+ import { openWorkspaceReducer } from '../processes/build-execution-state'
9
+ import {
10
+ phaseFailuresReducer,
11
+ publishedBranchHeadReducer,
12
+ setupStreakReducer,
13
+ verifyDiffBaseReducer,
14
+ } from '../processes/build-runner'
15
+ import {
16
+ baseBranchReducer,
17
+ openBuildWorkspaceReducer,
18
+ provisionMarkerReducer,
19
+ recoveryCheckpointReducer,
20
+ } from '../processes/dispatcher-selectors'
21
+ import {
22
+ lastExecutionOutcomeReducer,
23
+ openExecutionReducer,
24
+ } from '../processes/execution-settlement'
25
+ import { openHarvestExecutionsReducer } from '../processes/harvest-execution-state'
26
+ import { infrastructureFailureResetReducer } from '../processes/infrastructure-failure-budget'
27
+ import { publicationStateReducer } from '../processes/publication-state'
28
+ import { sandboxStatesReducer } from '../processes/sandbox-state'
29
+ import { openTickReducer } from '../processes/tick-state'
30
+ import { dashboardFactsReducer } from '../cli/dashboard/facts'
31
+ import { buildDigestReducer } from '../store/digest'
32
+ import { journalViewReducer, repositoryStateEventsReducer } from '../store/repo-state-events'
33
+ import { sessionReducer } from '../store/session-reducer'
34
+ import { pinnedAssetsReducer } from '../store/ticket-assets'
35
+ import { autoMergeDefaultReducer } from './auto-merge-default'
36
+ import { currentDeferralObservationReducer } from './auto-merge'
37
+ import { dispatchSettingsReducer } from './dispatch-settings'
38
+ import { dispatchStatusReducer } from './dispatch-status'
39
+ import { harvestReducer } from './harvest'
40
+ import type { IncrementalReducer } from './incremental'
41
+ import { logIndexReducer } from './log-index'
42
+ import {
43
+ currentPrAttachmentsReducer,
44
+ frozenPrImageHostReducer,
45
+ hostedPrAttachmentsReducer,
46
+ pendingPrAttachmentReclaimsReducer,
47
+ } from './pr-attachments'
48
+ import { buildReducer } from './reducer'
49
+
50
+ export interface RegisteredReducer {
51
+ // biome-ignore lint/suspicious/noExplicitAny: the registry is heterogeneous
52
+ reducer: IncrementalReducer<any, any, any>
53
+ /** The whole-array functions (or private methods) this reducer backs. */
54
+ covers: readonly string[]
55
+ }
56
+
57
+ /** Parameterized reducers are registered with representative arguments; the
58
+ * accumulator shape does not depend on them. */
59
+ export const REDUCERS: Record<string, RegisteredReducer> = {
60
+ build: { reducer: buildReducer, covers: ['reduceBuild'] },
61
+ harvest: { reducer: harvestReducer, covers: ['reduceHarvest'] },
62
+ dispatchSettings: { reducer: dispatchSettingsReducer, covers: ['reduceDispatchSettings'] },
63
+ dispatchStatus: { reducer: dispatchStatusReducer('run-a'), covers: ['reduceDispatchStatus'] },
64
+ session: { reducer: sessionReducer, covers: ['reduceSession'] },
65
+ buildDigest: { reducer: buildDigestReducer, covers: ['reduceBuildDigest'] },
66
+ sandboxStates: { reducer: sandboxStatesReducer, covers: ['sandboxStates'] },
67
+ logIndex: { reducer: logIndexReducer, covers: ['indexLog'] },
68
+ frozenPrImageHost: { reducer: frozenPrImageHostReducer, covers: ['frozenPrImageHost'] },
69
+ currentPrAttachments: { reducer: currentPrAttachmentsReducer, covers: ['currentPrAttachments'] },
70
+ hostedPrAttachments: { reducer: hostedPrAttachmentsReducer, covers: ['hostedPrAttachments'] },
71
+ pendingPrAttachmentReclaims: {
72
+ reducer: pendingPrAttachmentReclaimsReducer,
73
+ covers: ['pendingPrAttachmentReclaims'],
74
+ },
75
+ publicationState: {
76
+ reducer: publicationStateReducer,
77
+ covers: [
78
+ 'publicationRequestCompleted',
79
+ 'publicationRequestSettled',
80
+ 'latestUncompletedPublicationRequest',
81
+ 'publicationLostRecorded',
82
+ 'abandonedPublicationPending',
83
+ 'publicationPending',
84
+ ],
85
+ },
86
+ repositoryStateEvents: {
87
+ reducer: repositoryStateEventsReducer,
88
+ covers: ['projectRepositoryStateEvents'],
89
+ },
90
+ journalView: { reducer: journalViewReducer, covers: [] },
91
+ openExecution: { reducer: openExecutionReducer, covers: ['openExecution'] },
92
+ lastExecutionOutcome: { reducer: lastExecutionOutcomeReducer, covers: ['lastExecutionOutcome'] },
93
+ openHarvestExecutions: {
94
+ reducer: openHarvestExecutionsReducer,
95
+ covers: ['openHarvestExecutions'],
96
+ },
97
+ openTick: { reducer: openTickReducer('run-a'), covers: ['hasOpenTick'] },
98
+ infrastructureFailureReset: {
99
+ reducer: infrastructureFailureResetReducer,
100
+ covers: ['infrastructureFailureResetSeq'],
101
+ },
102
+ openWorkspace: { reducer: openWorkspaceReducer, covers: ['selectOpenWorkspace'] },
103
+ publishedBranchHead: {
104
+ reducer: publishedBranchHeadReducer,
105
+ covers: ['selectPublishedBranchHead'],
106
+ },
107
+ verifyDiffBase: { reducer: verifyDiffBaseReducer, covers: ['selectVerifyDiffBase'] },
108
+ recordedBaseSha: { reducer: recordedBaseShaReducer, covers: [] },
109
+ setupStreak: { reducer: setupStreakReducer, covers: ['setupStreak'] },
110
+ phaseFailures: { reducer: phaseFailuresReducer('plan', 1), covers: ['phaseFailures'] },
111
+ openBuildWorkspace: {
112
+ reducer: openBuildWorkspaceReducer,
113
+ covers: ['openBuildWorkspace', 'openWorkspace', 'forgeWorkspacePath'],
114
+ },
115
+ baseBranch: { reducer: baseBranchReducer, covers: ['baseBranchOf'] },
116
+ provisionMarker: { reducer: provisionMarkerReducer, covers: ['openProvisionMarker'] },
117
+ recoveryCheckpoint: { reducer: recoveryCheckpointReducer, covers: ['recoveryCheckpoint'] },
118
+ currentDeferralObservation: {
119
+ reducer: currentDeferralObservationReducer,
120
+ covers: [
121
+ 'currentDeferralObservation',
122
+ 'hasAutoMergeDeferralObservation',
123
+ 'currentAutoMergeDeferral',
124
+ ],
125
+ },
126
+ dashboardFacts: {
127
+ reducer: dashboardFactsReducer,
128
+ covers: ['phaseIntervals', 'projectSessions'],
129
+ },
130
+ autoMergeDefault: { reducer: autoMergeDefaultReducer, covers: ['latestAutoMergeDefault'] },
131
+ pinnedAssets: { reducer: pinnedAssetsReducer, covers: ['findPinnedRevision'] },
132
+ }
133
+
134
+ /** Event-array functions that are deliberately not reducers, each with the
135
+ * concrete reason. */
136
+ export const EXCLUDED: Record<string, string> = {
137
+ decideNext: 'a decision function over config; returns a Decision, not a state',
138
+ harvestCreationsDuringReadyScan:
139
+ 'a derived query over two reduceHarvest states (before/after), which is already incremental',
140
+ classifyHarvestOutcome: 'a decision over reduceHarvest state plus an input; returns a result',
141
+ collectUnclaimedObservations:
142
+ 'composes reduceBuild/reduceHarvest over a store-shaped per-build map',
143
+ unclaimedObservationCount: 'composes digests and reduceHarvest over store-shaped input',
144
+ evaluateHarvestPressureFromDigests: 'a decision over digests and reduceHarvest state',
145
+ evaluateHarvestPressureFromStore: 'async; reads the store',
146
+ settleExecution: 'async; writes the store',
147
+ settlePublicationBeforeRelease: 'async; writes the store',
148
+ recordInfrastructureFailure: 'async; writes the store',
149
+ ensureProvisionContinuation: 'async; reads and writes the store and provider',
150
+ cleanupAborted: 'async; reads and writes the store and provider',
151
+ cleanupDiscarded: 'async; reads and writes the store and provider',
152
+ checkPr: 'async; forge and store',
153
+ reapStaleWorkspace: 'async; provider and store',
154
+ reclaimPrAttachments: 'async; forge and store',
155
+ releaseWorkspace: 'async; provider and store',
156
+ checkReconcileProgress: 'async; git and store',
157
+ evaluateVerify: 'async; runs verify steps',
158
+ reclaimSessionsAndAttach: 'async; store',
159
+ refreshReconcileBase: 'async; git and store',
160
+ repairEscalateGap: 'async; store',
161
+ runAgentVerify: 'async; runs an agent',
162
+ runFinalizeStep: 'async; runs a step',
163
+ runPhase: 'async; runs an agent phase',
164
+ }