@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
@@ -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,21 @@ 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
 
488
+ Hosted ticket attachments use the ticket service's existing repository-keyed
489
+ asset routes with the ticket connection's URL and credential, independently
490
+ of the build store. Names, revisions, tombstones and service limits are
491
+ unchanged. Existing service attachments need no migration; old local
492
+ attachments are not moved or used as a fallback. A claim or ticket spec re-pin
493
+ copies exact revisions into immutable chunked repository artifacts in the
494
+ destination build store. Build pins reference explicit snapshot manifests;
495
+ build-scoped asset reads need no ticket credentials. Old pins without snapshot
496
+ references keep their existing ticket-row lookup.
497
+
473
498
  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
499
+ routes, authorized against the build's actual project. The
475
500
  server reduces the build's events to find the pinned revision, then returns that
476
501
  exact revision, so a later replace or removal never changes the answer and a
477
502
  finished build still answers:
@@ -785,140 +810,148 @@ These additions were purely additive in the version they landed in (protocol
785
810
  conforming server implements the stream routes together with this document's
786
811
  rules exactly as it does for the event and artifact routes.
787
812
 
788
- ## 7. Authentication and token scope
789
-
790
- ### Open and authenticated modes
813
+ ## 7. Authentication and project authority
791
814
 
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.
815
+ Project credentials are opaque `abp_1_<id>_<secret>` bearer values bound to one
816
+ registered normalized repository. The service stores only a SHA-256 digest of
817
+ the random secret, plus project, creator, creation time, last use, expiry and
818
+ revocation metadata. It checks credential status and project registration on
819
+ every request. Revocation takes effect on the next request without affecting
820
+ other credentials. Unknown, expired or revoked credentials receive `401 auth`;
821
+ removed projects receive `403 auth` with `project not registered`.
795
822
 
796
- A server configured with a secret requires this header on every `/builds`,
797
- `/repos`, and `/sessions` route:
823
+ The deployment owner runs these commands locally with database access:
798
824
 
799
- ```http
800
- Authorization: Bearer <token>
825
+ ```sh
826
+ ab-hosted-store credentials issue https://github.com/acme/app --user owner@example.com
827
+ ab-hosted-store credentials list https://github.com/acme/app
828
+ ab-hosted-store credentials revoke https://github.com/acme/app --id ID --user owner@example.com
801
829
  ```
802
830
 
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
810
-
811
- A token has two dot-separated base64url segments:
831
+ Issue prints the secret once; list contains metadata only. Issue optionally
832
+ accepts `--ttl-seconds N` or `--expires-at ISO-8601`. For administrative/non-interactive issuance, set the returned token as
833
+ `AB_TOKEN`. Interactive project onboarding uses `ab connect <service-url>`;
834
+ it saves the credential outside the repository and commits no secret. Previous `AB_TOKEN` values and `mint operator` / `mint build` are
835
+ retired: the service responds that the credential format changed and points
836
+ to `credentials issue`. Supported old protocol shapes still work with a new
837
+ credential; protocol compatibility does not restore retired authentication.
812
838
 
813
- ```text
814
- base64url(UTF-8 JSON scope) + "." + base64url(HMAC-SHA256(secret, payloadSegment))
815
- ```
839
+ `mint admin --ttl-seconds N` creates an explicit versioned, attributed owner
840
+ break-glass credential. It can cross project boundaries. Keep it owner-only:
841
+ never give it or `AB_STORE_SECRET` to a dispatcher or build agent.
816
842
 
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:
843
+ Service-owned signatures use `base64url(JSON scope) + "." +
844
+ base64url(HMAC-SHA256(AB_STORE_SECRET, payloadSegment))`. Signatures are checked
845
+ in constant time. Current strict scope shapes are:
820
846
 
821
847
  ```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
- }
848
+ { "version": 1, "ownerAdmin": { "user": "owner identity" }, "exp": 1800000000000 }
849
+ { "version": 1, "internalOperator": { "user": "owner identity", "repo": "https://github.com/acme/app" }, "exp": 1800000000000 }
843
850
  ```
844
851
 
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.
852
+ Internal operator scopes may also carry `via`, using the actor's existing
853
+ shape. Unknown fields are invalid. `exp` is epoch milliseconds and expiry is
854
+ inclusive (`exp <= now` is refused). These signatures are separate from opaque
855
+ project credentials and are never project onboarding credentials.
856
+
857
+ Every machine route requires Bearer authentication after version negotiation;
858
+ `GET /health` stays public. Explicitly unauthenticated local test servers may
859
+ remain open. Internal owner operator signatures cannot use raw store routes.
860
+
861
+ A project credential may read and write its repository journal, artifacts,
862
+ snapshots, leases, ticket assets, builds, sessions and streams. Explicit repo
863
+ paths and create bodies must match its project (`403 auth: wrong project`).
864
+ Indirect build/session/stream IDs are resolved internally and checked against
865
+ the resource's actual repository. Missing and foreign IDs both return the same
866
+ `404 not-found` error: `resource unavailable: unknown resource or wrong project`.
867
+ Remote clients continue mapping addressed GET 404 responses to null. Nested
868
+ streams must also belong to the addressed parent.
869
+
870
+ `GET /builds` is filtered by the server to the credential's project. Project
871
+ `POST /builds` always allocates a fresh random-suffixed slug from the requested
872
+ base and a matching `ab/<slug>` branch, even when the base is unused. The
873
+ returned BuildRecord slug and branch are authoritative for all subsequent
874
+ work. A foreign or missing base returns null on GET; it neither reveals
875
+ collisions nor blocks creation. Owner admin retains exact-slug creation and
876
+ all-project listing. Local BuildStore adapters retain exact-slug semantics.
877
+
878
+ Project credentials retain runner-style actor/session attribution within the
879
+ project; phase CLI scoped handles still constrain build and session access.
880
+ Authenticated raw writes cannot claim a delegated `via`. Owner-project
881
+ credentials also use their recorded creator for operator writes. Dispatcher
882
+ credentials have an immutable server-side purpose: raw store and tickets only,
883
+ never human operator controls. The hosted dispatcher issues one per project
884
+ invocation, expiring at its configured TTL, and does not revoke at tick return
885
+ for dispatcher plumbing. Spawned builds receive independent delegated grants. Shared content-addressed blobs
886
+ remain deduplicated; this is resource authorization, not blob partitioning.
887
+
888
+
889
+ ### Delegated build and harvest credentials
890
+
891
+ `POST /credentials/delegate` uses the standard identity headers and Bearer
892
+ machine authentication. Its strict target is one of:
893
+
894
+ - `{kind: "build-runner", id}` for an existing build;
895
+ - `{kind: "build-phase", id, session}` after its `session.started` fact;
896
+ - `{kind: "harvest-runner", repo, id, execution, leaseHolder}` after trusted
897
+ preparation has atomically claimed the scan and recorded
898
+ `harvest.execution.authorized` for that run/execution and adopted holder;
899
+ - `{kind: "harvest-phase", repo, id, session}` from its runner, after the
900
+ matching `harvest.session.started` fact.
901
+
902
+ The response is `{token}` only. Project credentials can issue only within their
903
+ registered project. A runner grant may issue only an exact phase grant within
904
+ its own lifecycle binding; phase grants cannot delegate. Unknown targets are
905
+ refused. Tokens and parent secrets never become artifacts, launch envelopes or
906
+ execution facts. Local-store execution requires no exchange; remote execution
907
+ fails closed when issuance fails.
908
+
909
+ The signed strict shape is `{version: 2, delegation: {repo, kind, id, anchor,
910
+ session?, execution?, leaseHolder?}}`. `anchor` is the authoritative event
911
+ sequence; harvest grants additionally bind the authorized execution. Session
912
+ claims exist only on phase grants. No fixed `exp` applies to this shape:
913
+ signature/schema verification precedes registered-project and lifecycle checks
914
+ on every request, with no authorization cache. Failure to read lifecycle state
915
+ fails closed. Parent credential expiry does not expire an already-issued grant.
916
+ Project removal refuses all its grants immediately.
917
+
918
+ Build grants live until 60 seconds after durable build termination. Phase grants
919
+ also retire 60 seconds after their own session ends, whichever comes first.
920
+ Harvest grants retire 60 seconds after their run stops or their authorized
921
+ execution ends/releases; harvest phases additionally retire on session end.
922
+ The boundary is exclusive: at `end + 60_000ms` the credential is refused. This
923
+ window permits final transcripts, diagnostics, deposits and stream closure.
924
+ Pause/escalation of a build and lease expiry do not end authority. Active grants
925
+ survive hours-long turns, parent expiry and dispatcher/service reconstruction.
926
+ A durable ending never resurrects: recovered harvest work gets a new
927
+ `harvest.execution.authorized` identity and token.
928
+
929
+ Delegated same-project reads remain available, including journals, artifacts,
930
+ digests, bounded state, pinned assets and collections. Writes by a build grant
931
+ are confined to its build's events/deposits/artifacts, snapshots, leases and
932
+ streams. Phase grants also reject agent actors naming another session, while
933
+ preserving the CLI's trusted kernel terminals. Delegated credentials cannot
934
+ create builds/operator sessions, change ticket assets or repository controls,
935
+ or invoke operator APIs. Harvest writes require the exact run in event payloads
936
+ and artifact metadata; artifacts use the harvest kinds. Every artifact in an
937
+ atomic deposit is checked before writing. Harvest streams have an immutable
938
+ `harvest:<run>:<execution>:` label namespace. Addressed streams resolve their actual owner;
939
+ a guessed ID cannot bypass the boundary. Only the runner can heartbeat or re-acquire its exact
940
+ adopted repository lease holder; another current holder still fences it. Recovery acknowledgements remain dispatcher-side.
941
+
942
+ Hosted ticket access is gated by grant kind. Build-runner and build-phase grants
943
+ cannot call ticket routes. Harvest-phase grants may call only list-ready, get
944
+ and dependency-states; harvest-runner grants may additionally create proposals.
945
+ Other ticket mutations require project or admin authority. A denied operation
946
+ returns 403 naming the grant kind and operation before parsing or source construction.
947
+ Ticket admission and repository-keyed assets use the authenticated project;
948
+ delegated harvest grants retain that same project boundary.
949
+
950
+ Workspace plugin factories receive a host-only `delegateCredential` callback
951
+ instead of a project token; their environment excludes `AB_TOKEN`. Provisioning
952
+ and launch must obtain per-resource authority before running guest code and pass
953
+ launch tokens separately from durable execution identity. Remote providers must
954
+ not serialize parent credentials or fall back to forwarding them.
922
955
 
923
956
  ## 8. Atomic deposits
924
957
 
@@ -977,7 +1010,7 @@ The shipped server maps failures as follows:
977
1010
  |---:|---|---|
978
1011
  | `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
