@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
@@ -54,7 +54,15 @@ Git installation never requires a separately published core package.
54
54
  `packages/core` owns the CLI, kernel, adapters, shared types, dashboard
55
55
  projection, plugin SDK, and reusable contract suites. The optional
56
56
  `packages/hosted-store-service` workspace composes the public remote-server
57
- surface with `packages/postgres-store`; neither package enters an ordinary CLI
57
+ surface with `packages/postgres-store`; the separate optional
58
+ `packages/hosted-dispatcher` workspace owns the cron-driven hosted dispatcher
59
+ (the `/api/dispatch` route driver, the deploy-time `pack-distribution` command,
60
+ and the packed-distribution trace helper) and peers on both the core and the
61
+ store service. The optional `packages/vercel-sandbox` workspace publishes the
62
+ `@defrex/autobuild-vercel-sandbox` provider plugin (AUT-517): while the
63
+ builtin hosts the implementation, its manifest re-registers the provider name
64
+ over the shared capability object and the loader skips it with a notice.
65
+ Neither optional package enters an ordinary CLI
58
66
  installation's runtime dependency closure. `tools/` is repository-only
59
67
  maintainer tooling. All workspace manifests share one version because the
60
68
  remote-store protocol requires matching client and server versions.
@@ -68,16 +76,19 @@ remote-store protocol requires matching client and server versions.
68
76
  | `packages/core/src/ontology.ts` | The shared nouns — findings, verdicts, phases, refs, the canonical verify outcome | §4 |
69
77
  | `packages/core/src/events/` | Separate build, repository, and operator-session envelopes/catalogs, frozen payload schemas, actor validation, the `via` attribution marker | §15 |
70
78
  | `packages/core/src/harvest/` | Structured occurrence, scan packet, proposal, and ledger schemas | §12 |
71
- | `packages/core/src/sessions/` | The operator-session reducer: status, open turn, pending approval, wake settings and cursors, and the turn list, reduced purely from the session event log | §7.1.1 |
79
+ | `packages/core/src/store/session-reducer.ts` | The operator-session reducer: status, open turn, pending approval, wake settings and cursors, and the turn list, reduced purely from the session event log | §7.1.1 |
80
+ | `packages/core/src/orchestrator/` | The embedded orchestrator's turn runner: the canonical-skill system prompt, durable conversation reconstruction, registry entries as AI SDK tools with the session's repository injected, gateway model resolution with the typed failure vocabulary, the approval-list configuration, and the CAS-guarded start/resume loop with budget suspension and step-boundary checkpoints | §7.1.2 |
72
81
  | `packages/core/src/store/` | BuildStore contract spanning builds, the repository journal, and operator sessions; interface-enforced build, operator-session, and local ambient-session scope wrappers; memory, SQLite/blob, and remote HTTP adapters | §7 |
73
82
  | `packages/core/src/store/streams/` | The stream primitive's shared core (§7.6): record/chunk types and the `ai-ui-message-stream/v1` constants, SDK-backed close-time `UIMessage[]` assembly, and the uniform bounded-wait read loop | §7.6 |
74
- | `packages/hosted-store-service/` | Environment-only hosted Fetch handler, lazy PostgreSQL/blob composition, offline token binary, tests, and deployment guide | §7.2, §18 |
75
- | `server.ts`, `vercel.json` | One host-neutral Bun listener used locally and by Vercel's Bun preset | §7.2 |
83
+ | `packages/hosted-store-service/` | Environment-only hosted Fetch handler, lazy PostgreSQL/blob composition, offline token binary, the Next.js operator web app (`app/`, `server.ts`, `next.config.ts`, `vercel.json`), tests, and deployment guide | §7.2, §18 |
84
+ | `packages/hosted-dispatcher/` | The optional cron-driven hosted dispatcher: endpoint driver, deploy-time `pack-distribution` binary, packed-distribution trace helper, tests, and operator guide | §7.2, §12, §18 |
85
+ | `packages/vercel-sandbox/` | Published `@defrex/autobuild-vercel-sandbox` provider plugin: transitional manifest over the shared capability object with a guarded factory (implementation stays builtin-hosted until AUT-505) | §3.2.1 |
86
+ | `packages/hosted-store-service/server.ts`, `packages/hosted-store-service/vercel.json` | One host-neutral Bun listener used locally and by Vercel's Bun preset | §7.2 |
76
87
  | `packages/core/src/kernel/` | Phase table, build reducer, engine; pure harvest, dispatcher-settings, dispatcher-status, and PR-attachment selectors; converge, stall detection, verify gating | §5, §7.5, §10, §12, §14, §15.4–15.5 |
77
88
  | `packages/core/src/ports/` | TicketSource / Workspace / Forge / AgentRunner / Telemetry interfaces, adapters, and fakes; registry-aware builtin/plugin construction; eager primary/alternate runtime routing and provider-failure classification under `ports/runner/` | §3.2, §9, §13 |
78
89
  | `packages/core/src/plugins/` | Strict versioned plugin manifests, dual-root repository/package Bun loading, owner-aware adapter registration, contract/credential metadata, and runtime-factory materialization | §3.2.1, §9 |
79
90
  | `packages/core/src/plugin-sdk/` | The sole supported `@defrex/autobuild/plugin-sdk` barrel: port/manifest types, contract suites, and reference fakes | §3.2.1 |
80
- | `packages/core/src/processes/` | build-runner and standalone child composition, durable execution config/diagnostics, dispatcher (+ janitor duty and harvest trigger), harvest deterministic core + runner | §3.3, §12, §15.7 |
91
+ | `packages/core/src/processes/` | build-runner and standalone child composition, durable execution config/diagnostics, dispatcher (+ janitor duty, harvest trigger, and the last-step orchestrator resume/reap/wake pass, origin-mode-gated), orchestrator tick step, harvest deterministic core + runner | §3.3, §7.1.2, §12, §15.7 |
81
92
  | `packages/core/src/cli/` and `bin/` | The `ab` CLI — the only agent↔store channel — plus the shared presentation-only durable-progress projection, init/upgrade, the Store-only dispatch frontend, its private supervised kernel entry, and `bin/ab-build-runner.ts` (one child per build) | §8, §14, §16.3 |
82
93
  | `packages/core/src/cli/dashboard/` | `ab dispatch`'s fixed live frame: pure projection, renderer, poll cache, and deterministic image renderer | §14 |
