@defrex/autobuild 0.11.0 → 0.12.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 (96) hide show
  1. package/README.md +3 -1
  2. package/SPEC.md +222 -22
  3. package/docs/architecture.md +66 -4
  4. package/docs/configuration.md +105 -16
  5. package/docs/hosted-dispatcher.md +17 -2
  6. package/docs/hosted-service.md +214 -0
  7. package/docs/mcp.md +8 -2
  8. package/docs/operator-api.md +90 -7
  9. package/docs/remote-store-protocol.md +189 -144
  10. package/docs/remote-ticket-protocol.md +107 -19
  11. package/docs/setup.md +173 -16
  12. package/docs/ticket-dependencies.md +1 -1
  13. package/package.json +7 -6
  14. package/packages/core/src/cli/attention.ts +209 -0
  15. package/packages/core/src/cli/configured-store.ts +17 -0
  16. package/packages/core/src/cli/connect.ts +261 -0
  17. package/packages/core/src/cli/dashboard/model.ts +4 -5
  18. package/packages/core/src/cli/dispatch.ts +183 -41
  19. package/packages/core/src/cli/help.ts +39 -10
  20. package/packages/core/src/cli/init-validation.ts +27 -2
  21. package/packages/core/src/cli/main.ts +41 -15
  22. package/packages/core/src/cli/mcp.ts +7 -1
  23. package/packages/core/src/cli/pr-summary-publication.ts +220 -0
  24. package/packages/core/src/cli/pr-summary.ts +2 -0
  25. package/packages/core/src/cli/project-credentials.ts +132 -0
  26. package/packages/core/src/cli/repo-state.ts +15 -2
  27. package/packages/core/src/cli/self-update.ts +10 -0
  28. package/packages/core/src/cli/store-opening.ts +22 -14
  29. package/packages/core/src/cli/terminals.ts +0 -10
  30. package/packages/core/src/cli/ticket.ts +8 -1
  31. package/packages/core/src/cli/upgrade-compatibility.ts +80 -0
  32. package/packages/core/src/cli/wait.ts +84 -17
  33. package/packages/core/src/cli/watch.ts +197 -60
  34. package/packages/core/src/config/live.ts +3 -0
  35. package/packages/core/src/config/schema.ts +56 -9
  36. package/packages/core/src/connection.ts +86 -0
  37. package/packages/core/src/events/catalog.ts +10 -2
  38. package/packages/core/src/events/payloads.ts +44 -0
  39. package/packages/core/src/events/repository.ts +13 -0
  40. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +130 -83
  41. package/packages/core/src/kernel/engine.ts +24 -0
  42. package/packages/core/src/kernel/generators/build-log.ts +10 -0
  43. package/packages/core/src/kernel/log-index.ts +17 -0
  44. package/packages/core/src/kernel/pr-summary.ts +89 -0
  45. package/packages/core/src/kernel/reducer-registry.ts +8 -0
  46. package/packages/core/src/kernel/reducer.ts +8 -0
  47. package/packages/core/src/ontology.ts +1 -0
  48. package/packages/core/src/operator/index.ts +7 -0
  49. package/packages/core/src/operator/query.ts +38 -16
  50. package/packages/core/src/operator/ticket-source-backend.ts +1 -0
  51. package/packages/core/src/operator/tickets.ts +6 -8
  52. package/packages/core/src/plugin-sdk/index.ts +1 -0
  53. package/packages/core/src/plugins/manifest.ts +5 -3
  54. package/packages/core/src/plugins/runtimes.ts +3 -1
  55. package/packages/core/src/ports/forge/fake.ts +13 -1
  56. package/packages/core/src/ports/forge/github-transport.ts +36 -1
  57. package/packages/core/src/ports/forge/github.ts +29 -5
  58. package/packages/core/src/ports/forge/local-git.ts +12 -4
  59. package/packages/core/src/ports/forge/summary-marker.ts +8 -0
  60. package/packages/core/src/ports/runner/session-env.ts +1 -0
  61. package/packages/core/src/ports/tickets/create.ts +11 -3
  62. package/packages/core/src/ports/tickets/hosted-index.ts +0 -3
  63. package/packages/core/src/ports/tickets/remote-protocol.ts +1 -0
  64. package/packages/core/src/ports/tickets/remote.ts +1 -0
  65. package/packages/core/src/ports/workspace/build-execution.ts +5 -1
  66. package/packages/core/src/ports/workspace/create.ts +11 -2
  67. package/packages/core/src/ports/workspace/distribution-archive.ts +3 -5
  68. package/packages/core/src/ports/workspace/harvest-execution.ts +3 -1
  69. package/packages/core/src/ports/workspace/local-build-execution.ts +8 -1
  70. package/packages/core/src/ports/workspace/vercel-sandbox.ts +47 -19
  71. package/packages/core/src/processes/build-execution-state.ts +1 -1
  72. package/packages/core/src/processes/build-runner.ts +87 -26
  73. package/packages/core/src/processes/dispatch-failure-budget.ts +83 -0
  74. package/packages/core/src/processes/dispatcher.ts +59 -10
  75. package/packages/core/src/processes/harvest-child.ts +1 -0
  76. package/packages/core/src/processes/harvest-execution-state.ts +19 -2
  77. package/packages/core/src/processes/harvest-runner.ts +142 -100
  78. package/packages/core/src/processes/phase-failure-budget.ts +1 -0
  79. package/packages/core/src/processes/publication-loss.ts +18 -1
  80. package/packages/core/src/store/remote/client.ts +1 -1
  81. package/packages/core/src/store/remote/compatibility.ts +90 -0
  82. package/packages/core/src/store/remote/credentials.ts +28 -0
  83. package/packages/core/src/store/remote/index.ts +14 -1
  84. package/packages/core/src/store/remote/protocol.ts +25 -0
  85. package/packages/core/src/store/remote/token.ts +74 -13
  86. package/packages/core/src/testing/index.ts +7 -0
  87. package/skills/guide/SKILL.md +144 -26
  88. package/skills/guide/references/operator-api.md +135 -13
  89. package/skills/guide/references/plugin-authoring.md +10 -2
  90. package/skills/guide/references/remote-store-protocol.md +189 -144
  91. package/skills/guide/references/remote-ticket-protocol.md +111 -18
  92. package/skills/guide/references/setup.md +173 -16
  93. package/skills/guide/references/ticket-dependencies.md +1 -1
  94. package/skills/operate/SKILL.md +126 -36
  95. package/templates/hosted-service/package.json +13 -0
  96. package/templates/hosted-service/vercel.json +7 -0
