@defrex/autobuild 0.8.0 → 0.9.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 (147) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +313 -26
  3. package/bin/ab-build-runner.ts +8 -2
  4. package/docs/architecture.md +57 -14
  5. package/docs/assets/headline-wide.png +0 -0
  6. package/docs/configuration.md +151 -25
  7. package/docs/hosted-dispatcher.md +69 -13
  8. package/docs/operator-api.md +2 -2
  9. package/docs/remote-store-protocol.md +169 -16
  10. package/docs/remote-ticket-protocol.md +1 -1
  11. package/docs/setup.md +12 -9
  12. package/docs/website.md +10 -0
  13. package/package.json +43 -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/model.ts +57 -7
  19. package/packages/core/src/cli/dashboard/poll.ts +69 -10
  20. package/packages/core/src/cli/dashboard/render.ts +34 -5
  21. package/packages/core/src/cli/dispatch-frontend.ts +54 -5
  22. package/packages/core/src/cli/dispatch.ts +320 -46
  23. package/packages/core/src/cli/harvest.ts +122 -8
  24. package/packages/core/src/cli/help.ts +47 -8
  25. package/packages/core/src/cli/init-readiness-remote.ts +110 -0
  26. package/packages/core/src/cli/init-readiness-shared.ts +72 -0
  27. package/packages/core/src/cli/init-validation.ts +248 -222
  28. package/packages/core/src/cli/main.ts +70 -11
  29. package/packages/core/src/cli/mcp.ts +23 -1
  30. package/packages/core/src/cli/plugin.ts +7 -4
  31. package/packages/core/src/cli/remote-poll.ts +277 -0
  32. package/packages/core/src/cli/repo-state.ts +22 -0
  33. package/packages/core/src/cli/repository-status.ts +39 -1
  34. package/packages/core/src/cli/status.ts +149 -27
  35. package/packages/core/src/cli/terminals.ts +2 -1
  36. package/packages/core/src/cli/ticket-assets.ts +247 -0
  37. package/packages/core/src/cli/ticket.ts +307 -7
  38. package/packages/core/src/cli/wait.ts +147 -39
  39. package/packages/core/src/cli/watch.ts +128 -166
  40. package/packages/core/src/config/live.ts +22 -0
  41. package/packages/core/src/config/pipeline-source.ts +376 -0
  42. package/packages/core/src/config/schema.ts +230 -19
  43. package/packages/core/src/events/catalog.ts +1 -0
  44. package/packages/core/src/events/globs.ts +47 -0
  45. package/packages/core/src/events/payloads.ts +52 -2
  46. package/packages/core/src/events/repository.ts +52 -1
  47. package/packages/core/src/events/sessions.ts +69 -10
  48. package/packages/core/src/events/workspace-remote.ts +12 -0
  49. package/packages/core/src/integration/harness.ts +2 -1
  50. package/packages/core/src/kernel/auto-merge-default.ts +123 -0
  51. package/packages/core/src/kernel/phases.ts +16 -2
  52. package/packages/core/src/kernel/reducer.ts +40 -1
  53. package/packages/core/src/operator/annotations.ts +6 -0
  54. package/packages/core/src/operator/control.ts +19 -5
  55. package/packages/core/src/operator/index.ts +71 -6
  56. package/packages/core/src/operator/query.ts +208 -19
  57. package/packages/core/src/operator/registry.ts +55 -10
  58. package/packages/core/src/operator/requests.ts +9 -6
  59. package/packages/core/src/operator/sandbox-host.ts +140 -0
  60. package/packages/core/src/operator/sandbox.ts +247 -14
  61. package/packages/core/src/operator/ticket-source-backend.ts +47 -0
  62. package/packages/core/src/orchestrator/approvals.ts +115 -0
  63. package/packages/core/src/orchestrator/conversation.ts +167 -0
  64. package/packages/core/src/orchestrator/model.ts +86 -0
  65. package/packages/core/src/orchestrator/prompt.ts +61 -0
  66. package/packages/core/src/orchestrator/tools.ts +117 -0
  67. package/packages/core/src/orchestrator/turn-runner.ts +457 -0
  68. package/packages/core/src/plugin-sdk/index.ts +51 -1
  69. package/packages/core/src/plugins/load.ts +135 -7
  70. package/packages/core/src/plugins/manifest.ts +183 -10
  71. package/packages/core/src/plugins/registry.ts +73 -4
  72. package/packages/core/src/ports/forge/create.ts +1 -1
  73. package/packages/core/src/ports/forge/fake.ts +31 -0
  74. package/packages/core/src/ports/forge/github-transport.ts +13 -2
  75. package/packages/core/src/ports/forge/github.ts +13 -2
  76. package/packages/core/src/ports/forge/local-git.ts +31 -1
  77. package/packages/core/src/ports/runner/claude.ts +2 -2
  78. package/packages/core/src/ports/runner/codex.ts +6 -2
  79. package/packages/core/src/ports/runner/fake.ts +7 -1
  80. package/packages/core/src/ports/runner/pi.ts +8 -6
  81. package/packages/core/src/ports/runner/provider-error.ts +13 -3
  82. package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
  83. package/packages/core/src/ports/types.ts +17 -2
  84. package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
  85. package/packages/core/src/ports/workspace/create.ts +74 -39
  86. package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
  87. package/packages/core/src/ports/workspace/fake.ts +169 -4
  88. package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
  89. package/packages/core/src/ports/workspace/github-origin.ts +36 -0
  90. package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
  91. package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
  92. package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
  93. package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
  94. package/packages/core/src/processes/build-child.ts +17 -2
  95. package/packages/core/src/processes/build-execution-state.ts +37 -0
  96. package/packages/core/src/processes/build-runner.ts +54 -3
  97. package/packages/core/src/processes/dispatcher.ts +247 -41
  98. package/packages/core/src/processes/harvest-child.ts +5 -0
  99. package/packages/core/src/processes/harvest-runner.ts +66 -19
  100. package/packages/core/src/processes/harvest.ts +196 -24
  101. package/packages/core/src/processes/orchestrator-tick.ts +319 -0
  102. package/packages/core/src/processes/sandbox-state.ts +10 -0
  103. package/packages/core/src/store/adapter.ts +32 -0
  104. package/packages/core/src/store/build-scope.ts +64 -0
  105. package/packages/core/src/store/contract.ts +868 -0
  106. package/packages/core/src/store/digest.ts +49 -0
  107. package/packages/core/src/store/local/schema.ts +29 -2
  108. package/packages/core/src/store/local/store.ts +338 -4
  109. package/packages/core/src/store/memory.ts +179 -0
  110. package/packages/core/src/store/new-build.ts +17 -0
  111. package/packages/core/src/store/phase-session.ts +71 -0
  112. package/packages/core/src/store/remote/client.ts +260 -1
  113. package/packages/core/src/store/remote/index.ts +12 -2
  114. package/packages/core/src/store/remote/protocol.ts +100 -12
  115. package/packages/core/src/store/remote/version.ts +6 -2
  116. package/packages/core/src/store/repo-state-events.ts +140 -0
  117. package/packages/core/src/store/retention.ts +7 -0
  118. package/packages/core/src/store/session-handle.ts +64 -0
  119. package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +28 -9
  120. package/packages/core/src/store/streams/session-writer.ts +42 -2
  121. package/packages/core/src/store/streams/wait.ts +4 -5
  122. package/packages/core/src/store/ticket-assets.ts +425 -0
  123. package/packages/core/src/store/types.ts +122 -1
  124. package/packages/core/src/testing/index.ts +59 -0
  125. package/skills/code-review/SKILL.md +5 -1
  126. package/skills/guide/SKILL.md +126 -33
  127. package/skills/guide/references/operator-api.md +2 -2
  128. package/skills/guide/references/plugin-authoring.md +17 -0
  129. package/skills/guide/references/remote-store-protocol.md +169 -16
  130. package/skills/guide/references/remote-ticket-protocol.md +1 -1
  131. package/skills/guide/references/setup.md +12 -9
  132. package/skills/implement/SKILL.md +3 -0
  133. package/skills/operate/SKILL.md +126 -0
  134. package/skills/plan/SKILL.md +4 -0
  135. package/skills/plan-review/SKILL.md +5 -1
  136. package/skills/spec/SKILL.md +46 -3
  137. package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
  138. package/packages/core/src/cli/store-opening.contract.ts +0 -289
  139. package/packages/core/src/cli/testkit.ts +0 -194
  140. package/packages/core/src/markdown.ts +0 -67
  141. package/packages/core/src/operator/client.ts +0 -311
  142. package/packages/core/src/operator/server.ts +0 -616
  143. package/packages/core/src/operator/sessions.ts +0 -223
  144. package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
  145. package/packages/core/src/ports/tickets/remote-server.ts +0 -185
  146. package/packages/core/src/store/remote/server.ts +0 -923
  147. 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,7 +126,7 @@ 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.