83
94
  | `bin/agent/ab` | Private launcher placed first on agent-session `PATH`; delegates to the canonical `bin/ab.ts` | §8.1 |
@@ -92,10 +103,15 @@ remote-store protocol requires matching client and server versions.
92
103
  ## Key boundaries
93
104
 
94
105
  **Hosted store service.** `@defrex/autobuild/remote-store` is the supported protocol
95
- server/client/token export. The hosted workspace validates its environment,
106
+ export for the remote store's client half, wire schemas, and token minting; the
107
+ protocol servers (the remote BuildStore HTTP server, the operator API server,
108
+ and the hosted ticket-source server) ship in
109
+ `@defrex/autobuild-hosted-store-service`, which builds against core's
110
+ `remote-store`, `operator`, and `hosted-tickets` subpaths only. The hosted
111
+ workspace validates its environment,
96
112
  serves health without touching persistence, and lazily retains one
97
113
  `openPostgresBuildStoreFromEnv` promise per warm process. Its Fetch handler has
98
- no Vercel branch; root `server.ts` is the sole `Bun.serve()` composition point.
114
+ no Vercel branch; the package's `server.ts` is the sole `Bun.serve()` composition point.
99
115
  The fixed 1 MiB decoded artifact policy is enforced before backing mutation.
100
116
  The `ab-hosted-store` binary signs admin or build/session tokens offline from
101
117
  `AB_STORE_SECRET`; the running service exposes no mint endpoint. Machine routes
@@ -110,11 +126,120 @@ and `packages/core/src/events/sessions.ts`
110
126
  are the frozen catalogs; every write passes `validateEventWrite` /
111
127
  `validateRepositoryEventWrite` / `validateSessionEventWrite`. `packages/core/src/kernel/reducer.ts` derives all build
112
128
  status; `packages/core/src/kernel/harvest.ts` and `packages/core/src/kernel/dispatch-settings.ts` reduce
113
- the repository journal independently of each other; `packages/core/src/sessions/reducer.ts` reduces an operator
129
+ the repository journal independently of each other; `packages/core/src/store/session-reducer.ts` reduces an operator
114
130
  session's status, open turn, pending approval, and wake cursors from its own
115
131
  closed catalog. No decision anywhere
116
132
  consults a snapshot in place of the append-only log.
117
133
 
134
+ **Incremental reducers.** Every pure projection of an event array is a fold plus
135
+ a final derivation, defined once with `defineReducer` in
136
+ `packages/core/src/kernel/incremental.ts`: `initial()` builds an empty
137
+ accumulator, `advance(acc, events)` folds in only the events newer than the
138
+ accumulator, and `finish(acc)` derives the public state. The whole-array
139
+ functions (`reduceBuild`, `reduceHarvest`, `reduceSession`, and the rest) are
140
+ wrappers over the same fold, so results cannot diverge, and a property test
141
+ checks every split point of fixture and generated logs. The accumulator is plain
142
+ JSON, so it survives a serialize-and-parse round trip. `advance` never mutates
143
+ its input: it clones, then folds, so a state derived earlier is never disturbed
144
+ by a later advance. Each reducer publishes a `version`; bump it whenever the
145
+ accumulator shape or the fold semantics change, because a cached accumulator is
146
+ only valid for the version that wrote it. `kernel/reducer-registry.ts` lists
147
+ every reducer with its version, and its snapshot test fails when an accumulator
148
+ shape changes without a bump. Most callers still read whole logs (the terminal
149
+ dashboard poll advances a carried build accumulator by per-build deltas; the
150
+ dispatcher view below restores from stored accumulators).
151
+
152
+ **Reducer snapshots.** The `BuildStore` port can keep, per scope (one build log
153
+ or the repository journal) and registered reducer, that reducer's accumulator as
154
+ of a cursor: `getReducerSnapshot(scope, reducer, version)` and
155
+ `putReducerSnapshot(scope, reducer, {version, cursor, state})`
156
+ (`packages/core/src/store/snapshots.ts`). The log stays the only authority. The
157
+ store enforces the invariants: the stored cursor never decreases (a write whose
158
+ cursor does not advance is a no-op, so concurrent writers cannot undo each
159
+ other), a row of another reducer version reads as absent and is replaced once a
160
+ writer advances past it, and a cursor beyond the log's tail is refused and never
161
+ returned. Every consumer treats a missing, mismatched, ahead-of-log, malformed
162
+ or unreadable snapshot as a miss and replays the log, with identical results;
163
+ deleting all snapshots changes no observable output (the integration harness
164
+ audits this for every scenario at cleanup). Memory, SQLite, Postgres and the
165
+ remote client implement it, and the hosted store service serves it on
166
+ `GET|PUT /builds|repos/{id}/snapshots/{reducer}`
167
+ (`docs/remote-store-protocol.md`). SQLite creates the `reducer_snapshots` table
168
+ at open. Postgres keeps it outside the versioned schema: `reducer_snapshots` is
169
+ created by the idempotent `SNAPSHOT_SCHEMA_DDL` in `migratePostgres`, with no
170
+ marker, version or checksum change, so a deployment at the current schema keeps
171
+ serving across the upgrade, a new client against a database that lacks the table
172
+ reads every snapshot as missing and replays (SQLSTATE `42P01` is never an
173
+ error), and an older client never asks. Run `bun run postgres:migrate` once to
174
+ gain the table; until then the dispatcher and every other reader replay in full.
175
+
176
+ **Dispatcher repository view.** A dispatcher process builds its view of the
177
+ repository once and refreshes it by deltas
178
+ (`packages/core/src/processes/repo-view.ts`). `RepoViewStore` decorates the
179
+ `BuildStore` port: the first `refresh()` reads the journal's high-water mark
180
+ (`getRepoHighWater`), the bounded journal subset, the build listing and digests,
181
+ and the full log of every build that still has work (the same predicate the tick
182
+ loops use); every later `refresh()` reads only events newer than its cursors —
183
+ one journal delta, one delta per held build, plus the listing and digest reads
184
+ that discover new builds. Every tick stage reads and writes through the view, so
185
+ an event a stage appends is folded in at once and later stages in the same tick
186
+ see it with no re-read; a foreign append interleaved with ours leaves a seq gap
187
+ that marks the log dirty, a lost compare-and-set does the same, and any delta
188
+ that is not contiguous from its cursor discards the log and re-reads it in full,
189
+ so no event is skipped. Every state transition of one source (a build, the
190
+ journal) runs on that source's single promise chain — snapshot and delta reads,
191
+ window opening and widening, the fold after an own append, a lost
192
+ compare-and-set's invalidation — and readers queue on it too, so overlapping
193
+ readers join the repair in flight instead of observing half-applied state.
194
+ Decisions that deliberately re-read at the last moment
195
+ (`refreshBuild`) take an explicit delta. Cursor-bearing readers such as the
196
+ orchestrator wake pass get per-source windows that hold only events above the
197
+ lowest session cursor. A `--once` invocation builds the view cold and reads each
198
+ log once; a long-running dispatcher pays that replay at its first tick and not
199
+ again.
200
+
201
+ The view restores from reducer snapshots on cold start. The journal comes from
202
+ the `journalView` reducer (the journal pruned on arrival: durable types plus the
203
+ tail from the latest `dispatcher.run-started`); each work build comes from one
204
+ `work` snapshot row — the `build`, `logIndex`, `openExecution`,
205
+ `openBuildWorkspace`, `publicationState`, `lastExecutionOutcome`, `buildDigest`,
206
+ `dashboardFacts` and `currentDeferralObservation` accumulators at a single
207
+ cursor. One row is one atomic write on every adapter, so a reader in another
208
+ process sees the old bundle or the new, never a mix; the bundle version is
209
+ derived from the component reducer versions, so a stale bundle misses and the
210
+ build is replayed once. The view
211
+ reads only the events newer than that cursor and holds, for a snapshot-backed
212
+ build, just that delta plus the reduced state of everything before it, so a tick
213
+ with no new events reads zero event rows (apart from the discovery listing and
214
+ digests) and one with M new events reads exactly M. Tick stages that run for every
215
+ build each tick take `buildFacts(slug)` (state, log index, open execution, open
216
+ workspace, publication view) instead of the event array, and read raw history
217
+ only on the branches that act on it. `persistSnapshots()` writes the advanced
218
+ state back at the end of each tick and on shutdown — best effort, throttled in a
219
+ resident dispatcher, always run by a cold single-tick process — and never writes
220
+ a cursor that was not read from the log.
221
+
222
+ **Operator reads.** The web dashboard, the repository and Harvest status
223
+ routes, the operator build listings, and the `ab watch`/`ab wait` discovery
224
+ passes derive from that same view instead of replaying the log
225
+ (`packages/core/src/operator/read-view.ts`). A dashboard row renders from the
226
+ `BuildState` plus the `dashboardFacts` reducer
227
+ (`packages/core/src/cli/dashboard/facts.ts`: queued-row dispatch text, session
228
+ brackets, phase intervals, pause time, and the per-round loop-output seqs) and
229
+ the auto-merge deferral ledger; `projectBuild(…, events)` is the same projection
230
+ over the whole-array reduction, so the snapshot and replay paths cannot differ.
231
+ Within one serving process, passes are single-flight per repository: requests
232
+ arriving while a pass runs join the one trailing pass, which restores from the
233
+ snapshot the previous pass persisted. A poll at idle therefore reads no event
234
+ rows beyond snapshot lookups and build discovery, and a poll after M new events
235
+ reads those M rows once, however many tabs or watchers the process serves.
236
+ Serving processes do not coordinate: with P of them, each may read the same M
237
+ rows once, so duplicate reads are bounded by P and never by history, tabs, or
238
+ watchers. A settled build listed under `--all` is restored from the snapshot the
239
+ dispatcher left at settlement, or read once and then persisted. The first
240
+ operator poll after upgrading replays each work build once, because the stored
241
+ bundle layout changed.
242
+
118
243
  **Session Store authority.** `packages/core/src/cli/binary.ts` validates a complete build or
