@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
@@ -9,21 +9,38 @@ and the implementation passes the shared `BuildStore` contract exported by
9
9
 
10
10
  The transport, wire shapes, validation order, atomicity, lease behavior,
11
11
  persistence requirements, and conformance bar for the protocol shipped in this
12
- distribution are all defined here. A protocol implementation must target the
13
- same Autobuild package version used by its clients; protocol changes update this
14
- document and the package's executable contracts together.
12
+ distribution are all defined here. Protocol implementations must retain contracts for every supported client line;
13
+ protocol changes update this document and executable contracts together.
14
+
15
+ A service accepts stable clients from its own `major.minor` release line and
16
+ the two preceding minor lines in the same major. A major starts a new family:
17
+ X.0 accepts only X.0, X.1 accepts X.0/X.1, and X.2 onward accepts three lines.
18
+ Patch versions are interchangeable in both directions and must never change
19
+ the protocol. Upgrade the service first, then projects independently within
20
+ its window. Supported release lines retain their wire handlers and known
21
+ protocol identities; an unknown protocol is refused independently of version.
22
+ Malformed, missing, and prerelease identities are refused with HTTP 409.
23
+ A newer client is told to upgrade the service; a client below the window is
24
+ told to upgrade the project's Autobuild. Diagnostics name the service version
25
+ and supported range.
26
+
27
+ Unauthenticated `GET /health` publishes `ok`, `autobuildVersion`,
28
+ `protocolVersion`, and `supportedClientRange: { minVersion, maxVersionExclusive }`.
29
+ The range is inclusive at the minimum and exclusive at the maximum; a 0.11.x
30
+ service publishes `>=0.9.0 <0.12.0`. Health needs no credentials, version
31
+ headers, or available backend.
15
32
 
16
33
  ## 1. Transport and conventions
17
34
 
18
35
  - The transport is JSON over HTTP or HTTPS. There is no API version prefix.
19
- Point Autobuild at the server's base URL with `--store`, or set that URL in
20
- `AB_STORE`; the shipped client appends the routes below.
36
+ Point Autobuild at the server's base URL with `--store`, `AB_STORE`, or
37
+ `[store] url` (in that precedence order, before the local default); the shipped client appends the routes below.
21
38
  - Every `/builds`, `/repos`, and `/sessions` request sends
22
39
  `X-Autobuild-Version` with the exact client package version and
23
40
  `X-Autobuild-Protocol-Version` with the remote protocol version (`3` in this
24
41
  distribution). Before authentication,
25
- body parsing, or resource lookup, the server compares both exact strings.
26
- Missing or different values receive `409 {"kind":"conflict","error":"…"}`;
42
+ body parsing, or resource lookup, the server validates the release-line window and known protocol identity.
43
+ Unsupported or missing values receive `409 {"kind":"conflict","error":"…"}`;
27
44
  the diagnostic names the client and server package and protocol versions.
28
45
  - Clients send `Content-Type: application/json` for requests with bodies. Every
29
46
  response, including errors and `null` artifact results, has
@@ -184,7 +201,7 @@ and section 7).
184
201
 
185
202
  Those event catalogs evolve with Autobuild's build lifecycle rather than with
186
203
  the storage transport. A server therefore must use or faithfully implement the
187
- catalog from the same Autobuild package version as its client. The public
204
+ catalog behavior required by every supported client release line. The public
188
205
  `BuildStore` type (including its typed write methods), representative valid
189
206
  writes, and unchanged contract suite exported by `@defrex/autobuild/plugin-sdk` are the
190
207
  package-versioned compatibility surface. Wire validation remains mandatory even
@@ -290,8 +307,8 @@ it to exist. An unknown build returns `404 not-found`.
290
307
 
291
308
  | BuildStore operation | HTTP route | Request | Success |
292
309
  |---|---|---|---|
293
- | `createBuild` | `POST /builds` | `{slug: string, repo: string, repoOrigin?: string, ticket?: TicketRef, branch?: string, created?: event write}`; `slug`, `repo`, and a supplied `branch` are nonempty. `created`, when present, must be a valid `build.created` write; the server appends it as seq 1 atomically with the record (both exist or neither), and an invalid one is `422` with nothing created | `201` + `BuildRecord`; duplicate slug is `409 conflict` |
294
- | `listBuilds` | `GET /builds` | none | `200` + `BuildRecord[]`; list order is unspecified |
310
+ | `createBuild` | `POST /builds` | `{slug: string, repo: string, repoOrigin?: string, ticket?: TicketRef, branch?: string, created?: event write}`; `slug`, `repo`, and a supplied `branch` are nonempty. `created`, when present, must be a valid `build.created` write; the server appends it as seq 1 atomically with the record (both exist or neither), and an invalid one is `422` with nothing created | `201` + authoritative `BuildRecord`; project credentials receive a fresh random-suffixed slug and matching branch. Owner admin/open mode keeps exact-slug semantics with `409 conflict` for duplicates |
311
+ | `listBuilds` | `GET /builds` | none | `200` + `BuildRecord[]`, filtered by the server to the credential project; owner admin may list all projects. List order is unspecified |
295
312
  | `getBuild` | `GET /builds/{slug}` | none | `200` + `BuildRecord`; absent is `404` (the shipped client maps this to `null`) |