@@ -503,9 +519,16 @@ It follows one dispatch run's repository facts incrementally through
503
519
  `packages/core/src/kernel/dispatch-status.ts`, retaining only low-volume settings/Harvest
504
520
  facts for their replay reducers, validates/caches its effective-config artifact,
505
521
  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
522
+ intervals. The same polling path samples the header's current count from build
523
+ digests plus the repository journal — one journal read plus one repo-scoped
524
+ digest read, plus a journal-record probe — paired with the effective config's
525
+ `policy.harvestThreshold`. A journal record that does not yet exist reads as an
526
+ empty journal: the sample, the operator query, the dispatcher's
527
+ repository-wide journal reads (controls, settlement stages, and ready
528
+ scans), and the operator sandbox service's journal reads all go through
529
+ the same shared missing-record journal read (the AUT-524 helper in
530
+ `packages/core/src/processes/harvest.ts`), which writes nothing (no
531
+ `ensureRepo` from a display path). That sample
509
532
  is process-local presentation state: a failed refresh preserves the last
510
533
  successful value and adds a local diagnostic, while an initial failure delays
511
534
  the complete frame rather than inventing zero. No repository fact transports
@@ -579,8 +602,8 @@ bun typecheck # tsc --noEmit
579
602
  For dashboard presentation work, run the repository-only hot CLI:
580
603
 
581
604
  ```sh
582
- bun run dev -- dispatch
583
- # Generic form: bun run dev -- <ab arguments>
605
+ bun run dev:cli -- dispatch
606
+ # Generic form: bun run dev:cli -- <ab arguments>
584
607
  ```
585
608
 
586
609
  Bun keeps the original CLI promise and its `DispatchLoop` alive while hot
@@ -596,7 +619,7 @@ installed `ab` binary remains the non-watching production entry.
596
619
  The seams are the contract. Five reusable contract families run the same
597
620
  behavioral assertions against every implementation:
598
621
 
599
- - `packages/core/src/store/contract.ts` — `BuildStore` and `BlobStore`, including the streams block (§7.6) every adapter runs;
622
+ - `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
623
  - `packages/core/src/ports/tickets/contract.ts` — `TicketSource`;
601
624
  - `packages/core/src/ports/workspace/contract.ts` — `WorkspaceProvider`;
602
625
  - `packages/core/src/ports/forge/contract.ts` — `Forge`, including idempotent PR close and
@@ -618,6 +641,19 @@ A normal `bun test` runs the memory/fake/local registrations, including a fake
618
641
  selected through the plugin ticket-source registry, the real filesystem and
619
642
  local-git adapters, the injected Claude and Codex CLI subprocess contracts,
620
643
  and the injected Pi CLI/RPC contract.
644
+ Ticket assets (SPEC §7.1.3) are files attached to a ticket and kept in the
645
+ BuildStore, never in the ticket source. `packages/core/src/store/ticket-assets.ts`
646
+ holds the types, the limits, and the one validator every adapter calls;
647
+ `packages/core/src/cli/ticket-assets.ts` holds the filesystem walk, the download
648
+ writer, and the note builder; `ab ticket attach`, `asset get`, and `asset rm`
649
+ live in `packages/core/src/cli/ticket.ts`. The only thing a ticket source sees is
650
+ a note through `comment()`, so no source or plugin protocol changed.
651
+ A claim freezes the ticket's current assets into the build (SPEC §6.3):
652
+ `build.created.assets` (and, when a spec is revised from the ticket,
653
+ `spec.revised.assets`) record the pinned revisions, `state.pinnedAssets` in the
654
+ reducer projects them, `getPinnedTicketAsset` reads them back by pinned revision,
655
+ and `ab context` writes them under `.ab/assets/`.
656
+
621
657
  Both `ab dispatch` and sessionless `ab ticket` load the repository's plugins
622
658
  before selecting their TicketSource; dispatch passes that one adapter instance
623
659
  through readiness, dependency, harvest, and completion paths. The Linear,
@@ -626,6 +662,13 @@ but reported as skipped: live provider access requires credentials/resources
626
662
  and an explicit opt-in. When
627
663
  adding an adapter, start from its contract suite, not only the interface.
628
664
 
665
+ The two remote clients get client-only unit coverage in core itself:
666
+ `RemoteBuildStore` (`store/remote/client.test.ts`, which also pins scoped-token
667
+ minting/verification) and `HostedTicketSource` (`ports/tickets/remote.test.ts`)
668
+ run against an injected `fetchFn` with responses built from the protocol's wire
669
+ schemas. The full over-the-wire seam suites (client → HTTP → server) run in
670
+ `packages/hosted-store-service` in the same `bun test`.
671
+
629
672
  To run the Linear contract manually against a destructive scratch target:
630
673
 
631
674
  ```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,10 +197,11 @@ 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. |
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. |
171
205
  | `tickets.readyLabels`, `tickets.readyState` | Next ready-ticket scan. |