1012
  | `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 |
1013
+ | `403` | `auth` | Wrong project, project not registered, or disallowed credential purpose |
981
1014
  | `404` | `not-found` | Unknown route, unsupported method, unknown build, unknown repository, unknown session, or an unknown or foreign-scoped stream |
982
1015
  | `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
1016
  | `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 +1136,8 @@ Four shipped behaviors do not add `BuildStore` routes:
1103
1136
  store lifecycle; there is no close endpoint.
1104
1137
  - `GET /health` is outside the `BuildStore` interface. It is always open and
1105
1138
  returns exactly `200 {"ok":true,"autobuildVersion":"<package>",
1106
- "protocolVersion":"3"}`. It reports HTTP-process availability and identity,
1139
+ "protocolVersion":"3", "supportedClientRange":{"minVersion":"0.9.0",
1140
+ "maxVersionExclusive":"0.12.0"}}`. It reports HTTP-process availability and identity,
1107
1141
  not a deeper backing-store transaction or migration check.
1108
1142
 
1109
1143
  There is no push/WebSocket subscription protocol, repository listing, artifact
@@ -1151,10 +1185,31 @@ Add protocol-specific tests alongside the shared contract for token encoding
1151
1185
  and resource isolation, session-attributed writes, exact validation feedback,
1152
1186
  atomic placeholder substitution, event paging, unauthenticated health, and
