@defrex/autobuild 0.11.0 → 0.13.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 (136) hide show
  1. package/README.md +3 -1
  2. package/SPEC.md +335 -31
  3. package/docs/architecture.md +125 -7
  4. package/docs/configuration.md +142 -21
  5. package/docs/hosted-dispatcher.md +17 -2
  6. package/docs/hosted-service.md +240 -0
  7. package/docs/mcp.md +8 -2
  8. package/docs/operator-api.md +97 -7
  9. package/docs/remote-store-protocol.md +199 -144
  10. package/docs/remote-ticket-protocol.md +249 -19
  11. package/docs/setup.md +199 -16
  12. package/docs/ticket-dependencies.md +1 -1
  13. package/docs/ticket-migration.md +64 -0
  14. package/package.json +7 -6
  15. package/packages/core/src/cli/attention.ts +209 -0
  16. package/packages/core/src/cli/build-control.ts +12 -1
  17. package/packages/core/src/cli/configured-store.ts +17 -0
  18. package/packages/core/src/cli/connect.ts +364 -0
  19. package/packages/core/src/cli/context.ts +7 -1
  20. package/packages/core/src/cli/dashboard/model.ts +4 -5
  21. package/packages/core/src/cli/dashboard/poll.ts +1 -1
  22. package/packages/core/src/cli/dispatch.ts +336 -41
  23. package/packages/core/src/cli/harvest.ts +13 -3
  24. package/packages/core/src/cli/help.ts +51 -16
  25. package/packages/core/src/cli/init-validation.ts +27 -2
  26. package/packages/core/src/cli/installation.ts +67 -13
  27. package/packages/core/src/cli/main.ts +60 -16
  28. package/packages/core/src/cli/mcp.ts +9 -2
  29. package/packages/core/src/cli/pr-summary-publication.ts +220 -0
  30. package/packages/core/src/cli/pr-summary.ts +2 -0
  31. package/packages/core/src/cli/project-credentials.ts +132 -0
  32. package/packages/core/src/cli/repo-state.ts +15 -2
  33. package/packages/core/src/cli/self-update.ts +15 -1
  34. package/packages/core/src/cli/store-opening.ts +22 -14
  35. package/packages/core/src/cli/terminals.ts +0 -10
  36. package/packages/core/src/cli/ticket-assets.ts +7 -2
  37. package/packages/core/src/cli/ticket-editor.ts +82 -0
  38. package/packages/core/src/cli/ticket.ts +172 -35
  39. package/packages/core/src/cli/upgrade-compatibility.ts +80 -0
  40. package/packages/core/src/cli/wait.ts +84 -17
  41. package/packages/core/src/cli/watch.ts +197 -60
  42. package/packages/core/src/config/live.ts +3 -0
  43. package/packages/core/src/config/schema.ts +90 -11
  44. package/packages/core/src/connection.ts +86 -0
  45. package/packages/core/src/events/catalog.ts +11 -2
  46. package/packages/core/src/events/payloads.ts +50 -0
  47. package/packages/core/src/events/repository.ts +13 -0
  48. package/packages/core/src/integration/harness.ts +37 -2
  49. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +145 -83
  50. package/packages/core/src/kernel/engine.ts +24 -0
  51. package/packages/core/src/kernel/generators/build-log.ts +11 -1
  52. package/packages/core/src/kernel/incremental-fixtures.ts +13 -2
  53. package/packages/core/src/kernel/log-index.ts +17 -0
  54. package/packages/core/src/kernel/pr-summary.ts +89 -0
  55. package/packages/core/src/kernel/reducer-registry.ts +10 -0
  56. package/packages/core/src/kernel/reducer.ts +8 -0
  57. package/packages/core/src/ontology.ts +2 -0
  58. package/packages/core/src/operator/annotations.ts +12 -0
  59. package/packages/core/src/operator/index.ts +12 -0
  60. package/packages/core/src/operator/protocol.ts +19 -2
  61. package/packages/core/src/operator/query.ts +38 -16
  62. package/packages/core/src/operator/registry.ts +61 -8
  63. package/packages/core/src/operator/ticket-source-backend.ts +17 -20
  64. package/packages/core/src/operator/tickets.ts +136 -28
  65. package/packages/core/src/plugin-sdk/index.ts +7 -0
  66. package/packages/core/src/plugins/manifest.ts +5 -3
  67. package/packages/core/src/plugins/runtimes.ts +7 -1
  68. package/packages/core/src/ports/forge/fake.ts +13 -1
  69. package/packages/core/src/ports/forge/github-transport.ts +36 -1
  70. package/packages/core/src/ports/forge/github.ts +29 -5
  71. package/packages/core/src/ports/forge/local-git.ts +12 -4
  72. package/packages/core/src/ports/forge/summary-marker.ts +8 -0
  73. package/packages/core/src/ports/runner/session-env.ts +3 -0
  74. package/packages/core/src/ports/tickets/contract.ts +66 -1
  75. package/packages/core/src/ports/tickets/create.ts +54 -10
  76. package/packages/core/src/ports/tickets/fake.ts +30 -1
  77. package/packages/core/src/ports/tickets/file-assets.ts +159 -0
  78. package/packages/core/src/ports/tickets/file-codec.ts +90 -0
  79. package/packages/core/src/ports/tickets/file.ts +114 -120
  80. package/packages/core/src/ports/tickets/grants.ts +47 -0
  81. package/packages/core/src/ports/tickets/hosted-index.ts +17 -3
  82. package/packages/core/src/ports/tickets/identity.ts +20 -0
  83. package/packages/core/src/ports/tickets/lifecycle.ts +18 -0
  84. package/packages/core/src/ports/tickets/linear.ts +74 -9
  85. package/packages/core/src/ports/tickets/migration.ts +336 -0
  86. package/packages/core/src/ports/tickets/operations.ts +38 -7
  87. package/packages/core/src/ports/tickets/portable-assets.ts +54 -0
  88. package/packages/core/src/ports/tickets/remote-protocol.ts +96 -0
  89. package/packages/core/src/ports/tickets/remote.ts +61 -3
  90. package/packages/core/src/ports/tickets/requested-id.ts +46 -0
  91. package/packages/core/src/ports/tickets/revision.ts +62 -0
  92. package/packages/core/src/ports/tickets/states.ts +61 -0
  93. package/packages/core/src/ports/types.ts +40 -3
  94. package/packages/core/src/ports/workspace/build-execution.ts +6 -1
  95. package/packages/core/src/ports/workspace/create.ts +15 -2
  96. package/packages/core/src/ports/workspace/distribution-archive.ts +3 -5
  97. package/packages/core/src/ports/workspace/harvest-execution.ts +3 -1
  98. package/packages/core/src/ports/workspace/local-build-execution.ts +8 -1
  99. package/packages/core/src/ports/workspace/operator-sandbox.ts +2 -0
  100. package/packages/core/src/ports/workspace/vercel-sandbox.ts +51 -19
  101. package/packages/core/src/processes/build-execution-state.ts +1 -1
  102. package/packages/core/src/processes/build-runner.ts +87 -26
  103. package/packages/core/src/processes/dispatch-failure-budget.ts +83 -0
  104. package/packages/core/src/processes/dispatcher.ts +168 -41
  105. package/packages/core/src/processes/execution-no-progress.ts +176 -0
  106. package/packages/core/src/processes/harvest-child.ts +31 -4
  107. package/packages/core/src/processes/harvest-execution-state.ts +19 -2
  108. package/packages/core/src/processes/harvest-runner.ts +166 -104
  109. package/packages/core/src/processes/harvest.ts +34 -14
  110. package/packages/core/src/processes/phase-failure-budget.ts +2 -0
  111. package/packages/core/src/processes/publication-loss.ts +18 -1
  112. package/packages/core/src/processes/repo-view.ts +35 -0
  113. package/packages/core/src/store/contract.ts +32 -1
  114. package/packages/core/src/store/local/store.ts +1 -1
  115. package/packages/core/src/store/remote/client.ts +1 -1
  116. package/packages/core/src/store/remote/compatibility.ts +90 -0
  117. package/packages/core/src/store/remote/credentials.ts +28 -0
  118. package/packages/core/src/store/remote/index.ts +14 -1
  119. package/packages/core/src/store/remote/protocol.ts +25 -0
  120. package/packages/core/src/store/remote/token.ts +74 -13
  121. package/packages/core/src/store/ticket-asset-snapshots.ts +192 -0
  122. package/packages/core/src/store/ticket-assets.ts +86 -8
  123. package/packages/core/src/store/types.ts +1 -1
  124. package/packages/core/src/testing/index.ts +7 -0
  125. package/skills/guide/SKILL.md +232 -34
  126. package/skills/guide/references/operator-api.md +142 -13
  127. package/skills/guide/references/plugin-authoring.md +10 -2
  128. package/skills/guide/references/remote-store-protocol.md +199 -144
  129. package/skills/guide/references/remote-ticket-protocol.md +253 -18
  130. package/skills/guide/references/setup.md +199 -16
  131. package/skills/guide/references/ticket-dependencies.md +1 -1
  132. package/skills/guide/references/ticket-migration.md +64 -0
  133. package/skills/operate/SKILL.md +126 -36
  134. package/skills/tickets/SKILL.md +71 -0
  135. package/templates/hosted-service/package.json +13 -0
  136. package/templates/hosted-service/vercel.json +7 -0