119
244
  Harvest ambient tuple before opening its phase Store through
120
245
  `packages/core/src/cli/store-opening.ts`. `packages/core/src/cli/env.ts` also classifies optional ambient
@@ -503,9 +628,16 @@ It follows one dispatch run's repository facts incrementally through
503
628
  `packages/core/src/kernel/dispatch-status.ts`, retaining only low-volume settings/Harvest
504
629
  facts for their replay reducers, validates/caches its effective-config artifact,
505
630
  and polls build streams independently while elapsed paints continue from cached
506
- intervals. The same polling path calls the canonical
507
- `scanUnclaimedObservations` BuildStore reduction for the header's current count
508
- and pairs it with the effective config's `policy.harvestThreshold`. That sample
631
+ intervals. The same polling path samples the header's current count from build
632
+ digests plus the repository journal — one journal read plus one repo-scoped
633
+ digest read, plus a journal-record probe — paired with the effective config's
634
+ `policy.harvestThreshold`. A journal record that does not yet exist reads as an
635
+ empty journal: the sample, the operator query, the dispatcher's
636
+ repository-wide journal reads (controls, settlement stages, and ready
637
+ scans), and the operator sandbox service's journal reads all go through
638
+ the same shared missing-record journal read (the AUT-524 helper in
639
+ `packages/core/src/processes/harvest.ts`), which writes nothing (no
640
+ `ensureRepo` from a display path). That sample
509
641
  is process-local presentation state: a failed refresh preserves the last
510
642
  successful value and adds a local diagnostic, while an initial failure delays
511
643
  the complete frame rather than inventing zero. No repository fact transports
@@ -579,8 +711,8 @@ bun typecheck # tsc --noEmit
579
711
  For dashboard presentation work, run the repository-only hot CLI:
580
712
 
581
713
  ```sh
582
- bun run dev -- dispatch
583
- # Generic form: bun run dev -- <ab arguments>
714
+ bun run dev:cli -- dispatch
715
+ # Generic form: bun run dev:cli -- <ab arguments>
584
716
  ```
585
717
 
586
718
  Bun keeps the original CLI promise and its `DispatchLoop` alive while hot
@@ -596,7 +728,7 @@ installed `ab` binary remains the non-watching production entry.
596
728
  The seams are the contract. Five reusable contract families run the same
597
729
  behavioral assertions against every implementation:
598
730
 
599
- - `packages/core/src/store/contract.ts` — `BuildStore` and `BlobStore`, including the streams block (§7.6) every adapter runs;
731
+ - `packages/core/src/store/contract.ts` — `BuildStore` and `BlobStore`, including the streams block (§7.6) and the ticket-asset block (§7.1.3) every adapter runs;
600
732
  - `packages/core/src/ports/tickets/contract.ts` — `TicketSource`;