1153
1187
  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
1188
+ 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
1189
  calling the backing store directly.
1157
1190
 
1158
1191
  Passing only route smoke tests is not conformance. The complete shared contract
1159
1192
  driven through an HTTP-backed client, plus the protocol-specific cases above,
1160
1193
  is the required compatibility test.
1194
+
1195
+ ## 13. Project connection protocol
1196
+
1197
+ `ab connect <url>` uses `POST /connect/v1/requests` with the normalized origin,
1198
+ a random credential id and SHA-256 digest of a CLI-held secret. The response
1199
+ contains a ten-minute request id, separate private polling capability, short
1200
+ browser code, verification URL and five-second interval. `POST /connect/v1/poll`
1201
+ and `/cancel` require id and capability in JSON; the browser code cannot poll.
1202
+ Poll returns pending, approved, denied or expired plus immutable repository
1203
+ and credential id, never the secret. Cancel denies pending work or revokes
1204
+ only that request's already approved credential. Polling and authenticated
1205
+ browser code lookup have durable limits; 429 includes Retry-After.
1206
+
1207
+ The web-enabled origin serves `/connect` and cookie-authenticated JSON
1208
+ `POST /connect/v1/browser/lookup` and `/decide`. Decisions read the saved
1209
+ proposal, require an allowed live Better Auth session and exact Origin, and
1210
+ atomically register the project, install the digest and record the outcome.
1211
+ GET cannot approve. `GET /connect/v1/status` and `POST /connect/v1/revoke`
1212
+ require a project credential and operate only on its own identity; admin and
1213
+ internal operator credentials cannot impersonate self-service. All protocol
1214
+ responses are no-store/no-referrer. Connection commands are unavailable in
1215
+ build/harvest sessions.