@defrex/autobuild 0.8.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 (182) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +323 -26
  3. package/bin/ab-build-runner.ts +8 -2
  4. package/docs/architecture.md +166 -14
  5. package/docs/assets/headline-wide.png +0 -0
  6. package/docs/configuration.md +175 -28
  7. package/docs/hosted-dispatcher.md +69 -13
  8. package/docs/operator-api.md +2 -2
  9. package/docs/remote-store-protocol.md +204 -16
  10. package/docs/remote-ticket-protocol.md +1 -1
  11. package/docs/setup.md +12 -9
  12. package/docs/website.md +14 -0
  13. package/package.json +45 -23
  14. package/packages/core/src/cli/artifact.ts +130 -6
  15. package/packages/core/src/cli/binary.ts +9 -1
  16. package/packages/core/src/cli/build-control.ts +110 -13
  17. package/packages/core/src/cli/context.ts +46 -0
  18. package/packages/core/src/cli/dashboard/facts.ts +215 -0
  19. package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
  20. package/packages/core/src/cli/dashboard/model.ts +141 -153
  21. package/packages/core/src/cli/dashboard/poll.ts +76 -12
  22. package/packages/core/src/cli/dashboard/render.ts +34 -5
  23. package/packages/core/src/cli/dispatch-frontend.ts +54 -5
  24. package/packages/core/src/cli/dispatch-process.ts +1 -13
  25. package/packages/core/src/cli/dispatch.ts +392 -75
  26. package/packages/core/src/cli/harvest.ts +122 -8
  27. package/packages/core/src/cli/help.ts +62 -9
  28. package/packages/core/src/cli/init-readiness-remote.ts +110 -0
  29. package/packages/core/src/cli/init-readiness-shared.ts +72 -0
  30. package/packages/core/src/cli/init-validation.ts +266 -222
  31. package/packages/core/src/cli/installation.ts +50 -8
  32. package/packages/core/src/cli/main.ts +70 -11
  33. package/packages/core/src/cli/mcp.ts +23 -1
  34. package/packages/core/src/cli/plugin.ts +7 -4
  35. package/packages/core/src/cli/remote-poll.ts +277 -0
  36. package/packages/core/src/cli/repo-state.ts +22 -0
  37. package/packages/core/src/cli/repository-status.ts +39 -1
  38. package/packages/core/src/cli/self-update.ts +77 -2
  39. package/packages/core/src/cli/status.ts +150 -28
  40. package/packages/core/src/cli/terminals.ts +2 -1
  41. package/packages/core/src/cli/ticket-assets.ts +247 -0
  42. package/packages/core/src/cli/ticket.ts +453 -8
  43. package/packages/core/src/cli/wait.ts +157 -41
  44. package/packages/core/src/cli/watch.ts +138 -168
  45. package/packages/core/src/config/live.ts +23 -0
  46. package/packages/core/src/config/pipeline-source.ts +388 -0
  47. package/packages/core/src/config/schema.ts +251 -19
  48. package/packages/core/src/events/catalog.ts +1 -0
  49. package/packages/core/src/events/globs.ts +47 -0
  50. package/packages/core/src/events/payloads.ts +52 -2
  51. package/packages/core/src/events/repository.ts +52 -1
  52. package/packages/core/src/events/sessions.ts +69 -10
  53. package/packages/core/src/events/workspace-remote.ts +12 -0
  54. package/packages/core/src/integration/harness.ts +115 -2
  55. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
  56. package/packages/core/src/kernel/auto-merge-default.ts +157 -0
  57. package/packages/core/src/kernel/auto-merge.ts +99 -22
  58. package/packages/core/src/kernel/dispatch-settings.ts +42 -17
  59. package/packages/core/src/kernel/dispatch-status.ts +23 -0
  60. package/packages/core/src/kernel/engine.ts +9 -438
  61. package/packages/core/src/kernel/generators/build-log.ts +259 -0
  62. package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
  63. package/packages/core/src/kernel/generators/session-log.ts +72 -0
  64. package/packages/core/src/kernel/harvest.ts +62 -18
  65. package/packages/core/src/kernel/incremental-contract.ts +75 -0
  66. package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
  67. package/packages/core/src/kernel/incremental.ts +53 -0
  68. package/packages/core/src/kernel/log-index.ts +620 -0
  69. package/packages/core/src/kernel/phases.ts +16 -2
  70. package/packages/core/src/kernel/pr-attachments.ts +164 -46
  71. package/packages/core/src/kernel/reducer-registry.ts +164 -0
  72. package/packages/core/src/kernel/reducer.ts +254 -73
  73. package/packages/core/src/operator/annotations.ts +6 -0
  74. package/packages/core/src/operator/control.ts +19 -5
  75. package/packages/core/src/operator/index.ts +71 -6
  76. package/packages/core/src/operator/query.ts +253 -24
  77. package/packages/core/src/operator/read-view.ts +98 -0
  78. package/packages/core/src/operator/registry.ts +55 -10
  79. package/packages/core/src/operator/requests.ts +9 -6
  80. package/packages/core/src/operator/sandbox-host.ts +140 -0
  81. package/packages/core/src/operator/sandbox.ts +247 -14
  82. package/packages/core/src/operator/ticket-source-backend.ts +47 -0
  83. package/packages/core/src/orchestrator/approvals.ts +115 -0
  84. package/packages/core/src/orchestrator/conversation.ts +167 -0
  85. package/packages/core/src/orchestrator/model.ts +86 -0
  86. package/packages/core/src/orchestrator/prompt.ts +61 -0
  87. package/packages/core/src/orchestrator/tools.ts +117 -0
  88. package/packages/core/src/orchestrator/turn-runner.ts +457 -0
  89. package/packages/core/src/plugin-sdk/index.ts +51 -1
  90. package/packages/core/src/plugins/load.ts +135 -7
  91. package/packages/core/src/plugins/manifest.ts +183 -10
  92. package/packages/core/src/plugins/registry.ts +73 -4
  93. package/packages/core/src/ports/forge/create.ts +1 -1
  94. package/packages/core/src/ports/forge/fake.ts +31 -0
  95. package/packages/core/src/ports/forge/github-transport.ts +13 -2
  96. package/packages/core/src/ports/forge/github.ts +13 -2
  97. package/packages/core/src/ports/forge/local-git.ts +31 -1
  98. package/packages/core/src/ports/runner/claude.ts +2 -2
  99. package/packages/core/src/ports/runner/codex.ts +6 -2
  100. package/packages/core/src/ports/runner/fake.ts +7 -1
  101. package/packages/core/src/ports/runner/pi.ts +8 -6
  102. package/packages/core/src/ports/runner/provider-error.ts +13 -3
  103. package/packages/core/src/ports/tickets/fake.ts +5 -1
  104. package/packages/core/src/ports/tickets/file.ts +169 -48
  105. package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
  106. package/packages/core/src/ports/tickets/linear.ts +33 -1
  107. package/packages/core/src/ports/tickets/operations.ts +1 -1
  108. package/packages/core/src/ports/tickets/remote.ts +8 -1
  109. package/packages/core/src/ports/types.ts +49 -3
  110. package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
  111. package/packages/core/src/ports/workspace/create.ts +74 -39
  112. package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
  113. package/packages/core/src/ports/workspace/fake.ts +169 -4
  114. package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
  115. package/packages/core/src/ports/workspace/github-origin.ts +36 -0
  116. package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
  117. package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
  118. package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
  119. package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
  120. package/packages/core/src/processes/build-child.ts +17 -2
  121. package/packages/core/src/processes/build-execution-state.ts +68 -15
  122. package/packages/core/src/processes/build-runner.ts +238 -93
  123. package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
  124. package/packages/core/src/processes/dispatcher.ts +501 -201
  125. package/packages/core/src/processes/execution-settlement.ts +72 -39
  126. package/packages/core/src/processes/harvest-child.ts +5 -0
  127. package/packages/core/src/processes/harvest-execution-state.ts +41 -29
  128. package/packages/core/src/processes/harvest-runner.ts +66 -19
  129. package/packages/core/src/processes/harvest.ts +196 -24
  130. package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
  131. package/packages/core/src/processes/orchestrator-tick.ts +360 -0
  132. package/packages/core/src/processes/publication-state.ts +164 -52
  133. package/packages/core/src/processes/repo-view.ts +1436 -0
  134. package/packages/core/src/processes/sandbox-state.ts +36 -2
  135. package/packages/core/src/processes/tick-state.ts +33 -0
  136. package/packages/core/src/store/adapter.ts +41 -0
  137. package/packages/core/src/store/build-scope.ts +90 -0
  138. package/packages/core/src/store/contract.ts +1205 -0
  139. package/packages/core/src/store/digest.ts +102 -0
  140. package/packages/core/src/store/local/schema.ts +46 -2
  141. package/packages/core/src/store/local/store.ts +445 -4
  142. package/packages/core/src/store/memory.ts +246 -0
  143. package/packages/core/src/store/new-build.ts +17 -0
  144. package/packages/core/src/store/phase-session.ts +94 -0
  145. package/packages/core/src/store/remote/client.ts +310 -2
  146. package/packages/core/src/store/remote/index.ts +14 -2
  147. package/packages/core/src/store/remote/protocol.ts +115 -12
  148. package/packages/core/src/store/remote/version.ts +6 -2
  149. package/packages/core/src/store/repo-state-events.ts +205 -0
  150. package/packages/core/src/store/retention.ts +7 -0
  151. package/packages/core/src/store/session-handle.ts +94 -0
  152. package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +79 -19
  153. package/packages/core/src/store/snapshots.ts +88 -0
  154. package/packages/core/src/store/streams/session-writer.ts +42 -2
  155. package/packages/core/src/store/streams/wait.ts +4 -5
  156. package/packages/core/src/store/ticket-assets.ts +458 -0
  157. package/packages/core/src/store/types.ts +160 -1
  158. package/packages/core/src/testing/index.ts +59 -0
  159. package/skills/code-review/SKILL.md +5 -1
  160. package/skills/guide/SKILL.md +152 -40
  161. package/skills/guide/references/operator-api.md +2 -2
  162. package/skills/guide/references/plugin-authoring.md +17 -0
  163. package/skills/guide/references/remote-store-protocol.md +204 -16
  164. package/skills/guide/references/remote-ticket-protocol.md +1 -1
  165. package/skills/guide/references/setup.md +12 -9
  166. package/skills/implement/SKILL.md +3 -0
  167. package/skills/operate/SKILL.md +126 -0
  168. package/skills/plan/SKILL.md +4 -0
  169. package/skills/plan-review/SKILL.md +5 -1
  170. package/skills/spec/SKILL.md +46 -3
  171. package/skills/tickets/SKILL.md +57 -5
  172. package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
  173. package/packages/core/src/cli/store-opening.contract.ts +0 -289
  174. package/packages/core/src/cli/testkit.ts +0 -194
  175. package/packages/core/src/markdown.ts +0 -67
  176. package/packages/core/src/operator/client.ts +0 -311
  177. package/packages/core/src/operator/server.ts +0 -616
  178. package/packages/core/src/operator/sessions.ts +0 -223
  179. package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
  180. package/packages/core/src/ports/tickets/remote-server.ts +0 -185
  181. package/packages/core/src/store/remote/server.ts +0 -923
  182. package/packages/core/src/testing/store-failures.ts +0 -32
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import type { Actor } from '../events/envelope'
11
11
  import type { AbEvent } from '../events/catalog'