601
733
  - `packages/core/src/ports/workspace/contract.ts` — `WorkspaceProvider`;
602
734
  - `packages/core/src/ports/forge/contract.ts` — `Forge`, including idempotent PR close and
@@ -618,6 +750,19 @@ A normal `bun test` runs the memory/fake/local registrations, including a fake
618
750
  selected through the plugin ticket-source registry, the real filesystem and
619
751
  local-git adapters, the injected Claude and Codex CLI subprocess contracts,
620
752
  and the injected Pi CLI/RPC contract.
753
+ Ticket assets (SPEC §7.1.3) are files attached to a ticket and kept in the
754
+ BuildStore, never in the ticket source. `packages/core/src/store/ticket-assets.ts`
755
+ holds the types, the limits, and the one validator every adapter calls;
756
+ `packages/core/src/cli/ticket-assets.ts` holds the filesystem walk, the download
757
+ writer, and the note builder; `ab ticket attach`, `asset get`, and `asset rm`
758
+ live in `packages/core/src/cli/ticket.ts`. The only thing a ticket source sees is
759
+ a note through `comment()`, so no source or plugin protocol changed.
760
+ A claim freezes the ticket's current assets into the build (SPEC §6.3):
761
+ `build.created.assets` (and, when a spec is revised from the ticket,
762
+ `spec.revised.assets`) record the pinned revisions, `state.pinnedAssets` in the
763
+ reducer projects them, `getPinnedTicketAsset` reads them back by pinned revision,
764
+ and `ab context` writes them under `.ab/assets/`.
765
+
621
766
  Both `ab dispatch` and sessionless `ab ticket` load the repository's plugins
622
767
  before selecting their TicketSource; dispatch passes that one adapter instance
623
768
  through readiness, dependency, harvest, and completion paths. The Linear,
@@ -626,6 +771,13 @@ but reported as skipped: live provider access requires credentials/resources
626
771
  and an explicit opt-in. When
627
772
  adding an adapter, start from its contract suite, not only the interface.
628
773
 
774
+ The two remote clients get client-only unit coverage in core itself:
775
+ `RemoteBuildStore` (`store/remote/client.test.ts`, which also pins scoped-token
776
+ minting/verification) and `HostedTicketSource` (`ports/tickets/remote.test.ts`)
777
+ run against an injected `fetchFn` with responses built from the protocol's wire
778
+ schemas. The full over-the-wire seam suites (client → HTTP → server) run in
779
+ `packages/hosted-store-service` in the same `bun test`.
780
+
629
781
  To run the Linear contract manually against a destructive scratch target:
630
782
 
631
783
  ```sh
Binary file
@@ -23,7 +23,11 @@ environment. They are published to npm as separate packages alongside the
23
23
  PostgreSQL, or blob-provider dependencies; a project that embeds the adapter
24
24
  or the service adds the package it needs (`bun add
25
25
  @defrex/autobuild-postgres-store` or `bun add
26
- @defrex/autobuild-hosted-store-service`). The deployable web application still
26
+ @defrex/autobuild-hosted-store-service`). The cron-driven hosted dispatcher is
27
+ separately published as `@defrex/autobuild-hosted-dispatcher`; a deployment
28
+ opts into building by installing that package, and its `AB_DISPATCHER_*`
29
+ variables are documented in the [hosted-dispatcher runbook](hosted-dispatcher.md),
30
+ not here. The deployable web application still
27
31
  runs from a release checkout: choose the compatible tag shown in