296
313
  | `append` | `POST /builds/{slug}/events` | event write | `201` + build event envelope |
297
314
  | `appendIfCurrent` | `POST /builds/{slug}/events/conditional` | `{"expectedSeq": nonnegative integer, "event": event write}` | appended: `201` + build event envelope; stream advanced: `200 null` |
@@ -361,8 +378,7 @@ no protocol-version bump. The store, not the client, enforces the invariants:
361
378
  A client treats `404` and `405` from either route as "no snapshot", so a client
362
379
  newer than its store replays the log with no error, and a client older than its
363
380
  store never calls the routes. Authentication and transport failures still
364
- surface normally. A build token may touch only its own build's snapshots and a
365
- repository token only its own repository's, exactly as for events.
381
+ surface normally. Snapshot authorization follows the owning project, exactly as for events.
366
382
 
367
383
  ### The event-read bounded wait
368
384
 
@@ -399,7 +415,7 @@ artifacts, and leases. `{repo}` is one percent-encoded repository id; encode
399
415
  embedded `/` characters. All resource routes authorize and require the
400
416
  repository to exist. An unknown repository returns `404 not-found` — with one
401
417
  carve-out: the build-digest batch read of this section is keyed to build
402
- records, not the repository journal record, so it authorizes the repo token
418
+ records, not the repository journal record, so it authorizes the project credential
403
419
  but answers `200` (possibly empty) even for an unknown repository, a repository
404
420
  without a journal record, or a repository without builds.
405
421
 
@@ -451,7 +467,7 @@ reading every build's full history per evaluation. That is a breaking wire
451
467
  change, not an additive one: a server that predates it returns the old shape
452
468
  and would fail the client's schema parse mid-request, which is why the version
453
469
  moved — a stale peer is now rejected at the handshake (`409 conflict`) instead.
454
- This is the same discipline the transport's exact-match version rules promise:
470
+ This is the same discipline the transport's supported-window rules promise:
455
471
  wire shapes that independently implemented servers must reproduce update this
456
472
  document and the protocol version together.
457
473
 
@@ -466,12 +482,11 @@ a ticket, keyed by `(repo, ticketId, kind, name)`. These routes live under the
466
482
  repository but, unlike the journal routes, **do not require a repository
467
483
  record**: assets can precede any journal row, so the repository-existence gate
468
484
  does not apply. `{repo}` and `{id}` are percent-encoded path segments. Token
469
- authorization follows the one-matching-repo column of the matrix (a repo token
470
- for its own repository, or admin); build and session tokens receive `403 auth`.
485
+ authorization requires a credential for that registered project, or owner admin.
471
486
  The family is additive, so `REMOTE_STORE_PROTOCOL_VERSION` did not change.
472
487
 
473
488
  A build also reads the assets it froze at its claim through its own build-scoped
474
- routes, which a build token may use (no ticket-asset authority needed). The
489
+ routes, authorized against the build's actual project. The
475
490
  server reduces the build's events to find the pinned revision, then returns that
476
491
  exact revision, so a later replace or removal never changes the answer and a
477
492
  finished build still answers:
@@ -785,140 +800,148 @@ These additions were purely additive in the version they landed in (protocol
785
800
  conforming server implements the stream routes together with this document's
786
801
  rules exactly as it does for the event and artifact routes.
787
802
 
788
- ## 7. Authentication and token scope
803
+ ## 7. Authentication and project authority
789
804
 
790
- ### Open and authenticated modes
805
+ Project credentials are opaque `abp_1_<id>_<secret>` bearer values bound to one
806
+ registered normalized repository. The service stores only a SHA-256 digest of
807
+ the random secret, plus project, creator, creation time, last use, expiry and
808
+ revocation metadata. It checks credential status and project registration on
809
+ every request. Revocation takes effect on the next request without affecting
810
+ other credentials. Unknown, expired or revoked credentials receive `401 auth`;
811
+ removed projects receive `403 auth` with `project not registered`.
791
812
 
792
- A server configured without a signing secret is in open mode: no route checks
793
- a token or event session attribution. This is intended for local development
794
- and the open contract harness.
813
+ The deployment owner runs these commands locally with database access:
795
814
 
796
- A server configured with a secret requires this header on every `/builds`,
797
- `/repos`, and `/sessions` route:
798
-
799
- ```http
800
- Authorization: Bearer <token>
815
+ ```sh
816
+ ab-hosted-store credentials issue https://github.com/acme/app --user owner@example.com
817
+ ab-hosted-store credentials list https://github.com/acme/app
818
+ ab-hosted-store credentials revoke https://github.com/acme/app --id ID --user owner@example.com
801
819
  ```
802
820
 
803
- `GET /health` is always unauthenticated. Package/protocol identity validation
804
- occurs first on machine routes. Authorization then occurs before request body
805
- processing on collection routes and before resource lookup on resource routes.
806
- A caller with the wrong scope therefore cannot use existence or validation
807
- differences to inspect another resource.
808
-
809
- ### Token encoding
821
+ Issue prints the secret once; list contains metadata only. Issue optionally
822
+ accepts `--ttl-seconds N` or `--expires-at ISO-8601`. For administrative/non-interactive issuance, set the returned token as
823
+ `AB_TOKEN`. Interactive project onboarding uses `ab connect <service-url>`;
824
+ it saves the credential outside the repository and commits no secret. Previous `AB_TOKEN` values and `mint operator` / `mint build` are
825
+ retired: the service responds that the credential format changed and points
826
+ to `credentials issue`. Supported old protocol shapes still work with a new
827
+ credential; protocol compatibility does not restore retired authentication.
810
828
 
811
- A token has two dot-separated base64url segments:
812
-
813
- ```text
814
- base64url(UTF-8 JSON scope) + "." + base64url(HMAC-SHA256(secret, payloadSegment))
815
- ```
829
+ `mint admin --ttl-seconds N` creates an explicit versioned, attributed owner
830
+ break-glass credential. It can cross project boundaries. Keep it owner-only:
831
+ never give it or `AB_STORE_SECRET` to a dispatcher or build agent.
816
832
 
817
- The HMAC input is the first token segment exactly as encoded, not the decoded
818
- JSON bytes. The signature is compared in constant time. The scope JSON must
819
- match exactly one of these shapes; unknown keys are invalid:
833
+ Service-owned signatures use `base64url(JSON scope) + "." +
834
+ base64url(HMAC-SHA256(AB_STORE_SECRET, payloadSegment))`. Signatures are checked
835
+ in constant time. Current strict scope shapes are:
820
836
 
821
837
  ```jsonc
822
- // Legacy build scope, retained for wire compatibility
823
- { "build": "build-slug", "session": "s_123", "exp": 1784116800000 }
824
-
825
- // Explicit resource scope
826
- {
827
- "resource": { "kind": "build", "id": "build-slug" },
828
- "session": "s_123",
829
- "exp": 1784116800000
830
- }
831
-
832
- {
833
- "resource": { "kind": "repo", "id": "acme/autobuild" },
834
- "session": "hs_123",
835
- "exp": 1784116800000
836
- }
837
-
838
- {
839
- "resource": { "kind": "session", "id": "os_123" },
840
- "session": "*",
841
- "exp": 1784116800000
842
- }
838
+ { "version": 1, "ownerAdmin": { "user": "owner identity" }, "exp": 1800000000000 }
839
+ { "version": 1, "internalOperator": { "user": "owner identity", "repo": "https://github.com/acme/app" }, "exp": 1800000000000 }
843
840
  ```
844
841
 
845
- `build`, resource `id`, and `session` are nonempty strings. A resource or
846
- operator scope may carry an optional `via` claim (the same shape as the
847
- actor's `via` marker). `exp` is an integer
848
- Unix epoch in **milliseconds**. A token is expired when `exp <=` the server's
849
- current epoch milliseconds. A malformed token, malformed scope, bad signature,
850
- or expired token is invalid and receives `401 auth` without a more specific
851
- verification diagnostic.
852
-
853
- The legacy scope `{"build":"*", ...}` is the admin resource scope. The
854
- normal runner/admin form also uses `"session":"*"`, allowing event writes on
855
- behalf of any valid actor. Only the legacy `build: "*"` spelling grants admin
856
- resource access; an explicit resource whose id is `"*"` is not admin.
857
-
858
- ### Resource authorization matrix
859
-
860
- | Token resource | `/builds` create/list | one matching build | `/repos` ensure | one matching repo | `/repos/{repo}/sessions` create/list | `/repos/{repo}/build-digests` | `/repos/{repo}/state-events` | `/repos/{repo}/tickets/{id}/assets` and `/repos/{repo}/ticket-asset-limits` | one matching session |
861
- |---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
862
- | admin (`build: "*"`) | yes | any | yes | any | yes | any | any | any | any |
863
- | build | no | exact id only | no | no | no | no | no | no | no |
864
- | repo | no | no | no | exact id only | yes | exact id only | exact id only | exact repo only | no |
865
- | session | no | no | no | no | no | no | no | no | exact id only |
866
-
867
- The snapshot routes (`/builds/{slug}/snapshots/{reducer}` and
868
- `/repos/{repo}/snapshots/{reducer}`) follow the one-matching-build and
869
- one-matching-repo columns: a build token reads and writes only its own build's
870
- snapshots, a repo token only its own repository's.
871
-
872
- A valid token used for the wrong resource receives `403 auth`. Resource scope
873
- gates all operations, including reads, artifact operations, and leases. A
874
- session resource token's authority is exactly its one session — it cannot
875
- create or list sessions, and it cannot touch another session's events,
876
- artifacts, or streams. A repo token owns its repository's session collection
877
- routes but not any session resource. Session resource and repo/build tokens
878
- never cross.
879
-
880
- The token's session dimension adds a second gate only to event-bearing writes:
881
- `POST .../events`, `POST .../events/conditional`, and `POST .../deposits`.
882
-
883
- - `session: "*"` may submit any actor that the event catalog itself allows.
884
- - Any other session may submit only an actor with `kind: "agent"` and a
885
- `session` exactly equal to the token session.
886
- - Another agent session or a kernel, human, dispatcher, or ingester actor
887
- receives `403 auth` before catalog validation.
888
- - Reads, standalone artifact puts, and lease operations do not apply session
889
- attribution beyond the resource-scope check.
890
-
891
- ### Delegated-write attribution (`via`)
892
-
893
- On every event-bearing write (`POST .../events`, `POST .../events/conditional`,
894
- and `POST .../deposits` on build, repository, and session resources), a server
895
- in authenticated mode enforces `via` attribution after token verification and
896
- before catalog validation:
897
-
898
- - A human actor claiming a `via` the token does not carry is `403 auth` — the
899
- caller must be able to distinguish "not your delegate" from "malformed
900
- event", which is why this is authority (403) and not validation (422).
901
- - A token carrying a `via` claim stamps that `via` onto every human actor in
902
- the write: the token is authoritative, and a matching claim or no claim both
903
- end with the token's `via` on the stored event.
904
- - A non-human actor carrying a `via` key is not an authority question; it falls
905
- through to catalog validation, which rejects it with the rule message.
906
- - An unscoped operator token carries no `via` and never authorizes raw store
907
- routes at all.
908
-
909
- In open mode there is no token to be authoritative and writes pass through
910
- unchanged. Events without a `via` member are stored, replayed, and read
911
- byte-identically to before.
912
-
913
- Build and repository scopes never cross: a harvest repository token cannot
914
- read a build stream, and a build token cannot read a repository journal.
915
- Attributed human-operator scopes have
916
- `{ "operator": { "user": "nonblank identity" }, "exp": … }`. They are accepted
917
- only by the [operator API](operator-api.md), never by raw store or ticket routes;
918
- conversely, deployment/admin/build/repository scopes are refused there.
919
- Deployment scopes have `{ "operator": true, "session": "*", "exp": … }` and
920
- cover raw store and [hosted ticket](remote-ticket-protocol.md) routes without
921
- granting attributed operator controls.
842
+ Internal operator scopes may also carry `via`, using the actor's existing
843
+ shape. Unknown fields are invalid. `exp` is epoch milliseconds and expiry is
844
+ inclusive (`exp <= now` is refused). These signatures are separate from opaque
845
+ project credentials and are never project onboarding credentials.
846
+
847
+ Every machine route requires Bearer authentication after version negotiation;
848
+ `GET /health` stays public. Explicitly unauthenticated local test servers may
849
+ remain open. Internal owner operator signatures cannot use raw store routes.
850
+
851
+ A project credential may read and write its repository journal, artifacts,
852
+ snapshots, leases, ticket assets, builds, sessions and streams. Explicit repo
853
+ paths and create bodies must match its project (`403 auth: wrong project`).
854
+ Indirect build/session/stream IDs are resolved internally and checked against
855
+ the resource's actual repository. Missing and foreign IDs both return the same
856
+ `404 not-found` error: `resource unavailable: unknown resource or wrong project`.
857
+ Remote clients continue mapping addressed GET 404 responses to null. Nested
858
+ streams must also belong to the addressed parent.
859
+
860
+ `GET /builds` is filtered by the server to the credential's project. Project
861
+ `POST /builds` always allocates a fresh random-suffixed slug from the requested
862
+ base and a matching `ab/<slug>` branch, even when the base is unused. The
863
+ returned BuildRecord slug and branch are authoritative for all subsequent
864
+ work. A foreign or missing base returns null on GET; it neither reveals
865
+ collisions nor blocks creation. Owner admin retains exact-slug creation and
866
+ all-project listing. Local BuildStore adapters retain exact-slug semantics.
867
+
868
+ Project credentials retain runner-style actor/session attribution within the
869
+ project; phase CLI scoped handles still constrain build and session access.
870
+ Authenticated raw writes cannot claim a delegated `via`. Owner-project
871
+ credentials also use their recorded creator for operator writes. Dispatcher
872
+ credentials have an immutable server-side purpose: raw store and tickets only,
873
+ never human operator controls. The hosted dispatcher issues one per project
874
+ invocation, expiring at its configured TTL, and does not revoke at tick return
875
+ for dispatcher plumbing. Spawned builds receive independent delegated grants. Shared content-addressed blobs
876
+ remain deduplicated; this is resource authorization, not blob partitioning.
877
+
878
+
879
+ ### Delegated build and harvest credentials
880
+
881
+ `POST /credentials/delegate` uses the standard identity headers and Bearer
882
+ machine authentication. Its strict target is one of:
883
+
884
+ - `{kind: "build-runner", id}` for an existing build;
885
+ - `{kind: "build-phase", id, session}` after its `session.started` fact;
886
+ - `{kind: "harvest-runner", repo, id, execution, leaseHolder}` after trusted
887
+ preparation has atomically claimed the scan and recorded
888
+ `harvest.execution.authorized` for that run/execution and adopted holder;
889
+ - `{kind: "harvest-phase", repo, id, session}` from its runner, after the
890
+ matching `harvest.session.started` fact.
891
+
892
+ The response is `{token}` only. Project credentials can issue only within their
893
+ registered project. A runner grant may issue only an exact phase grant within
894
+ its own lifecycle binding; phase grants cannot delegate. Unknown targets are
895
+ refused. Tokens and parent secrets never become artifacts, launch envelopes or
896
+ execution facts. Local-store execution requires no exchange; remote execution
897
+ fails closed when issuance fails.
898
+
899
+ The signed strict shape is `{version: 2, delegation: {repo, kind, id, anchor,
900
+ session?, execution?, leaseHolder?}}`. `anchor` is the authoritative event
901
+ sequence; harvest grants additionally bind the authorized execution. Session
902
+ claims exist only on phase grants. No fixed `exp` applies to this shape:
903
+ signature/schema verification precedes registered-project and lifecycle checks
904
+ on every request, with no authorization cache. Failure to read lifecycle state
905
+ fails closed. Parent credential expiry does not expire an already-issued grant.
906
+ Project removal refuses all its grants immediately.
907
+
908
+ Build grants live until 60 seconds after durable build termination. Phase grants
909
+ also retire 60 seconds after their own session ends, whichever comes first.
910
+ Harvest grants retire 60 seconds after their run stops or their authorized
911
+ execution ends/releases; harvest phases additionally retire on session end.
912
+ The boundary is exclusive: at `end + 60_000ms` the credential is refused. This
913
+ window permits final transcripts, diagnostics, deposits and stream closure.
914
+ Pause/escalation of a build and lease expiry do not end authority. Active grants
915
+ survive hours-long turns, parent expiry and dispatcher/service reconstruction.
916
+ A durable ending never resurrects: recovered harvest work gets a new
917
+ `harvest.execution.authorized` identity and token.
918
+
919
+ Delegated same-project reads remain available, including journals, artifacts,
920
+ digests, bounded state, pinned assets and collections. Writes by a build grant
921
+ are confined to its build's events/deposits/artifacts, snapshots, leases and
922
+ streams. Phase grants also reject agent actors naming another session, while
923
+ preserving the CLI's trusted kernel terminals. Delegated credentials cannot
924
+ create builds/operator sessions, change ticket assets or repository controls,
925
+ or invoke operator APIs. Harvest writes require the exact run in event payloads
926
+ and artifact metadata; artifacts use the harvest kinds. Every artifact in an
927
+ atomic deposit is checked before writing. Harvest streams have an immutable
928
+ `harvest:<run>:<execution>:` label namespace. Addressed streams resolve their actual owner;
929
+ a guessed ID cannot bypass the boundary. Only the runner can heartbeat or re-acquire its exact
930
+ adopted repository lease holder; another current holder still fences it. Recovery acknowledgements remain dispatcher-side.
931
+
932
+ Hosted ticket access is gated by grant kind. Build-runner and build-phase grants
933
+ cannot call ticket routes. Harvest-phase grants may call only list-ready, get
934
+ and dependency-states; harvest-runner grants may additionally create proposals.
935
+ Other ticket mutations require project or admin authority. A denied operation
936
+ returns 403 naming the grant kind and operation before parsing or source construction.
937
+ Ticket admission and repository-keyed assets use the authenticated project;
938
+ delegated harvest grants retain that same project boundary.
939
+
940
+ Workspace plugin factories receive a host-only `delegateCredential` callback
941
+ instead of a project token; their environment excludes `AB_TOKEN`. Provisioning
942
+ and launch must obtain per-resource authority before running guest code and pass
943
+ launch tokens separately from durable execution identity. Remote providers must
944
+ not serialize parent credentials or fall back to forwarding them.
922
945
 
923
946
  ## 8. Atomic deposits
924
947
 
@@ -977,7 +1000,7 @@ The shipped server maps failures as follows:
977
1000
  |---:|---|---|
978
1001
  | `400` | `validation` | Invalid JSON, request body schema, malformed percent-encoding, missing/empty required `kind`, a `since`/`rev` value whose JavaScript `Number` conversion is not an integer, or a `wait` value that is not one or more ASCII digits |
979
1002
  | `401` | `auth` | Missing bearer credentials or an invalid, malformed, badly signed, or expired token |
980
- | `403` | `auth` | Valid token with the wrong resource scope or event-session attribution |
1003
+ | `403` | `auth` | Wrong project, project not registered, or disallowed credential purpose |
981
1004
  | `404` | `not-found` | Unknown route, unsupported method, unknown build, unknown repository, unknown session, or an unknown or foreign-scoped stream |
982
1005
  | `409` | `conflict` | Missing/mismatched package or protocol identity, duplicate build creation, appending to a closed stream (`stream "…" is closed`), or a backing conflict reported as already existing |
983
1006
  | `413` | `validation` | Decoded artifact or serialized stream batch exceeds the configured ceiling; a ticket-asset request or response exceeds the deployment's request ceiling; the message names the ceiling |
@@ -1103,7 +1126,8 @@ Four shipped behaviors do not add `BuildStore` routes:
1103
1126
  store lifecycle; there is no close endpoint.
1104
1127
  - `GET /health` is outside the `BuildStore` interface. It is always open and
1105
1128
  returns exactly `200 {"ok":true,"autobuildVersion":"<package>",
1106
- "protocolVersion":"3"}`. It reports HTTP-process availability and identity,
1129
+ "protocolVersion":"3", "supportedClientRange":{"minVersion":"0.9.0",
1130
+ "maxVersionExclusive":"0.12.0"}}`. It reports HTTP-process availability and identity,
1107
1131
  not a deeper backing-store transaction or migration check.
1108
1132
 
1109
1133
  There is no push/WebSocket subscription protocol, repository listing, artifact
@@ -1151,10 +1175,31 @@ Add protocol-specific tests alongside the shared contract for token encoding
1151
1175
  and resource isolation, session-attributed writes, exact validation feedback,
1152
1176
  atomic placeholder substitution, event paging, unauthenticated health, and
1153
1177
  continuity across server restarts. The session family (section 5), the
1154
- session-scoped token's authority matrix, and the `via` stamp/reject order
1155
- (section 7) are part of the contract suite and of the protocol-specific cases. Exercise all routes through HTTP, not by
1178
+ project credential boundary, revocation, and delegated-write attribution are part of the contract suite and of the protocol-specific cases. Exercise all routes through HTTP, not by
1156
1179
  calling the backing store directly.
1157
1180
 
1158
1181
  Passing only route smoke tests is not conformance. The complete shared contract
1159
1182
  driven through an HTTP-backed client, plus the protocol-specific cases above,
1160
1183
  is the required compatibility test.
1184
+
1185
+ ## 13. Project connection protocol
1186
+
1187
+ `ab connect <url>` uses `POST /connect/v1/requests` with the normalized origin,
1188
+ a random credential id and SHA-256 digest of a CLI-held secret. The response
1189
+ contains a ten-minute request id, separate private polling capability, short
1190
+ browser code, verification URL and five-second interval. `POST /connect/v1/poll`
1191
+ and `/cancel` require id and capability in JSON; the browser code cannot poll.
1192
+ Poll returns pending, approved, denied or expired plus immutable repository
1193
+ and credential id, never the secret. Cancel denies pending work or revokes
1194
+ only that request's already approved credential. Polling and authenticated
1195
+ browser code lookup have durable limits; 429 includes Retry-After.
1196
+
1197
+ The web-enabled origin serves `/connect` and cookie-authenticated JSON
1198
+ `POST /connect/v1/browser/lookup` and `/decide`. Decisions read the saved
1199
+ proposal, require an allowed live Better Auth session and exact Origin, and
1200
+ atomically register the project, install the digest and record the outcome.
1201
+ GET cannot approve. `GET /connect/v1/status` and `POST /connect/v1/revoke`
1202
+ require a project credential and operate only on its own identity; admin and
1203
+ internal operator credentials cannot impersonate self-service. All protocol
1204
+ responses are no-store/no-referrer. Connection commands are unavailable in
1205
+ build/harvest sessions.
@@ -2,22 +2,71 @@
2
2
 
3
3
  The hosted ticket protocol exposes the complete `TicketSource` port without
4
4
  exposing backend credentials. It shares the Autobuild and remote protocol
5
- version headers with the remote BuildStore. Both must exactly match the service;
6
- this release uses remote protocol version `3`.
5
+ version headers with the remote BuildStore. The version must be in the
6
+ published service window and the protocol must
7
+ match its known release-line identity (`3` for 0.9, 0.10, and 0.11).
8
+
9
+ A service accepts stable clients from its own `major.minor` release line and
10
+ the two preceding minor lines in the same major. A major starts a new family:
11
+ X.0 accepts only X.0, X.1 accepts X.0/X.1, and X.2 onward accepts three lines.
12
+ Patch versions are interchangeable in both directions and must never change
13
+ the protocol. Upgrade the service first, then projects independently within
14
+ its window. Supported release lines retain their wire handlers and known
15
+ protocol identities; an unknown protocol is refused independently of version.
16
+ Malformed, missing, and prerelease identities are refused with HTTP 409.
17
+ A newer client is told to upgrade the service; a client below the window is
18
+ told to upgrade the project's Autobuild. Diagnostics name the service version
19
+ and supported range.
20
+
21
+ Unauthenticated `GET /health` publishes `ok`, `autobuildVersion`,
22
+ `protocolVersion`, and `supportedClientRange: { minVersion, maxVersionExclusive }`.
23
+ The range is inclusive at the minimum and exclusive at the maximum; a 0.11.x
24
+ service publishes `>=0.9.0 <0.12.0`. Health needs no credentials, version
25
+ headers, or available backend.
7
26
 
8
27
  ## Authentication and context
9
28
 
10
- Every request is `POST /tickets/<operation>`, has `Content-Type:
11
- application/json`, and requires `Authorization: Bearer <token>`. Only a signed,
12
- unexpired **deployment operator** token (`{ "operator": true, … }`) is accepted.
13
- Attributed human-operator, legacy admin, build, repository, and session tokens
14
- remain valid for their own routes but are denied on `/tickets`. Mint a deployment
15
- operator token offline with:
29
+ Every request is `POST /tickets/<operation>`, has `Content-Type: application/json`,
30
+ and requires `Authorization: Bearer <project credential>`. Authentication and
31
+ registration checks precede body parsing and source construction. Owner admin
32
+ is the explicit cross-project exception; internal operator signatures have no
33
+ ticket authority.
34
+
35
+ Project credentials are opaque `abp_1_<id>_<secret>` bearer values bound to one
36
+ registered normalized repository. The service stores only a SHA-256 digest of
37
+ the random secret, plus project, creator, creation time, last use, expiry and
38
+ revocation metadata. It checks credential status and project registration on
39
+ every request. Revocation takes effect on the next request without affecting
40
+ other credentials. Unknown, expired or revoked credentials receive `401 auth`;
41
+ removed projects receive `403 auth` with `project not registered`.
42
+
43
+ The deployment owner runs these commands locally with database access:
16
44
 
17
45
  ```sh
18
- AB_STORE_SECRET='…' ab-hosted-store mint operator --ttl-seconds 3600
46
+ ab-hosted-store credentials issue https://github.com/acme/app --user owner@example.com
47
+ ab-hosted-store credentials list https://github.com/acme/app
48
+ ab-hosted-store credentials revoke https://github.com/acme/app --id ID --user owner@example.com
19
49
  ```
20
50
 
51
+ Issue prints the secret once; list contains metadata only. Issue optionally
52
+ accepts `--ttl-seconds N` or `--expires-at ISO-8601`. For administrative/non-interactive issuance, set the returned token as
53
+ `AB_TOKEN`. Interactive project onboarding uses `ab connect <service-url>`;
54
+ it saves the credential outside the repository and commits no secret. Previous `AB_TOKEN` values and `mint operator` / `mint build` are
55
+ retired: the service responds that the credential format changed and points
56
+ to `credentials issue`. Supported old protocol shapes still work with a new
57
+ credential; protocol compatibility does not restore retired authentication.
58
+
59
+ `mint admin --ttl-seconds N` creates an explicit versioned, attributed owner
60
+ break-glass credential. It can cross project boundaries. Keep it owner-only:
61
+ never give it or `AB_STORE_SECRET` to a dispatcher or build agent.
62
+
63
+ Hosted tickets, comments, blockers, creation-key deduplication and assets belong
64
+ to exactly one registered project, even when projects use the same team key.
65
+ Project credentials select that repository. An optional `context.repo` selector
66
+ must match the credential's project; mismatches are 403. Owner admin requests
67
+ require this selector and registration is checked before configuration is read.
68
+ Internal operator credentials remain forbidden on machine ticket routes.
69
+
21
70
  The strict request envelope is:
22
71
 
23
72
  ```json
@@ -31,12 +80,30 @@ The strict request envelope is:
31
80
  }
32
81
  ```
33
82
 
34
- `teamKey` is required. The state overrides are optional. They come from the
35
- requesting repository's `[tickets]` configuration and are applied only to that
36
- request; the service does not cache a team-bound adapter. `list-ready` also
37
- receives that repository's exact ready state and label criteria in `input`.
38
- Backend credentials, including `LINEAR_API_KEY`, are service environment only
39
- and never occur in this envelope or any response.
83
+ `teamKey` remains required for older client compatibility. It and the optional
84
+ `claimedState` / `createState` fields are ignored as policy inputs. The service
85
+ loads the project's latest effective configuration from its repository journal
86
+ and artifact bytes for every operation. Missing or invalid configuration returns
87
+ an actionable 409; run the project's dispatcher to publish its configuration.
88
+ There is no deployment-wide lifecycle fallback.
89
+
90
+ Hosted lifecycle defaults are triage `Backlog`, claimed `Doing`, done `Done`,
91
+ and create/proposal equal to triage. `readyState` is required. The allowed states
92
+ are the distinct union of these configured roles, including custom create and
93
+ proposal states; roles may overlap. Historical states remain readable unchanged.
94
+ `tickets.doneState` controls completion and the dispatcher's merged transition.
95
+
96
+ The service never contacts Linear or relays external trackers. If the project's
97
+ source is Linear, file or another non-hosted source, machine HTTP, operator and
98
+ MCP ticket surfaces refuse with a message naming that project and source: its
99
+ tickets live in its own source, accessed directly by its dispatcher. This is a
100
+ 409 conflict, not an empty queue. The dispatcher's direct adapters are unchanged.
101
+
102
+ `AB_TICKET_BACKEND` and `LINEAR_API_KEY` are removed service settings. Their
103
+ presence (even blank or unknown values) is ignored and emits one startup removal
104
+ message. `AB_TICKET_TRIAGE_STATE`, `AB_TICKET_READY_STATE`,
105
+ `AB_TICKET_DOING_STATE` and `AB_TICKET_DONE_STATE` are removed and ignored,
106
+ including malformed values. These retired settings do not prevent startup.
40
107
 
41
108
  ## Operations
42
109
 
@@ -74,11 +141,32 @@ environment values, or credentials. Client 401/403 responses become
74
141
  `AuthError`; other backend messages remain actionable.
75
142
 
76
143
  The shared `TicketSource` contract is normative for the database adapter and
77
- the hosted client/server pair. External pass-through is tested with a fake
78
- source behind HTTP. The existing opt-in live Linear contract remains the proof
79
- of Linear's GraphQL behavior; the hosted service uses that adapter unchanged.
144
+ the hosted client/server pair. PostgreSQL migration and isolation tests prove
145
+ project partitions. The direct Linear client retains its separate opt-in
146
+ GraphQL contract; no Linear adapter ships through the hosted ticket entry point.
80
147
 
81
148
  Every ticket returned by list, get, or create requires a boolean `resolved`,
82
149
  with the same source-native completion semantics as `DependencyState.resolved`.
83
150
  The client preserves it when remapping ticket references. Missing or non-boolean
84
- values fail wire validation. Client and service still require exact version matches.
151
+ values fail wire validation. Client and service follow the published compatibility window.
152
+
153
+ Legacy 0.9.x and 0.10.x ticket responses omit `resolved` and `blockersResolved`
154
+ because those clients use strict decoders without these fields. This projection
155
+ applies to list-ready, get, and create responses. Current clients retain the
156
+ current required resolution fields; domain and storage types are unchanged.
157
+
158
+ ## Delegated execution credentials
159
+
160
+ Build and harvest runners exchange project authority for signed, project-bound
161
+ execution and phase credentials through `/credentials/delegate` (see the remote
162
+ store protocol). Build-runner and build-phase credentials cannot call any ticket
163
+ operation. Harvest-phase credentials permit only list-ready, get and
164
+ dependency-states. Harvest-runner credentials permit those reads plus create
165
+ for filing proposals. Claim, comment, transition, update, add-blocker and
166
+ remove-blocker require project or admin authority; their access is unchanged.
167
+ Denied delegated calls return 403 with an error naming the grant kind and
168
+ operation before body parsing or source construction. Lifecycle retirement and
169
+ project removal are also checked before parsing or source construction.
170
+
171
+ Ticket admission and assets use the authenticated project, including for
172
+ delegated harvest grants.