@@ -14,6 +14,18 @@ and sessionless `ab init --validate` readiness check. Examples labelled as
14
14
  fragments are intended to be added to an existing file; the [complete
15
15
  example](#complete-example) is valid on its own.
16
16
 
17
+ ## `[store]`
18
+
19
+ | Key | Required | Values / purpose |
20
+ |---|---|---|
21
+ | `url` | When `[store]` exists | Absolute HTTPS service base URL; loopback HTTP is allowed for development. No credentials, query or fragment. Trailing slashes are normalized. |
22
+
23
+ Selection is explicit `--store` > nonblank `AB_STORE` > `[store] url` > the main
24
+ checkout's `.autobuild/`. A configured service without a user credential fails
25
+ with `ab connect` guidance. `AB_TOKEN` overrides the saved service/project
26
+ credential. See [connecting a project](hosted-service.md#connect-a-project).
27
+ Changing the store requires restarting dispatch.
28
+
17
29
  ## Hosted service and PostgreSQL BuildStore environment
18
30
 
19
31
  The optional `@defrex/autobuild-hosted-store-service` and its
@@ -59,14 +71,14 @@ separate signed-user token described by the [operator API](operator-api.md).
59
71
  | `AB_HOST` | Optional, ordinary Bun host | Bind hostname; defaults to `0.0.0.0`. Vercel supplies its own listener. |
60
72
  | `PORT` | Optional, ordinary Bun host | Integer 1–65535; defaults to `3000`. Vercel supplies its own listener. |
61
73
  | `AB_STORE` | Remote client | Hosted service base URL used by the kernel, phase commands, and dashboard. |
62
- | `AB_TOKEN` | Authenticated remote client | Offline-minted bearer token; this is not the signing secret. |
74
+ | `AB_TOKEN` | Authenticated remote client | Explicit project bearer credential override for CI; otherwise use ab connect. This is not the signing secret. |
63
75
  | `BETTER_AUTH_SECRET` | Web dashboard | Independent server-only session secret with at least 32 high-entropy characters. |
64
76
  | `BETTER_AUTH_URL` | Web dashboard | Exact HTTP(S) origin, without credentials or a path; HTTPS is mandatory in production. |
65
77
  | `GITHUB_CLIENT_ID` | GitHub sign-in | OAuth application client id. |
66
78
  | `GITHUB_CLIENT_SECRET` | GitHub sign-in | Server-only OAuth application secret. |
67
79
  | `AB_WEB_AUTH_PROVIDERS` | Web dashboard | Comma-separated provider set; currently exactly `github`. |
68
80
  | `AB_WEB_ALLOWED_EMAILS` | Web dashboard | Nonempty comma-separated operator email allowlist; matching is case-insensitive. |
69
- | `AB_WEB_REPOSITORIES` | Web dashboard | Nonempty comma-separated repository allowlist exposed to signed-in operators. Entries are repository identities — normalized `https://` origins (ssh-like spellings such as `git@github.com:owner/repo.git` are accepted and normalized); they must match the Store's identity for the repository. |
81
+ | `AB_WEB_REPOSITORIES` | Hosted service | Deprecated once-per-database bootstrap CSV for the durable project registry. Use owner commands thereafter; see [connected projects](setup.md#connected-projects). |
70
82
 
71
83
  GitHub's callback is `{BETTER_AUTH_URL}/api/auth/callback/github`; the app needs
72
84
  the `user:email` scope (or GitHub App read-only email permission). Sessions are
@@ -212,6 +224,7 @@ remain pinned to their startup values and are reported explicitly:
212
224
 
213
225
  | Restart-required field | Reason |
214
226
  |---|---|
227
+ | `store` | Store selection and project authority are fixed when dispatch starts. |
215
228
  | `plugins` | Plugin modules and registration catalogs load before wiring. |
216
229
  | `forge` | Selects the constructed Forge adapter. |
217
230
  | `workspace.provider`, `workspace.config` | Select and configure the constructed workspace provider. |
@@ -357,7 +370,7 @@ manifest parse), and each `capabilities.requiredEnv` group must declare at
357
370
  least one of `dispatchMessage` / `validationMessage` — a group with neither is
358
371
  rejected at manifest parse, because the host would silently skip it at both
359
372
  enforcement sites. API 1.7 extends the workspace-provider factory context with
360
- the host-derived seams: `storeRef` and `storeToken` (guaranteed present under
373
+ the host-derived seams: `storeRef` and `delegateCredential` (guaranteed present under
361
374
  `capabilities.storeRequirements`), `runtimeReferences`, and the checkout-less
362
375
  `origin` and `remoteBranchHead` readers, all optional and ignored by factories
363
376
  that do not read them. A plugin reading the new seams should require `^1.7.0`.
@@ -631,7 +644,7 @@ no longer matches the identity the current system would deliver is refreshed
631
644
  by reinstalling the archive before reuse, so an upgraded dispatcher retrofits
632
645
  its persistent guests (see "Guest distribution version selection and delivery"
633
646
  in docs/setup.md for the release-and-upgrade delivery path and the hosted
634
- store's version-lockstep gate). Immediately
647
+ store's supported client window). Immediately
635
648
  before every build-child launch, the adapter verifies the
636
649
  provisioned absolute Bun executable and every referenced runtime again. A
637
650
  failure names the runtime, selecting role/alternate, stage, and provisioning
@@ -757,6 +770,28 @@ as written.
757
770
 
758
771
  ## `[verify]` and `[verify.<step>]`
759
772
 
773
+ Post-reconcile verification defaults to enabled: `[verify].afterReconcile = true`.
774
+ Set it to `false` to retain pre-reconcile verification and skip all steps after
775
+ reconciliation. An optional boolean `[verify.<step>].afterReconcile` overrides
776
+ the repository setting in either direction; overrides require a configured step.
777
+ For example, set `afterReconcile = false` in `[verify]` and
778
+ `afterReconcile = true` in `[verify.types]` to keep only that step re-running.
779
+
780
+ The kernel freezes the resolved ordered policy in
781
+ `verify.reconcile-policy-captured` at the next engine action after reconcile
782
+ completion. Live configuration changes apply at that boundary; a cycle already
783
+ started keeps its policy through recovery. Historical cycles already started
784
+ without a captured policy retain the rerun-all behavior. Initial verification
785
+ and verification after subsequent code review always use ordinary applicability.
786
+ Policy exclusion precedes plan selection and paths, including `always = true`.
787
+ Enabled steps still follow plan selection and path rules. Excluded steps record
788
+ `verify.completed` with outcome `skipped`, `skipCause = "post-reconcile-policy"`,
789
+ and a reason naming the setting. Dashboards and PR summaries call this
790
+ "skipped by post-reconcile policy"; it is never a pass. Forge-side required
791
+ checks and branch protections still gate merge. For excluded steps, merged code
792
+ is not re-verified, so breakage that only exists in the combination can land.
793
+
794
+
760
795
  `[verify].steps` defines the configured universe and execution order. It is
761
796
  optional and defaults to no verify phases. Every listed name needs one matching
762
797
  subtable, and every named subtable must be listed; a defined-but-unlisted step
@@ -776,6 +811,7 @@ There are two strict step variants:
776
811
  | `command` | — | required for check; nonempty key in `[commands]` | Deterministic shell verb to run. Forbidden for agent steps. |
777
812
  | `skill` | — | required for agent; nonempty string | Exact installed verifier skill. Forbidden for check steps. |
778
813
  | `paths` | omitted (unconditional) | optional nonempty array of positive repository-relative globs | Apply only when at least one changed path matches. Available to both variants. |
814
+ | `afterReconcile` | repository: `true`; step: inherited | boolean | Step override wins over `[verify].afterReconcile`; false skips this step after reconciliation only. |
779
815
  | `always` | omitted (`false`) | optional boolean | `true` makes the step mandatory in plan selection and bypasses path gating. Available to both variants. |
780
816
 
781
817
  <!-- config-fragment:verify -->
@@ -1132,6 +1168,7 @@ Optional. Every field receives its own default. All are positive integers except
1132
1168
  | `sessionBudgetSeconds` | `3600` | positive integer | Wall-clock bound for each build agent session unless its logical role overrides it. |
1133
1169
  | `stallRounds` | `3` | positive integer | Escalate when the same review finding survives this many rounds. |
1134
1170
  | `maxVerifyAttempts` | `3` | positive integer | Bound failure-driven verify → implement retry cycles. |
1171
+ | `maxDispatchAttempts` | `3` | positive integer | Bound consecutive dispatch failures before human escalation. |
1135
1172
  | `maxSetupAttempts` | `3` | positive integer | Bound consecutive workspace setup failures before human escalation. |
1136
1173
  | `maxInfrastructureAttempts` | `3` | positive integer | Bound consecutive provision/start/wait/cleanup failures before a setup-targeted policy escalation; a human `retry` answer re-arms this budget. |
1137
1174
  | `maxReconcileAttempts` | `3` | positive integer | Bound completed reconciles that leave the PR conflicted against an unchanged authoritative base. Moving-base races do not consume the bound. |
@@ -1139,6 +1176,15 @@ Optional. Every field receives its own default. All are positive integers except
1139
1176
  | `harvestThreshold` | `5` | positive integer | New unclaimed observation occurrences needed to start one harvest run. |
1140
1177
  | `harvestMaxDrift` | `3` | nonnegative integer | Other builds merged after the oldest unclaimed observation needed to start a run; `0` disables drift. |
1141
1178
 
1179
+ Dispatch failures share a `[policy].maxDispatchAttempts` budget (default 3) across
1180
+ create, workspace, spec, comment, and launch stages. Exhaustion raises one
1181
+ `dispatch-failure-limit` policy escalation naming the latest stage and error,
1182
+ and parks dispatch until answered. Both bare `ab answer <slug>` retries and
1183
+ text guidance grant a fresh budget; abort does not. Confirmed runner attachment
1184
+ clears the streak. Recovery-provider workspace failures belong exclusively to
1185
+ the infrastructure budget. Exhaustion is repaired from durable facts on restart
1186
+ before further dispatch work.
1187
+
1142
1188
  For each repeat conflict, Autobuild compares the base merged by the most recent
1143
1189
  completed reconcile with a fresh authoritative base snapshot. An advanced base
1144
1190
  proves the attempt lost a race and permits another reconcile regardless of the
@@ -1189,9 +1235,10 @@ state. Within a present table, `source` and `readyState` are required.
1189
1235
  | `readyState` | — | required nonblank string | The one workflow state eligible for dispatch. |
1190
1236
  | `readyAssignee` | — | optional; `"me"` or an email address; Linear only (rejected for file, hosted, and plugin sources) | Require the ticket to be assigned to one Linear user, in addition to the state and label gates. |
1191
1237
  | `teamKey` | — | required for Linear and hosted; forbidden for file; optional for plugins | Backend team key such as `"ENG"`, or an existing plugin config field. |
1192
- | `claimedState` | `"In Progress"` for direct Linear; hosted backend default otherwise | optional nonempty string; forbidden for file; allowed for plugins | Workflow state entered when a ticket is claimed. |
1238
+ | `claimedState` | `"In Progress"` for direct Linear; `"Doing"` for hosted | optional nonempty string; forbidden for file; allowed for plugins | Workflow state entered when a ticket is claimed. |
1239
+ | `doneState` | `"Done"` | optional nonblank string; hosted | Project completion state and merged-ticket transition. |
1193
1240
  | `createState` | provider default | optional nonempty string | Default state for newly created tickets when `ab ticket create` omits `--state`. |
1194
- | `triageState` | Linear: `"Backlog"`; file/plugin: `"Triage"` | optional nonempty string | State used for spec-gate bounces, aborts, and closed-unmerged PRs. |
1241
+ | `triageState` | Linear/hosted: `"Backlog"`; file/plugin: `"Triage"` | optional nonempty string | State used for spec-gate bounces, aborts, and closed-unmerged PRs. |
1195
1242
  | `proposalState` | the resolved `triageState` | optional nonempty string | State harvest files its synthesized proposals into. Setting it to `readyState` waives the human grooming gate, not dependency eligibility. |
1196
1243
  | `dir` | file: selected local state root's `tickets/`; plugin: omitted | optional nonempty path; forbidden for Linear; allowed for plugins | Root containing file-source state directories, or an existing plugin config field. Relative file paths resolve from the repository. |
1197
1244
 
@@ -1303,17 +1350,18 @@ createState = "Triage"
1303
1350
  triageState = "Triage"
1304
1351
  ```
1305
1352
 
1306
- Set `AB_STORE` to the service's HTTP(S) URL and `AB_TOKEN` to a deployment
1307
- operator token (`mint operator` without `--user`). The same source-agnostic wiring powers dispatch, harvest, ticket-aware
1353
+ Set `AB_STORE` to the service's HTTP(S) URL and `AB_TOKEN` to a stored credential
1354
+ for that registered project (`credentials issue REPOSITORY --user IDENTITY`). The same source-agnostic wiring powers dispatch, harvest, ticket-aware
1308
1355
  skills, and all seven command families: `ab ticket create`, `update`, `block`,
1309
1356
  `unblock`, `list`, `show`, and `move`. See the
1310
- [hosted ticket protocol](remote-ticket-protocol.md). In hosted Linear mode,
1311
- `LINEAR_API_KEY` is configured only on the service; direct Linear remains
1312
- supported.
1357
+ [hosted ticket protocol](remote-ticket-protocol.md). Tickets are partitioned by
1358
+ the credential's registered project; lifecycle comes from its latest effective
1359
+ configuration. The service has no Linear backend and refuses ticket operations
1360
+ for projects using external sources. Direct Linear remains dispatcher-side.
1313
1361
 
1314
1362
  When filing dependent tickets into the ready state, follow the
1315
- [safe blocker-filing workflow](ticket-dependencies.md). Direct and hosted Linear
1316
- create the issue before recording its blockers, so `create --blocked-by` is not
1363
+ [safe blocker-filing workflow](ticket-dependencies.md). Direct Linear
1364
+ creates the issue before recording its blockers, so `create --blocked-by` is not
1317
1365
  an atomic publication guarantee. Stage outside ready, verify the relationships,
1318
1366
  then move to the intended ready state last.
1319
1367
 
@@ -1494,6 +1542,7 @@ sessionBudgetSeconds = 7200
1494
1542
  sessionBudgetSeconds = 3600
1495
1543
  stallRounds = 3
1496
1544
  maxVerifyAttempts = 3
1545
+ maxDispatchAttempts = 3
1497
1546
  maxSetupAttempts = 3
1498
1547
  maxInfrastructureAttempts = 3
1499
1548
  maxReconcileAttempts = 3
@@ -1653,9 +1702,9 @@ Secrets and store selection accompany the file through environment variables:
1653
1702
 
1654
1703
  | Variable | Used for | Notes |
1655
1704
  |---|---|---|
1656
- | `LINEAR_API_KEY` | Direct Linear ticket source | Required and nonempty when `tickets.source = "linear"`; in hosted Linear mode it is service-only. |
1657
- | `AB_STORE` | BuildStore and hosted ticket selection | A local path or HTTP(S) remote-store URL. `tickets.source = "hosted"` requires a nonblank HTTP(S) URL. A command's explicit `--store` wins for BuildStore selection, then nonblank `AB_STORE`, then the main checkout's `.autobuild/`. Relative local paths resolve from the main checkout. |
1658
- | `AB_TOKEN` | Protected remote BuildStore and hosted tickets | Bearer credential forwarded to the service. Hosted tickets require deployment operator scope (`mint operator` without `--user`). Empty means no token; nonempty token bytes are treated as opaque. |
1705
+ | `LINEAR_API_KEY` | Direct Linear ticket source | Required and nonempty when `tickets.source = "linear"`; never used by the hosted service. |
1706
+ | `AB_STORE` | BuildStore and hosted ticket selection | A local path or HTTP(S) remote-store URL. `tickets.source = "hosted"` requires a nonblank HTTP(S) URL. A command's explicit `--store` wins for BuildStore selection, then nonblank `AB_STORE`, then `[store] url`, then the main checkout's `.autobuild/`. Relative local paths resolve from the main checkout. |
1707
+ | `AB_TOKEN` | Protected remote BuildStore and hosted tickets | Bearer credential forwarded to the service. Hosted tickets require a stored credential for a registered project. Owner admin is an explicit break-glass exception. Empty means no token; nonempty token bytes are treated as opaque. |
1659
1708
 
1660
1709
  A local store selection relocates the state database, blobs, worktrees, and the
1661
1710
  default file-ticket directory together. With a remote store, Git worktrees and
@@ -1795,3 +1844,43 @@ progress is not shown. Harvest does not consume build capacity.
1795
1844
  - **Remote BuildStore:** confirm the effective `AB_STORE` URL and `AB_TOKEN`.
1796
1845
  A protected store reports 401 for a missing, invalid, or expired token and
1797
1846
  403 when the token is valid but scoped to another build/session.
1847
+
1848
+ ### Hosted project selection
1849
+
1850
+ The durable PostgreSQL project registry controls dashboard, MCP and operator
1851
+ API access. Every allowlisted user sees every active entry. Administer it with
1852
+ `ab-hosted-store projects list|add|remove` using deployment database credentials;
1853
+ mutations require `--user IDENTITY` for declared attribution. See
1854
+ [connected projects](setup.md#connected-projects) for migration and retention.
1855
+ `AB_DISPATCHER_REPOSITORIES` optionally selects an ordered subset of current
1856
+ registrations. When unset, each authorized tick queries all active entries.
1857
+ Removed credential override keys are inert in dynamic mode; explicit-list
1858
+ configuration still rejects unknown override keys.
1859
+
1860
+ ## Hosted service client compatibility
1861
+
1862
+ A service accepts stable clients from its own `major.minor` release line and
1863
+ the two preceding minor lines in the same major. A major starts a new family:
1864
+ X.0 accepts only X.0, X.1 accepts X.0/X.1, and X.2 onward accepts three lines.
1865
+ Patch versions are interchangeable in both directions and must never change
1866
+ the protocol. Upgrade the service first, then projects independently within
1867
+ its window. Supported release lines retain their wire handlers and known
1868
+ protocol identities; an unknown protocol is refused independently of version.
1869
+ Malformed, missing, and prerelease identities are refused with HTTP 409.
1870
+ A newer client is told to upgrade the service; a client below the window is
1871
+ told to upgrade the project's Autobuild. Diagnostics name the service version
1872
+ and supported range.
1873
+
1874
+ Unauthenticated `GET /health` publishes `ok`, `autobuildVersion`,
1875
+ `protocolVersion`, and `supportedClientRange: { minVersion, maxVersionExclusive }`.
1876
+ The range is inclusive at the minimum and exclusive at the maximum; a 0.11.x
1877
+ service publishes `>=0.9.0 <0.12.0`. Health needs no credentials, version
1878
+ headers, or available backend.
1879
+
1880
+ Remote workspace factories must use `delegateCredential(storeRef, undefined,
1881
+ target)` to obtain per-resource grants before provisioning or launching guests.
1882
+ The callback keeps parent authority in host composition. `storeToken` is retired
1883
+ and is never supplied; factory environments also exclude `AB_TOKEN`. Launch
1884
+ credentials travel through the separate transient execution-auth argument, never
1885
+ through serialized execution identity. Existing plugins that forwarded
1886
+ `storeToken` must migrate to this callback and fail closed if it is unavailable.
@@ -14,6 +14,16 @@ it once a minute by default), so builds no longer depend on a maintainer's
14
14
  machine. A deployment that does not install the package serves the hosted
15
15
  store, ticket, operator, and web endpoints and never attempts a dispatch tick.
16
16
 
17
+ The [minimal hosted service deployment](hosted-service.md) defaults to store-only
18
+ mode and supplies no cron. The source-checkout deployment below retains the
19
+ opt-in dispatcher packing and shipping pipeline.
20
+
21
+ For a dispatcher running as a local user or service manager, first
22
+ [connect the project](hosted-service.md#connect-a-project). Origin-mode dispatch
23
+ can read the committed `[store] url` and the same user credential by origin.
24
+ A deployment running under another identity needs its own project credential;
25
+ `AB_TOKEN` remains the explicit CI override.
26
+
17
27
  ## Enabling the schedule
18
28
 
19
29
  1. Deploy the hosted service with the dispatcher package installed and the
@@ -255,6 +265,11 @@ presents (`sandboxAuth=oidc-header`, `oidc-env`, `token`, or `none`); `none`
255
265
  means the project has OIDC federation disabled (Settings → Security). No
256
266
  long-lived `VERCEL_TOKEN` belongs on the service. Guests never receive a Vercel
257
267
  token or forge credential; they authenticate to the Store with the short-TTL
258
- deployment operator token the dispatcher mints for the tick
268
+ stored project credential the dispatcher issues for each repository tick
259
269
  (`AB_DISPATCHER_TOKEN_TTL_SECONDS`, default 7 days — keep it longer than your
260
- largest `timeoutSeconds` guest lifetime).
270
+ largest `timeoutSeconds` guest lifetime). Its immutable dispatcher purpose
271
+ permits raw Store and hosted tickets only, never operator controls. The service
272
+ checks registration and revocation on each request. Tick return does not revoke
273
+ it; spawned builds may outlive the tick. Owner-issued project credentials and
274
+ the owner-only break-glass admin are documented in the
275
+ [remote Store protocol](remote-store-protocol.md#7-authentication-and-project-authority).
@@ -0,0 +1,214 @@
1
+ # Deploy a hosted service
2
+
3
+ A service hosts the BuildStore, tickets, dashboard, sign-in, and MCP endpoint.
4
+ The default deployment stores state while dispatch runs on another machine.
5
+ You need Bun 1.4.x, a Git repository, a Vercel project, PostgreSQL, a blob
6
+ backend, and a GitHub OAuth application.
7
+
8
+ Create a new directory containing these two files (also shipped in the hosted
9
+ package's `deployment/` directory and in `templates/hosted-service/`).
10
+
11
+ `package.json`:
12
+
13
+ ```json
14
+ {
15
+ "private": true,
16
+ "scripts": {
17
+ "build": "ab-hosted-service-build"
18
+ },
19
+ "dependencies": {
20
+ "@defrex/autobuild-hosted-store-service": "0.12.0",
21
+ "@defrex/autobuild": "0.12.0",
22
+ "next": "16.1.6",
23
+ "react": "19.2.4",
24
+ "react-dom": "19.2.4"
25
+ }
26
+ }
27
+ ```
28
+
29
+ `vercel.json`:
30
+
31
+ ```json
32
+ {
33
+ "$schema": "https://openapi.vercel.sh/vercel.json",
34
+ "framework": "nextjs",
35
+ "installCommand": "bun install --frozen-lockfile",
36
+ "buildCommand": "bun run build",
37
+ "bunVersion": "1.4.x"
38
+ }
39
+ ```
40
+
41
+ Run `bun install`, then commit both files and `bun.lock`. Add `.gitignore`
42
+ entries for `node_modules/`, `.next/`, `.vercel/`, `app/`, `next.config.ts`,
43
+ `next-env.d.ts`, `tsconfig.json`, and `.autobuild-service-assets.json`.
44
+ These application assets are generated during each build. Keep your own files
45
+ outside those names; the builder refuses to overwrite consumer-owned files.
46
+ Do not commit generated application sources or edit them to customize deployment.
47
+
48
+ Push the directory to your Git host. Import it in Vercel with the repository
49
+ root as Root Directory and Next.js as the framework. The checked-in platform
50
+ configuration installs the lockfile and runs `bun run build`. Each build stages
51
+ the installed package, runs its idempotent database migrations against the
52
+ configured database, then builds Next into the root `.next/`. Missing database
53
+ configuration, incompatible schema, or a failed migration stops the build.
54
+ Provide database access from the build environment as well as runtime.
55
+
56
+ ## Connect a project
57
+
58
+ From a checkout with an origin remote, run `ab connect https://YOUR_ORIGIN`.
59
+ The CLI prints a verification URL and short code and attempts to open a browser.
60
+ Sign in as an allowed owner, check the full repository identity and code, and
61
+ approve or deny. Another device can open the printed URL, or visit `/connect`
62
+ and enter the code. Approval requires the web-enabled service origin; a
63
+ standalone machine-only listener cannot present browser consent. Requests
64
+ expire after ten minutes. Denial and timeout leave local configuration unchanged.
65
+
66
+ On approval the service registers the project and issues a project credential.
67
+ The CLI keeps it in `$XDG_CONFIG_HOME/autobuild/credentials` (or
68
+ `~/.config/autobuild/credentials`), keyed by normalized service and origin,
69
+ with directory mode 0700 and file mode 0600. Credentials never belong in the
70
+ repository. It adds the non-secret `[store] url` to `autobuild.toml` when
71
+ absent; review and commit that change yourself. An existing different URL
72
+ must be changed explicitly before connecting. Reconnecting replaces the saved
73
+ credential only after approval and revokes the previous credential.
74
+
75
+ All worktrees share the main checkout's configuration and the user's saved
76
+ credential. A service manager running as that user uses the same HOME and
77
+ XDG_CONFIG_HOME, with no AB_STORE or AB_TOKEN exports. A fresh clone with
78
+ committed store configuration but no saved credential fails with `ab connect`
79
+ guidance; it never silently creates local state. Run connect on each host/user.
80
+
81
+ `ab connect` without a URL reports service, project, and credential status:
82
+ accepted, rejected, missing, or unreachable. `ab disconnect` revokes the saved
83
+ credential remotely before removing it locally, keeping the project registered,
84
+ its data, and store configuration. A network/server failure retains the local
85
+ entry for retry; an already revoked credential can be removed. If saving a new
86
+ connection fails, the CLI cancels/revokes it and retains the previous entry.
87
+ If remote cleanup cannot be confirmed, check the printed credential id with
88
+ the owner's administrative tools before retrying.
89
+
90
+ Store selection is `--store` > nonblank `AB_STORE` > `[store] url` > the
91
+ main checkout's `.autobuild/`. `AB_TOKEN` overrides the exact service/project's
92
+ saved credential for CI and other non-interactive hosts. A service override
93
+ never borrows another service's credential. Build and harvest sessions keep
94
+ their explicit scoped authority.
95
+
96
+ ## Environment
97
+
98
+ Set these server-side variables in every Vercel environment you deploy:
99
+
100
+ - `AB_POSTGRES_URL` or `DATABASE_URL`: PostgreSQL connection URL. The explicit
101
+ `AB_POSTGRES_URL` takes precedence; use the same database at build and runtime.
102
+ - `AB_STORE_SECRET`: a high-entropy secret for machine token signing.
103
+ - `AB_BLOB_BACKEND=vercel` with `BLOB_READ_WRITE_TOKEN`, or `AB_BLOB_BACKEND=s3`
104
+ with `AB_S3_BUCKET`, `AB_S3_REGION`, `AB_S3_ACCESS_KEY_ID`, and
105
+ `AB_S3_SECRET_ACCESS_KEY` (and optional endpoint for compatible storage).
106
+ - `BETTER_AUTH_SECRET`: an independent high-entropy secret of at least 32 characters.
107
+ - `BETTER_AUTH_URL`: the exact production HTTPS origin, with no path.
108
+ - `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`: GitHub OAuth application credentials.
109
+ - `AB_WEB_AUTH_PROVIDERS=github`.
110
+ - `AB_WEB_ALLOWED_EMAILS`: comma-separated operator email allowlist.
111
+ - `AB_WEB_REPOSITORIES`: comma-separated repository origins visible to operators.
112
+
113
+ Register `https://YOUR_ORIGIN/api/auth/callback/github` as the GitHub callback
114
+ and allow read-only email access (`user:email`). Sign-in must use the configured
115
+ production origin. Do not expose secrets through `NEXT_PUBLIC_` variables.
116
+ See the [complete environment reference](configuration.md) for storage,
117
+ project ticket lifecycle, credential administration, and optional MCP resource configuration.
118
+
119
+ ## Check the deployment
120
+
121
+ `GET /health` returns service and protocol versions. Open `/sign-in` and sign
122
+ in; `/` redirects unauthenticated browsers to sign-in. Machine routes
123
+ require a machine token; `/mcp` challenges an unauthenticated client with OAuth
124
+ resource metadata. Check both discovery documents:
125
+
126
+ ```sh
127
+ curl https://YOUR_ORIGIN/.well-known/oauth-protected-resource
128
+ curl https://YOUR_ORIGIN/.well-known/oauth-authorization-server
129
+ ```
130
+
131
+ The authorization-server document advertises a reachable `/api/auth/jwks`
132
+ URI and `/api/auth/mcp/register` dynamic registration endpoint. MCP clients
133
+ register with `redirect_uris` as an array of strings and
134
+ `token_endpoint_auth_method: "none"`; treat the returned `client_id` as opaque.
135
+ See [MCP](mcp.md) for authorization and client setup.
136
+
137
+ Without the optional hosted dispatcher installed, `GET /api/dispatch` returns
138
+ 503 JSON: `{"error":"dispatch runs elsewhere for this deployment"}`. The
139
+ template has no cron and installs no dispatcher/provider package. Run dispatch
140
+ from a connected project or follow the separate, opt-in
141
+ [hosted dispatcher procedure](hosted-dispatcher.md), which retains its
142
+ source-checkout packaging and cron setup.
143
+
144
+ ## Upgrade
145
+
146
+ Bump the exact hosted-service and core package pins in `package.json` to the
147
+ same new release, run `bun install` to refresh `bun.lock`, commit, and push.
148
+ Vercel's Git integration migrates and builds the pinned package automatically.
149
+ Upgrade the service **before connected projects**: that is the order expected
150
+ by the protocol compatibility window. No source extraction, patch application,
151
+ or manual production deployment is required.
152
+
153
+ ## Hosted ticket projects and v1 migration
154
+
155
+ Hosted queues are partitioned by registered repository. Credentials select the
156
+ project, and its latest uploaded effective `[tickets]` configuration selects the
157
+ lifecycle; see the [ticket protocol](remote-ticket-protocol.md). If configuration
158
+ is unavailable, run that project's dispatcher to publish it. Projects using file,
159
+ Linear or another external source continue to access it directly through their
160
+ dispatcher. Service ticket surfaces refuse those projects with source feedback.
161
+ The service never connects to Linear.
162
+
163
+ The removed `AB_TICKET_BACKEND` and `LINEAR_API_KEY` settings are ignored for
164
+ all values, including blanks, with one startup message asking for removal.
165
+ The removed `AB_TICKET_TRIAGE_STATE`, `AB_TICKET_READY_STATE`,
166
+ `AB_TICKET_DOING_STATE` and `AB_TICKET_DONE_STATE` settings are ignored even if
167
+ malformed. Lifecycle now comes only from project configuration.
168
+
169
+ The environment-aware migration first imports `AB_WEB_REPOSITORIES` into the
170
+ registry once, including upgrades from released main schema v9 where registry
171
+ tables do not yet exist. This import and the main-schema promotion share the
172
+ ticket transaction: a stopped ticket migration rolls them back too. The owner
173
+ `projects add` and `tickets assign-team` commands can independently bootstrap
174
+ the registry while that old main marker remains, so recovery does not require
175
+ a successful ticket migration. If the retired environment is unavailable,
176
+ register the intended project with `ab-hosted-store projects add REPOSITORY
177
+ --user IDENTITY` before assigning its legacy team. Once the import succeeds,
178
+ later changes to the retired environment are ignored.
179
+
180
+ Fresh databases install ticket schema v2 directly. Existing team-keyed v1
181
+ queues migrate in one transaction. For each registered project, migration reads
182
+ the effective configuration selected by its latest dispatcher run and reloads,
183
+ including the actual immutable artifact bytes in blob storage. A legacy team
184
+ must have one claimant, or an explicit owner assignment. A registered project
185
+ with no dispatcher run or no selected effective-config reference makes no team
186
+ claim and does not block other projects. Unclaimed teams still stop unchanged.
187
+ When ownership must be inferred, a selected but missing, inaccessible or invalid
188
+ configuration fails closed with the project and remaining teams in the message.
189
+ Restore those bytes or explicitly assign every remaining legacy team, then
190
+ rerun; complete assignments to registered projects settle ownership without
191
+ reading unrelated configurations. Conflicting claimants and asset ownership
192
+ conflicts also stop without changing schema markers or tickets.
193
+ Historical ids, states, timestamps, blockers, comments and asset rows remain
194
+ unchanged. Review the named projects and resolve a shared legacy team locally:
195
+
196
+ ```sh
197
+ ab-hosted-store tickets assign-team ENG https://github.com/acme/app --user owner@example.com
198
+ ab-postgres-store migrate
199
+ ```
200
+
201
+ The assignment command works before the v2 migration succeeds and records the
202
+ actor in an append-only journal. The latest assignment moves the whole legacy
203
+ team once; it grants no future cross-project access. It must name a registered
204
+ project. Rerun migration after resolving every named team or configuration error.
205
+
206
+ For populated-v1 upgrades, both the owner migration CLI and `deploy:build` need
207
+ the same blob backend, access, credentials and `AB_BLOB_PREFIX` as the running
208
+ service: S3 bucket/region/credentials (and endpoint/path style when configured),
209
+ or Vercel access mode and token/OIDC store identity. The deployment build inherits
210
+ these variables and runs the environment-aware migration before Next build.
211
+ Missing blob configuration or denied reads stop deployment before Next builds;
212
+ diagnostics never print credentials. SQL-only fresh installs, empty-v1 upgrades
213
+ and already-v2 reruns do not open blob storage and keep their existing database
214
+ requirements. Migration reruns are idempotent.
package/docs/mcp.md CHANGED
@@ -110,8 +110,14 @@ the standard surface the endpoint serves.
110
110
  the default MCP resource's base.
111
111
  - `AB_WEB_ALLOWED_EMAILS`: the operator allowlist. Sign-in admission and every
112
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).
113
+ - `AB_WEB_REPOSITORIES` (deprecated): optional once-per-database registry
114
+ bootstrap, never an ongoing allowlist. `repositories.list` queries current
115
+ active registrations on every call, and every tool checks current membership.
116
+
117
+ Every allowlisted user sees every registered project. Database-authorized
118
+ owners use the [project commands](setup.md#connected-projects) to add or remove
119
+ projects without redeploying. Removal hides retained data until re-registration;
120
+ registry failures never authorize a call.
115
121
 
116
122
  The endpoint requires no additional variables beyond the hosted service's
117
123
  existing web/auth set. Note that consent renders when the client sends
@@ -6,17 +6,48 @@ Every request must send `X-Autobuild-Version`,
6
6
  `X-Autobuild-Protocol-Version: 3`, and `Authorization: Bearer …`. Identity is
7
7
  checked before authentication or resource lookup.
8
8
 
9
- Mint a token offline; the signed user is used as the human actor on every write:
9
+ Project credentials are opaque `abp_1_<id>_<secret>` bearer values bound to one
10
+ registered normalized repository. The service stores only a SHA-256 digest of
11
+ the random secret, plus project, creator, creation time, last use, expiry and
12
+ revocation metadata. It checks credential status and project registration on
13
+ every request. Revocation takes effect on the next request without affecting
14
+ other credentials. Unknown, expired or revoked credentials receive `401 auth`;
15
+ removed projects receive `403 auth` with `project not registered`.
16
+
17
+ The deployment owner runs these commands locally with database access:
10
18
 
11
19
  ```sh
12
- AB_STORE_SECRET='…' bun packages/hosted-store-service/src/bin.ts mint operator \
13
- --user 'Ada Lovelace' --ttl-seconds 3600
20
+ ab-hosted-store credentials issue https://github.com/acme/app --user owner@example.com
21
+ ab-hosted-store credentials list https://github.com/acme/app
22
+ ab-hosted-store credentials revoke https://github.com/acme/app --id ID --user owner@example.com
14
23
  ```
15
24
 
16
- Admin, build, repository, and deployment (`{ "operator": true, … }`) tokens
17
- have no attributed operator authority. Human-operator tokens have no raw
18
- `/builds`, `/repos`, `/sessions`, or `/tickets` authority. Encode repository,
19
- build, session, and artifact kind as separate URL path segments.
25
+ Issue prints the secret once; list contains metadata only. Issue optionally
26
+ accepts `--ttl-seconds N` or `--expires-at ISO-8601`. For administrative/non-interactive issuance, set the returned token as
27
+ `AB_TOKEN`. Interactive project onboarding uses `ab connect <service-url>`;
28
+ it saves the credential outside the repository and commits no secret. Previous `AB_TOKEN` values and `mint operator` / `mint build` are
29
+ retired: the service responds that the credential format changed and points
30
+ to `credentials issue`. Supported old protocol shapes still work with a new
31
+ credential; protocol compatibility does not restore retired authentication.
32
+
33
+ `mint admin --ttl-seconds N` creates an explicit versioned, attributed owner
34
+ break-glass credential. It can cross project boundaries. Keep it owner-only:
35
+ never give it or `AB_STORE_SECRET` to a dispatcher or build agent.
36
+
37
+ Owner-purpose project credentials attribute every write to their recorded creator.
38
+ Dispatcher-purpose credentials are refused. Signed-in owners in the dashboard
39
+ and MCP receive short-lived internal signatures with an explicit version,
40
+ selected repository, signed user, expiry and MCP `via`. Only the service holds
41
+ the signing key. These signatures authorize operator routes only and require a
42
+ registered project; replay against another project is refused.
43
+
44
+ The path project is checked before registry lookup, body parsing and generic
45
+ tool execution. Body repositories and secondary build/session identifiers are
46
+ also checked inside that project. Explicit project mismatches return `403 auth:
47
+ wrong project`; removal returns `403 auth: project not registered`. Encode each
48
+ repository, build, session and artifact kind as a separate URL path segment.
49
+ Registration changes use the database-local `projects list|add|remove` owner
50
+ commands; see [owner workflow](setup.md#connected-projects).
20
51
 
21
52
  ## Operator sessions
22
53
 
@@ -201,3 +232,55 @@ identity, plus the usual repo/build/state/cursor fields. It invents no durable
201
232
  sequence or actor. Retain the cursor to avoid repeat reports across invocations.
202
233
  No lease alone is not evidence of death. SIGINT, SIGHUP and SIGTERM drain dispatch,
203
234
  record the first signal in the stop fact, and release its lease before exit.
235
+
236
+ ### CLI attention monitoring
237
+
238
+ The default watch reports actionable conditions only:
239
+
240
+ - Build `escalation.raised`, or `build.merge-decision`: a finalized open PR
241
+ with auto-merge off or a current actionable merge-gate deferral. Armed consent
242
+ awaiting application is silent. One merge decision is reported per build/PR.
243
+ - Repository `harvest.escalated`, `harvest.recovery-exhausted`,
244
+ `dispatcher.config-rejected`, `dispatcher.config-reloaded` with nonempty
245
+ `restartRequired`, abnormal `dispatcher.run-stopped`, and `dispatcher.lost`.
246
+ - `dispatcher.ticket-source-persistent`: degradation past its advertised
247
+ reset time, once a tick started at or after reset has completed or failed
248
+ with the episode still open; crossing reset alone is silent. Without a reset,
249
+ the deadline is 15 minutes after onset. Repeated evidence never postpones
250
+ the deadline; recovery ends the episode.
251
+ - `dispatcher.tick-failure-persistent`: continuously failing completed ticks
252
+ for five minutes in one run. A successful tick, new run, or matching stop
253
+ ends the episode. Source grace and cooldown-driven failures do not start
254
+ this clock; after grace, the first qualifying failure starts a new five-minute
255
+ interval. Each persistence episode is reported once.
256
+
257
+ Repository conditions are included with no slugs or with `--repository`.
258
+ Routine outcomes and self-healing failures are silent. Repeatable
259
+ `--event <glob>` replaces the default and selects exactly matching catalog
260
+ events (plus the existing `dispatcher.lost` observation); use explicit globs
261
+ to opt into failures, finalize, merge, or completion events.
262
+
263
+ Derived records use `observation` instead of `event`, without inventing a
264
+ durable sequence or actor. Merge observations carry `type`, `pr`, `reason`,
265
+ and `observedAt`, plus the build slug and normal reduced state. Persistence
266
+ observations carry source/cause/message/resetAt when advertised (or
267
+ run/firstFailure/error), `episodeSeq`, and `observedAt`; repository records
268
+ have `repo` and null build/state. Source observations also report
269
+ `dependencyLimited`.
270
+
271
+ Every record carries a cursor, including timer-only records. Resuming suppresses
272
+ delivered merge decisions and persistence episodes even when stream positions
273
+ did not advance. Old v1 cursors remain valid: historical eligible merge
274
+ decisions are treated as consumed; active overdue repository episodes may
275
+ report because old watches had no persistence notification. Fresh attachment
276
+ silently baselines historical direct events and merge decisions, but retains
277
+ active repository timers. Reconcile existing conditions through status after
278
+ arming the watch. Named watches still end when every named build is terminal;
279
+ learn their outcomes from status reconciliation at exit.
280
+
281
+ `ab wait --for attention` is build-only and uses the same build conditions:
282
+ an open escalation or a merge decision. Existing conditions return immediately,
283
+ even with `--since`; live and backlog matches ignore routine outcomes and
284
+ self-healing failures. A merge match uses the watch observation with
285
+ `condition: "attention"`. Explicit `--event` globs remain event-only.
286
+ Watch and wait preserve each other's cursor notification metadata.