28
32
  [GitHub Releases](https://github.com/defrex/autobuild/releases), clone that exact
29
33
  revision into a dedicated checkout, and run the idempotent migration there:
@@ -37,7 +41,9 @@ DATABASE_URL=postgres://… bun run postgres:migrate
37
41
 
38
42
  Replace `v0.6.0` with the selected release tag. Schema diagnostics' root
39
43
  `postgres:migrate` script is relative to that pinned checkout. A hosted build
40
- can run the same step itself: `bun run deploy:build` migrates against the
44
+ can run the same step itself: `bun run deploy:build` — run from the hosted
45
+ store service package directory (`packages/hosted-store-service`), the Next.js
46
+ project directory — migrates against the
41
47
  deployment's database URL, logs the host it prepared, and then builds the web
42
48
  application, failing the build when no database URL is configured or the
43
49
  schema is incompatible. The database
@@ -146,6 +152,33 @@ snapshot. Raising capacity can fill the new slots immediately; lowering it
146
152
  prevents new claims without terminating builds already above the limit. `ab
147
153
  dispatch --once` performs one pass and does not watch or reload.
148
154
 
155
+ The sections `autobuild.toml` delivers to a build fall into three ownership
156
+ classes. **Build-owned** sections define the pipeline a build runs — `[verify]`
157
+ with `[verify.<step>]`, `[finalize]` with `[finalize.<step>]`, `[commands]`,
158
+ and `[workspace]` (provider and config, including `provisioning` and
159
+ `runtimeProvisioning`). They are pinned to the build's own branch: the
160
+ dispatcher reads them at the build's recorded base commit, or at its branch
161
+ head once the build has published commits, and records the exact commit in the
162
+ effective-config artifact metadata. In checkout mode the worktree read is the
163
+ exception: when it cannot be attributed to the branch-head commit — because
164
+ the worktree file has uncommitted edits, because the clean/dirty comparison
165
+ could not be performed (non-git workspace or a transient git error), or because
166
+ the branch head never carried the file — the deposit records the source
167
+ `worktree-dirty` with no commit. The build still runs the worktree bytes, but
168
+ provenance declines to attribute them to any commit. **Deployment-owned** sections — `[roles]`
169
+ (runtime, model, args, alternates, per-role `sessionBudgetSeconds`) and
170
+ `[policy]` — keep flowing live. **Dispatcher-owned** sections (`baseBranch`,
171
+ `capacity`, `forge`, `plugins`, `pr`, `tickets`, `orchestrator`) configure the
172
+ dispatcher itself and are never interpreted by the build runner.
173
+
174
+ Consequently, an accepted base-branch reload re-deposits only the
175
+ deployment-owned sections to active builds; it does not retarget an in-flight
176
+ build's verify universe, finalize steps, commands, or provisioning. A build
177
+ that publishes a pipeline change to its own branch picks it up at its next
178
+ launch, so config changes still flow through the pipeline for the build that
179
+ carries them. In-flight environments are never re-provisioned: a fresh build
180
+ from the new base is the path for a changed provisioning definition.
181
+
149
182
  Every accepted revision is deposited verbatim as a `dispatcher-config`
150
183
  repository artifact and referenced by a `dispatcher.config-reloaded` repository
151
184
  event. The dashboard reports the reload and reprojects values such as capacity;
@@ -164,11 +197,12 @@ These fields hot-reload:
164
197
  | `baseBranch` | Next dispatch or base fallback decision; existing `build.created` facts remain immutable. |
165
198
  | `capacity` | Next dispatcher tick. |
166
199
  | `[pr]` | Next build creation. |
167
- | `[commands]` | Next setup, verify check, or finalize check selected. |
168
- | `[verify]` and `[finalize]` | Next engine step selection. An approved plan's stored verify selection remains authoritative. |
169
- | `[roles]` | Next agent invocation for that role. Runtime, model, args, alternates, and the session budget are captured together. |
170
- | `[policy]` | Next policy, convergence, or agent-session boundary. A running session keeps its captured budget. |
171
- | `tickets.readyLabels`, `tickets.readyState` | Next ready-ticket scan. |
200
+ | `[commands]` | Next setup, verify check, or finalize check selected for a build whose OWN branch carries it. A base-branch reload does not retarget an in-flight build. |
201
+ | `[verify]` and `[finalize]` | Next engine step selection for a build whose OWN branch carries it. An approved plan's stored verify selection remains authoritative, and a base-branch reload does not retarget an in-flight build. |
202
+ | `[workspace]` provider and config (including `provisioning`) | Next launch of a build whose OWN branch carries it. A base-branch reload never re-provisions an in-flight build. |
203
+ | `[roles]` | Next agent invocation for that role. Runtime, model, args, alternates, and the session budget are captured together and delivered live. |
204
+ | `[policy]` | Next policy, convergence, or agent-session boundary, delivered live. A running session keeps its captured budget. |
205
+ | `tickets.readyLabels`, `tickets.readyState`, `tickets.readyAssignee` | Next ready-ticket scan. |
172
206
  | `tickets.triageState`, `tickets.proposalState` | Next dispatcher handback or harvest filing boundary. |
173
207
 
174
208
  Adapter selectors and factory inputs are constructed once and require a
@@ -239,6 +273,40 @@ earlier configured plugins are reserved, and declaration order never permits
239
273
  shadowing. A collision between distinct plugin declarations continues to name
240
274
  the conflicting adapter and both owners.
241
275
 
276
+ Two transitional downgrade paths announce themselves instead of failing
277
+ startup (AUT-517). First, while the builtin hosts the `vercel-sandbox`
278
+ implementation, a configured plugin that re-registers that workspace-provider
279
+ name — `@defrex/autobuild-vercel-sandbox` is exactly such a plugin — is
280
+ skipped with a one-line notice and the builtin keeps serving the provider:
281
+
282
+ ```text
283
+ plugin "autobuild-vercel-sandbox" declares only builtin workspace-provider registration(s) "vercel-sandbox"; skipping it — the builtin keeps serving the provider
284
+ ```
285
+
286
+ The skip applies only when the manifest declares at least one registration,
287
+ only workspace-provider registrations, and every declared name collides with
288
+ a builtin registration; any other collision still throws. The rule keys on
289
+ builtin ownership, so it retires itself when the builtin is removed and a
290
+ second plugin registering the name collides and fails startup as always.
291
+ `ab plugin list` and `ab plugin doctor` show skipped modules as `SKIP` lines
292
+ and exit 0. Second, guest processes (build and harvest children,
293
+ `ab init`'s guest readiness probe, and scoped phase CLI commands) tolerate a
294
+ configured bare package specifier they cannot resolve — guests never
295
+ construct workspace providers, so a provider plugin they need not load is
296
+ skipped with a notice instead of failing the process:
297
+
298
+ ```text
299
+ plugin module "@defrex/autobuild-vercel-sandbox" could not be resolved from repository "…" or installation "…"; guests never construct workspace providers, so the provider plugin is skipped here
300
+ ```
301
+
302
+ Repository-path specifiers and every post-resolution failure (evaluation,
303
+ manifest, collision) stay fail-closed everywhere, including guests. The guest
304
+ readiness probe surfaces a resolution-stage skip in its `configuration and
305
+ plugins` check detail: the detail names the missing module (the skip notice
306
+ quoted above) and the remediation — add the package to the repository's
307
+ dependencies so it resolves from the repository install during
308
+ `commands.setup`.
309
+
242
310
  Plugin authors import the stable surface from `@defrex/autobuild/plugin-sdk`, normally
243
311
  with `import type`, and can develop against Autobuild as a dev/peer dependency
244
312
  without adding a runtime Autobuild dependency to the plugin. That entry point
@@ -255,6 +323,24 @@ require `^1.3.0`. API 1.4 adds the optional `Ticket.creationKey` projection: a
255
323
  ticket-source plugin should return its stable external create/adoption key from
256
324
  create, get, and ready listings so dispatch can correlate Autobuild's durable
257
325
  in-flight creations. Legacy tickets may omit it and remain dispatchable.
326
+ API 1.6 adds workspace-provider capability declarations: a
327
+ `workspaceProviders` registration may carry a `capabilities` object declaring
328
+ its `[workspace.config]` schema (or an outright refusal), required runtime
329
+ provisioning, supported forges, required environment groups (with per-site
330
+ messages), variables that must exist in the launcher process environment,
331
+ additional operator-sandbox forbidden variable names, HTTPS/token store
332
+ requirements, an origin validator, and a remote readiness validation for
333
+ `ab init --validate`. The host enforces every declared capability at its seam
334
+ without naming the provider; required environment belongs on
335
+ `capabilities.requiredEnv` (a descriptor-level `requiredEnv` is rejected at
336
+ manifest parse), and each `capabilities.requiredEnv` group must declare at
337
+ least one of `dispatchMessage` / `validationMessage` — a group with neither is
338
+ rejected at manifest parse, because the host would silently skip it at both
339
+ enforcement sites. API 1.7 extends the workspace-provider factory context with
340
+ the host-derived seams: `storeRef` and `storeToken` (guaranteed present under
341
+ `capabilities.storeRequirements`), `runtimeReferences`, and the checkout-less
342
+ `origin` and `remoteBranchHead` readers, all optional and ignored by factories
343
+ that do not read them. A plugin reading the new seams should require `^1.7.0`.
258
344
 
259
345
  ```ts
260
346
  import type { AutobuildPluginManifest } from '@defrex/autobuild/plugin-sdk'
@@ -438,7 +524,7 @@ gitPasswordEnv = "AB_GIT_READ_TOKEN"
438
524
 
439
525
  [workspace.config.runtimeProvisioning.pi]
440
526
  install = "npm install --global --ignore-scripts @earendil-works/pi-coding-agent@0.84.4"
441
- preflight = "test \"$(pi --version)\" = \"0.84.4\""
527
+ preflight = "test \"$(pi --version)\" = \"0.84.4\" && pi update --models"
442
528
  ```
443
529
 
444
530
  | Vercel field | Default | Constraints |
@@ -518,13 +604,14 @@ checkout-only hooks such as Husky are not provisioning dependencies. A Bun
518
604
  install or verification failure identifies the configured image and expected
519
605
  universal-image capabilities, deletes the partial environment, and leaves no
520
606
  readiness marker. A retry also deletes any named sandbox without that marker
521
- instead of adopting partial setup. The installed distribution's version is
522
- recorded in the guest's `/opt/autobuild/.distribution-version` marker; a
523
- reused sandbox whose marker no longer matches the version the current system
524
- would deliver is refreshed by reinstalling the archive before reuse, so an
525
- upgraded dispatcher retrofits its persistent guests (see "Guest distribution
526
- version selection and delivery" in docs/setup.md for the release-and-upgrade
527
- delivery path and the hosted store's version-lockstep gate). Immediately
607
+ instead of adopting partial setup. The installed distribution's identity — package version and remote-store
608
+ protocol version — is recorded in the guest's
609
+ `/opt/autobuild/.distribution-version` marker; a reused sandbox whose marker
610
+ no longer matches the identity the current system would deliver is refreshed
611
+ by reinstalling the archive before reuse, so an upgraded dispatcher retrofits
612
+ its persistent guests (see "Guest distribution version selection and delivery"
613
+ in docs/setup.md for the release-and-upgrade delivery path and the hosted
614
+ store's version-lockstep gate). Immediately
528
615
  before every build-child launch, the adapter verifies the
529
616
  provisioned absolute Bun executable and every referenced runtime again. A
530
617
  failure names the runtime, selecting role/alternate, stage, and provisioning
@@ -884,7 +971,7 @@ session starts, with a copyable table and all registered runtime names.
884
971
  | `args` | inherited; otherwise `[]` | optional array of nonempty strings; `[]` allowed | Ordered extra CLI argv tokens applied to every phase and tool-free one-shot invocation. A supplied list replaces, rather than unions with, the inherited list. |
885
972
  | `extensions` | no effect | deprecated optional array of nonempty strings | Compatibility-only field. Dispatch warns for each declaring role; migrate explicit runtime options to `args`. |
886
973
  | `sessionBudgetSeconds` | inherited; otherwise `[policy].sessionBudgetSeconds` | optional positive integer | Wall-clock budget for each build phase session routed through this logical role. One budget covers the primary and every alternate target. |
887
- | `alternates` | inherited; otherwise `[]` | optional ordered array of strict `{ runtime?, model?, args?, extensions? }` entries; `[]` allowed | Failure-triggered execution targets. A role's list replaces the inherited list wholesale; each entry overlays that role's effective primary axes. `extensions` is accepted there only as the same deprecated no-op. |
974
+ | `alternates` | inherited; otherwise `[]` | optional ordered array of strict `{ runtime?, model?, args?, extensions? }` entries; `[]` allowed | Failure-triggered execution targets (provider failures and a runtime whose CLI is not installed). A role's list replaces the inherited list wholesale; each entry overlays that role's effective primary axes. `extensions` is accepted there only as the same deprecated no-op. |
888
975
 
889
976
  <!-- config-fragment:roles -->
890
977
  ```toml
@@ -984,7 +1071,7 @@ useful; mixed models are not a configuration inconsistency.
984
1071
  Each session attempt starts with its role's primary. Overload, rate limits, 5xx,
985
1072
  timeout, transport, unknown provider failures, and quota/usage/billing
986
1073
  exhaustion try alternates in declaration order inside that same phase attempt.
987
- Authentication, permission, and local runtime-configuration failures do not.
1074
+ Authentication, permission, and local runtime-configuration failures do not, with one exception: a runtime whose CLI executable is not installed is skipped like any other eligible failure, so the phase runs on the first declared target whose CLI is present. An unsupported installed version is still a stopping configuration failure. If no target's CLI is installed, the chain ends in one non-retried `configuration` failure carrying the last target's missing-CLI message. The remote provisioning preflight stays fail-closed: a missing runtime there is a provisioning defect.
988
1075
  Each target gets a separate session and transcript; a continuation that moves
989
1076
  to another target starts fresh from durable context and cannot inherit the
990
1077
  failed provider's conversation. Selection is not sticky: the next phase, next
@@ -1080,6 +1167,7 @@ state. Within a present table, `source` and `readyState` are required.
1080
1167
  | `source` | — | required nonblank builtin or plugin registration name | Select the ticket adapter after configured plugins load. |
1081
1168
  | `readyLabels` | source-aware | optional array of nonempty strings; `[]` allowed | Require every listed label in addition to the state gate. |
1082
1169
  | `readyState` | — | required nonblank string | The one workflow state eligible for dispatch. |
1170
+ | `readyAssignee` | — | optional; `"me"` or an email address; Linear only (rejected for file, hosted, and plugin sources) | Require the ticket to be assigned to one Linear user, in addition to the state and label gates. |
1083
1171
  | `teamKey` | — | required for Linear and hosted; forbidden for file; optional for plugins | Backend team key such as `"ENG"`, or an existing plugin config field. |
1084
1172
  | `claimedState` | `"In Progress"` for direct Linear; hosted backend default otherwise | optional nonempty string; forbidden for file; allowed for plugins | Workflow state entered when a ticket is claimed. |
1085
1173
  | `createState` | provider default | optional nonempty string | Default state for newly created tickets when `ab ticket create` omits `--state`. |
@@ -1087,13 +1175,33 @@ state. Within a present table, `source` and `readyState` are required.
1087
1175
  | `proposalState` | the resolved `triageState` | optional nonempty string | State harvest files its synthesized proposals into. Setting it to `readyState` waives the human grooming gate, not dependency eligibility. |
1088
1176
  | `dir` | file: selected local state root's `tickets/`; plugin: omitted | optional nonempty path; forbidden for Linear; allowed for plugins | Root containing file-source state directories, or an existing plugin config field. Relative file paths resolve from the repository. |
1089
1177
 
1178
+ `readyAssignee = "me"` resolves to the Linear user who owns `LINEAR_API_KEY`,
1179
+ so one committed value makes each person's local dispatcher take only the work
1180
+ assigned to them; an email names a specific user, for a shared or bot key. The
1181
+ gate is conjunctive with `readyState` and `readyLabels`, and a ticket with no
1182
+ assignee is never ready while it is set. It applies to every ready surface
1183
+ (dispatch, the queued count, and `ab ticket list` without filters); blockers
1184
+ still prevent claim. It is empty or blank, or neither `"me"` nor an email,
1185
+ fails validation. `ab init --validate` prints the user the gate resolves to and
1186
+ fails when an email matches no workspace user. The field reloads without a
1187
+ restart.
1188
+
1090
1189
  When `readyLabels` is absent, direct Linear uses `["autobuild"]`; hosted, file,
1091
1190
  and plugin sources use `[]`, meaning no host-imposed label gate. An explicit value always
1092
1191
  wins. A nonempty list is
1093
1192
  conjunctive: every configured label must be present. `readyState` remains
1094
1193
  mandatory regardless of labels. Linear compares state and label names exactly
1095
- and case-sensitively. The file source accepts state names case-insensitively and
1096
- canonicalizes them to `triage/`, `ready/`, `doing/`, or `done/`.
1194
+ and case-sensitively. The file source matches state names case-insensitively against the directories
1195
+ under the tracker root: the four lifecycle directories (`triage/`, `ready/`,
1196
+ `doing/`, `done/`, always present) plus any directory the repository adds, such
1197
+ as `icebox/`. A name matching no directory is an error listing those that
1198
+ exist, so creating a state stays an explicit act: `ab ticket state create <name>
1199
+ [--about <text>]` makes one, and `ab ticket states` lists every state with its
1200
+ roles, ticket count, and purpose. A purpose is stored as a one-line `.about` file
1201
+ inside the state directory, so it travels with a git-tracked `dir` and is never
1202
+ read as a ticket. `createState`, `readyState`,
1203
+ `triageState`, and `proposalState` may each name any existing directory;
1204
+ `ab ticket list` with no filter still lists only `readyState`.
1097
1205
 
1098
1206
  Source-specific validation is strict:
1099
1207
 
@@ -1203,14 +1311,19 @@ Set `COMPANY_TICKET_TOKEN` in the environment, never in this table.
1203
1311
 
1204
1312
  ## `[orchestrator]`
1205
1313
 
1206
- Gates the operator-sandbox feature: a persistent, **credential-free**
1207
- environment per operator × repository that an operator agent drives through the
1208
- `sandbox.*` registry tools (served by `ab mcp` and every later binding). The
1209
- table is closed to unknown keys and restart-classified as a whole.
1314
+ Gates the operator-agent feature (AUT-340, AUT-342): the per-operator sandbox
1315
+ environments the `sandbox.*` registry tools drive, and — when the embedded
1316
+ orchestrator is enabled — the turn runner that answers operator sessions and
1317
+ wakes on build events. The table is closed to unknown keys and
1318
+ restart-classified as a whole.
1210
1319
 
1211
1320
  | Field | Default | Constraints | Purpose |
1212
1321
  |---|---:|---|---|
1213
- | `enabled` | `false` | boolean | Master gate. With the table absent or `enabled = false`, the sandbox tools are absent from every binding and no environment is ever provisioned. |
1322
+ | `enabled` | `false` | boolean | Master gate. With the table absent or `enabled = false`, the sandbox tools are absent from every binding, no environment is ever provisioned, and no dispatcher tick or message post ever runs orchestrator code — a local dispatcher is inert even when enabled, because the turn runner exists only in the hosted deployment. |
1323
+ | `model` | — | nonblank string, required when `enabled = true` | Provider-qualified model string for the turn runner, in the same vocabulary as `[roles]` model strings (a leading `vercel-ai-gateway/` prefix is accepted and stripped). Resolved through the deployment's gateway credential. |
1324
+ | `invocationBudgetSeconds` | `240` | positive integer, clamped to 300 | Wall-clock budget for one turn invocation. A larger value is clamped — not rejected — to the 300-second hosted function limit the runner executes under (`ORCHESTRATOR_ROUTE_LIMIT_SECONDS`, the same number the operator route pins as `maxDuration`). |
1325
+ | `approvals` | see below | array of `tool` or `tool:qualifier` strings | Registry tool names whose calls suspend a turn until the operator answers. Default: `["builds.control:abort", "builds.control:discard", "builds.answer:revise-spec", "sandbox.publish", "tickets.move:ready"]`. Empty (`[]`) means none — autonomous operation. An entry naming an absent or unknown tool is **inert by design**, never a config error: the list stays stable while the registry grows, and a tool's removal cannot break parsing. A `tool:qualifier` entry applies to the named discriminator value only (for example `tickets.move:ready` approves moving a ticket to `ready`); for a tool without a discriminator, only the bare form matches. |
1326
+ | `wake` | inherits the default | array of event-type globs | The attention events a **new** session wakes for. Kept schema-optional so absent (inherit the default attention set — the build events `ab watch` filters on by default plus the repository-journal events `ab watch --repository` follows) and `[]` (never wake; message-only) stay distinct. Each glob must match at least one build or repository-journal event type, validated with the same compiler `ab watch` uses. Existing sessions never retroactively inherit; an explicit `PUT .../wake` always wins. |
1214
1327
  | `sandbox` | — | strict subtable; absence reads as the defaults | Sandbox behavior knobs. |
1215
1328
 
1216
1329
  `[orchestrator.sandbox]` fields:
@@ -1225,7 +1338,16 @@ forge, ticket-provider, or model credential, and naming any of `AB_STORE`,
1225
1338
  `AB_TOKEN`, `GITHUB_TOKEN`, `GH_TOKEN`, `LINEAR_API_KEY`, `VERCEL_OIDC_TOKEN`,
1226
1339
  `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, `VERCEL_PROJECT_ID`, or `AI_GATEWAY_API_KEY`
1227
1340
  in `environmentVariables` is a config error. The typed operator tools remain
1228
- the only route to build state from inside the sandbox. Tool bounds:
1341
+ the only route to build state from inside the sandbox. A workspace-provider
1342
+ plugin can declare additional forbidden names via its registration's
1343
+ `capabilities.sandboxForbiddenEnv`; those are enforced at the post-load seams
1344
+ (provider construction and `ab init --validate`) rather than at config parse,
1345
+ because plugins are not loaded when configuration is parsed — a deliberate
1346
+ deferral, not an oversight. Concretely, the shared names above are rejected
1347
+ when the config is parsed, plugin-declared extras are rejected when the
1348
+ provider is constructed (dispatch and `ab mcp`), and `ab init --validate`
1349
+ re-checks both sets after plugins load, before any infrastructure is
1350
+ provisioned. Tool bounds:
1229
1351
  `sandbox.exec` waits at most 300 seconds; stdout and stderr are each truncated
1230
1352
  at 65,536 bytes with a `[truncated by autobuild: output exceeded 65536 bytes]`
1231
1353
  marker; `sandbox.reset` and archiving an operator's last session are
@@ -1236,6 +1358,10 @@ one environment per operator and repository.
1236
1358
  ```toml
1237
1359
  [orchestrator]
1238
1360
  enabled = true
1361
+ model = "anthropic/claude-sonnet-4"
1362
+ invocationBudgetSeconds = 240
1363
+ approvals = ["builds.control:abort", "sandbox.publish", "tickets.move:ready"]
1364
+ # wake omitted: new sessions inherit the default attention set
1239
1365
 
1240
1366
  [orchestrator.sandbox]
1241
1367
  idleMinutes = 30
@@ -1245,6 +1371,25 @@ environmentVariables = ["MY_TOOL_CONFIG"]
1245
1371
  Set `MY_TOOL_CONFIG` in the host environment of the `ab mcp` process; a missing
1246
1372
  value fails the tool call that needs it, named as a typed `environment` error.
1247
1373
 
1374
+ In the hosted deployment the `sandbox.*` tools are served to orchestrator
1375
+ turns from two bindings, both composing the backend from the repository's
1376
+ deposited effective config: the store service's message-turn runner (and its
1377
+ same-invocation approval resumes), and the origin-mode dispatcher tick's wake
1378
+ and resume runner, which reuses the dispatcher's wired workspace provider and
1379
+ forge. Both use the builtin workspace provider and the builtin `github` forge
1380
+ from that config — a plugin-provider or forgeless config degrades that
1381
+ binding's sandbox tools only, with a reported diagnostic. The store-service
1382
+ path authenticates the Sandbox SDK with the request's `x-vercel-oidc-token`
1383
+ header (or an explicit `VERCEL_OIDC_TOKEN`, which takes precedence, or the
1384
+ durable `VERCEL_TOKEN`/`VERCEL_TEAM_ID`/`VERCEL_PROJECT_ID` triple, which
1385
+ needs no header); the tick path inherits the dispatcher's already-forwarded
1386
+ token. Every serving path derives the same deterministic environment name per
1387
+ (repository origin, operator), so an operator's environment is one
1388
+ environment per repository no matter which binding drove it, and the guest
1389
+ still receives no credential. Serving stays gated exactly as locally: the
1390
+ effective `[orchestrator].enabled`, the registry's closed-table filtering,
1391
+ and the `approvals` suspension list in the turn loop.
1392
+
1248
1393
  ## Complete example
1249
1394
 
1250
1395
  The following deliberately exercises every fixed table and both verify/finalize
@@ -1427,8 +1572,10 @@ pre-attempt index and reports the original Git failure; merged worktree files
1427
1572
  remain in place and the merge's exit status is unchanged. Upgrade never pushes
1428
1573
  or rewrites history.
1429
1574
 
1430
- Autobuild now installs 11 skills; only `ab-spec`, `ab-tickets`, and `ab-guide`
1431
- are model-invocable. The setup reference is an ordinary support file in the
1575
+ Autobuild now installs 12 skills; only `ab-spec`, `ab-tickets`, and `ab-guide`
1576
+ are model-invocable. (`ab-operate` — the embedded orchestrator's operating
1577
+ manual — also carries `disable-model-invocation: true`; it is read by the
1578
+ hosted turn runner, never invoked by a model in a build session.) The setup reference is an ordinary support file in the
1432
1579
  editable/pristine `ab-guide` tree and participates in the same three-way
1433
1580
  upgrade merge as every other vendored file.
1434
1581
 
@@ -1459,7 +1606,7 @@ poll. Editing TOML cannot change them.
1459
1606
  |---|---:|---|---|
1460
1607
  | Ticket intake | on | `ab dispatch --intake` / `--no-intake`; `i` on the dashboard's global row, and `p` (pause all) / `r` (resume all) on that row or their sessionless equivalents `ab pause --all` / `ab resume --all`, which turn intake off and on as part of quiescing the repository | When off, skip only new ticket list/claim/dispatch work. Janitor work, lease recovery, in-flight builds, and harvesting continue. Turning intake off does not hold builds the repository has already accepted — that is the repository pause below. |
1461
1608
  | Repository pause | off | `p` (pause all) / `r` (resume all) on the dashboard's global row, or `ab pause --all` / `ab resume --all` | While on, the dashboard controls line shows `repository PAUSED`, and each held queued row shows `(held)` while retaining its literal `QUEUED` status. No dispatcher tick attaches a runner to such a build — recovery, startup resume, and the lease sweep all skip them. Running builds are parked by the per-build pauses the same command writes; the janitor still settles aborts and discards. |
1462
- | Claim-time auto-merge default | off | `ab dispatch --auto-merge` / `--no-auto-merge`; `m` on the global row | Seeds durable auto-merge intent only on builds claimed after the setting is enabled. Existing builds never change with the default. |
1609
+ | Repository auto-merge default | off | `ab dispatch --auto-merge` / `--no-auto-merge`; `m` on the global row | Seeds durable auto-merge intent on builds claimed while the setting is enabled, and — on each change — also applies as a bulk action to every current non-terminal build: turning it on requests consent for each build (turning it off withdraws it), attributed to the operator who toggled it exactly as if they had pressed `m` on every row. A per-build toggle overrides its build's state until the global row is toggled again. |
1463
1610
  | Harvest gate | on | `h` on the dashboard's global row | Pauses or resumes repository observation harvesting. The header shows the kernel-acknowledged gate, not merely a pending keypress. |
1464
1611
 
1465
1612
  The opposite flag forms for each dispatch setting are mutually exclusive.