@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.
- package/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- 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).
|