@defrex/autobuild 0.12.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 (88) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +119 -15
  3. package/docs/architecture.md +59 -3
  4. package/docs/configuration.md +41 -9
  5. package/docs/hosted-service.md +31 -5
  6. package/docs/operator-api.md +7 -0
  7. package/docs/remote-store-protocol.md +10 -0
  8. package/docs/remote-ticket-protocol.md +151 -9
  9. package/docs/setup.md +33 -7
  10. package/docs/ticket-migration.md +64 -0
  11. package/package.json +3 -3
  12. package/packages/core/src/cli/build-control.ts +12 -1
  13. package/packages/core/src/cli/connect.ts +141 -38
  14. package/packages/core/src/cli/context.ts +7 -1
  15. package/packages/core/src/cli/dashboard/poll.ts +1 -1
  16. package/packages/core/src/cli/dispatch.ts +156 -3
  17. package/packages/core/src/cli/harvest.ts +13 -3
  18. package/packages/core/src/cli/help.ts +14 -8
  19. package/packages/core/src/cli/installation.ts +67 -13
  20. package/packages/core/src/cli/main.ts +20 -2
  21. package/packages/core/src/cli/mcp.ts +2 -1
  22. package/packages/core/src/cli/self-update.ts +5 -1
  23. package/packages/core/src/cli/ticket-assets.ts +7 -2
  24. package/packages/core/src/cli/ticket-editor.ts +82 -0
  25. package/packages/core/src/cli/ticket.ts +164 -34
  26. package/packages/core/src/config/schema.ts +35 -3
  27. package/packages/core/src/connection.ts +3 -3
  28. package/packages/core/src/events/catalog.ts +3 -2
  29. package/packages/core/src/events/payloads.ts +6 -0
  30. package/packages/core/src/integration/harness.ts +37 -2
  31. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +15 -0
  32. package/packages/core/src/kernel/generators/build-log.ts +1 -1
  33. package/packages/core/src/kernel/incremental-fixtures.ts +13 -2
  34. package/packages/core/src/kernel/reducer-registry.ts +2 -0
  35. package/packages/core/src/ontology.ts +1 -0
  36. package/packages/core/src/operator/annotations.ts +12 -0
  37. package/packages/core/src/operator/index.ts +5 -0
  38. package/packages/core/src/operator/protocol.ts +19 -2
  39. package/packages/core/src/operator/registry.ts +61 -8
  40. package/packages/core/src/operator/ticket-source-backend.ts +17 -21
  41. package/packages/core/src/operator/tickets.ts +130 -20
  42. package/packages/core/src/plugin-sdk/index.ts +6 -0
  43. package/packages/core/src/plugins/runtimes.ts +5 -1
  44. package/packages/core/src/ports/runner/session-env.ts +2 -0
  45. package/packages/core/src/ports/tickets/contract.ts +66 -1
  46. package/packages/core/src/ports/tickets/create.ts +48 -12
  47. package/packages/core/src/ports/tickets/fake.ts +30 -1
  48. package/packages/core/src/ports/tickets/file-assets.ts +159 -0
  49. package/packages/core/src/ports/tickets/file-codec.ts +90 -0
  50. package/packages/core/src/ports/tickets/file.ts +114 -120
  51. package/packages/core/src/ports/tickets/grants.ts +47 -0
  52. package/packages/core/src/ports/tickets/hosted-index.ts +17 -0
  53. package/packages/core/src/ports/tickets/identity.ts +20 -0
  54. package/packages/core/src/ports/tickets/lifecycle.ts +18 -0
  55. package/packages/core/src/ports/tickets/linear.ts +74 -9
  56. package/packages/core/src/ports/tickets/migration.ts +336 -0
  57. package/packages/core/src/ports/tickets/operations.ts +38 -7
  58. package/packages/core/src/ports/tickets/portable-assets.ts +54 -0
  59. package/packages/core/src/ports/tickets/remote-protocol.ts +95 -0
  60. package/packages/core/src/ports/tickets/remote.ts +60 -3
  61. package/packages/core/src/ports/tickets/requested-id.ts +46 -0
  62. package/packages/core/src/ports/tickets/revision.ts +62 -0
  63. package/packages/core/src/ports/tickets/states.ts +61 -0
  64. package/packages/core/src/ports/types.ts +40 -3
  65. package/packages/core/src/ports/workspace/build-execution.ts +1 -0
  66. package/packages/core/src/ports/workspace/create.ts +5 -1
  67. package/packages/core/src/ports/workspace/operator-sandbox.ts +2 -0
  68. package/packages/core/src/ports/workspace/vercel-sandbox.ts +8 -4
  69. package/packages/core/src/processes/dispatcher.ts +110 -32
  70. package/packages/core/src/processes/execution-no-progress.ts +176 -0
  71. package/packages/core/src/processes/harvest-child.ts +30 -4
  72. package/packages/core/src/processes/harvest-runner.ts +24 -4
  73. package/packages/core/src/processes/harvest.ts +34 -14
  74. package/packages/core/src/processes/phase-failure-budget.ts +1 -0
  75. package/packages/core/src/processes/repo-view.ts +35 -0
  76. package/packages/core/src/store/contract.ts +32 -1
  77. package/packages/core/src/store/local/store.ts +1 -1
  78. package/packages/core/src/store/ticket-asset-snapshots.ts +192 -0
  79. package/packages/core/src/store/ticket-assets.ts +86 -8
  80. package/packages/core/src/store/types.ts +1 -1
  81. package/skills/guide/SKILL.md +91 -11
  82. package/skills/guide/references/operator-api.md +7 -0
  83. package/skills/guide/references/remote-store-protocol.md +10 -0
  84. package/skills/guide/references/remote-ticket-protocol.md +151 -9
  85. package/skills/guide/references/setup.md +33 -7
  86. package/skills/guide/references/ticket-migration.md +64 -0
  87. package/skills/tickets/SKILL.md +71 -0
  88. package/templates/hosted-service/package.json +2 -2