@@ -0,0 +1,240 @@
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.13.0",
21
+ "@defrex/autobuild": "0.13.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. Independent ticket policy selects lifecycle; `ab connect --tickets URL`
157
+ publishes it before dispatch. Older clients retain uploaded dispatcher configuration
158
+ as a fallback. See the [ticket protocol](remote-ticket-protocol.md). 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.
215
+
216
+ ## Local builds with hosted tickets
217
+
218
+ Hosted tickets do not require hosted builds. Set `[tickets] source = "hosted"`
219
+ and `teamKey = "ENG"`, then run `ab connect --tickets https://your-service.example`.
220
+ Approve the project in the browser. This saves a service-scoped credential and
221
+ `[tickets] url` while leaving `[store]` unchanged. Ticket commands work immediately,
222
+ before any dispatcher runs. `ab connect` reports every service and whether it serves
223
+ builds, tickets, or both. Use `ab disconnect --tickets` to revoke ticket access;
224
+ when services differ use `--builds` to select build access. Shared uses share one
225
+ credential, so disconnecting either shared use affects both.
226
+
227
+ Ticket URL selection is `[tickets] url`, then the selected build service (including
228
+ AB_STORE or a supported --store override). With local builds, `[tickets] url` is
229
+ required. Builds, events, artifacts, and harvest journals remain in the local store.
230
+ The service stores ticket records, project ticket policy, and restricted ticket
231
+ grants. Normal project clients synchronize current `[tickets]` policy before use;
232
+ failed synchronization fails the operation. Harvest grants can read and create;
233
+ phase grants can only read. Neither can change policy or access BuildStore routes.
234
+
235
+ New hosted policy defaults to `triage`, `ready`, `doing`, `done`, with creation and
236
+ harvest proposals in `triage`. Explicit state names preserve their spelling.
237
+ To retain earlier names configure `triageState = "Backlog"`,
238
+ `claimedState = "Doing"`, `doneState = "Done"`, and the desired `readyState`.
239
+ Existing ticket rows are never renamed. Older combined-service clients continue
240
+ using the uploaded dispatcher configuration until independent policy is published.
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
 