172
206
  | `tickets.triageState`, `tickets.proposalState` | Next dispatcher handback or harvest filing boundary. |
173
207
 
@@ -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
@@ -1203,14 +1290,19 @@ Set `COMPANY_TICKET_TOKEN` in the environment, never in this table.
1203
1290
 
1204
1291
  ## `[orchestrator]`
1205
1292
 
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.
1293
+ Gates the operator-agent feature (AUT-340, AUT-342): the per-operator sandbox
1294
+ environments the `sandbox.*` registry tools drive, and — when the embedded
1295
+ orchestrator is enabled — the turn runner that answers operator sessions and
1296
+ wakes on build events. The table is closed to unknown keys and
1297
+ restart-classified as a whole.
1210
1298
 
1211
1299
  | Field | Default | Constraints | Purpose |
1212
1300
  |---|---:|---|---|
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. |
1301
+ | `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. |
1302
+ | `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. |
1303
+ | `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`). |
1304
+ | `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. |
1305
+ | `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
1306
  | `sandbox` | — | strict subtable; absence reads as the defaults | Sandbox behavior knobs. |
1215
1307
 
1216
1308
  `[orchestrator.sandbox]` fields:
@@ -1225,7 +1317,16 @@ forge, ticket-provider, or model credential, and naming any of `AB_STORE`,
1225
1317
  `AB_TOKEN`, `GITHUB_TOKEN`, `GH_TOKEN`, `LINEAR_API_KEY`, `VERCEL_OIDC_TOKEN`,
1226
1318
  `VERCEL_TOKEN`, `VERCEL_TEAM_ID`, `VERCEL_PROJECT_ID`, or `AI_GATEWAY_API_KEY`
1227
1319
  in `environmentVariables` is a config error. The typed operator tools remain
1228
- the only route to build state from inside the sandbox. Tool bounds:
1320
+ the only route to build state from inside the sandbox. A workspace-provider
1321
+ plugin can declare additional forbidden names via its registration's
1322
+ `capabilities.sandboxForbiddenEnv`; those are enforced at the post-load seams
1323
+ (provider construction and `ab init --validate`) rather than at config parse,
1324
+ because plugins are not loaded when configuration is parsed — a deliberate
1325
+ deferral, not an oversight. Concretely, the shared names above are rejected
1326
+ when the config is parsed, plugin-declared extras are rejected when the
1327
+ provider is constructed (dispatch and `ab mcp`), and `ab init --validate`
1328
+ re-checks both sets after plugins load, before any infrastructure is
1329
+ provisioned. Tool bounds:
1229
1330
  `sandbox.exec` waits at most 300 seconds; stdout and stderr are each truncated
1230
1331
  at 65,536 bytes with a `[truncated by autobuild: output exceeded 65536 bytes]`
1231
1332
  marker; `sandbox.reset` and archiving an operator's last session are
