@defrex/autobuild 0.8.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 (251) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +192 -0
  3. package/SPEC.md +2269 -0
  4. package/bin/ab-build-runner.ts +47 -0
  5. package/bin/ab-dev.ts +27 -0
  6. package/bin/ab-dispatch-kernel.ts +92 -0
  7. package/bin/ab-harvest-runner.ts +29 -0
  8. package/bin/ab-init-probe.ts +19 -0
  9. package/bin/ab-process-group-reaper.ts +32 -0
  10. package/bin/ab.ts +6 -0
  11. package/bin/agent/ab +3 -0
  12. package/docs/answering-blocked-builds.md +89 -0
  13. package/docs/architecture.md +726 -0
  14. package/docs/assets/headline-wide.png +0 -0
  15. package/docs/configuration.md +1623 -0
  16. package/docs/hosted-dispatcher-cutover-validation.md +178 -0
  17. package/docs/hosted-dispatcher.md +204 -0
  18. package/docs/mcp.md +119 -0
  19. package/docs/operator-api.md +186 -0
  20. package/docs/remote-store-protocol.md +972 -0
  21. package/docs/remote-ticket-protocol.md +79 -0
  22. package/docs/setup.md +289 -0
  23. package/docs/spec-standard.md +63 -0
  24. package/docs/ticket-dependencies.md +59 -0
  25. package/docs/vercel-sandbox-rollout-probe.md +7 -0
  26. package/package.json +135 -0
  27. package/packages/core/src/cli/args.ts +74 -0
  28. package/packages/core/src/cli/artifact.ts +182 -0
  29. package/packages/core/src/cli/binary.ts +198 -0
  30. package/packages/core/src/cli/build-control.ts +789 -0
  31. package/packages/core/src/cli/build-progress.ts +55 -0
  32. package/packages/core/src/cli/bulk-control.ts +385 -0
  33. package/packages/core/src/cli/context.ts +470 -0
  34. package/packages/core/src/cli/dashboard/actions.ts +59 -0
  35. package/packages/core/src/cli/dashboard/cells.ts +92 -0
  36. package/packages/core/src/cli/dashboard/composer.ts +230 -0
  37. package/packages/core/src/cli/dashboard/detail.ts +100 -0
  38. package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
  39. package/packages/core/src/cli/dashboard/live.ts +152 -0
  40. package/packages/core/src/cli/dashboard/model.ts +1173 -0
  41. package/packages/core/src/cli/dashboard/poll.ts +196 -0
  42. package/packages/core/src/cli/dashboard/render.ts +1568 -0
  43. package/packages/core/src/cli/dashboard/selection.ts +66 -0
  44. package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
  45. package/packages/core/src/cli/dashboard/session-view.ts +506 -0
  46. package/packages/core/src/cli/dashboard/transcript.ts +108 -0
  47. package/packages/core/src/cli/dev.ts +46 -0
  48. package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
  49. package/packages/core/src/cli/dispatch-process.ts +259 -0
  50. package/packages/core/src/cli/dispatch.ts +3692 -0
  51. package/packages/core/src/cli/dotenv.ts +48 -0
  52. package/packages/core/src/cli/env.ts +206 -0
  53. package/packages/core/src/cli/harvest.ts +698 -0
  54. package/packages/core/src/cli/help.ts +713 -0
  55. package/packages/core/src/cli/init-agent.ts +106 -0
  56. package/packages/core/src/cli/init-validation.ts +583 -0
  57. package/packages/core/src/cli/init.ts +756 -0
  58. package/packages/core/src/cli/installation.ts +333 -0
  59. package/packages/core/src/cli/keyboard.ts +261 -0
  60. package/packages/core/src/cli/main.ts +1399 -0
  61. package/packages/core/src/cli/mcp.ts +279 -0
  62. package/packages/core/src/cli/models.ts +65 -0
  63. package/packages/core/src/cli/observe.ts +55 -0
  64. package/packages/core/src/cli/plugin.ts +227 -0
  65. package/packages/core/src/cli/pr-attachments.ts +138 -0
  66. package/packages/core/src/cli/pr-summary.ts +124 -0
  67. package/packages/core/src/cli/publication-settlement.ts +188 -0
  68. package/packages/core/src/cli/repo-state.ts +213 -0
  69. package/packages/core/src/cli/repository-status.ts +80 -0
  70. package/packages/core/src/cli/self-update.ts +443 -0
  71. package/packages/core/src/cli/status.ts +895 -0
  72. package/packages/core/src/cli/store-opening.contract.ts +289 -0
  73. package/packages/core/src/cli/store-opening.ts +167 -0
  74. package/packages/core/src/cli/store-ref.ts +33 -0
  75. package/packages/core/src/cli/terminal-restore.ts +174 -0
  76. package/packages/core/src/cli/terminal.ts +361 -0
  77. package/packages/core/src/cli/terminals.ts +995 -0
  78. package/packages/core/src/cli/testkit.ts +194 -0
  79. package/packages/core/src/cli/ticket.ts +484 -0
  80. package/packages/core/src/cli/upgrade-agent.ts +180 -0
  81. package/packages/core/src/cli/upgrade-commit.ts +468 -0
  82. package/packages/core/src/cli/upgrade-notice.ts +91 -0
  83. package/packages/core/src/cli/upgrade-progress.ts +110 -0
  84. package/packages/core/src/cli/upgrade.ts +888 -0
  85. package/packages/core/src/cli/wait.ts +587 -0
  86. package/packages/core/src/cli/watch.ts +932 -0
  87. package/packages/core/src/config/live.ts +255 -0
  88. package/packages/core/src/config/load.ts +98 -0
  89. package/packages/core/src/config/roles.ts +438 -0
  90. package/packages/core/src/config/schema.ts +774 -0
  91. package/packages/core/src/distribution.ts +24 -0
  92. package/packages/core/src/events/catalog.ts +201 -0
  93. package/packages/core/src/events/envelope.ts +59 -0
  94. package/packages/core/src/events/payloads.ts +618 -0
  95. package/packages/core/src/events/repository.ts +559 -0
  96. package/packages/core/src/events/sessions.ts +180 -0
  97. package/packages/core/src/harvest/schema.ts +147 -0
  98. package/packages/core/src/ids.ts +45 -0
  99. package/packages/core/src/integration/harness.ts +681 -0
  100. package/packages/core/src/kernel/auto-merge.ts +259 -0
  101. package/packages/core/src/kernel/converge.ts +117 -0
  102. package/packages/core/src/kernel/dispatch-settings.ts +60 -0
  103. package/packages/core/src/kernel/dispatch-status.ts +141 -0
  104. package/packages/core/src/kernel/engine.ts +1047 -0
  105. package/packages/core/src/kernel/harvest.ts +823 -0
  106. package/packages/core/src/kernel/origin.ts +69 -0
  107. package/packages/core/src/kernel/phases.ts +196 -0
  108. package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
  109. package/packages/core/src/kernel/pr-attachments.ts +82 -0
  110. package/packages/core/src/kernel/reducer.ts +752 -0
  111. package/packages/core/src/kernel/stall.ts +155 -0
  112. package/packages/core/src/kernel/verify-applicability.ts +52 -0
  113. package/packages/core/src/markdown.ts +67 -0
  114. package/packages/core/src/ontology.ts +288 -0
  115. package/packages/core/src/open-map.ts +203 -0
  116. package/packages/core/src/operator/annotations.ts +187 -0
  117. package/packages/core/src/operator/client.ts +311 -0
  118. package/packages/core/src/operator/control.ts +117 -0
  119. package/packages/core/src/operator/index.ts +62 -0
  120. package/packages/core/src/operator/presentation.ts +22 -0
  121. package/packages/core/src/operator/protocol.ts +98 -0
  122. package/packages/core/src/operator/query.ts +201 -0
  123. package/packages/core/src/operator/registry.ts +1043 -0
  124. package/packages/core/src/operator/requests.ts +138 -0
  125. package/packages/core/src/operator/sandbox.ts +458 -0
  126. package/packages/core/src/operator/server.ts +616 -0
  127. package/packages/core/src/operator/sessions.ts +223 -0
  128. package/packages/core/src/operator/tickets.ts +177 -0
  129. package/packages/core/src/plugin-sdk/index.ts +174 -0
  130. package/packages/core/src/plugins/contract-entry.ts +74 -0
  131. package/packages/core/src/plugins/contract-env.ts +3 -0
  132. package/packages/core/src/plugins/load.ts +259 -0
  133. package/packages/core/src/plugins/manifest.ts +236 -0
  134. package/packages/core/src/plugins/registry.ts +274 -0
  135. package/packages/core/src/plugins/runtimes.ts +75 -0
  136. package/packages/core/src/ports/forge/contract.ts +258 -0
  137. package/packages/core/src/ports/forge/create.ts +74 -0
  138. package/packages/core/src/ports/forge/fake.ts +488 -0
  139. package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
  140. package/packages/core/src/ports/forge/github-transport.ts +352 -0
  141. package/packages/core/src/ports/forge/github.ts +1162 -0
  142. package/packages/core/src/ports/forge/local-git.ts +568 -0
  143. package/packages/core/src/ports/runner/claude.ts +873 -0
  144. package/packages/core/src/ports/runner/cli-stream.ts +90 -0
  145. package/packages/core/src/ports/runner/codex.ts +873 -0
  146. package/packages/core/src/ports/runner/contract.ts +492 -0
  147. package/packages/core/src/ports/runner/fake.ts +213 -0
  148. package/packages/core/src/ports/runner/json-record.ts +2 -0
  149. package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
  150. package/packages/core/src/ports/runner/one-shot.ts +29 -0
  151. package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
  152. package/packages/core/src/ports/runner/pi-bridge.js +6 -0
  153. package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
  154. package/packages/core/src/ports/runner/pi.ts +617 -0
  155. package/packages/core/src/ports/runner/production.ts +74 -0
  156. package/packages/core/src/ports/runner/provider-error.ts +121 -0
  157. package/packages/core/src/ports/runner/routing.ts +234 -0
  158. package/packages/core/src/ports/runner/runtime.ts +214 -0
  159. package/packages/core/src/ports/runner/session-env.ts +34 -0
  160. package/packages/core/src/ports/runner/stream-parts.ts +103 -0
  161. package/packages/core/src/ports/tickets/contract.ts +570 -0
  162. package/packages/core/src/ports/tickets/create.ts +135 -0
  163. package/packages/core/src/ports/tickets/fake.ts +215 -0
  164. package/packages/core/src/ports/tickets/file.ts +559 -0
  165. package/packages/core/src/ports/tickets/linear.ts +811 -0
  166. package/packages/core/src/ports/tickets/operations.ts +161 -0
  167. package/packages/core/src/ports/tickets/remote-index.ts +13 -0
  168. package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
  169. package/packages/core/src/ports/tickets/remote-server.ts +185 -0
  170. package/packages/core/src/ports/tickets/remote.ts +130 -0
  171. package/packages/core/src/ports/tickets/update.ts +51 -0
  172. package/packages/core/src/ports/types.ts +528 -0
  173. package/packages/core/src/ports/workspace/build-execution.ts +123 -0
  174. package/packages/core/src/ports/workspace/contract.ts +136 -0
  175. package/packages/core/src/ports/workspace/create.ts +132 -0
  176. package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
  177. package/packages/core/src/ports/workspace/fake.ts +432 -0
  178. package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
  179. package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
  180. package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
  181. package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
  182. package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
  183. package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
  184. package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
  185. package/packages/core/src/processes/build-child-exit.ts +78 -0
  186. package/packages/core/src/processes/build-child.ts +119 -0
  187. package/packages/core/src/processes/build-execution-state.ts +95 -0
  188. package/packages/core/src/processes/build-parent-watch.ts +35 -0
  189. package/packages/core/src/processes/build-runner.ts +2614 -0
  190. package/packages/core/src/processes/dispatcher.ts +2825 -0
  191. package/packages/core/src/processes/execution-settlement.ts +171 -0
  192. package/packages/core/src/processes/harvest-child.ts +115 -0
  193. package/packages/core/src/processes/harvest-execution-state.ts +121 -0
  194. package/packages/core/src/processes/harvest-runner.ts +1656 -0
  195. package/packages/core/src/processes/harvest.ts +595 -0
  196. package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
  197. package/packages/core/src/processes/phase-failure-budget.ts +52 -0
  198. package/packages/core/src/processes/process-group-reaper.ts +35 -0
  199. package/packages/core/src/processes/process-group.ts +65 -0
  200. package/packages/core/src/processes/publication-loss.ts +62 -0
  201. package/packages/core/src/processes/publication-state.ts +93 -0
  202. package/packages/core/src/processes/sandbox-state.ts +101 -0
  203. package/packages/core/src/registry.ts +24 -0
  204. package/packages/core/src/sessions/reducer.ts +185 -0
  205. package/packages/core/src/skills.ts +7 -0
  206. package/packages/core/src/spec-standard.ts +47 -0
  207. package/packages/core/src/store/adapter.ts +80 -0
  208. package/packages/core/src/store/build-scope.ts +272 -0
  209. package/packages/core/src/store/contract.ts +2581 -0
  210. package/packages/core/src/store/local/blobs.ts +38 -0
  211. package/packages/core/src/store/local/schema.ts +180 -0
  212. package/packages/core/src/store/local/store.ts +1713 -0
  213. package/packages/core/src/store/memory.ts +1138 -0
  214. package/packages/core/src/store/phase-session.ts +425 -0
  215. package/packages/core/src/store/remote/client.ts +780 -0
  216. package/packages/core/src/store/remote/index.ts +12 -0
  217. package/packages/core/src/store/remote/protocol.ts +348 -0
  218. package/packages/core/src/store/remote/server.ts +923 -0
  219. package/packages/core/src/store/remote/token.ts +137 -0
  220. package/packages/core/src/store/remote/version.ts +10 -0
  221. package/packages/core/src/store/retention.ts +91 -0
  222. package/packages/core/src/store/session-handle.ts +265 -0
  223. package/packages/core/src/store/streams/assemble.ts +79 -0
  224. package/packages/core/src/store/streams/lock.ts +42 -0
  225. package/packages/core/src/store/streams/session-writer.ts +306 -0
  226. package/packages/core/src/store/streams/types.ts +171 -0
  227. package/packages/core/src/store/streams/wait.ts +127 -0
  228. package/packages/core/src/store/subscribe.ts +105 -0
  229. package/packages/core/src/store/types.ts +466 -0
  230. package/packages/core/src/testing/fixed.ts +44 -0
  231. package/packages/core/src/testing/store-failures.ts +32 -0
  232. package/packages/core/src/zod-issues.ts +91 -0
  233. package/skills/code-review/SKILL.md +100 -0
  234. package/skills/finalize/SKILL.md +44 -0
  235. package/skills/guide/SKILL.md +1781 -0
  236. package/skills/guide/references/operator-api.md +164 -0
  237. package/skills/guide/references/plugin-authoring.md +354 -0
  238. package/skills/guide/references/remote-store-protocol.md +972 -0
  239. package/skills/guide/references/remote-ticket-protocol.md +79 -0
  240. package/skills/guide/references/setup.md +289 -0
  241. package/skills/guide/references/spec-standard.md +63 -0
  242. package/skills/guide/references/ticket-dependencies.md +59 -0
  243. package/skills/harvest/SKILL.md +66 -0
  244. package/skills/harvest-review/SKILL.md +63 -0
  245. package/skills/implement/SKILL.md +82 -0
  246. package/skills/plan/SKILL.md +123 -0
  247. package/skills/plan-review/SKILL.md +121 -0
  248. package/skills/reconcile/SKILL.md +65 -0
  249. package/skills/spec/SKILL.md +132 -0
  250. package/skills/tickets/SKILL.md +139 -0
  251. package/templates/autobuild.toml +36 -0