@@ -56,6 +87,7 @@ turn runner (a later ticket) is the only producer of turn facts.
56
87
  | `GET …/harvest/status` | Harvest gate, runs, steps, recovery, and attention projection. |
57
88
  | `GET …/builds/{slug}/artifacts/{kind}?rev=N` | Raw bytes (`application/octet-stream`) plus content-disposition and `X-Autobuild-Artifact-*` metadata headers. Omit `rev` for latest. |
58
89
  | `GET …/tickets?state=S&label=L` | `{states,tickets,diagnostics,criteria,triageState,readyState}`. The two lifecycle names come from effective repository configuration (including provider fallback). With no `state`, `criteria.state` is `triageState`; repeated `label` parameters narrow that state with AND filters. An explicit `state` selects that exact backend workflow state. |
90
+ | `GET …/tickets/states` | Full state metadata `{name,tickets,about?}[]`, including empty states (`tickets.states` in MCP). |
59
91
  | `GET …/tickets/{id}` | `{ticket,blockers,build}`. Blockers include native `exists`/`resolved` status; `build` prefers an active matching repository build, otherwise the most recently updated one. |
60
92
 
61
93
  For artifact reads, an absent `rev` selects the latest revision. A supplied value
@@ -88,6 +120,7 @@ JSON bodies are strict: unknown fields are rejected.
88
120
  | `POST …/settings/auto-merge-default/toggle` | Empty body. |
89
121
  | `POST …/bulk-control` | `{"action":"pause"\|"resume"}`. The hold fact, intake fact, then eligible build events are written in that order. |
90
122
  | `POST …/harvest/control` | `{"action":"toggle-gate"}` or `{"action":"run","run":"…"}`. A run action is bound to that concrete projected run. |