12
- import { normalizeVerifyCompletion, type EventPayload } from '../events/payloads'
12
+ import { normalizeVerifyCompletion, type EventPayload, type PinnedAsset } from '../events/payloads'
13
13
  import type {
14
14
  ArtifactRef,
15
15
  BuildOutcome,
@@ -24,6 +24,7 @@ import type {
24
24
  VerifyOutcome,
25
25
  } from '../ontology'
26
26
  import { verifyPhase } from '../ontology'
27
+ import { defineReducer, type IncrementalReducer } from './incremental'
27
28
 
28
29
  /** An `escalation.raised` without a matching `escalation.answered` — the
29
30
  * definition of `blocked` (§15.5). Matched by id, so answers may arrive in
@@ -52,6 +53,8 @@ export interface AnsweredEscalation extends OpenEscalation {
52
53
  resolution: EscalationResolution
53
54
  /** Exact replacement spec revision authorized by a `revise-spec` answer. */
54
55
  artifact?: ArtifactRef
56
+ /** Ticket assets sampled with a revise-from-ticket authorization. */
57
+ assets?: PinnedAsset[]
55
58
  /** Absolute review budget accepted with a review-round-limit answer. */
56
59
  reviewRoundCeiling?: number
57
60
  /** seq of the `escalation.answered` event. */
@@ -130,6 +133,11 @@ export interface AutoMergeProjection {
130
133
  /** Latest human command. False with no commandSeq means the default: off. */
131
134
  requested: boolean
132
135
  commandSeq?: number
136
+ /** The repository seq of the `dispatcher.auto-merge-default-set` fact the
137
+ * latest answering command (claim-time seed or fan-out) sampled, when one
138
+ * did. A per-build command with no `defaultSeq` preserves the previous
139
+ * value, so the default fan-out can tell an override from staleness. */
140
+ defaultSeq?: number
133
141
  /** Latest recorded external application. It may acknowledge an older
134
142
  * command; such a stale fact never changes `requested`/`commandSeq`. */
135
143
  applied?: { enabled: boolean; commandSeq: number }
@@ -194,6 +202,9 @@ export interface BuildState {
194
202
  lastEvent?: AbEvent
195
203
  /** 0 for an empty log; `runner.attached {resumedFromSeq}` cites this. */
196
204
  lastSeq: number
205
+ /** Ticket assets the build froze: set by `build.created`, replaced by a
206
+ * `spec.revised` that carries `assets` (§6.3). Empty for historical logs. */
207
+ pinnedAssets: PinnedAsset[]
197
208
  /** Latest spec artifact rev — `spec.imported`/`spec.authored` set it,
198
209
  * `spec.revised` bumps it (§6.3: rev N+1 restarts the build from plan). */
199
210
  specRev?: number
@@ -279,48 +290,143 @@ export interface BuildState {
279
290
  failures: Record<string, number>
280
291
  }
281
292
 
282
- export function reduceBuild(events: AbEvent[]): BuildState {
283
- let attached = false
284
- let pausedFlag = false
285
- let terminal: 'done' | 'aborted' | undefined
286
- let outcome: BuildOutcome | undefined
287
- let round = 0
288
- let currentPhase: PhaseContext | undefined
289
- let lastCompletedPhase: PhaseContext | undefined
290
- const openEscalations = new Map<string, OpenEscalation>()
291
- const answeredEscalations: AnsweredEscalation[] = []
292
- const reviewRoundCeilings: BuildState['reviewRoundCeilings'] = {}
293
- let pr: BuildState['pr']
294
- let prState: PrLifecycle | undefined
295
- const autoMerge: AutoMergeProjection = { requested: false }
296
- let specRev: number | undefined
297
- let restartSince = 0
298
- let finalizeCompletedSeq = 0
299
- const finalizeSteps: BuildState['finalizeSteps'] = []
300
- const plan: BuildState['plan'] = { round: 0, approved: false }
301
- const implement: BuildState['implement'] = { round: 0 }
302
- let codeReviewApproved = false
303
- let codeReviewApproval: BuildState['codeReviewApproval']
304
- const planReviewFindings: Finding[][] = []
305
- const codeReviewFindings: Finding[][] = []
306
- const verify: BuildState['verify'] = { maxAttemptSeen: 0, results: [], cycleSince: 0 }
307
- let reconcileAttempts = 0
308
- const observations: ObservationRecord[] = []
309
- const setupFailures: BuildState['setupFailures'] = []
310
- let setupFailure: BuildState['setupFailure']
311
- const dispatchFailures: BuildState['dispatchFailures'] = []
312
- const executions: BuildState['executions'] = []
313
- const infrastructureFailures: BuildState['infrastructureFailures'] = []
314
- let infrastructureFailure: BuildState['infrastructureFailure']
315
- const cleanupAttempts: BuildState['cleanupAttempts'] = []
316
- let discardRequest: BuildState['discardRequest']
317
- const pending: Record<PendingCommand['command'], PendingCommand[]> = {
318
- pause: [],
319
- resume: [],
320
- abort: [],
293
+ /** Whether the build carries an outstanding `discardRequest`. Present exactly
294
+ * while the build is non-terminal and its discard is unsettled; the reducer
295
+ * settles it only by terminal completion. This is the single predicate shared
296
+ * by the auto-merge default fan-out (`autoMergeDefaultEligible`, AUT-418) and
297
+ * the per-build consent guard (`controlBuild`'s `discard-pending` refusal), so
298
+ * the two controls' discard exclusions cannot diverge silently. */
299
+ export function discardInFlight(state: Pick<BuildState, 'discardRequest'>): boolean {
300
+ return state.discardRequest !== undefined
301
+ }
302
+
303
+ /** The reducer's carried accumulator: everything `reduceBuild` folds, before
304
+ * the final status derivation. Plain JSON-shaped data (the escalation and
305
+ * session maps are insertion-ordered entry arrays) so it survives a round
306
+ * trip through `JSON.stringify` — the foundation for incremental advancement
307
+ * and cached state. */
308
+ export interface BuildAcc {
309
+ attached: boolean
310
+ pausedFlag: boolean
311
+ terminal?: 'done' | 'aborted'
312
+ outcome?: BuildOutcome
313
+ round: number
314
+ currentPhase?: PhaseContext
315
+ lastCompletedPhase?: PhaseContext
316
+ openEscalations: OpenEscalation[]
317
+ answeredEscalations: AnsweredEscalation[]
318
+ reviewRoundCeilings: BuildState['reviewRoundCeilings']
319
+ pr: BuildState['pr']
320
+ prState?: PrLifecycle
321
+ autoMerge: AutoMergeProjection
322
+ specRev?: number
323
+ restartSince: number
324
+ pinnedAssets: PinnedAsset[]
325
+ finalizeCompletedSeq: number
326
+ finalizeSteps: BuildState['finalizeSteps']
327
+ plan: BuildState['plan']
328
+ implement: BuildState['implement']
329
+ codeReviewApproved: boolean
330
+ codeReviewApproval: BuildState['codeReviewApproval']
331
+ planReviewFindings: Finding[][]
332
+ codeReviewFindings: Finding[][]
333
+ verify: BuildState['verify']
334
+ reconcileAttempts: number
335
+ observations: ObservationRecord[]
336
+ setupFailures: BuildState['setupFailures']
337
+ setupFailure: BuildState['setupFailure']
338
+ dispatchFailures: BuildState['dispatchFailures']
339
+ executions: BuildState['executions']
340
+ infrastructureFailures: BuildState['infrastructureFailures']
341
+ infrastructureFailure: BuildState['infrastructureFailure']
342
+ cleanupAttempts: BuildState['cleanupAttempts']
343
+ discardRequest: BuildState['discardRequest']
344
+ pending: Record<PendingCommand['command'], PendingCommand[]>
345
+ openSessions: OpenSession[]
346
+ failures: Record<string, number>
347
+ lastEvent?: AbEvent
348
+ }
349
+
350
+ /** Bump when `BuildAcc`'s shape or the fold's semantics change. */
351
+ export const BUILD_REDUCER_VERSION = 1
352
+
353
+ function initialBuildAcc(): BuildAcc {
354
+ return {
355
+ attached: false,
356
+ pausedFlag: false,
357
+ round: 0,
358
+ openEscalations: [],
359
+ answeredEscalations: [],
360
+ reviewRoundCeilings: {},
361
+ pr: undefined,
362
+ autoMerge: { requested: false },
363
+ restartSince: 0,
364
+ pinnedAssets: [],
365
+ finalizeCompletedSeq: 0,
366
+ finalizeSteps: [],
367
+ plan: { round: 0, approved: false },
368
+ implement: { round: 0 },
369
+ codeReviewApproved: false,
370
+ codeReviewApproval: undefined,
371
+ planReviewFindings: [],
372
+ codeReviewFindings: [],
373
+ verify: { maxAttemptSeen: 0, results: [], cycleSince: 0 },
374
+ reconcileAttempts: 0,
375
+ observations: [],
376
+ setupFailures: [],
377
+ setupFailure: undefined,
378
+ dispatchFailures: [],
379
+ executions: [],
380
+ infrastructureFailures: [],
381
+ infrastructureFailure: undefined,
382
+ cleanupAttempts: [],
383
+ discardRequest: undefined,
384
+ pending: { pause: [], resume: [], abort: [] },
385
+ openSessions: [],
386
+ failures: {},
321
387
  }
322
- const openSessions = new Map<string, OpenSession>()
323
- const failures: Record<string, number> = {}
388
+ }
389
+
390
+ /** Fold `events` into `acc` in place. */
391
+ function foldBuild(acc: BuildAcc, events: readonly AbEvent[]): void {
392
+ let attached = acc.attached
393
+ let pausedFlag = acc.pausedFlag
394
+ let terminal = acc.terminal
395
+ let outcome = acc.outcome
396
+ let round = acc.round
397
+ let currentPhase = acc.currentPhase
398
+ let lastCompletedPhase = acc.lastCompletedPhase
399
+ const openEscalations = new Map(acc.openEscalations.map((e) => [e.id, e]))
400
+ const answeredEscalations = acc.answeredEscalations
401
+ const reviewRoundCeilings = acc.reviewRoundCeilings
402
+ let pr = acc.pr
403
+ let prState = acc.prState
404
+ const autoMerge = acc.autoMerge
405
+ let specRev = acc.specRev
406
+ let restartSince = acc.restartSince
407
+ let pinnedAssets = acc.pinnedAssets
408
+ let finalizeCompletedSeq = acc.finalizeCompletedSeq
409
+ const finalizeSteps = acc.finalizeSteps
410
+ const plan = acc.plan
411
+ const implement = acc.implement
412
+ let codeReviewApproved = acc.codeReviewApproved
413
+ let codeReviewApproval = acc.codeReviewApproval
414
+ const planReviewFindings = acc.planReviewFindings
415
+ const codeReviewFindings = acc.codeReviewFindings
416
+ const verify = acc.verify
417
+ let reconcileAttempts = acc.reconcileAttempts
418
+ const observations = acc.observations
419
+ const setupFailures = acc.setupFailures
420
+ let setupFailure = acc.setupFailure
421
+ const dispatchFailures = acc.dispatchFailures
422
+ const executions = acc.executions
423
+ const infrastructureFailures = acc.infrastructureFailures
424
+ let infrastructureFailure = acc.infrastructureFailure
425
+ const cleanupAttempts = acc.cleanupAttempts
426
+ let discardRequest = acc.discardRequest
427
+ const pending = acc.pending
428
+ const openSessions = new Map(acc.openSessions.map((s) => [s.session, s]))
429
+ const failures = acc.failures
324
430
 
325
431
  const start = (ctx: PhaseContext): void => {
326
432
  currentPhase = ctx
@@ -341,12 +447,14 @@ export function reduceBuild(events: AbEvent[]): BuildState {
341
447
  per[r - 1] = findings
342
448
  }
343
449
 
344
- let lastEvent: AbEvent | undefined
450
+ let lastEvent = acc.lastEvent
345
451
  for (const event of events) {
346
452
  switch (event.type) {
347
453
  // Facts the projection does not need (workspace liveness is the
348
454
  // dispatcher's concern; finalize post-steps are failure-tolerant §5).
349
455
  case 'build.created':
456
+ pinnedAssets = event.payload.assets ?? []
457
+ break
350
458
  case 'workspace.provisioned':
351
459
  case 'workspace.released':
352
460
  case 'publication.requested':
@@ -443,10 +551,23 @@ export function reduceBuild(events: AbEvent[]): BuildState {
443
551
  case 'build.auto-merge-requested':
444
552
  autoMerge.requested = true
445
553
  autoMerge.commandSeq = event.seq
554
+ // A fan-out (or seeded) command carries the default fact it answers; a
555
+ // bare per-build command must preserve the previous provenance.
556
+ if (event.payload.defaultSeq !== undefined) autoMerge.defaultSeq = event.payload.defaultSeq
446
557
  break
447
558
  case 'build.auto-merge-cancelled':
448
559
  autoMerge.requested = false
449
560
  autoMerge.commandSeq = event.seq
561
+ if (event.payload.defaultSeq !== undefined) autoMerge.defaultSeq = event.payload.defaultSeq
562
+ break
563
+ case 'build.auto-merge-default-observed':
564
+ // A no-op fan-out still advances the build's provenance. Without it, a
565
+ // matching default fact would stay strictly ahead of a bare per-build
566
+ // command and be re-applied on the next tick, reverting the operator's
567
+ // explicit choice (f_28b3fba1).
568
+ if (event.payload.defaultSeq > (autoMerge.defaultSeq ?? 0)) {
569
+ autoMerge.defaultSeq = event.payload.defaultSeq
570
+ }
450
571
  break
451
572
  case 'build.paused':
452
573
  pausedFlag = true
@@ -467,6 +588,7 @@ export function reduceBuild(events: AbEvent[]): BuildState {
467
588
  break
468
589
  case 'spec.revised':
469
590
  specRev = event.payload.artifact.rev
591
+ if (event.payload.assets !== undefined) pinnedAssets = event.payload.assets
470
592
  // §6.3: rev N+1 restarts the build from plan. The restart boundary
471
593
  // invalidates every approval and result at or before it, so it is also
472
594
  // a verify cycle boundary. Finalize post-steps remain in full-log
@@ -661,6 +783,7 @@ export function reduceBuild(events: AbEvent[]): BuildState {
661
783
  answer: event.payload.answer,
662
784
  resolution: event.payload.resolution,
663
785
  ...(event.payload.artifact !== undefined ? { artifact: event.payload.artifact } : {}),
786
+ ...(event.payload.assets !== undefined ? { assets: event.payload.assets } : {}),
664
787
  ...(event.payload.reviewRoundCeiling !== undefined
665
788
  ? { reviewRoundCeiling: event.payload.reviewRoundCeiling }
666
789
  : {}),
@@ -699,54 +822,112 @@ export function reduceBuild(events: AbEvent[]): BuildState {
699
822
  lastEvent = event
700
823
  }
701
824
 
825
+ acc.attached = attached
826
+ acc.pausedFlag = pausedFlag
827
+ acc.terminal = terminal
828
+ acc.outcome = outcome
829
+ acc.round = round
830
+ acc.currentPhase = currentPhase
831
+ acc.lastCompletedPhase = lastCompletedPhase
832
+ acc.openEscalations = [...openEscalations.values()]
833
+ acc.pr = pr
834
+ acc.prState = prState
835
+ acc.specRev = specRev
836
+ acc.restartSince = restartSince
837
+ acc.pinnedAssets = pinnedAssets
838
+ acc.finalizeCompletedSeq = finalizeCompletedSeq
839
+ acc.codeReviewApproved = codeReviewApproved
840
+ acc.codeReviewApproval = codeReviewApproval
841
+ acc.reconcileAttempts = reconcileAttempts
842
+ acc.setupFailure = setupFailure
843
+ acc.infrastructureFailure = infrastructureFailure
844
+ acc.discardRequest = discardRequest
845
+ acc.openSessions = [...openSessions.values()]
846
+ acc.lastEvent = lastEvent
847
+ }
848
+
849
+ function finishBuild(acc: BuildAcc): BuildState {
850
+ const {
851
+ terminal,
852
+ pausedFlag,
853
+ attached,
854
+ currentPhase,
855
+ lastCompletedPhase,
856
+ lastEvent,
857
+ discardRequest,
858
+ pending,
859
+ } = acc
860
+
702
861
  // §15.5 precedence: aborted/done (terminal, latest wins) > paused >
703
862
  // blocked > running > queued. See BuildState.status for the paused+blocked
704
863
  // overlap rule.
705
864
  const status: BuildStatus =
706
865
  terminal ??
707
- (pausedFlag ? 'paused' : openEscalations.size > 0 ? 'blocked' : attached ? 'running' : 'queued')
866
+ (pausedFlag
867
+ ? 'paused'
868
+ : acc.openEscalations.length > 0
869
+ ? 'blocked'
870
+ : attached
871
+ ? 'running'
872
+ : 'queued')
708
873
 
709
874
  const active = currentPhase ?? lastCompletedPhase
710
875
 
711
876
  return {
712
877
  status,
713
- outcome,
878
+ outcome: acc.outcome,
714
879
  phase: active?.phase,
715
- round,
880
+ round: acc.round,
716
881
  currentPhase,
717
882
  lastCompletedPhase,
718
- openEscalations: [...openEscalations.values()],
719
- answeredEscalations,
720
- reviewRoundCeilings,
721
- pr,
722
- prState,
723
- autoMerge,
724
- finalizeCompletedSeq,
725
- finalizeSteps,
883
+ openEscalations: [...acc.openEscalations],
884
+ answeredEscalations: acc.answeredEscalations,
885
+ reviewRoundCeilings: acc.reviewRoundCeilings,
886
+ pr: acc.pr,
887
+ prState: acc.prState,
888
+ autoMerge: acc.autoMerge,
889
+ finalizeCompletedSeq: acc.finalizeCompletedSeq,
890
+ finalizeSteps: acc.finalizeSteps,
726
891
  lastEvent,
727
892
  lastSeq: lastEvent?.seq ?? 0,
728
- specRev,
729
- restartSince,
730
- plan,
731
- implement,
732
- codeReviewApproved,
733
- codeReviewApproval,
734
- reviewFindings: { planReview: planReviewFindings, codeReview: codeReviewFindings },
735
- verify,
736
- reconcileAttempts,
737
- observations,
738
- setupFailures,
739
- setupFailure,
740
- dispatchFailures,
741
- executions,
742
- infrastructureFailures,
743
- infrastructureFailure,
744
- cleanupAttempts,
893
+ pinnedAssets: acc.pinnedAssets,
894
+ specRev: acc.specRev,
895
+ restartSince: acc.restartSince,
896
+ plan: acc.plan,
897
+ implement: acc.implement,
898
+ codeReviewApproved: acc.codeReviewApproved,
899
+ codeReviewApproval: acc.codeReviewApproval,
900
+ reviewFindings: { planReview: acc.planReviewFindings, codeReview: acc.codeReviewFindings },
901
+ verify: acc.verify,
902
+ reconcileAttempts: acc.reconcileAttempts,
903
+ observations: acc.observations,
904
+ setupFailures: acc.setupFailures,
905
+ setupFailure: acc.setupFailure,
906
+ dispatchFailures: acc.dispatchFailures,
907
+ executions: acc.executions,
908
+ infrastructureFailures: acc.infrastructureFailures,
909
+ infrastructureFailure: acc.infrastructureFailure,
910
+ cleanupAttempts: acc.cleanupAttempts,
745
911
  ...(terminal === undefined && discardRequest !== undefined ? { discardRequest } : {}),
746
912
  pendingCommands: [...pending.pause, ...pending.resume, ...pending.abort].sort(
747
913
  (a, b) => a.seq - b.seq,
748
914
  ),
749
- sessions: { open: [...openSessions.values()] },
750
- failures,
915
+ sessions: { open: [...acc.openSessions] },
916
+ failures: acc.failures,
751
917
  }
752
918
  }
919
+
920
+ /** The incremental form of `reduceBuild`: advance a carried accumulator with
921
+ * only the events newer than it, then derive the state. */
922
+ export const buildReducer: IncrementalReducer<BuildAcc, AbEvent, BuildState> = defineReducer({
923
+ version: BUILD_REDUCER_VERSION,
924
+ initial: initialBuildAcc,
925
+ fold: foldBuild,
926
+ finish: finishBuild,
927
+ })
928
+
929
+ export function reduceBuild(events: AbEvent[]): BuildState {
930
+ const acc = initialBuildAcc()
931
+ foldBuild(acc, events)
932
+ return finishBuild(acc)
933
+ }
@@ -182,6 +182,12 @@ export const OPERATOR_TOOL_ANNOTATIONS = {
182
182
  idempotentHint: false,
183
183
  approval: 'default',
184
184
  },
185
+ 'sandbox.publish': {
186
+ readOnlyHint: false,
187
+ destructiveHint: true,
188
+ idempotentHint: false,
189
+ approval: 'default',
190
+ },
185
191
  } as const satisfies Record<string, ToolAnnotationsTableEntry>
186
192
 
187
193
  export type OperatorToolName = keyof typeof OPERATOR_TOOL_ANNOTATIONS
@@ -29,19 +29,27 @@ export async function setRepositorySetting(opts: {
29
29
  enabled: boolean
30
30
  /** Delegated-write attribution marker threaded onto the event's actor. */
31
31
  via?: Via
32
+ /** Dispatcher invocation that performed the write. Present on the
33
+ * dashboard/launch-flag write paths so the two are distinguishable in the
34
+ * journal; optional so the operator API (which has no run) is unchanged. */
35
+ run?: string
32
36
  }): Promise<{ enabled: boolean; event: RepositoryEventEnvelope }> {
33
37
  await opts.store.ensureRepo(opts.repo)
38
+ const payload = {
39
+ enabled: opts.enabled,
40
+ ...(opts.run !== undefined ? { run: opts.run } : {}),
41
+ }
34
42
  const event =
35
43
  opts.setting === 'intake'
36
44
  ? await opts.store.appendRepo(opts.repo, {
37
45
  actor: humanActor(opts.user, opts.via),
38
46
  type: 'dispatcher.intake-set',
39
- payload: { enabled: opts.enabled },
47
+ payload,
40
48
  })
41
49
  : await opts.store.appendRepo(opts.repo, {
42
50
  actor: humanActor(opts.user, opts.via),
43
51
  type: 'dispatcher.auto-merge-default-set',
44
- payload: { enabled: opts.enabled },
52
+ payload,
45
53
  })
46
54
  return { enabled: opts.enabled, event }
47
55
  }
@@ -52,9 +60,12 @@ export async function toggleRepositorySetting(opts: {
52
60
  user: string
53
61
  setting: RepositorySetting
54
62
  via?: Via
63
+ /** Dispatcher invocation that performed the write (see setRepositorySetting). */
64
+ run?: string
55
65
  }): Promise<{ enabled: boolean; event: RepositoryEventEnvelope }> {
56
66
  await opts.store.ensureRepo(opts.repo)
57
- const settings = reduceDispatchSettings(await opts.store.getRepoEvents(opts.repo))
67
+ // Bounded read (AUT-489): the settings reducer consumes durable types only.
68
+ const settings = reduceDispatchSettings(await opts.store.getRepoStateEvents(opts.repo))
58
69
  const enabled = opts.setting === 'intake' ? !settings.intake : !settings.defaultAutoMerge
59
70
  return setRepositorySetting({ ...opts, enabled })
60
71
  }
@@ -66,7 +77,8 @@ export async function toggleHarvestGate(opts: {
66
77
  via?: Via
67
78
  }): Promise<{ command: 'pause' | 'resume'; event: RepositoryEventEnvelope }> {
68
79
  await opts.store.ensureRepo(opts.repo)
69
- const state = reduceHarvest(await opts.store.getRepoEvents(opts.repo))
80
+ // Bounded read (AUT-489): the harvest reducer consumes durable types only.
81
+ const state = reduceHarvest(await opts.store.getRepoStateEvents(opts.repo))
70
82
  const pending = state.pendingCommands.at(-1)
71
83
  const requestedPaused = pending === undefined ? state.paused : pending.command === 'pause'
72
84
  const command = requestedPaused ? 'resume' : 'pause'
@@ -87,7 +99,9 @@ export async function controlHarvestRun(opts: {
87
99
  via?: Via
88
100
  }): Promise<{ action: HarvestRunAction; event: RepositoryEventEnvelope }> {
89
101
  await opts.store.ensureRepo(opts.repo)
90
- const events = await opts.store.getRepoEvents(opts.repo)
102
+ // Bounded read (AUT-489): the harvest reducer and the dashboard harvest
103
+ // projection consume durable types only.
104
+ const events = await opts.store.getRepoStateEvents(opts.repo)
91
105
  const state = reduceHarvest(events)
92
106
  const projected = projectHarvest(events)
93
107
  if (projected === undefined || projected.run !== opts.run) {
@@ -1,7 +1,7 @@
1
- export { OperatorApiClient, OperatorApiError } from './client'
2
- export type { DownloadedArtifact, OperatorApiClientOptions } from './client'
3
- export { createOperatorServer } from './server'
4
- export type { OperatorServerOptions } from './server'
1
+ // Core's operator surface: the tool registry behind `ab mcp`, the CLI-facing
2
+ // control/query/tickets/requests primitives, and the operator wire protocol.
3
+ // The operator HTTP server and client ship in
4
+ // `@defrex/autobuild-hosted-store-service` (see its `operator-api` subpath).
5
5
  export {
6
6
  answerRequestSchema,
7
7
  buildControlRequestSchema,
@@ -9,6 +9,10 @@ export {
9
9
  bulkControlRequestSchema,
10
10
  harvestControlRequestSchema,
11
11
  operatorErrorSchema,
12
+ sessionApprovalRequestSchema,
13
+ sessionCreateRequestSchema,
14
+ sessionMessageRequestSchema,
15
+ sessionWakeRequestSchema,
12
16
  settingRequestSchema,
13
17
  ticketBlockerRequestSchema,
14
18
  ticketCreateRequestSchema,
@@ -19,6 +23,10 @@ export type {
19
23
  OperatorAnswerRequest,
20
24
  OperatorBuildControlRequest,
21
25
  OperatorErrorBody,
26
+ OperatorSessionApprovalRequest,
27
+ OperatorSessionCreateRequest,
28
+ OperatorSessionMessageRequest,
29
+ OperatorSessionWakeRequest,
22
30
  OperatorTicketBlockerRequest,
23
31
  OperatorTicketCreateRequest,
24
32
  OperatorTicketMoveRequest,
@@ -29,8 +37,32 @@ export type {
29
37
  OperatorBuildView,
30
38
  OperatorDashboardSnapshot,
31
39
  } from './query'
32
- export { OperatorControlError } from './control'
33
- export { OperatorQueryError } from './query'
40
+ export {
41
+ controlHarvestRun,
42
+ OperatorControlError,
43
+ setRepositorySetting,
44
+ toggleHarvestGate,
45
+ toggleRepositorySetting,
46
+ } from './control'
47
+ export {
48
+ getHarvestStatus,
49
+ getOperatorBuild,
50
+ getOperatorDashboard,
51
+ getRepositoryStatus,
52
+ listOperatorBuilds,
53
+ OperatorQueryError,
54
+ OrchestratorConfigError,
55
+ orchestratorConfig,
56
+ } from './query'
57
+ export {
58
+ orchestratorWakeGlobs,
59
+ ORCHESTRATOR_ROUTE_LIMIT_SECONDS,
60
+ type OrchestratorConfig,
61
+ } from '../config/schema'
62
+ export type { OrchestratorTurnRunner } from '../orchestrator/turn-runner'
63
+ export { createOrchestratorTurnRunner } from '../orchestrator/turn-runner'
64
+ export { randomIds } from '../ids'
65
+ export type { Config } from '../config/schema'
34
66
  export {
35
67
  OPERATOR_NOTES_ARTIFACT,
36
68
  TOOLS,
@@ -59,4 +91,37 @@ export type {
59
91
  OperatorTicketDetail,
60
92
  OperatorTicketQueue,
61
93
  } from './tickets'
94
+ export {
95
+ getOperatorTicket,
96
+ listOperatorTickets,
97
+ mutateOperatorTicket,
98
+ } from './tickets'
99
+ export type { OperatorSandboxService } from './sandbox'
62
100
  export * from './presentation'
101
+
102
+ /**
103
+ * Hosted operator-server composition. The moved server (in
104
+ * `@defrex/autobuild-hosted-store-service`) builds its routes from these core
105
+ * primitives; they are re-exported here so the hosted package needs no core
106
+ * internals. Deliberately not a general public API — do not grow it for
107
+ * core-internal convenience.
108
+ */
109
+ export {
110
+ BuildControlError,
111
+ controlBuild,
112
+ type BuildControlAction,
113
+ type BuildControlResult,
114
+ } from '../cli/build-control'
115
+ export {
116
+ BulkWalkError,
117
+ bulkControlRepository,
118
+ type BulkControlSummary,
119
+ } from '../cli/bulk-control'
120
+ export { effectiveStatus } from '../cli/dashboard/model'
121
+ export { reduceBuild } from '../kernel/reducer'
122
+ export { TicketOperationError } from '../ports/tickets/operations'
123
+ export { humanActor, type Via } from '../events/envelope'
124
+ export type { HarvestStatusView } from '../cli/harvest'
125
+ export type { RepositoryStatus } from '../cli/repository-status'
126
+ export type { BuildSummary } from '../cli/status'
127
+ export type { SessionRecord } from '../store/types'