@@ -0,0 +1,178 @@
1
+ # Hosted dispatcher cutover validation — AUT-320
2
+
3
+ - **Validation run:** 2026-09-11, window 19:06:24Z – 19:41:52Z; verification re-derived from fresh Store snapshots at 22:41Z and again at 23:29Z after the implement phase re-ran in a fresh workspace (the durable log below is live and keeps accumulating)
4
+ - **Build:** `validate-hosted-dispatcher` on `https://autobuild.defrex.com` (durable event log: `GET /builds/validate-hosted-dispatcher/events` or the web dashboard)
5
+ - **Branch:** `ab/validate-hosted-dispatcher` (PR publication requested with auto-merge; publication and merge happen after the implement session — the build slug carries the final merged state)
6
+ - **Tickets:** [AUT-320](https://linear.app/issue/AUT-320) (this validation), [AUT-304](https://linear.app/issue/AUT-304) (acceptance evidence attached to its body)
7
+ - **Method note:** all numbers below were re-derived from fresh Store snapshots at implement time, at the guidance round (repository journal seq 1–926, build event log seq 1–45, as of 2026-09-11T22:41Z), and again at 23:29Z on the re-run implement session (journal seq 1–1117, build event log seq 1–60). Snapshots are labeled as-of; the durable event log is authoritative.
8
+
9
+ ## Checklist verdicts
10
+
11
+ | Checklist item | Verdict | Evidence |
12
+ | --- | --- | --- |
13
+ | Environment variables set | ✅ | Hosted ticks deposit `dispatcher-effective-config` artifacts (rev 50 as of 19:54:50Z) declaring workspace provider `vercel-sandbox`, env var `AI_GATEWAY_API_KEY`, AI-gateway model sessions (`vercel-ai-gateway/zai/glm-5.3-flash`, `vercel-ai-gateway/meta/muse-spark-1.3`), and runtime provisioning with preflight checks. Every hosted tick since 19:06 completes and deposits the artifact. |
14
+ | Cron live | ✅ | 265 `dispatcher.run-started` events at ≈60 s median cadence from 19:06:24Z through 23:28:50Z (latest snapshot), all run ids `hosted-dispatcher-<uuid>`, all 265 ending `run-stopped outcome=normal`. |
15
+ | Harvest gate off | ✅ | Last `harvest.completed` at 03:05:04Z (journal seq 59); no `harvest.*` event since. |
16
+ | Local `ab-dispatch-kernel` stopped on every maintainer machine | ✅ (store-side layers + maintainer attestation with attached evidence) | Three-layer verification below. The store-side layers prove no interactive local dispatcher ever wrote to this repository and no dispatcher of any mode claimed build work in the window; the process-level residue on maintainer machines is covered by the maintainer's attestation with attached command output (see Process evidence). |
17
+
18
+ ## Three-layer verification
19
+
20
+ The journal alone cannot prove absence of local dispatchers: only the interactive
21
+ frontend's child kernel carries a `kernelRunId`, so a headless local run
22
+ (`ab dispatch --once --plain`) writes no `dispatcher.run-started`/tick events at
23
+ all. The verification therefore stacks three bounded layers.
24
+
25
+ ### Layer 1 — Repository journal (proves: hosted cadence live; no *interactive* local dispatcher)
26
+
27
+ Journal seq 1–1117, complete from 2026-09-11T02:17:59Z (no gaps; the hosted
28
+ repository store came up that morning). Re-derived 22:41Z and again 23:29Z.
29
+ Findings:
30
+
31
+ - 265 `dispatcher.run-started` events — **every one** with run id prefixed
32
+ `hosted-dispatcher-`; zero bare-UUID (interactive local) run ids in the
33
+ entire journal history.
34
+ - Cadence ≈60 s median (min 11 s, max 63 s; first 19:06:24Z, last in snapshot
35
+ 23:28:50Z); 265/265 `run-stopped` with `outcome=normal`.
36
+ - The only two `dispatcher.tick-yielded` events (seq 144, 19:25:50Z; seq 1111,
37
+ 23:27:52Z) are both hosted→hosted (run/holder ids all `hosted-dispatcher-*`).
38
+ - No `harvest.*` event after 03:05:04Z (none after the window opened).
39
+ - Zero journal events mention any maintainer hostname (`aron-desktop` does not
40
+ appear anywhere in the journal).
41
+
42
+ **Bound:** proves nothing about headless local dispatchers, which never write
43
+ journal events.
44
+
45
+ ### Layer 2 — Build-scoped execution facts (proves: no dispatcher of *any* mode claimed work in the window)
46
+
47
+ A dispatcher that actually claims and works a build writes build-scoped facts
48
+ regardless of interactive/headless mode: `workspace.provision-started` and,
49
+ decisively, `execution.started` whose `instance` is
50
+ `${hostname()}-${slug}-inst_<id>` — a plain store append, never gated on
51
+ `kernelRunId`. The hosted deployment stamps the Vercel function's hostname
52
+ (link-local `169.254.x.x` form); a maintainer machine's dispatcher would stamp
53
+ its own hostname.
54
+
55
+ - Build census (store `GET /builds`): the only build created after the window
56
+ opened is `validate-hosted-dispatcher` (19:06:33Z); no older build was
57
+ updated during the window (re-derived 22:41Z and again 23:29Z).
58
+ - This build's `execution.started` events both stamp hosted instances:
59
+ seq 21 (19:26:06Z, generation 0)
60
+ `instance=169.254.67.163-validate-hosted-dispatcher-inst_e031dd81`,
61
+ `provider=vercel-sandbox`, `sessionId=sbx_d4pBpd0TAJQyKxBW0setr3e8F7sE`;
62
+ seq 56 (23:28:09Z, generation 1)
63
+ `instance=169.254.30.93-validate-hosted-dispatcher-inst_d69843ce` —
64
+ the hosted host component in both cases.
65
+ - Zero `execution.started` events with a foreign hostname in the window;
66
+ zero `workspace.provision-started` events anywhere in the store's builds
67
+ outside this build during the window.
68
+
69
+ **Bound:** an idle local dispatcher that claims nothing still leaves no trace;
70
+ that residue is covered by Layer 3.
71
+
72
+ **Recovery-fact caveat (recorded for honesty):** `infrastructure.failed`
73
+ recovery facts carry the fixed instance name `dispatcher-recovery` and are
74
+ attributed to the supervising (hosted) tick, so they are bounded by the hosted
75
+ tick cadence rather than their own instance string. This build's three
76
+ `infrastructure.failed` events are all provision failures of the incident
77
+ below.
78
+
79
+ ### Layer 3 — Maintainer-machine process evidence (supplied by the maintainer)
80
+
81
+ An idle local `ab dispatch` process that claims nothing leaves no store trace,
82
+ so this layer cannot be derived from the Store. The implementer escalated
83
+ (`esc_e93b2831`, raised from this build) asking the maintainer to run, on every
84
+ maintainer machine during the window, `pgrep -fl "ab-dispatch-kernel|ab
85
+ dispatch"` plus scheduler listings (launchd/cron/systemd user timers) and
86
+ attach the output to the escalation answer. The maintainer answered with a
87
+ full attestation **and attached command output**, folded into the Process
88
+ evidence section below.
89
+
90
+ ## Process evidence (maintainer attestation, escalation esc_e93b2831)
91
+
92
+ The maintainer collected the evidence at 2026-09-11T19:49Z on **aron-desktop**
93
+ — the **only** maintainer machine (Linux; there is no macOS or Windows
94
+ maintainer machine, so launchd/launchctl and PowerShell checks do not apply).
95
+ Summary of the attached evidence:
96
+
97
+ 1. **Processes.** `pgrep -fl "ab-dispatch-kernel|ab dispatch"` found exactly
98
+ two processes (1096539 `bun …/bin/ab.ts dispatch`, 1096573
99
+ `bun …/bin/ab-dispatch-kernel.ts`), both started 19:39:54Z, both with
100
+ working directory `/home/defrex/code/parakeeto` — a **different
101
+ repository's checkout** with its own Store repository identity. They cannot
102
+ claim or supervise builds for `https://github.com/defrex/autobuild`. No `ab
103
+ dispatch` process with cwd `/home/defrex/code/autobuild` exists.
104
+ 2. **Last local dispatcher for this repository.** A headless `ab dispatch
105
+ --plain --auto-merge` (kernel run id prefix `aron-desktop-*`) exited at
106
+ 18:06:13Z — its log ends "dispatcher superseded by another invocation —
107
+ detaching / ab dispatch stopped" — 60 minutes **before** the validation
108
+ window opened at 19:06Z. It was never restarted.
109
+ 3. **User schedulers.** `crontab -l` → crontab is not installed on the machine
110
+ (no user cron entries possible); `systemctl --user list-timers --all` → "0
111
+ timers listed". No launchd (not macOS).
112
+ 4. **Corroborating durable evidence.** Every `dispatcher.run-started` /
113
+ tick / `run-stopped` fact for this repository from 19:06:24Z onward carries
114
+ a `hosted-dispatcher-<uuid>` run id (first:
115
+ `hosted-dispatcher-c5fa724f-fa11-4250-8dbb-3274510a2022`); no
116
+ `aron-desktop-*` holder appears in the journal after 18:06Z. This build's
117
+ own `execution.started` (seq 21) names instance
118
+ `169.254.67.163-validate-hosted-dispatcher-inst_e031dd81` — a hosted
119
+ function address, not a maintainer host.
120
+
121
+ **Maintainer conclusion (verbatim intent):** no local `ab dispatch` process for
122
+ this repository was running during 19:06–19:42Z, and none is running now.
123
+ Recorded as maintainer attestation with the attached outputs; the store-side
124
+ Layers 1–2 independently prove no dispatcher of any mode claimed or executed
125
+ build work in the window.
126
+
127
+ ## Provision incident (honest record)
128
+
129
+ The first three provision attempts failed with
130
+ `Executable not found in $PATH: "bun"` (19:06–19:15Z) — the exact symptom
131
+ `docs/hosted-dispatcher.md` documents for a deployment that skipped
132
+ `pack-distribution`. After `maxInfrastructureAttempts` (3) was exhausted the
133
+ kernel raised escalation `esc_84b0cddf` (19:15:51Z, policy guard
134
+ `infrastructure-failure-limit`); the operator answered with a bare retry at
135
+ 19:24:37Z, and provisioning succeeded at 19:25:51Z on the next hosted tick.
136
+ The generation-1 re-provision (23:26:54Z → 23:27:53Z, after the publication
137
+ note below) succeeded with no operator intervention.
138
+ **Verdict: the cutover works, with one operator intervention.** A follow-up
139
+ observation was recorded for the deployment's build/pack step.
140
+
141
+ ## Publication note (honest record)
142
+
143
+ The implement phase requested publication at 22:43:01Z (event seq 49,
144
+ `publication.requested`; auto-merge had been requested at build creation,
145
+ seq 2). That first publication did not complete before the implement
146
+ workspace's environment lifetime ended: `execution.ended` at 23:26:51Z
147
+ (seq 51), the workspace was released, and a fresh generation-1 workspace was
148
+ provisioned for the re-run implement session. The prior workspace's un-pushed
149
+ commits were lost with it; store-side deposits (build artifacts, observations,
150
+ the escalation answer, the AUT-304 evidence section) persisted. This session
151
+ re-committed the repository deliverable (this document) and re-requests
152
+ publication. Publication, verify, review, and merge happen after this
153
+ session; the build slug `validate-hosted-dispatcher` carries the final state.
154
+ A follow-up observation was recorded for the publication path.
155
+
156
+ ## Cutover outcome (as of implement time)
157
+
158
+ - Ticket moved to Todo → claimed and built by the hosted deployment alone:
159
+ **confirmed** (Layers 1–2; single active build, hosted execution instances).
160
+ - Published as a PR, and merged: PR publication is **requested** (auto-merge
161
+ requested at build creation; publication re-requested by this session) from
162
+ branch `ab/validate-hosted-dispatcher` into `main`; verify, review,
163
+ publication, and merge happen after this session, and the build slug
164
+ `validate-hosted-dispatcher` on the dashboard carries the final state. This
165
+ document deliberately does not claim "published" or "merged".
166
+
167
+ ## Evidence index
168
+
169
+ - Build event log (durable, live): `https://autobuild.defrex.com` → build
170
+ `validate-hosted-dispatcher` (API: `GET /builds/validate-hosted-dispatcher/events`)
171
+ - Repository journal (durable, live): `GET /repos/https%3A%2F%2Fgithub.com%2Fdefrex%2Fautobuild/events`
172
+ - Deposited build artifacts: `cutover-build-event-log`, `cutover-repository-journal`,
173
+ `cutover-validation-report` (attached to the PR description at finalize)
174
+ - Dispatcher effective config: repository artifact kind
175
+ `dispatcher-effective-config`, rev 50 as of 2026-09-11T19:54:50Z
176
+ - Branch: `ab/validate-hosted-dispatcher` → PR into `main` (auto-merge requested)
177
+ - Tickets: [AUT-320](https://linear.app/issue/AUT-320),
178
+ [AUT-304](https://linear.app/issue/AUT-304) (evidence section appended to body)
@@ -0,0 +1,204 @@
1
+ # The hosted dispatcher
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
6
+ tick: the kernel claims ready tickets, provisions and launches sandboxes,
7
+ observes running builds, settles publications, opens and merges PRs, and runs
8
+ the janitor, exactly as a local `ab dispatch --once --repository <origin>`
9
+ pass does. The schedule is what you attach to that endpoint (Vercel Cron calls
10
+ it once a minute by default), so builds no longer depend on a maintainer's
11
+ machine.
12
+
13
+ ## Enabling the schedule
14
+
15
+ 1. Deploy the hosted service with the dispatcher variables from
16
+ [its README](../packages/hosted-store-service/README.md#hosted-dispatcher)
17
+ — `AB_DISPATCHER_ORIGIN`, the repository set, `CRON_SECRET`, and the forge
18
+ credentials the kernel needs (`GITHUB_TOKEN` or `GH_TOKEN`, plus any
19
+ guest-forwarded variables such as `AI_GATEWAY_API_KEY`). A deployment
20
+ serving repositories across GitHub identities configures per-repository
21
+ tokens with `AB_DISPATCHER_GITHUB_TOKENS`; repositories without an
22
+ override keep using the shared credential. A served repository with
23
+ neither fails its tick: origin-mode dispatch requires an exported token
24
+ and never falls back to the gh CLI login of whoever runs the service.
25
+ 2. Schedule the route by adding a `crons` entry to the deployment's
26
+ `vercel.json` and redeploying. Vercel issues a plain GET with
27
+ `Authorization: Bearer <CRON_SECRET>` once per minute:
28
+
29
+ ```json
30
+ {
31
+ "crons": [{ "path": "/api/dispatch", "schedule": "* * * * *" }]
32
+ }
33
+ ```
34
+
35
+ 3. Verify: `curl -H "Authorization: Bearer $CRON_SECRET" https://YOUR_ORIGIN/api/dispatch`
36
+ returns `200 {"ok":true,"repositories":[{"repository":"…","outcome":"ticked","runId":"hosted-dispatcher-…"}]}`.
37
+ Unauthenticated or wrongly authorized calls are rejected (401) and do no
38
+ work; a deployment without `CRON_SECRET` answers 403 with the endpoint
39
+ disabled. One repository's failure never prevents another's tick.
40
+
41
+ ## Shipping the guest distribution
42
+
43
+ Every sandbox installs the Autobuild distribution the launching dispatcher
44
+ runs. A local dispatcher packs it from its source checkout with `bun pm pack`;
45
+ a bundled deployment has neither `bun` nor a source tree at runtime, so it
46
+ must pack the archive while both exist — in its build step — and carry it into
47
+ the function bundle:
48
+
49
+ ```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" } }
51
+ ```
52
+
53
+ `pack-distribution` writes `.autobuild-dist/autobuild-<version>.tgz` under the
54
+ distribution root (`--root DIR` overrides). The Next.js config's
55
+ `outputFileTracingIncludes: { '/api/dispatch': ['./.autobuild-dist/**'] }`
56
+ records the intent to include that directory in the cron route's bundle, but
57
+ Next 16's default Turbopack builds never apply `outputFileTracingIncludes`
58
+ (only webpack builds do), so a deployment cannot rely on the config alone. The
59
+ 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
61
+ dispatch route's `route.js.nft.json` — the trace file Vercel's Next builder
62
+ 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
64
+ pipeline cannot ship without the archive; deployments that skip both packing
65
+ and the trace step still fail at runtime.
66
+
67
+ At runtime the kernel takes, in order: `AB_DISTRIBUTION_ARCHIVE` (an explicit
68
+ archive path), the single archive under `.autobuild-dist/` of the
69
+ distribution root or working directory, a source checkout packed on the spot,
70
+ and finally the running version's published distribution — the npm registry
71
+ tarball, else the GitHub release asset. A tick that
72
+ reports `Executable not found in $PATH: "bun"` during `provision` is a
73
+ deployment that skipped the pack step (or, outside this pipeline, the trace
74
+ step that ships the archive).
75
+
76
+ ## Bounding the invocation
77
+
78
+ The endpoint is pinned to `maxDuration = 300` seconds (Vercel Pro's default
79
+ function duration). `AB_DISPATCHER_BUDGET_SECONDS` (default 240) bounds the
80
+ kernel's work inside that: an already-expired budget skips the tick, a budget
81
+ reached mid-drain stops awaiting running builds, and a repository whose budget
82
+ no longer covers a minimum-remaining floor is answered as `skipped`. Unfinished
83
+ work resumes on the next invocation: guests keep running while the supervising
84
+ function detaches, and the next tick settles their completion from the Store
85
+ plus provider liveness. Raise `maxDuration` and the budget **together** (e.g.
86
+ `"maxDuration": 800` with a budget of 780 — the parser clamps 10–780); keep the
87
+ budget at least ~20 s below the function duration so the response always
88
+ returns.
89
+
90
+ ## Overlap and missed ticks
91
+
92
+ Invocations are serialized by the durable repository supervisor lease: a second
93
+ overlapping invocation yields (it records `dispatcher.tick-yielded` naming the
94
+ holder in the repository journal and performs no claims, launches, or
95
+ publications), and the loser's response still answers 200. A missed or late
96
+ invocation is never incorrect: a build whose guest finished while no invocation
97
+ ran is settled — completion facts, lease, publication — by the next
98
+ invocation's settlement stage, and a build whose sandbox timed out is recovered
99
+ by the lease sweep as with any dispatcher.
100
+
101
+ One provider behavior matters for that settlement: Vercel's plain command
102
+ lookup reports no exit code for a command that finished while no client was
103
+ waiting on it, and keeps reporting none until some client issues a wait on the
104
+ command. The dispatcher's execution observation therefore issues one bounded
105
+ (~5 s) wait on the recorded command whenever the lookup shows no exit: a wait
106
+ that resolves proves the guest's end (with its exit code) and the build settles
107
+ in that same tick, while a timed-out wait leaves the guest running — one
108
+ observation never approaches the tick's operation timeout, and the next tick
109
+ retries. Settlement of a guest that exited after the launching invocation
110
+ detached is expected within one tick of its exit.
111
+
112
+ ## Pausing during an incident
113
+
114
+ - **Stop the schedule**: remove the `crons` entry (or disable the cron in the
115
+ Vercel dashboard) and redeploy.
116
+ - **Revoke the credential**: rotate `CRON_SECRET` — every in-flight schedule
117
+ call starts answering 401.
118
+ - **Soft stop**: turn repository intake OFF (operator API `PUT
119
+ /operator/v1/repos/{repo}/settings/intake` with `{"enabled": false}`, or the
120
+ web dashboard) — running builds finish their current phases but no new ticket
121
+ is claimed. A repository-wide or per-build pause holds queued work the same
122
+ way and takes effect on the next invocation.
123
+
124
+ ## Observing the hosted dispatcher
125
+
126
+ Every invocation is durable: tick reports
127
+ (`dispatcher.tick-started`/`-completed`/`-yielded`), run boundaries
128
+ (`dispatcher.run-started`/`-stopped`), and a per-minute
129
+ `dispatcher-effective-config` artifact deposit all carry the
130
+ `hosted-dispatcher-<uuid>` run id, so the web dashboard's repository journal
131
+ and the operator API show hosted activity exactly as they show a local
132
+ dispatcher. Expect the repository journal to grow by roughly one config
133
+ artifact per minute per repository; the dispatcher run/config artifacts
134
+ themselves are retention-bounded (see below), while the journal's events keep
135
+ accumulating by design.
136
+
137
+ This is true of *every* dispatcher, not only the hosted one. A local `ab
138
+ dispatch` pointed at the hosted Store — a watch, an operator cron on a host,
139
+ a manual one-shot — synthesizes its own `<host>-dispatch-<id>` run id and
140
+ journals the same run boundaries and tick facts under it. That makes cutover
141
+ verification answerable from the journal alone:
142
+
143
+ - Every `dispatcher.*` event with a non-`hosted-dispatcher` run id is direct
144
+ evidence that another dispatcher was writing to the hosted Store.
145
+ - A journal window with no `dispatcher.*` events at all means no dispatcher
146
+ ran against the Store in that window.
147
+ - The launch-flag facts (`dispatcher.intake-set`,
148
+ `dispatcher.auto-merge-default-set`) carry the writing invocation's run id
149
+ alongside their human actor, so even those writes identify the dispatcher
150
+ that performed them.
151
+
152
+ ### Runtime logs
153
+
154
+ Every invocation also writes to the deployment's runtime logs (Vercel's
155
+ **Logs** tab, or `vercel logs`), which is where to look first when the journal
156
+ shows no hosted activity at all — a rejected or misconfigured invocation never
157
+ reaches the journal. Every line starts with `hosted-dispatcher <invocation id>`
158
+ so one invocation's lines can be filtered together:
159
+
160
+ - the request line (`GET /api/dispatch agent="vercel-cron/1.0"`) — the user
161
+ agent tells a cron call apart from an operator's curl;
162
+ - `rejected 401|403|405 <kind>: <reason>` at error level, with whether the
163
+ authorization header was absent or present-but-wrong (never its value);
164
+ - `500 configuration invalid: <error>` naming the offending variable;
165
+ - `tick start repositories=N budgetSeconds=S origin=…`, then per repository
166
+ `tick run=hosted-dispatcher-…`, `ticked … ms=…`, `skipped`, or
167
+ `failed … : <error with stack>` at error level, then
168
+ `tick complete ticked=… failed=… skipped=…` and `200 ok ms=…`;
169
+ - the kernel's own report lines (the same lines `ab dispatch --plain` prints
170
+ locally) prefixed `<repository> [kernel]`, warnings at error level.
171
+
172
+ Secrets, forge tokens, and minted guest tokens never appear in any line.
173
+
174
+ ## Artifact retention
175
+
176
+ Dispatcher-generated run/config artifacts — the repository-scoped
177
+ `dispatcher-effective-config` and `dispatcher-config` kinds and the
178
+ build-scoped `build-runner-effective-config` kind — keep the **latest 200
179
+ revisions per kind** (per repository, respectively per build). When a new
180
+ revision of one of these kinds is deposited, the store prunes the older
181
+ revisions past the bound at deposit time, inside the same transaction.
182
+ Events and journal entries are never pruned, so historical event payloads may
183
+ reference a pruned revision; the current run's snapshot is always among the
184
+ newest deposits and stays retrievable, and every existing read surface
185
+ (latest-by-default artifact reads, listings) works unchanged. The bound is
186
+ overridable per deployment with `AB_ARTIFACT_RETENTION_MAX_REVISIONS` (a
187
+ positive integer; see the store environment documentation). All other
188
+ artifact kinds (build logs, `pr-description`, phase artifacts) are not
189
+ subject to retention.
190
+
191
+ ## Sandbox authentication
192
+
193
+ Inside a Vercel Function the Sandbox SDK authenticates with the deployment's
194
+ own OIDC identity: Vercel hands each invocation its token on the
195
+ `x-vercel-oidc-token` request header (not as a `VERCEL_OIDC_TOKEN` environment
196
+ variable), and the endpoint passes it to the kernel under that name for the
197
+ duration of the tick. The tick log line reports which credential the kernel
198
+ presents (`sandboxAuth=oidc-header`, `oidc-env`, `token`, or `none`); `none`
199
+ means the project has OIDC federation disabled (Settings → Security). No
200
+ long-lived `VERCEL_TOKEN` belongs on the service. Guests never receive a Vercel
201
+ token or forge credential; they authenticate to the Store with the short-TTL
202
+ deployment operator token the dispatcher mints for the tick
203
+ (`AB_DISPATCHER_TOKEN_TTL_SECONDS`, default 7 days — keep it longer than your
204
+ largest `timeoutSeconds` guest lifetime).
package/docs/mcp.md ADDED
@@ -0,0 +1,119 @@
1
+ # The hosted MCP server
2
+
3
+ The hosted service exposes the agent tool registry over [Streamable HTTP](https://modelcontextprotocol.io) at
4
+ `/mcp`, on the same origin as the operator dashboard. External agents — Claude
5
+ web, Claude Desktop, Claude Code, Codex, or any harness with an MCP client —
6
+ get the same operator surface the embedded orchestrator has, at parity by
7
+ construction: tool names, descriptions, JSON schemas, and annotations are read
8
+ from the registry's closed table, and every execution goes through the
9
+ operator API's token-verified protocol.
10
+
11
+ ## What the endpoint serves
12
+
13
+ - **Every registry tool**, with its schema and annotations, plus
14
+ **`repositories.list`** — a read-only tool returning the repository
15
+ identities this deployment serves (the values every tool's `repo` field
16
+ accepts). The binding is data-driven: when a future ticket adds registry
17
+ entries, they appear on the endpoint with no change to the deployment.
18
+ - **Server instructions** summarizing how Autobuild works (dispatcher, builds,
19
+ tickets, harvest, notes) and pointing clients at `repositories.list`.
20
+ - A **stateless transport**: fresh transport and server per request, JSON
21
+ responses, no session ids. Every request is authenticated independently, so
22
+ cold starts behave identically to warm instances.
23
+
24
+ ## Authentication: OAuth 2.1 through Better Auth
25
+
26
+ The endpoint is an OAuth 2.1 **protected resource**; the deployment's Better
27
+ Auth instance is the **authorization server**. An unauthenticated request to
28
+ `/mcp` receives `401` with an RFC 9728 `WWW-Authenticate` challenge pointing at
29
+ `/.well-known/oauth-protected-resource`. From there a client:
30
+
31
+ 1. Reads the protected-resource metadata (served at the origin root and under
32
+ `/api/auth`) to discover the authorization server.
33
+ 2. Reads the authorization-server metadata
34
+ (`/.well-known/oauth-authorization-server`) — authorization, token, and
35
+ dynamic-registration endpoints, plus the `jwks_uri`.
36
+ 3. Registers dynamically (`POST /api/auth/mcp/register`, RFC 7591); the
37
+ registered `client_name` becomes the `via` marker recorded on writes.
38
+ 4. Runs the authorization-code flow with PKCE through `GET /api/auth/mcp/authorize`.
39
+ A signed-out person is redirected to the deployment's sign-in page (the
40
+ existing GitHub sign-in and email allowlist); the flow resumes after
41
+ sign-in. A person outside the allowlist cannot complete the flow — and the
42
+ resource re-checks the allowlist on every tool call, so a person dropped
43
+ from the list has their live tokens refused immediately (`403`).
44
+ 5. Exchanges the code at `/api/auth/mcp/token`; access tokens are short-lived
45
+ and refresh tokens rotate. Consent renders only when the client sends
46
+ `prompt=consent` (the plugin's designed behavior); clients that omit it
47
+ receive a code directly.
48
+
49
+ Every tool call resolves the token's subject to the signed-in operator, mints
50
+ the same 30-second attributed operator token the web gateway mints — stamped
51
+ with `via: {kind: "mcp", client}` — and executes the named tool through the
52
+ operator server. A mutating tool's durable event therefore names the person
53
+ who authorized the client and which client executed it:
54
+
55
+ ```json
56
+ { "kind": "human", "user": "ada@example.com", "via": { "kind": "mcp", "client": "Claude web" } }
57
+ ```
58
+
59
+ Tool calls naming a repository outside the deployment's served set are refused
60
+ (`kind: "validation"`).
61
+
62
+ ## Duration limits
63
+
64
+ The MCP route's `maxDuration` is 300 s (Vercel Pro default) — the same pairing
65
+ the [dispatcher route](hosted-dispatcher.md) documents. Tool-requested bounded
66
+ waits (`builds.events`' `waitSeconds`) are clamped to **`MCP_MAX_WAIT_SECONDS`
67
+ = 240 s** at the binding, so no single MCP request outlives the route's limit;
68
+ the registry's own schema currently caps that tool's waits lower, and the
69
+ clamp is the guard that holds if that cap rises.
70
+
71
+ ## Connecting clients
72
+
73
+ ### Claude web (custom connector)
74
+
75
+ Settings → Connectors → **Add custom connector**, enter the deployment URL
76
+ plus `/mcp` (e.g. `https://operator.example/mcp`). Claude discovers the
77
+ authorization server from the protected-resource metadata, registers
78
+ dynamically, and sends the person through GitHub sign-in and the consent page;
79
+ the OAuth callback is `https://claude.ai/api/mcp/auth_callback` (allowed by
80
+ dynamic registration — no configuration needed on the deployment).
81
+
82
+ ### Claude Code
83
+
84
+ ```sh
85
+ claude mcp add --transport http autobuild https://operator.example/mcp
86
+ ```
87
+
88
+ Claude Code runs the same OAuth 2.1 flow in the terminal (it opens a browser
89
+ for sign-in and consent).
90
+
91
+ ### Codex
92
+
93
+ ```sh
94
+ codex mcp add autobuild --url https://operator.example/mcp
95
+ ```
96
+
97
+ ### Any MCP client
98
+
99
+ Point the client at `<deployment>/mcp` over Streamable HTTP. A client only
100
+ needs RFC 9728 discovery, dynamic client registration, PKCE, and refresh —
101
+ the standard surface the endpoint serves.
102
+
103
+ ## Operational variables
104
+
105
+ - `AB_WEB_MCP_RESOURCE` (optional): the protected resource the plugin binds
106
+ tokens to. Defaults to `<BETTER_AUTH_URL>/mcp`, which is correct for the
107
+ standard deployment; override only when a proxy or custom domain fronts the
108
+ function. Must be an absolute http(s) URL (https in production).
109
+ - `BETTER_AUTH_URL`: the public origin — the authorization-server issuer and
110
+ the default MCP resource's base.
111
+ - `AB_WEB_ALLOWED_EMAILS`: the operator allowlist. Sign-in admission and every
112
+ MCP tool call are checked against it.
113
+ - `AB_WEB_REPOSITORIES`: the served repository identities (the
114
+ `repositories.list` payload and the per-call `repo` scope).
115
+
116
+ The endpoint requires no additional variables beyond the hosted service's
117
+ existing web/auth set. Note that consent renders when the client sends
118
+ `prompt=consent`; a client that omits it gets a code directly (the plugin's
119
+ designed behavior).