@@ -1236,6 +1337,10 @@ one environment per operator and repository.
1236
1337
  ```toml
1237
1338
  [orchestrator]
1238
1339
  enabled = true
1340
+ model = "anthropic/claude-sonnet-4"
1341
+ invocationBudgetSeconds = 240
1342
+ approvals = ["builds.control:abort", "sandbox.publish", "tickets.move:ready"]
1343
+ # wake omitted: new sessions inherit the default attention set
1239
1344
 
1240
1345
  [orchestrator.sandbox]
1241
1346
  idleMinutes = 30
@@ -1245,6 +1350,25 @@ environmentVariables = ["MY_TOOL_CONFIG"]
1245
1350
  Set `MY_TOOL_CONFIG` in the host environment of the `ab mcp` process; a missing
1246
1351
  value fails the tool call that needs it, named as a typed `environment` error.
1247
1352
 
1353
+ In the hosted deployment the `sandbox.*` tools are served to orchestrator
1354
+ turns from two bindings, both composing the backend from the repository's
1355
+ deposited effective config: the store service's message-turn runner (and its
1356
+ same-invocation approval resumes), and the origin-mode dispatcher tick's wake
1357
+ and resume runner, which reuses the dispatcher's wired workspace provider and
1358
+ forge. Both use the builtin workspace provider and the builtin `github` forge
1359
+ from that config — a plugin-provider or forgeless config degrades that
1360
+ binding's sandbox tools only, with a reported diagnostic. The store-service
1361
+ path authenticates the Sandbox SDK with the request's `x-vercel-oidc-token`
1362
+ header (or an explicit `VERCEL_OIDC_TOKEN`, which takes precedence, or the
1363
+ durable `VERCEL_TOKEN`/`VERCEL_TEAM_ID`/`VERCEL_PROJECT_ID` triple, which
1364
+ needs no header); the tick path inherits the dispatcher's already-forwarded
1365
+ token. Every serving path derives the same deterministic environment name per
1366
+ (repository origin, operator), so an operator's environment is one
1367
+ environment per repository no matter which binding drove it, and the guest
1368
+ still receives no credential. Serving stays gated exactly as locally: the
1369
+ effective `[orchestrator].enabled`, the registry's closed-table filtering,
1370
+ and the `approvals` suspension list in the turn loop.
1371
+
1248
1372
  ## Complete example
1249
1373
 
1250
1374
  The following deliberately exercises every fixed table and both verify/finalize
@@ -1427,8 +1551,10 @@ pre-attempt index and reports the original Git failure; merged worktree files
1427
1551
  remain in place and the merge's exit status is unchanged. Upgrade never pushes
1428
1552
  or rewrites history.
1429
1553
 
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
1554
+ Autobuild now installs 12 skills; only `ab-spec`, `ab-tickets`, and `ab-guide`
1555
+ are model-invocable. (`ab-operate` — the embedded orchestrator's operating
1556
+ manual — also carries `disable-model-invocation: true`; it is read by the
1557
+ hosted turn runner, never invoked by a model in a build session.) The setup reference is an ordinary support file in the
1432
1558
  editable/pristine `ab-guide` tree and participates in the same three-way
1433
1559
  upgrade merge as every other vendored file.
1434
1560
 
@@ -1459,7 +1585,7 @@ poll. Editing TOML cannot change them.
1459
1585
  |---|---:|---|---|
1460
1586
  | 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
1587
  | 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. |
1588
+ | 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
1589
  | 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
1590
 
1465
1591
  The opposite flag forms for each dispatch setting are mutually exclusive.
@@ -1,19 +1,24 @@
1
1
  # The hosted dispatcher
2
2
 
3
- The optional [hosted service](../packages/hosted-store-service/README.md) can
4
- own the dispatch kernel. One authenticated HTTP endpoint — `GET /api/dispatch`
5
- — runs, for each configured repository, one bounded origin-mode dispatcher
3
+ The optional [`@defrex/autobuild-hosted-dispatcher`](../packages/hosted-dispatcher/README.md)
4
+ package lets a deployment own the dispatch kernel. Installing it and mounting
5
+ its route — `GET /api/dispatch`, backed by the package's
6
+ `dispatcherEndpoint()` singleton — makes the hosted deployment the kernel
7
+ owner. One authenticated HTTP invocation runs, for each configured
8
+ repository, one bounded origin-mode dispatcher
6
9
  tick: the kernel claims ready tickets, provisions and launches sandboxes,
7
10
  observes running builds, settles publications, opens and merges PRs, and runs
8
11
  the janitor, exactly as a local `ab dispatch --once --repository <origin>`
9
12
  pass does. The schedule is what you attach to that endpoint (Vercel Cron calls
10
13
  it once a minute by default), so builds no longer depend on a maintainer's
11
- machine.
14
+ machine. A deployment that does not install the package serves the hosted
15
+ store, ticket, operator, and web endpoints and never attempts a dispatch tick.
12
16
 
13
17
  ## Enabling the schedule
14
18
 
15
- 1. Deploy the hosted service with the dispatcher variables from
16
- [its README](../packages/hosted-store-service/README.md#hosted-dispatcher)
19
+ 1. Deploy the hosted service with the dispatcher package installed and the
20
+ dispatcher variables from
21
+ [its README](../packages/hosted-dispatcher/README.md)
17
22
  — `AB_DISPATCHER_ORIGIN`, the repository set, `CRON_SECRET`, and the forge
18
23
  credentials the kernel needs (`GITHUB_TOKEN` or `GH_TOKEN`, plus any
19
24
  guest-forwarded variables such as `AI_GATEWAY_API_KEY`). A deployment
@@ -44,26 +49,77 @@ Every sandbox installs the Autobuild distribution the launching dispatcher
44
49
  runs. A local dispatcher packs it from its source checkout with `bun pm pack`;
45
50
  a bundled deployment has neither `bun` nor a source tree at runtime, so it
46
51
  must pack the archive while both exist — in its build step — and carry it into
47
- the function bundle:
52
+ the function bundle. The script lives in the hosted store service package
53
+ (`packages/hosted-store-service/package.json`), whose directory is the Next.js
54
+ project directory and the deployment build's working directory:
48
55
 
49
56
  ```json