package/README.md CHANGED
@@ -24,7 +24,7 @@ needs an authenticated [`gh` CLI](https://cli.github.com) or an exported
24
24
  <!-- release-install:start -->
25
25
 
26
26
  ```sh
27
- bun add -g @defrex/autobuild@0.12.0
27
+ bun add -g @defrex/autobuild@0.13.0
28
28
  ```
29
29
 
30
30
  <!-- release-install:end -->
package/SPEC.md CHANGED
@@ -432,6 +432,13 @@ body file leaves the pinned assets unchanged. The one case this cannot cover is
432
432
  a record an older dispatcher created with an empty stream: recovery can only
433
433
  sample the ticket's current assets for it.
434
434
 
435
+ For hosted tickets, exact sampled revisions are copied into immutable repository
436
+ artifacts in the destination build store before pins are published. Pins carry
437
+ explicit manifest references; bounded binary chunks preserve files and empty
438
+ directories without contacting the ticket service during build reads. Original
439
+ ticket revision numbers are preserved. Old pins without snapshot references
440
+ continue reading their existing ticket rows.
441
+
435
442
  ## 7. The build store
436
443
 
437
444
  The v1 repo-bloat and no-observability problems share one root cause: the
@@ -618,15 +625,23 @@ resume or turn — no second turn, no concurrent turns, no subagents.
618
625
 
619
626
  A **ticket asset** is a named, versioned bundle of files attached to a ticket —
620
627
  a design export, a reference screenshot, a sample payload — that shapes a change
621
- but belongs neither in the repository (where it goes stale once the change
622
- ships) nor in the ticket tool (which build agents cannot reach). It is keyed by
628
+ but belongs outside the repository, where it would go stale once the change ships. It is keyed by
623
629
  `(repo, ticketId, kind, name)` and works identically whichever ticket source is
624
630
  configured. It is a fourth resource family beside builds, repository journals,
625
631
  and sessions, but a deliberately thin one: no event log, because nothing here
626
632
  needs reduction, and no retention pruning, because assets live as long as the
627
633
  ticket's other durable data and never expire on their own.
628
634
 
629
- Each put creates a new 0-based **revision**; earlier revisions stay retrievable.
635
+ Hosted ticket assets live in the ticket service's repository-keyed storage,
636
+ regardless of where builds run or which BuildStore is selected. File tickets use
637
+ native portable storage composed with legacy BuildStore attachments; Linear
638
+ tickets retain BuildStore storage. Existing service assets need no migration;
639
+ old local hosted-ticket attachments are not moved or used as a fallback.
640
+
641
+ Each put creates a new increasing **revision**; earlier revisions stay retrievable.
642
+ Adapters normally start at 0. Native file assets reserve revisions from
643
+ 1,000,000,000 upward so explicit reads of old BuildStore revisions remain
644
+ unambiguous when native and legacy assets are composed.
630
645
  An asset is a `file` (exactly one file) or a `tree` (a folder: its root is
631
646
  implicit, so a tree with zero entries is a valid empty folder). The manifest is
632
647
  a list of entries — `file` entries with size and content address, and `dir`
@@ -1071,9 +1086,9 @@ kind and operation.
1071
1086
 
1072
1087
  Ticket admission uses the credential project. Hosted tickets, comments, blockers, creation-key
1073
1088
  deduplication and assets are partitioned by registered repository, never team.
1074
- Lifecycle comes from that project's latest durable effective configuration
1075
- (triage Backlog, claim Doing, completion Done by default; configurable
1076
- `doneState`). Owner admin ticket requests explicitly select a registered
1089
+ Lifecycle comes from independent project ticket policy (triage, ready, doing, done
1090
+ by default; configurable doneState). Legacy projects without independent policy
1091
+ use their uploaded configuration with Backlog / Doing / Done defaults. Owner admin ticket requests explicitly select a registered
1077
1092
  repository. The service has no Linear ticket backend: external tracker and
1078
1093
  file sources remain dispatcher-side, and service ticket surfaces refuse those
1079
1094
  projects with actionable source feedback. Legacy team queues migrate atomically
@@ -1273,9 +1288,10 @@ implementer: ab context (findings.json now materialized) → …
1273
1288
 
1274
1289
  Human/pre-build ticket grooming uses one configured-source namespace
1275
1290
  (`ab ticket create|update|block|unblock|list|show|move`, plus the ticket-asset
1276
- forms below). These commands are
1291
+ forms below). Migration adds `ab ticket export <dir>` and `ab ticket import <dir>`
1292
+ (see [Ticket source migration](#ticket-source-migration)). These commands are
1277
1293
  sessionless, source-agnostic, and never available as a mid-build spec
1278
- mutation path. Every subcommand accepts `--json` and then writes exactly one
1294
+ mutation path. The CRUD subcommands accept `--json` and then writes exactly one
1279
1295
  bare value: `list` emits `Ticket[]`; every other form emits the complete
1280
1296
  created, read, or post-mutation `Ticket`. Human-readable confirmations are not
1281
1297
  a data format and callers must not parse ids from them.
@@ -1290,8 +1306,10 @@ creates tickets directly in its ready state must therefore create a dependency
1290
1306
  chain in dependency order, carrying each predecessor id into the next create,
1291
1307
  before a later ticket can be claimed.
1292
1308
 
1293
- **Ticket assets** (§7.1) attach files to a ticket without touching the ticket
1294
- source's storage:
1309
+ **Ticket assets** (§7.1) attach files to a ticket. Hosted tickets use their
1310
+ ticket service storage independently of the selected build store; file tickets use
1311
+ native storage with legacy fallback, and sources without native assets use the
1312
+ selected BuildStore:
1295
1313
 
1296
1314
  - `ab ticket attach <id> <kind> <path> [--name <name>] [--store <ref>] [--json]`
1297
1315
  stores a file or a whole folder (its relative tree preserved) as an asset.
@@ -1762,9 +1780,10 @@ event-level mechanics live in the repository catalog and reducer tests):
1762
1780
  repository; an owner selects a registered repository explicitly. Team and state
1763
1781
  fields from clients convey no authority or lifecycle policy. Tickets, comments,
1764
1782
  blockers, creation-key deduplication and assets are isolated by repository.
1765
- The project's latest effective configuration supplies hosted lifecycle names:
1766
- triage Backlog, claimed Doing, completion Done (`doneState`) and create/proposal
1767
- equal to triage by default; `readyState` is required. Configured roles may overlap.
1783
+ Independent project ticket policy supplies hosted lifecycle names: triage, ready,
1784
+ doing, done (`doneState`), and create/proposal equal to triage by default. Hosted
1785
+ readyState defaults to ready; other sources require it. Legacy clients without
1786
+ independent policy use uploaded dispatcher config and its old defaults. Configured roles may overlap.
1768
1787
  The service has no Linear backend and never relays external ticket sources.
1769
1788
  Projects using Linear, files or plugins keep their direct dispatcher connection;
1770
1789
  service ticket surfaces refuse with project/source feedback instead of empty data.
@@ -1780,14 +1799,16 @@ This keeps the abstraction honest: a file-based TicketSource with nowhere to
1780
1799
  put blobs must be fully workable.
1781
1800
 
1782
1801
  **Ticket assets follow the same rule.** The contents of a ticket asset (§7.1)
1783
- live in the BuildStore and the ticket source never stores them. Attaching,
1802
+ live in the ticket service for hosted tickets, independently of build storage;
1803
+ file sources keep portable bundles and source-local history, while sources without
1804
+ native assets keep them in the BuildStore. Attaching,
1784
1805
  replacing, or removing an asset instead leaves a note on the ticket through the
1785
1806
  existing `comment()` port — the outward projection every source, including the
1786
1807
  plugin surface, already supports. The note names the asset, its revision, file
1787
1808
  count, and size, and gives the `ab ticket asset get` command that fetches it, so
1788
1809
  a person reading the ticket in Linear, the hosted service, or a local file knows
1789
1810
  the asset exists. The note is a projection outward only; nothing reads it back,
1790
- and no TicketSource port or plugin-protocol change is needed.
1811
+ and existing sources and plugins need no asset capability.
1791
1812
 
1792
1813
  **Claim lifecycle.** Claim acquires nonterminal work: it returns `true` and
1793
1814
  moves a claimable ticket to the source's claimed state. It returns `false` for
@@ -2230,6 +2251,21 @@ adapter never re-cuts it from a newer base (§7.4). `ab context` rehydrates
2230
2251
  scratch from the store into a fresh session. Uncommitted round-2 work is lost by
2231
2252
  design (§7.3 — phase boundaries are the resume points).
2232
2253
 
2254
+ The dispatcher also applies a fixed, non-configurable limit of three consecutive
2255
+ executions that complete normally with exit code zero and no durable build
2256
+ progress. Execution start/end, runner attachment and workspace lifecycle
2257
+ bookkeeping do not count as progress. Pipeline, session, spec, publication/PR,
2258
+ observation and acknowledged operator facts reset the streak; infrastructure,
2259
+ budget and provider failures do not consume the allowance and retain their own
2260
+ retry limits. Before a fourth actionable launch, the dispatcher raises one
2261
+ setup-targeted `execution-no-progress` policy escalation naming the count and
2262
+ the next action it expected the runner to take. Waiting, paused and terminal
2263
+ builds are left parked, and pending operator commands remain executable.
2264
+ Answering that escalation with guidance or a bare `ab answer <slug>` retry grants
2265
+ three fresh executions. The guard is derived from durable facts, including on
2266
+ supervisor restart, and uses the ordinary watch, dashboard and operator API
2267
+ attention paths.
2268
+
2233
2269
  Two liveness rules complete the picture. Within one dispatcher process,
2234
2270
  build-runner launches are single-flighted by slug through supervised process
2235
2271
  liveness — execution completion means the complete environment has been reaped,
@@ -2781,3 +2817,71 @@ or installs releases in the foreground or background.
2781
2817
  enforcement and will colocate later session-authenticated human routes.
2782
2818
  Artifacts remain content-by-value for now; direct-to-blob references are a
2783
2819
  possible later protocol revision, not a second current deployment path.
2820
+
2821
+ ## Revision-checked ticket writes
2822
+
2823
+ Every ticket read includes an opaque `revision`. Pass it as `expectedRevision`
2824
+ (on MCP/operator requests) or `--expected-revision <opaque>` on `ab ticket update`,
2825
+ `move`, `block` and `unblock`. A stale write fails without changing anything and
2826
+ reports the current revision. Omission preserves unguarded behavior. Blocker
2827
+ checks cover the entire batch. Dispatcher moves remain unguarded and change the
2828
+ revision. Sources without guarded writes, including Linear, refuse supplied
2829
+ revisions rather than ignoring them.
2830
+
2831
+ `ab ticket edit <id>` opens `$VISUAL`, falling back to `$EDITOR`, using TOML
2832
+ frontmatter and a Markdown body. Only title, body and labels are editable;
2833
+ id, blockers, creation key and reference comments are read-only. It saves only
2834
+ changed fields with the opening revision. Unchanged or formatting-only edits
2835
+ write nothing. On conflict, invalid input or editor failure, the error names an
2836
+ absolute file path containing the edited text. Read the current ticket and
2837
+ reapply the intended changes before retrying; conflicts are never merged.
2838
+
2839
+ File and Linear revisions fingerprint title, body, canonical label/blocker sets
2840
+ and state; reverting identical content restores its fingerprint. File checks
2841
+ re-read immediately before writing, with the existing no-lock race window.
2842
+ Direct file edits and state-directory moves change revisions. File revisions also
2843
+ cover structured comments and portable attachments. Legacy file comments change
2844
+ the body; structured comments preserve it. Separate hosted comments do not change
2845
+ ticket revisions. Hosted revisions
2846
+ are monotonic decimal bigint strings, and checks run under the same row lock
2847
+ and transaction as all requested changes. Hosted reads return a coherent
2848
+ snapshot of fields, blockers and revision.
2849
+
2850
+ ### Independent hosted ticket service
2851
+
2852
+ `[tickets] url` selects hosted ticket transport independently of BuildStore selection;
2853
+ without it, tickets use the selected remote build store. Local builds require an
2854
+ explicit ticket URL. Project registration and ticket policy publication through
2855
+ `ab connect --tickets URL` work before dispatch. Policy is a durable per-registration
2856
+ record, outside BuildStore events/artifacts; legacy uploaded configuration remains
2857
+ a compatibility fallback. Hosted lifecycle defaults are triage/ready/doing/done.
2858
+ Independent harvest uses revocable, expiring, service-bound ticket grants with
2859
+ read/create runner authority and read-only child authority. Build state and harvest
2860
+ journals remain exclusively in the selected BuildStore.
2861
+
2862
+ ### Ticket source migration
2863
+
2864
+ `ab ticket export <dir>` enumerates all states through TicketSource and writes a
2865
+ portable file tracker atomically to an absent or empty destination.
2866
+ `ab ticket import <dir>` preflights the entire tracker and target inventory, then
2867
+ creates or adopts tickets, maps blockers, deduplicates comments by exact body and
2868
+ restores current attachments. Both run sessionless without forge access.
2869
+
2870
+ TicketCreateOptions includes optional `requestedId`. Unsupported sources reject
2871
+ it. File keeps safe ids exactly; hosted preserves canonical own-team positive
2872
+ numbers and advances its counter above every occupied number. Creation-key
2873
+ adoption precedes collision checks. Known kept-number clashes stop import before
2874
+ any target mutation. Stable migration identities and atomic mapping files make
2875
+ interrupted imports resumable without content-based adoption.
2876
+
2877
+ File frontmatter additionally supports optional structured `comments` (body,
2878
+ createdAt, optional author) and `attachments` (kind, name, layout, entries with
2879
+ file base64 bytes or directory paths). Legacy absent fields retain old behavior;
2880
+ new creates use structured comments. Native file assets retain historical
2881
+ revisions in hidden source-local metadata and compose with legacy BuildStore
2882
+ assets. Build claims and spec re-pins freeze native assets into durable snapshots.
2883
+
2884
+ State normalization and the complete operator procedure are documented in
2885
+ [Ticket migration](docs/ticket-migration.md). The same reference is delivered in
2886
+ the installed guide. Export keeps lifecycle membership without inferring roles;
2887
+ import reuses unique case-insensitive states and creates normalized missing ones.
@@ -364,6 +364,10 @@ once. Guidance answering the policy escalation after failed-report exhaustion
364
364
  instead routes to `implement` and outranks the pending report. A bare retry carries no
365
365
  feedback.
366
366
 
367
+ The dispatcher launch boundary uses `processes/execution-no-progress.ts` to
368
+ project clean empty execution streaks through `RepoViewStore` snapshots and
369
+ conditionally raise an ordinary setup policy escalation before a fourth launch.
370
+
367
371
  **Launch ownership.** `packages/core/src/cli/dispatch.ts` is the kernel owner and
368
372
  single-flights supervised build executions per slug within its process. It
369
373
  publishes effective config into the build artifact namespace, claims the exact
@@ -771,17 +775,24 @@ selected through the plugin ticket-source registry, the real filesystem and
771
775
  local-git adapters, the injected Claude and Codex CLI subprocess contracts,
772
776
  and the injected Pi CLI/RPC contract.
773
777
  Ticket assets (SPEC §7.1.3) are files attached to a ticket and kept in the
774
- BuildStore, never in the ticket source. `packages/core/src/store/ticket-assets.ts`
778
+ ticket service for hosted tickets, independently of build storage, and in the
779
+ BuildStore for other sources. `packages/core/src/store/ticket-assets.ts`
775
780
  holds the types, the limits, and the one validator every adapter calls;
776
781
  `packages/core/src/cli/ticket-assets.ts` holds the filesystem walk, the download
777
782
  writer, and the note builder; `ab ticket attach`, `asset get`, and `asset rm`
778
- live in `packages/core/src/cli/ticket.ts`. The only thing a ticket source sees is
779
- a note through `comment()`, so no source or plugin protocol changed.
783
+ live in `packages/core/src/cli/ticket.ts`. The optional hosted source asset
784
+ capability reuses RemoteBuildStore transport with the ticket URL and credential.
785
+ Other sources retain the existing store path.
780
786
  A claim freezes the ticket's current assets into the build (SPEC §6.3):
781
787
  `build.created.assets` (and, when a spec is revised from the ticket,
782
788
  `spec.revised.assets`) record the pinned revisions, `state.pinnedAssets` in the
783
789
  reducer projects them, `getPinnedTicketAsset` reads them back by pinned revision,
784
790
  and `ab context` writes them under `.ab/assets/`.
791
+ For hosted tickets, `store/ticket-asset-snapshots.ts` copies exact revisions
792
+ into chunked immutable repository artifacts in the build store before pinning.
793
+ Reads use explicit manifest/chunk revisions without a ticket connection. Old
794
+ pins retain ticket-row reads; existing service attachments need no migration,
795
+ and attachments in old local stores are not moved.
785
796
 
786
797
  Both `ab dispatch` and sessionless `ab ticket` load the repository's plugins
787
798
  before selecting their TicketSource; dispatch passes that one adapter instance
@@ -938,3 +949,48 @@ the shared `connection.ts` wire types, and Postgres `connections.ts`. Approval
938
949
  commits registry membership, a credential digest, and the outcome atomically.
939
950
  The web connection routes authenticate the existing Better Auth session and
940
951
  reuse the sign-in/consent layout; CLI polling uses a separate private capability.
952
+
953
+ ## Revision-checked ticket writes
954
+
955
+ Every ticket read includes an opaque `revision`. Pass it as `expectedRevision`
956
+ (on MCP/operator requests) or `--expected-revision <opaque>` on `ab ticket update`,
957
+ `move`, `block` and `unblock`. A stale write fails without changing anything and
958
+ reports the current revision. Omission preserves unguarded behavior. Blocker
959
+ checks cover the entire batch. Dispatcher moves remain unguarded and change the
960
+ revision. Sources without guarded writes, including Linear, refuse supplied
961
+ revisions rather than ignoring them.
962
+
963
+ `ab ticket edit <id>` opens `$VISUAL`, falling back to `$EDITOR`, using TOML
964
+ frontmatter and a Markdown body. Only title, body and labels are editable;
965
+ id, blockers, creation key and reference comments are read-only. It saves only
966
+ changed fields with the opening revision. Unchanged or formatting-only edits
967
+ write nothing. On conflict, invalid input or editor failure, the error names an
968
+ absolute file path containing the edited text. Read the current ticket and
969
+ reapply the intended changes before retrying; conflicts are never merged.
970
+
971
+ File and Linear revisions fingerprint title, body, canonical label/blocker sets
972
+ and state; reverting identical content restores its fingerprint. File checks
973
+ re-read immediately before writing, with the existing no-lock race window.
974
+ Direct file edits and state-directory moves change revisions. File comments
975
+ change the body and revision; separate hosted comments do not. Hosted revisions
976
+ are monotonic decimal bigint strings, and checks run under the same row lock
977
+ and transaction as all requested changes. Hosted reads return a coherent
978
+ snapshot of fields, blockers and revision.
979
+
980
+ ### Independent hosted ticket service
981
+
982
+ `[tickets] url` selects hosted ticket transport independently of BuildStore selection;
983
+ without it, tickets use the selected remote build store. Local builds require an
984
+ explicit ticket URL. Project registration and ticket policy publication through
985
+ `ab connect --tickets URL` work before dispatch. Policy is a durable per-registration
986
+ record, outside BuildStore events/artifacts; legacy uploaded configuration remains
987
+ a compatibility fallback. Hosted lifecycle defaults are triage/ready/doing/done.
988
+ Independent harvest uses revocable, expiring, service-bound ticket grants with
989
+ read/create runner authority and read-only child authority. Build state and harvest
990
+ journals remain exclusively in the selected BuildStore.
991
+
992
+ Hosted ticket detail reads expose complete, oldest-first structured comments in
993
+ `ticket.comments`, preserving exact bodies. Scans do not fetch comments. Hosted
994
+ HTTP clients opt in with `x-autobuild-ticket-comments: 1` so historical strict
995
+ decoders retain compatible responses. File comments remain embedded in the body;
996
+ Linear comment reads are unchanged.
@@ -202,6 +202,10 @@ dashboard notice row or on plain-mode stderr, and suppresses repeats while the
202
202
  same failure remains. Restoring `autobuild.toml` with valid configuration clears
203
203
  the rejection and ordinary live reload resumes without a dispatcher restart.
204
204
 
205
+ Dispatcher merge readiness, startup resume, and execution recovery use the same pinned
206
+ commands, verify, and finalize configuration as the build runner. Missing pins retain
207
+ the legacy live-configuration fallback.
208
+
205
209
  These fields hot-reload:
206
210
 
207
211
  | Field or table | Boundary where a new value applies |
@@ -210,8 +214,8 @@ These fields hot-reload:
210
214
  | `ticketScanIntervalSeconds` | Next tick; evaluates elapsed time against the new interval. |
211
215
  | `capacity` | Next dispatcher tick. |
212
216
  | `[pr]` | Next build creation. |
213
- | `[commands]` | Next setup, verify check, or finalize check selected for a build whose OWN branch carries it. A base-branch reload does not retarget an in-flight build. |
214
- | `[verify]` and `[finalize]` | Next engine step selection for a build whose OWN branch carries it. An approved plan's stored verify selection remains authoritative, and a base-branch reload does not retarget an in-flight build. |
217
+ | `[commands]` | Next setup, verify check, or finalize check selected for a build whose OWN branch carries it. Dispatcher readiness, resume, and recovery use the same pin. A base-branch reload does not retarget an in-flight build. |
218
+ | `[verify]` and `[finalize]` | Next engine step selection for a build whose OWN branch carries it. Dispatcher readiness, resume, and recovery use the same pin. An approved plan's stored verify selection remains authoritative, and a base-branch reload does not retarget an in-flight build. |
215
219
  | `[workspace]` provider and config (including `provisioning`) | Next launch of a build whose OWN branch carries it. A base-branch reload never re-provisions an in-flight build. |
216
220
  | `[roles]` | Next agent invocation for that role. Runtime, model, args, alternates, and the session budget are captured together and delivered live. |
217
221
  | `[policy]` | Next policy, convergence, or agent-session boundary, delivered live. A running session keeps its captured budget. |
@@ -1185,6 +1189,21 @@ clears the streak. Recovery-provider workspace failures belong exclusively to
1185
1189
  the infrastructure budget. Exhaustion is repaired from durable facts on restart
1186
1190
  before further dispatch work.
1187
1191
 
1192
+ The dispatcher also applies a fixed, non-configurable limit of three consecutive
1193
+ executions that complete normally with exit code zero and no durable build
1194
+ progress. Execution start/end, runner attachment and workspace lifecycle
1195
+ bookkeeping do not count as progress. Pipeline, session, spec, publication/PR,
1196
+ observation and acknowledged operator facts reset the streak; infrastructure,
1197
+ budget and provider failures do not consume the allowance and retain their own
1198
+ retry limits. Before a fourth actionable launch, the dispatcher raises one
1199
+ setup-targeted `execution-no-progress` policy escalation naming the count and
1200
+ the next action it expected the runner to take. Waiting, paused and terminal
1201
+ builds are left parked, and pending operator commands remain executable.
1202
+ Answering that escalation with guidance or a bare `ab answer <slug>` retry grants
1203
+ three fresh executions. The guard is derived from durable facts, including on
1204
+ supervisor restart, and uses the ordinary watch, dashboard and operator API
1205
+ attention paths.
1206
+
1188
1207
  For each repeat conflict, Autobuild compares the base merged by the most recent
1189
1208
  completed reconcile with a fresh authoritative base snapshot. An advanced base
1190
1209
  proves the attempt lost a race and permits another reconcile regardless of the
@@ -1224,21 +1243,25 @@ invariants, not additional configuration fields.
1224
1243
 
1225
1244
  ## `[tickets]`
1226
1245
 
1246
+ To move an existing project between sources, follow the
1247
+ [ticket migration procedure](ticket-migration.md).
1248
+
1227
1249
  Required in practice: `readyState` deliberately has no default, so omitting the
1228
1250
  table fails at `tickets.readyState` rather than allowing tickets from every
1229
- state. Within a present table, `source` and `readyState` are required.
1251
+ state. Within a present table, `source` is required; hosted tickets default `readyState` to `ready`. Other sources require it.
1230
1252
 
1231
1253
  | Field | Default | Constraints | Purpose |
1232
1254
  |---|---:|---|---|
1233
1255
  | `source` | — | required nonblank builtin or plugin registration name | Select the ticket adapter after configured plugins load. |
1234
1256
  | `readyLabels` | source-aware | optional array of nonempty strings; `[]` allowed | Require every listed label in addition to the state gate. |
1235
- | `readyState` | — | required nonblank string | The one workflow state eligible for dispatch. |
1257
+ | `url` | selected build service | HTTPS service URL; HTTP allowed on loopback; no credentials, query or fragment; hosted only | Ticket service independent of [store] url and AB_STORE. Required with local builds. |
1258
+ | `readyState` | hosted: `"ready"` | nonblank string; required for other sources | The one workflow state eligible for dispatch. |
1236
1259
  | `readyAssignee` | — | optional; `"me"` or an email address; Linear only (rejected for file, hosted, and plugin sources) | Require the ticket to be assigned to one Linear user, in addition to the state and label gates. |
1237
1260
  | `teamKey` | — | required for Linear and hosted; forbidden for file; optional for plugins | Backend team key such as `"ENG"`, or an existing plugin config field. |
1238
- | `claimedState` | `"In Progress"` for direct Linear; `"Doing"` for hosted | optional nonempty string; forbidden for file; allowed for plugins | Workflow state entered when a ticket is claimed. |
1239
- | `doneState` | `"Done"` | optional nonblank string; hosted | Project completion state and merged-ticket transition. |
1261
+ | `claimedState` | `"In Progress"` for direct Linear; `"doing"` for hosted | optional nonempty string; forbidden for file; allowed for plugins | Workflow state entered when a ticket is claimed. |
1262
+ | `doneState` | hosted: `"done"` | optional nonblank string; hosted | Project completion state and merged-ticket transition. |
1240
1263
  | `createState` | provider default | optional nonempty string | Default state for newly created tickets when `ab ticket create` omits `--state`. |
1241
- | `triageState` | Linear/hosted: `"Backlog"`; file/plugin: `"Triage"` | optional nonempty string | State used for spec-gate bounces, aborts, and closed-unmerged PRs. |
1264
+ | `triageState` | Linear: `"Backlog"`; hosted: `"triage"`; file/plugin: `"Triage"` | optional nonempty string | State used for spec-gate bounces, aborts, and closed-unmerged PRs. |
1242
1265
  | `proposalState` | the resolved `triageState` | optional nonempty string | State harvest files its synthesized proposals into. Setting it to `readyState` waives the human grooming gate, not dependency eligibility. |
1243
1266
  | `dir` | file: selected local state root's `tickets/`; plugin: omitted | optional nonempty path; forbidden for Linear; allowed for plugins | Root containing file-source state directories, or an existing plugin config field. Relative file paths resolve from the repository. |
1244
1267
 
@@ -1257,7 +1280,7 @@ When `readyLabels` is absent, direct Linear uses `["autobuild"]`; hosted, file,
1257
1280
  and plugin sources use `[]`, meaning no host-imposed label gate. An explicit value always
1258
1281
  wins. A nonempty list is
1259
1282
  conjunctive: every configured label must be present. `readyState` remains
1260
- mandatory regardless of labels. Linear compares state and label names exactly
1283
+ mandatory regardless of labels (hosted defaults to `ready`). Linear compares state and label names exactly
1261
1284
  and case-sensitively. The file source matches state names case-insensitively against the directories
1262
1285
  under the tracker root: the four lifecycle directories (`triage/`, `ready/`,
1263
1286
  `doing/`, `done/`, always present) plus any directory the repository adds, such
@@ -1279,6 +1302,15 @@ Source-specific validation is strict:
1279
1302
  - Plugin sources receive the existing fields in this table unchanged and own
1280
1303
  any additional semantic validation. No untyped plugin-options table exists.
1281
1304
 
1305
+ Hosted states are durable service data scoped to a project. `ab ticket states`
1306
+ lists configured lifecycle roles first, then custom states alphabetically, with
1307
+ counts and optional purposes. `ab ticket state create <name> [--about <text>]`
1308
+ adds a state using the file source's lowercase name grammar. Hosted duplicates
1309
+ in any letter case are refused; file creation remains idempotent. Configured
1310
+ names always exist, preserve their spelling, and remain custom states after
1311
+ removal from config. Any existing state supports ticket create, move, and list;
1312
+ unknown names report the available states. Purposes must be one nonblank line.
1313
+
1282
1314
  For Linear, omitting `createState` lets the team's default state apply. For the
1283
1315
  file source it defaults to `Triage`. A caller may override that default for one
1284
1316
  create with `ab ticket create --state <state>`; the value is passed unchanged to
@@ -1703,7 +1735,7 @@ Secrets and store selection accompany the file through environment variables:
1703
1735
  | Variable | Used for | Notes |
1704
1736
  |---|---|---|
1705
1737
  | `LINEAR_API_KEY` | Direct Linear ticket source | Required and nonempty when `tickets.source = "linear"`; never used by the hosted service. |
1706
- | `AB_STORE` | BuildStore and hosted ticket selection | A local path or HTTP(S) remote-store URL. `tickets.source = "hosted"` requires a nonblank HTTP(S) URL. A command's explicit `--store` wins for BuildStore selection, then nonblank `AB_STORE`, then `[store] url`, then the main checkout's `.autobuild/`. Relative local paths resolve from the main checkout. |
1738
+ | `AB_STORE` | BuildStore and hosted ticket selection | A local path or HTTP(S) remote-store URL. Hosted tickets use this only when `[tickets] url` is absent; local builds with hosted tickets require `[tickets] url`. A command's explicit `--store` wins for BuildStore selection, then nonblank `AB_STORE`, then `[store] url`, then the main checkout's `.autobuild/`. Relative local paths resolve from the main checkout. |
1707
1739
  | `AB_TOKEN` | Protected remote BuildStore and hosted tickets | Bearer credential forwarded to the service. Hosted tickets require a stored credential for a registered project. Owner admin is an explicit break-glass exception. Empty means no token; nonempty token bytes are treated as opaque. |
1708
1740
 
1709
1741
  A local store selection relocates the state database, blobs, worktrees, and the
@@ -17,8 +17,8 @@ package's `deployment/` directory and in `templates/hosted-service/`).
17
17
  "build": "ab-hosted-service-build"
18
18
  },
19
19
  "dependencies": {
20
- "@defrex/autobuild-hosted-store-service": "0.12.0",
21
- "@defrex/autobuild": "0.12.0",
20
+ "@defrex/autobuild-hosted-store-service": "0.13.0",
21
+ "@defrex/autobuild": "0.13.0",
22
22
  "next": "16.1.6",
23
23
  "react": "19.2.4",
24
24
  "react-dom": "19.2.4"
@@ -153,9 +153,9 @@ or manual production deployment is required.
153
153
  ## Hosted ticket projects and v1 migration
154
154
 
155
155
  Hosted queues are partitioned by registered repository. Credentials select the
156
- project, and its latest uploaded effective `[tickets]` configuration selects the
157
- lifecycle; see the [ticket protocol](remote-ticket-protocol.md). If configuration
158
- is unavailable, run that project's dispatcher to publish it. Projects using file,
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
159
  Linear or another external source continue to access it directly through their
160
160
  dispatcher. Service ticket surfaces refuse those projects with source feedback.
161
161
  The service never connects to Linear.
@@ -212,3 +212,29 @@ Missing blob configuration or denied reads stop deployment before Next builds;
212
212
  diagnostics never print credentials. SQL-only fresh installs, empty-v1 upgrades
213
213
  and already-v2 reruns do not open blob storage and keep their existing database
214
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.
@@ -87,6 +87,7 @@ turn runner (a later ticket) is the only producer of turn facts.
87
87
  | `GET …/harvest/status` | Harvest gate, runs, steps, recovery, and attention projection. |
88
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. |
89
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). |
90
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. |
91
92
 
92
93
  For artifact reads, an absent `rev` selects the latest revision. A supplied value
@@ -119,6 +120,7 @@ JSON bodies are strict: unknown fields are rejected.
119
120
  | `POST …/settings/auto-merge-default/toggle` | Empty body. |
120
121
  | `POST …/bulk-control` | `{"action":"pause"\|"resume"}`. The hold fact, intake fact, then eligible build events are written in that order. |
121
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. |
122
124
  | `POST …/tickets` | `{"title":"…","body":"…","labels"?:[],"state"?:"…","blockedBy"?:[]}`; omitted state preserves the backend default. |
123
125
  | `PATCH …/tickets/{id}` | Any nonempty subset of `title`, `body`, and complete-replacement `labels`. Body bytes are not normalized. |
124
126
  | `POST …/tickets/{id}/move` | `{"state":"backend state name"}`. |
@@ -284,3 +286,8 @@ even with `--since`; live and backlog matches ignore routine outcomes and
284
286
  self-healing failures. A merge match uses the watch observation with
285
287
  `condition: "attention"`. Explicit `--event` globs remain event-only.
286
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.
@@ -485,6 +485,16 @@ does not apply. `{repo}` and `{id}` are percent-encoded path segments. Token
485
485
  authorization requires a credential for that registered project, or owner admin.
486
486
  The family is additive, so `REMOTE_STORE_PROTOCOL_VERSION` did not change.
487
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
+
488
498
  A build also reads the assets it froze at its claim through its own build-scoped
489
499
  routes, authorized against the build's actual project. The
490
500
  server reduces the build's events to find the pinned revision, then returns that