123
+ | `POST …/tickets/states` | `{name,about?}` → `{state,created}`; requires attributed operator identity (`tickets.state.create` in MCP). Hosted duplicate names are refused case-insensitively. |
91
124
  | `POST …/tickets` | `{"title":"…","body":"…","labels"?:[],"state"?:"…","blockedBy"?:[]}`; omitted state preserves the backend default. |
92
125
  | `PATCH …/tickets/{id}` | Any nonempty subset of `title`, `body`, and complete-replacement `labels`. Body bytes are not normalized. |
93
126
  | `POST …/tickets/{id}/move` | `{"state":"backend state name"}`. |
@@ -201,3 +234,60 @@ identity, plus the usual repo/build/state/cursor fields. It invents no durable
201
234
  sequence or actor. Retain the cursor to avoid repeat reports across invocations.
202
235
  No lease alone is not evidence of death. SIGINT, SIGHUP and SIGTERM drain dispatch,
203
236
  record the first signal in the stop fact, and release its lease before exit.
237
+
238
+ ### CLI attention monitoring
239
+
240
+ The default watch reports actionable conditions only:
241
+
242
+ - Build `escalation.raised`, or `build.merge-decision`: a finalized open PR
243
+ with auto-merge off or a current actionable merge-gate deferral. Armed consent
244
+ awaiting application is silent. One merge decision is reported per build/PR.
245
+ - Repository `harvest.escalated`, `harvest.recovery-exhausted`,
246
+ `dispatcher.config-rejected`, `dispatcher.config-reloaded` with nonempty
247
+ `restartRequired`, abnormal `dispatcher.run-stopped`, and `dispatcher.lost`.
248
+ - `dispatcher.ticket-source-persistent`: degradation past its advertised
249
+ reset time, once a tick started at or after reset has completed or failed
250
+ with the episode still open; crossing reset alone is silent. Without a reset,
251
+ the deadline is 15 minutes after onset. Repeated evidence never postpones
252
+ the deadline; recovery ends the episode.
253
+ - `dispatcher.tick-failure-persistent`: continuously failing completed ticks
254
+ for five minutes in one run. A successful tick, new run, or matching stop
255
+ ends the episode. Source grace and cooldown-driven failures do not start
256
+ this clock; after grace, the first qualifying failure starts a new five-minute
257
+ interval. Each persistence episode is reported once.
258
+
259
+ Repository conditions are included with no slugs or with `--repository`.
260
+ Routine outcomes and self-healing failures are silent. Repeatable
261
+ `--event <glob>` replaces the default and selects exactly matching catalog
262
+ events (plus the existing `dispatcher.lost` observation); use explicit globs
263
+ to opt into failures, finalize, merge, or completion events.
264
+
265
+ Derived records use `observation` instead of `event`, without inventing a
266
+ durable sequence or actor. Merge observations carry `type`, `pr`, `reason`,
267
+ and `observedAt`, plus the build slug and normal reduced state. Persistence
268
+ observations carry source/cause/message/resetAt when advertised (or
269
+ run/firstFailure/error), `episodeSeq`, and `observedAt`; repository records
270
+ have `repo` and null build/state. Source observations also report
271
+ `dependencyLimited`.
272
+
273
+ Every record carries a cursor, including timer-only records. Resuming suppresses
274
+ delivered merge decisions and persistence episodes even when stream positions
275
+ did not advance. Old v1 cursors remain valid: historical eligible merge
276
+ decisions are treated as consumed; active overdue repository episodes may
277
+ report because old watches had no persistence notification. Fresh attachment
278
+ silently baselines historical direct events and merge decisions, but retains
279
+ active repository timers. Reconcile existing conditions through status after
280
+ arming the watch. Named watches still end when every named build is terminal;
281
+ learn their outcomes from status reconciliation at exit.
282
+
283
+ `ab wait --for attention` is build-only and uses the same build conditions:
284
+ an open escalation or a merge decision. Existing conditions return immediately,
285
+ even with `--since`; live and backlog matches ignore routine outcomes and
286
+ self-healing failures. A merge match uses the watch observation with
287
+ `condition: "attention"`. Explicit `--event` globs remain event-only.
288
+ Watch and wait preserve each other's cursor notification metadata.
289
+
290
+ Hosted ticket tools resolve independent project ticket policy before legacy uploaded
291
+ configuration. Queue, detail, state discovery and mutations work before any build
292
+ or dispatcher run; detail reports a null matching build. Ticket policy publication
293
+ never creates synthetic build or repository history.