50
- { "scripts": { "deploy:build": "bun packages/hosted-store-service/src/bin.ts pack-distribution && bun run postgres:migrate && bun run build && bun tools/ship-packed-distribution.ts" } }
57
+ { "scripts": { "deploy:build": "bun ../../packages/hosted-dispatcher/src/bin.ts pack-distribution --root . && bun ../../packages/postgres-store/src/bin.ts migrate && bun run build && bun ../../packages/hosted-dispatcher/src/ship-packed-distribution.ts && bun ../../packages/hosted-dispatcher/src/ship-provider-plugin.ts" } }
51
58
  ```
52
59
 
53
- `pack-distribution` writes `.autobuild-dist/autobuild-<version>.tgz` under the
54
- distribution root (`--root DIR` overrides). The Next.js config's
60
+ `pack-distribution` (the `ab-hosted-dispatcher` bin) writes
61
+ `.autobuild-dist/autobuild-<version>.tgz` under the
62
+ distribution root (`--root DIR` overrides) — this deployment passes
63
+ `--root .`, because the packer's default root is the repository's distribution
64
+ root while the trace step below and the Next config's
55
65
  `outputFileTracingIncludes: { '/api/dispatch': ['./.autobuild-dist/**'] }`
56
- records the intent to include that directory in the cron route's bundle, but
66
+ resolve against the deployment build's working directory, where the archive
67
+ must land beside the built output. The Next.js config's
68
+ `outputFileTracingIncludes` records the intent to include that directory in the cron route's bundle, but
57
69
  Next 16's default Turbopack builds never apply `outputFileTracingIncludes`
58
70
  (only webpack builds do), so a deployment cannot rely on the config alone. The
59
71
  autobuild-api pipeline therefore ends `deploy:build` with a post-build trace
60
- step (`bun tools/ship-packed-distribution.ts`) that appends the archive to the
72
+ step (`bun ../../packages/hosted-dispatcher/src/ship-packed-distribution.ts`) that appends the archive to the
61
73
  dispatch route's `route.js.nft.json` — the trace file Vercel's Next builder
62
74
  consumes when assembling the function bundle — and fails the deploy loudly if
63
- nothing was packed or the trace file is missing. A deployment using this
75
+ nothing was packed or the trace file is missing. The same step appends the
76
+ distribution root's `package.json`, which provisioning reads for the running
77
+ version and which no import traces once the Next.js project directory sits
78
+ below the repository root. A deployment using this
64
79
  pipeline cannot ship without the archive; deployments that skip both packing
65
80
  and the trace step still fail at runtime.
66
81
 
82
+ A second post-build trace step ships the provider plugin package (AUT-517):
83
+ `bun ../../packages/hosted-dispatcher/src/ship-provider-plugin.ts` locates the
84
+ plugin's source through the repository root's `workspaces` tree (never through
85
+ `node_modules`, so a previous run's staged directory cannot shadow the source),
86
+ bundles its entrypoint with `bun build --target=bun --format=esm` into a
87
+ single self-contained module (the plugin-sdk closure inlines — the
88
+ deployment's repository-root `node_modules` carries no `@defrex/autobuild`
89
+ either — while node builtins stay external), stages it into
90
+ `<repoRoot>/node_modules/@defrex/autobuild-vercel-sandbox/` (replacing the
91
+ workspace symlink a local `bun install` leaves there — or a previous run's
92
+ staged directory, which `bun install` does not relink; the deployment never
93
+ runs `bun install` again), and appends the staged files to the dispatch
94
+ route's trace at the same repository-root depth as the existing
95
+ `node_modules/.bun/**` entries. A bare plugin specifier resolves only
96
+ through a `node_modules/` path, and the compiled dispatcher's
97
+ `distributionRoot()` is the repository root, so only this staging makes
98
+ `plugins = ["@defrex/autobuild-vercel-sandbox"]` loadable inside the
99
+ deployed function. The step is idempotent across runs — including in a
100
+ fresh process after an earlier run consumed the workspace link — and fails
101
+ the deploy loudly when the package, its entrypoint, or the trace file is
102
+ missing. The npm published package remains the real `src` — only the
103
+ deployment stages the bundle.
104
+
105
+ What the staged directory does afterwards depends on the build environment.
106
+ Inside a Vercel deployment build (which always sets `VERCEL`, including a
107
+ local `vercel build` run) the staged directory stays: the deployment never
108
+ runs `bun install` again and the function bundle's runtime resolution needs
109
+ it — a deployment building outside Vercel must set `VERCEL=1` in its build
110
+ environment to keep the staged bundle. A plain local `bun run deploy:build`
111
+ (no `VERCEL` environment) removes the staged directory afterwards and
112
+ restores the workspace symlink, so a local run leaves the checkout exactly
113
+ as `bun install` had it and local test runs keep resolving the workspace
114
+ source (the capability parity test's reference equality holds). On checkouts
115
+ of older versions the staged directory stayed behind and survived `bun
116
+ install` — it reports "no changes" because the staged package satisfies the
117
+ manifest — so later local runs resolved the plugin to the self-contained
118
+ bundle and the parity test failed on its reference-equality assertion; the
119
+ recovery is `rm -rf node_modules/@defrex/autobuild-vercel-sandbox && bun
120
+ install`, or simply re-run `deploy:build` locally on a version with this
121
+ fix, which restores the symlink itself.
122
+
67
123
  At runtime the kernel takes, in order: `AB_DISTRIBUTION_ARCHIVE` (an explicit
68
124
  archive path), the single archive under `.autobuild-dist/` of the
69
125
  distribution root or working directory, a source checkout packed on the spot,
@@ -1,9 +1,9 @@
1
1
  # Operator API v1
2
2
 
3
3
  The hosted service exposes the polling-oriented operator surface at
4
- `/operator/v1/repos/{repo}`. It is versioned with remote store protocol **2**.
4
+ `/operator/v1/repos/{repo}`. It is versioned with remote store protocol **3**.
5
5
  Every request must send `X-Autobuild-Version`,
6
- `X-Autobuild-Protocol-Version: 2`, and `Authorization: Bearer …`. Identity is
6
+ `X-Autobuild-Protocol-Version: 3`, and `Authorization: Bearer …`. Identity is
7
7
  checked before authentication or resource lookup.
8
8
 
9
9
  Mint a token offline; the signed user is used as the human actor on every write: