@defrex/autobuild 0.8.0 → 0.10.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 (182) hide show
  1. package/README.md +1 -1
  2. package/SPEC.md +323 -26
  3. package/bin/ab-build-runner.ts +8 -2
  4. package/docs/architecture.md +166 -14
  5. package/docs/assets/headline-wide.png +0 -0
  6. package/docs/configuration.md +175 -28
  7. package/docs/hosted-dispatcher.md +69 -13
  8. package/docs/operator-api.md +2 -2
  9. package/docs/remote-store-protocol.md +204 -16
  10. package/docs/remote-ticket-protocol.md +1 -1
  11. package/docs/setup.md +12 -9
  12. package/docs/website.md +14 -0
  13. package/package.json +45 -23
  14. package/packages/core/src/cli/artifact.ts +130 -6
  15. package/packages/core/src/cli/binary.ts +9 -1
  16. package/packages/core/src/cli/build-control.ts +110 -13
  17. package/packages/core/src/cli/context.ts +46 -0
  18. package/packages/core/src/cli/dashboard/facts.ts +215 -0
  19. package/packages/core/src/cli/dashboard/frame-parse.ts +223 -0
  20. package/packages/core/src/cli/dashboard/model.ts +141 -153
  21. package/packages/core/src/cli/dashboard/poll.ts +76 -12
  22. package/packages/core/src/cli/dashboard/render.ts +34 -5
  23. package/packages/core/src/cli/dispatch-frontend.ts +54 -5
  24. package/packages/core/src/cli/dispatch-process.ts +1 -13
  25. package/packages/core/src/cli/dispatch.ts +392 -75
  26. package/packages/core/src/cli/harvest.ts +122 -8
  27. package/packages/core/src/cli/help.ts +62 -9
  28. package/packages/core/src/cli/init-readiness-remote.ts +110 -0
  29. package/packages/core/src/cli/init-readiness-shared.ts +72 -0
  30. package/packages/core/src/cli/init-validation.ts +266 -222
  31. package/packages/core/src/cli/installation.ts +50 -8
  32. package/packages/core/src/cli/main.ts +70 -11
  33. package/packages/core/src/cli/mcp.ts +23 -1
  34. package/packages/core/src/cli/plugin.ts +7 -4
  35. package/packages/core/src/cli/remote-poll.ts +277 -0
  36. package/packages/core/src/cli/repo-state.ts +22 -0
  37. package/packages/core/src/cli/repository-status.ts +39 -1
  38. package/packages/core/src/cli/self-update.ts +77 -2
  39. package/packages/core/src/cli/status.ts +150 -28
  40. package/packages/core/src/cli/terminals.ts +2 -1
  41. package/packages/core/src/cli/ticket-assets.ts +247 -0
  42. package/packages/core/src/cli/ticket.ts +453 -8
  43. package/packages/core/src/cli/wait.ts +157 -41
  44. package/packages/core/src/cli/watch.ts +138 -168
  45. package/packages/core/src/config/live.ts +23 -0
  46. package/packages/core/src/config/pipeline-source.ts +388 -0
  47. package/packages/core/src/config/schema.ts +251 -19
  48. package/packages/core/src/events/catalog.ts +1 -0
  49. package/packages/core/src/events/globs.ts +47 -0
  50. package/packages/core/src/events/payloads.ts +52 -2
  51. package/packages/core/src/events/repository.ts +52 -1
  52. package/packages/core/src/events/sessions.ts +69 -10
  53. package/packages/core/src/events/workspace-remote.ts +12 -0
  54. package/packages/core/src/integration/harness.ts +115 -2
  55. package/packages/core/src/kernel/__snapshots__/reducer-registry.test.ts.snap +1369 -0
  56. package/packages/core/src/kernel/auto-merge-default.ts +157 -0
  57. package/packages/core/src/kernel/auto-merge.ts +99 -22
  58. package/packages/core/src/kernel/dispatch-settings.ts +42 -17
  59. package/packages/core/src/kernel/dispatch-status.ts +23 -0
  60. package/packages/core/src/kernel/engine.ts +9 -438
  61. package/packages/core/src/kernel/generators/build-log.ts +259 -0
  62. package/packages/core/src/kernel/generators/repository-journals.ts +206 -0
  63. package/packages/core/src/kernel/generators/session-log.ts +72 -0
  64. package/packages/core/src/kernel/harvest.ts +62 -18
  65. package/packages/core/src/kernel/incremental-contract.ts +75 -0
  66. package/packages/core/src/kernel/incremental-fixtures.ts +211 -0
  67. package/packages/core/src/kernel/incremental.ts +53 -0
  68. package/packages/core/src/kernel/log-index.ts +620 -0
  69. package/packages/core/src/kernel/phases.ts +16 -2
  70. package/packages/core/src/kernel/pr-attachments.ts +164 -46
  71. package/packages/core/src/kernel/reducer-registry.ts +164 -0
  72. package/packages/core/src/kernel/reducer.ts +254 -73
  73. package/packages/core/src/operator/annotations.ts +6 -0
  74. package/packages/core/src/operator/control.ts +19 -5
  75. package/packages/core/src/operator/index.ts +71 -6
  76. package/packages/core/src/operator/query.ts +253 -24
  77. package/packages/core/src/operator/read-view.ts +98 -0
  78. package/packages/core/src/operator/registry.ts +55 -10
  79. package/packages/core/src/operator/requests.ts +9 -6
  80. package/packages/core/src/operator/sandbox-host.ts +140 -0
  81. package/packages/core/src/operator/sandbox.ts +247 -14
  82. package/packages/core/src/operator/ticket-source-backend.ts +47 -0
  83. package/packages/core/src/orchestrator/approvals.ts +115 -0
  84. package/packages/core/src/orchestrator/conversation.ts +167 -0
  85. package/packages/core/src/orchestrator/model.ts +86 -0
  86. package/packages/core/src/orchestrator/prompt.ts +61 -0
  87. package/packages/core/src/orchestrator/tools.ts +117 -0
  88. package/packages/core/src/orchestrator/turn-runner.ts +457 -0
  89. package/packages/core/src/plugin-sdk/index.ts +51 -1
  90. package/packages/core/src/plugins/load.ts +135 -7
  91. package/packages/core/src/plugins/manifest.ts +183 -10
  92. package/packages/core/src/plugins/registry.ts +73 -4
  93. package/packages/core/src/ports/forge/create.ts +1 -1
  94. package/packages/core/src/ports/forge/fake.ts +31 -0
  95. package/packages/core/src/ports/forge/github-transport.ts +13 -2
  96. package/packages/core/src/ports/forge/github.ts +13 -2
  97. package/packages/core/src/ports/forge/local-git.ts +31 -1
  98. package/packages/core/src/ports/runner/claude.ts +2 -2
  99. package/packages/core/src/ports/runner/codex.ts +6 -2
  100. package/packages/core/src/ports/runner/fake.ts +7 -1
  101. package/packages/core/src/ports/runner/pi.ts +8 -6
  102. package/packages/core/src/ports/runner/provider-error.ts +13 -3
  103. package/packages/core/src/ports/tickets/fake.ts +5 -1
  104. package/packages/core/src/ports/tickets/file.ts +169 -48
  105. package/packages/core/src/ports/tickets/{remote-index.ts → hosted-index.ts} +3 -2
  106. package/packages/core/src/ports/tickets/linear.ts +33 -1
  107. package/packages/core/src/ports/tickets/operations.ts +1 -1
  108. package/packages/core/src/ports/tickets/remote.ts +8 -1
  109. package/packages/core/src/ports/types.ts +49 -3
  110. package/packages/core/src/ports/workspace/builtin-capabilities.ts +114 -0
  111. package/packages/core/src/ports/workspace/create.ts +74 -39
  112. package/packages/core/src/ports/workspace/distribution-archive.ts +171 -12
  113. package/packages/core/src/ports/workspace/fake.ts +169 -4
  114. package/packages/core/src/ports/workspace/git-worktree.ts +246 -26
  115. package/packages/core/src/ports/workspace/github-origin.ts +36 -0
  116. package/packages/core/src/ports/workspace/operator-sandbox.ts +7 -1
  117. package/packages/core/src/ports/workspace/provider-capabilities.ts +171 -0
  118. package/packages/core/src/ports/workspace/vercel-capabilities.ts +110 -0
  119. package/packages/core/src/ports/workspace/vercel-sandbox.ts +142 -114
  120. package/packages/core/src/processes/build-child.ts +17 -2
  121. package/packages/core/src/processes/build-execution-state.ts +68 -15
  122. package/packages/core/src/processes/build-runner.ts +238 -93
  123. package/packages/core/src/processes/dispatcher-selectors.ts +123 -0
  124. package/packages/core/src/processes/dispatcher.ts +501 -201
  125. package/packages/core/src/processes/execution-settlement.ts +72 -39
  126. package/packages/core/src/processes/harvest-child.ts +5 -0
  127. package/packages/core/src/processes/harvest-execution-state.ts +41 -29
  128. package/packages/core/src/processes/harvest-runner.ts +66 -19
  129. package/packages/core/src/processes/harvest.ts +196 -24
  130. package/packages/core/src/processes/infrastructure-failure-budget.ts +31 -18
  131. package/packages/core/src/processes/orchestrator-tick.ts +360 -0
  132. package/packages/core/src/processes/publication-state.ts +164 -52
  133. package/packages/core/src/processes/repo-view.ts +1436 -0
  134. package/packages/core/src/processes/sandbox-state.ts +36 -2
  135. package/packages/core/src/processes/tick-state.ts +33 -0
  136. package/packages/core/src/store/adapter.ts +41 -0
  137. package/packages/core/src/store/build-scope.ts +90 -0
  138. package/packages/core/src/store/contract.ts +1205 -0
  139. package/packages/core/src/store/digest.ts +102 -0
  140. package/packages/core/src/store/local/schema.ts +46 -2
  141. package/packages/core/src/store/local/store.ts +445 -4
  142. package/packages/core/src/store/memory.ts +246 -0
  143. package/packages/core/src/store/new-build.ts +17 -0
  144. package/packages/core/src/store/phase-session.ts +94 -0
  145. package/packages/core/src/store/remote/client.ts +310 -2
  146. package/packages/core/src/store/remote/index.ts +14 -2
  147. package/packages/core/src/store/remote/protocol.ts +115 -12
  148. package/packages/core/src/store/remote/version.ts +6 -2
  149. package/packages/core/src/store/repo-state-events.ts +205 -0
  150. package/packages/core/src/store/retention.ts +7 -0
  151. package/packages/core/src/store/session-handle.ts +94 -0
  152. package/packages/core/src/{sessions/reducer.ts → store/session-reducer.ts} +79 -19
  153. package/packages/core/src/store/snapshots.ts +88 -0
  154. package/packages/core/src/store/streams/session-writer.ts +42 -2
  155. package/packages/core/src/store/streams/wait.ts +4 -5
  156. package/packages/core/src/store/ticket-assets.ts +458 -0
  157. package/packages/core/src/store/types.ts +160 -1
  158. package/packages/core/src/testing/index.ts +59 -0
  159. package/skills/code-review/SKILL.md +5 -1
  160. package/skills/guide/SKILL.md +152 -40
  161. package/skills/guide/references/operator-api.md +2 -2
  162. package/skills/guide/references/plugin-authoring.md +17 -0
  163. package/skills/guide/references/remote-store-protocol.md +204 -16
  164. package/skills/guide/references/remote-ticket-protocol.md +1 -1
  165. package/skills/guide/references/setup.md +12 -9
  166. package/skills/implement/SKILL.md +3 -0
  167. package/skills/operate/SKILL.md +126 -0
  168. package/skills/plan/SKILL.md +4 -0
  169. package/skills/plan-review/SKILL.md +5 -1
  170. package/skills/spec/SKILL.md +46 -3
  171. package/skills/tickets/SKILL.md +57 -5
  172. package/packages/core/src/cli/dashboard/frame-image.ts +0 -373
  173. package/packages/core/src/cli/store-opening.contract.ts +0 -289
  174. package/packages/core/src/cli/testkit.ts +0 -194
  175. package/packages/core/src/markdown.ts +0 -67
  176. package/packages/core/src/operator/client.ts +0 -311
  177. package/packages/core/src/operator/server.ts +0 -616
  178. package/packages/core/src/operator/sessions.ts +0 -223
  179. package/packages/core/src/ports/runner/live-contract-fixture.ts +0 -176
  180. package/packages/core/src/ports/tickets/remote-server.ts +0 -185
  181. package/packages/core/src/store/remote/server.ts +0 -923
  182. package/packages/core/src/testing/store-failures.ts +0 -32
@@ -24,7 +24,17 @@ import type {
24
24
  SessionEventType,
25
25
  SessionEventWrite,
26
26
  } from '../events/sessions'
27
+ export type { SessionEventEnvelope }
27
28
  import type { TicketRef } from '../ontology'
29
+ import type { ReducerSnapshot, SnapshotScope } from './snapshots'
30
+ export type { ReducerSnapshot, SnapshotScope }
31
+ import type {
32
+ TicketAsset,
33
+ TicketAssetInput,
34
+ TicketAssetLimits,
35
+ TicketAssetMeta,
36
+ TicketAssetSummary,
37
+ } from './ticket-assets'
28
38
  import type {
29
39
  StreamChunk,
30
40
  StreamOutcome,
@@ -64,6 +74,12 @@ export interface NewBuildInput {
64
74
  repoOrigin?: string
65
75
  ticket?: TicketRef
66
76
  branch?: string
77
+ /** The build's `build.created` fact, appended as seq 1 in the same atomic
78
+ * step as the record: both exist or neither does, so the claim-time facts it
79
+ * freezes (ticket assets, SPEC §6.3) can never be lost to a crash between
80
+ * the two writes. An invalid payload, or any other event type, rejects and
81
+ * creates nothing. */
82
+ created?: EventWrite<'build.created'>
67
83
  }
68
84
 
69
85
  export interface ArtifactMeta {
@@ -77,6 +93,38 @@ export interface ArtifactMeta {
77
93
  createdAt: string
78
94
  }
79
95
 
96
+ /** The projection of one build's event log onto exactly the two facts an
97
+ * operator dashboard needs from every build (AUT-487): the latest terminal
98
+ * fact's kind — `done` for `build.completed`, `aborted` for `build.aborted`,
99
+ * in-order overwrite, exactly `reduceBuild`'s terminal rule — and the seqs of
100
+ * the log's `observation.recorded` occurrences. `terminal` is absent while
101
+ * the log carries neither terminal fact (a queued, running, paused, blocked,
102
+ * or mid-cleanup build). Everything else a dashboard shows comes from the
103
+ * journal, the build records, and the full histories of just the builds that
104
+ * render rows. Nothing is persisted: every adapter derives the digest from
105
+ * the event log on each call, so the log stays the sole authority and the
106
+ * digest can always be discarded and rebuilt. */
107
+ export interface BuildDigest {
108
+ slug: string
109
+ terminal?: 'done' | 'aborted'
110
+ /** Ascending-seq `{seq, ts}` pairs for every `observation.recorded` in the
111
+ * build's log; `ts` is the event envelope timestamp (AUT-521: the harvest
112
+ * pressure gate measures drift from the oldest unclaimed occurrence's ts,
113
+ * so per-occurrence timestamps travel with the digest). */
114
+ observations: { seq: number; ts: string }[]
115
+ /** Envelope ts of the log's latest `pr.merged` event, absent when the log
116
+ * has none (AUT-521: latest-wins suffices for drift, which counts a build
117
+ * when any of its merges is strictly after the oldest unclaimed
118
+ * observation). */
119
+ merged?: string
120
+ /** A `pr-attachment.hosted` has no later matching `pr-attachment.reclaimed`
121
+ * (the `pendingPrAttachmentReclaims` rule). Absent otherwise. */
122
+ reclaimPending?: true
123
+ /** The latest `execution.started` has no later `execution.ended` with the
124
+ * same `instance` (the `openExecution` rule). Absent otherwise. */
125
+ executionOpen?: true
126
+ }
127
+
80
128
  export interface RepositoryRecord {
81
129
  repo: string
82
130
  createdAt: string
@@ -103,7 +151,8 @@ export interface RepositoryArtifact {
103
151
  /** An operator session: hosted-only durable orchestrator-conversation state
104
152
  * (SPEC §7.1.1). The record carries only identity and timestamps; everything
105
153
  * else — status, open turn, pending approval, wake settings — is a reduction
106
- * of the session's own event log (sessions/reducer.ts), never a column. */
154
+ * of the session's own event log (the hosted package's session-reducer.ts),
155
+ * never a column. */
107
156
  export interface SessionRecord {
108
157
  /** Store-assigned (`os_<uuid>`). */
109
158
  id: string
@@ -257,6 +306,18 @@ export interface BuildStore {
257
306
  opts?: { waitSeconds?: number; signal?: AbortSignal },
258
307
  ): Promise<AbEvent[]>
259
308
 
309
+ /** One entry for EVERY build whose record's repo is `repo` (completeness is
310
+ * contractual: a freshly created build whose log holds none of the
311
+ * digest-relevant event types still gets its entry, with empty
312
+ * `observations` and no `terminal`), keyed by slug. A read-only, repo-scoped
313
+ * batch query shaped like `listBuilds`: it performs no `ensureRepo`, never
314
+ * writes, and an unknown or build-less repo answers an empty map rather
315
+ * than rejecting. Derived from the event log on every call — the log is the
316
+ * sole authority, so there is nothing to refresh or invalidate. The
317
+ * derivation is the shared `reduceBuildDigest` (store/digest.ts), so every
318
+ * adapter answers identical results for identical logs. */
319
+ getRepoBuildDigests(repo: string): Promise<Map<string, BuildDigest>>
320
+
260
321
  putArtifact(slug: string, artifact: ArtifactInput): Promise<ArtifactMeta>
261
322
  /** Latest revision when `rev` is omitted; null if kind (or rev) absent. */
262
323
  getArtifact(slug: string, kind: string, rev?: number): Promise<Artifact | null>
@@ -305,9 +366,92 @@ export interface BuildStore {
305
366
  sinceSeq?: number,
306
367
  opts?: { waitSeconds?: number; signal?: AbortSignal },
307
368
  ): Promise<RepositoryEvent[]>
369
+ /** Bounded repository-journal read for stateless full-state readers
370
+ * (AUT-489): every durable fact (REPOSITORY_STATE_EVENT_TYPES) plus — when
371
+ * the journal has one — the tail from the latest `dispatcher.run-started`.
372
+ * Reducing this subset with the state readers yields exactly what a full
373
+ * replay yields (`projectRepositoryStateEvents` is the normative oracle).
374
+ * Unknown repo rejects like `getRepoEvents`. */
375
+ getRepoStateEvents(repo: string): Promise<RepositoryEvent[]>
376
+ /** The journal's true high-water mark: the greatest assigned seq of any
377
+ * event type, 0 for an empty journal. Returns no event rows — the cursor a
378
+ * long-lived reader (the dispatcher's repository view) needs to resume with
379
+ * `getRepoEvents(repo, cursor)` even when the bounded subset excludes the
380
+ * journal's trailing events. Unknown repo rejects like `getRepoEvents`;
381
+ * never an `ensureRepo` side effect. */
382
+ getRepoHighWater(repo: string): Promise<number>
383
+ /** The stored reducer snapshot for a scope (a build log or a repository
384
+ * journal), a cache beside the log and never an authority. `null` when
385
+ * absent, when the stored version differs from `version`, when the stored
386
+ * cursor is ahead of the scope's log tail, or when the scope is unknown (no
387
+ * `ensureRepo` side effect, no rejection). A store without snapshot storage
388
+ * answers `null`. */
389
+ getReducerSnapshot(
390
+ scope: SnapshotScope,
391
+ reducer: string,
392
+ version: number,
393
+ ): Promise<ReducerSnapshot | null>
394
+ /** Upsert a reducer snapshot; the stored cursor never decreases. Writes only
395
+ * when no row exists, when `cursor` is above the stored one (whatever the
396
+ * versions — a mismatched row is replaced as soon as any writer advances
397
+ * past it), or when the cursor is equal and `version` is higher. Everything
398
+ * else, and any cursor beyond the log tail, is a no-op. Atomic, so
399
+ * concurrent writers cannot undo each other's progress. Resolves `true`
400
+ * when the row was written. */
401
+ putReducerSnapshot(
402
+ scope: SnapshotScope,
403
+ reducer: string,
404
+ snapshot: ReducerSnapshot,
405
+ ): Promise<boolean>
308
406
  putRepoArtifact(repo: string, artifact: ArtifactInput): Promise<RepositoryArtifactMeta>
309
407
  getRepoArtifact(repo: string, kind: string, rev?: number): Promise<RepositoryArtifact | null>
310
408
  listRepoArtifacts(repo: string, kind?: string): Promise<RepositoryArtifactMeta[]>
409
+ // ── Ticket assets (SPEC §7.1 — files attached to a ticket) ─────────────
410
+ // Keyed by (repo, ticketId, kind, name); revisions are 0-based. The store
411
+ // owns the bytes, the ticket source only ever gets a note (SPEC §13).
412
+ /** The effective ceilings this store accepts for `repo` (a remote
413
+ * deployment may advertise lower ones than the defaults). */
414
+ ticketAssetLimits(repo: string): Promise<TicketAssetLimits>
415
+ /** Validate, then store a new revision. Over-limit or invalid input rejects
416
+ * with `TicketAssetValidationError` and stores nothing. */
417
+ putTicketAsset(repo: string, ticketId: string, asset: TicketAssetInput): Promise<TicketAssetMeta>
418
+ /** Latest live revision when `rev` is omitted; null when the asset is
419
+ * absent, removed (latest row is a tombstone), or `rev` is not a real
420
+ * revision. An explicit `rev` of an earlier revision keeps working after a
421
+ * removal, so a build that pinned it still retrieves it. */
422
+ getTicketAsset(
423
+ repo: string,
424
+ ticketId: string,
425
+ kind: string,
426
+ name: string,
427
+ rev?: number,
428
+ ): Promise<TicketAsset | null>
429
+ /** The ticket asset `kind/name` exactly as build `slug` froze it (SPEC
430
+ * §6.3): the latest pinned revision, or with `rev` any revision the build
431
+ * ever pinned. Reads the build's own events, never the ticket's current
432
+ * state, so it is stable after a replace or removal and works for finished
433
+ * builds. Null when the build never pinned it. Rejects an unknown build. */
434
+ getPinnedTicketAsset(
435
+ slug: string,
436
+ kind: string,
437
+ name: string,
438
+ rev?: number,
439
+ ): Promise<TicketAsset | null>
440
+ /** Default: the latest live revision per (kind, name). `revisions: true`:
441
+ * every real revision (no tombstones). Summaries carry no manifest. */
442
+ listTicketAssets(
443
+ repo: string,
444
+ ticketId: string,
445
+ opts?: { revisions?: boolean },
446
+ ): Promise<TicketAssetSummary[]>
447
+ /** Tombstone the asset (the next revision number, empty manifest). Returns
448
+ * the removed revision's meta, or null when no live asset exists. */
449
+ removeTicketAsset(
450
+ repo: string,
451
+ ticketId: string,
452
+ kind: string,
453
+ name: string,
454
+ ): Promise<TicketAssetMeta | null>
311
455
  claimRepoLease(repo: string, holder: string, ttlMs: number): Promise<boolean>
312
456
  heartbeatRepo(repo: string, holder: string): Promise<boolean>
313
457
  releaseRepoLease(repo: string, holder: string): Promise<void>
@@ -339,6 +483,21 @@ export interface BuildStore {
339
483
  id: string,
340
484
  event: SessionEventWrite<T>,
341
485
  ): Promise<SessionEventEnvelope<T>>
486
+ /** Atomically append one validated session event only when the session's
487
+ * current sequence equals `expectedSeq` (0 would only match a session with
488
+ * no facts — impossible, since `session.created` is always seq 1). Returns
489
+ * null when the session has advanced; a comparison miss must not mutate the
490
+ * log or the record's `updatedAt`. The three turn-start sites (message
491
+ * route, answer route, dispatcher tick) race on the same idle session, and
492
+ * two concurrent `turn.started` facts would break the single-open-turn
493
+ * invariant — this is the sessions mirror of the build log's
494
+ * `appendIfCurrent`. Invalid events and unknown sessions reject just as
495
+ * they do for `appendSessionEvent`. */
496
+ appendSessionEventIfCurrent<T extends SessionEventType>(
497
+ id: string,
498
+ expectedSeq: number,
499
+ event: SessionEventWrite<T>,
500
+ ): Promise<SessionEventEnvelope<T> | null>
342
501
  /** Events with seq strictly greater than `sinceSeq` (default 0), in order.
343
502
  * When nothing newer exists, an adapter honors `waitSeconds` with the
344
503
  * same clamp/early-return rules as stream reads (§7.6): whole seconds,
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Public testing entry point: the deterministic doubles, CLI entry points,
3
+ * and the end-to-end harness that out-of-tree packages' tests build against
4
+ * (the hosted store-service suite is the in-repo consumer). Production code
5
+ * never imports from here — it is test-facing surface, like plugin-sdk's
6
+ * fakes, just CLI-shaped.
7
+ */
8
+ // The plugin-sdk's contract suites, fakes, and memory stores are the shared
9
+ // testing vocabulary; re-export so `@defrex/autobuild/testing` is one import.
10
+ export * from '../plugin-sdk'
11
+ export { manualClock, steppingClock } from './fixed'
12
+ export { agentActor, DISPATCHER, KERNEL, humanActor, type Via } from '../events/envelope'
13
+ export { EventValidationError } from '../events/catalog'
14
+ export type { EscalationSource } from '../ontology'
15
+ export { abDispatch, type DispatchOpts } from '../cli/dispatch'
16
+ // CLI entry points for the hosted-dispatcher integration test: programmatic
17
+ // `ab` invocation and its environment resolution.
18
+ export { resolveCliEnv } from '../cli/env'
19
+ export { runCli } from '../cli/main'
20
+ // Deterministic id doubles for the hosted-dispatcher integration test.
21
+ export { randomUuids, sequentialIds, type IdSource } from '../ids'
22
+ export { abWatch } from '../cli/watch'
23
+ export { abTicket, type TicketSourceFactory } from '../cli/ticket'
24
+ export { createTerminalModeController } from '../cli/terminal-restore'
25
+ export { abBuildControl, type BuildControlAction } from '../cli/build-control'
26
+ export { abBulkControl } from '../cli/bulk-control'
27
+ export { createTicketSource } from '../ports/tickets/create'
28
+ export { createOperatorSandboxService } from '../operator/sandbox'
29
+ export { parseConfig } from '../config/load'
30
+ export { spawnExec, type Exec } from '../ports/workspace/git-worktree'
31
+ // Durable guest-launch state readers + the in-process guest runner for the
32
+ // hosted-dispatcher integration test's GuestExecution double, which supervises
33
+ // a real BuildRunner exactly as a real sandbox execution would.
34
+ export {
35
+ BUILD_EFFECTIVE_CONFIG_ARTIFACT,
36
+ diagnosticArtifact,
37
+ parseEffectiveBuildConfig,
38
+ selectOpenWorkspace,
39
+ } from '../processes/build-execution-state'
40
+ export {
41
+ BuildRunner,
42
+ LeaseHeldError,
43
+ SetupFailureError,
44
+ } from '../processes/build-runner'
45
+ export { readEventsWithWait } from '../store/streams/wait'
46
+ export { textContent } from '../store/types'
47
+ export {
48
+ CONFIG_TOML,
49
+ GIT_ID,
50
+ git,
51
+ happyHandlers,
52
+ makeHarness,
53
+ readyTicket,
54
+ typesOf,
55
+ writeFileIn,
56
+ type Cli,
57
+ type E2eHarness,
58
+ type SkillHandlers,
59
+ } from '../integration/harness'
@@ -27,7 +27,11 @@ goes nowhere.
27
27
  fake or mock are not contract evidence because they can repeat the
28
28
  author's assumption.
29
29
  - **Spec conformance** — every acceptance criterion met; nothing beyond
30
- the spec's scope smuggled in.
30
+ the spec's scope smuggled in. A pinned `design` asset (listed under
31
+ `assets` in `.ab/context.json`, files under `.ab/assets/<kind>/<name>/`)
32
+ is a contract on observable visual and reference outcomes, not on implementation: judge the rendered output against it wherever the code lets you
33
+ check, and treat a divergence as a finding. A gap in the asset is not a
34
+ reason to invent requirements.
31
35
  - **Tests** — do the new tests exercise the seams this change created or
32
36
  moved? Would they fail if the change were wrong?
33
37
  - **Fit** — matches the codebase's idioms; no needless indirection.
@@ -65,6 +65,13 @@ spec → plan ⇄ plan-review → implement ⇄ code-review → verify:* → fin
65
65
  claims tickets that pass the `[tickets]` ready
66
66
  gate, chooses a short immutable slug from the final conforming spec, and
67
67
  starts builds up to the top-level `capacity` bound.
68
+ - **Ticket assets** — files or folders attached to a ticket (see
69
+ [Ticket assets](#ticket-assets)) are frozen into the build at claim and
70
+ appear under `.ab/assets/` in every phase, as inputs alongside the spec.
71
+ A `design` asset is part of the contract: `plan`, `plan-review`, `implement`,
72
+ `code-review`, and agent verify steps judge against it, as a contract on
73
+ observable visual and reference outcomes, not on implementation. Other kinds
74
+ are reference material.
68
75
  - **`spec`** — the ticket's spec becomes the build's contract. The `ab-spec`
69
76
  skill is the human-interactive surface for producing it, and it runs *before*
70
77
  a build exists.
@@ -337,7 +344,7 @@ gitPasswordEnv = "AB_GIT_READ_TOKEN"
337
344
 
338
345
  [workspace.config.runtimeProvisioning.pi]
339
346
  install = "npm install --global --ignore-scripts @earendil-works/pi-coding-agent@0.84.4"
340
- preflight = "test \"$(pi --version)\" = \"0.84.4\""
347
+ preflight = "test \"$(pi --version)\" = \"0.84.4\" && pi update --models"
341
348
  ```
342
349
 
343
350
  `timeoutSeconds` is required (60–86400; Hobby currently caps at 2700), `vcpus`
@@ -413,6 +420,12 @@ Names must already exist in `[verify].steps` with matching tables; duplicates,
413
420
  blanks, unknown names, malformed metadata, and omission of an `always = true`
414
421
  step make the planner's `ab done` fail before `plan.completed` is appended.
415
422
 
423
+ An agent verify step is handed the build's pinned ticket assets under
424
+ `.ab/assets/` (listed in `.ab/context.json`) as well as the spec. A verify skill
425
+ should treat a `design` asset as part of the contract, a contract on observable
426
+ visual and reference outcomes, not on implementation, and judge the result
427
+ against it.
428
+
416
429
  An agent verify step selects `[roles.<step>]` by its **logical step name** —
417
430
  the name in `[verify].steps`, not the skill it runs. An agent finalize
418
431
  post-step follows the same rule.
@@ -719,7 +732,8 @@ state eligible.
719
732
  |---|---|---|---|
720
733
  | `source` | — | **required**, nonblank builtin or plugin registration name | Which provider backs all ticket operations. Unknown names report every available builtin and plugin source. |
721
734
  | `readyLabels` | — (source-aware) | optional; array of nonempty strings | A ticket must carry **every** listed label to be dispatchable. `[]` = **no label gate**. Absent uses the source default below. |
722
- | `readyState` | — | **required**, non-blank string | The one workflow state a ticket must sit in to be dispatchable. Linear matches exactly and case-sensitively; file canonicalizes it to a state directory (`ready` → `ready/`). There is no any-state mode. |
735
+ | `readyState` | — | **required**, non-blank string | The one workflow state a ticket must sit in to be dispatchable. Linear matches exactly and case-sensitively; file matches it case-insensitively to a state directory (`ready` → `ready/`). There is no any-state mode. |
736
+ | `readyAssignee` | — | optional; `"me"` or an email; `linear` only | Also require the ticket to be assigned to that Linear user (`me` = owner of `LINEAR_API_KEY`); unassigned tickets are never ready. |
723
737
  | `teamKey` | — | required by `linear`, forbidden by `file`, allowed for plugins | The Linear team key (e.g. `"ENG"`) or an existing plugin configuration field. |
724
738
  | `claimedState` | — | optional nonempty string; forbidden by `file`, allowed for plugins | Workflow state `claim()` moves a ticket to when a build starts. |
725
739
  | `createState` | — | optional, nonempty string | Default state for new tickets when a create does not name one. Absent = the provider's default (Linear: the team's default, e.g. Backlog; file: Triage). |
@@ -759,11 +773,16 @@ Cross-field rules, each an **error**:
759
773
  janitor completion. Every source-agnostic `ab ticket` command loads and uses
760
774
  the same configured registration.
761
775
 
762
- The file tracker is **directory-per-state**: `<dir>/<state>/<id>.md` over
763
- `triage/ ready/ doing/ done/`. The directory *is* the state, so a transition or
776
+ The file tracker is **directory-per-state**: `<dir>/<state>/<id>.md`, where
777
+ every directory directly under the root is a state: the four lifecycle
778
+ directories `triage/ ready/ doing/ done/` (always present, created on first use)
779
+ plus any others the repository adds, such as `icebox/`. A state exists when its
780
+ directory exists; state names match directories case-insensitively, and a name
781
+ with no directory is an error listing those that exist. The directory *is* the
782
+ state, so a transition or
764
783
  claim is a rename — frontmatter carries no `state`/`claimedBy`, and a ticket
765
784
  body survives byte-exactly because a move never rewrites the file. A malformed
766
- record in any lifecycle directory is diagnosed with its path and validation
785
+ record in any state directory is diagnosed with its path and validation
767
786
  problem, left untouched, and excluded from listing/claim so unrelated valid
768
787
  ready work can continue. `ab ticket list` sends those diagnostics to stderr and
769
788
  keeps JSON stdout as one bare valid-ticket array. Tracker-wide safety checks
@@ -771,8 +790,8 @@ remain fatal: the same id in two state dirs is a loud error naming both paths,
771
790
  as are root-level ticket files with no state and filesystem failures. When
772
791
  `dir` is defaulted, the backlog writes its own `.gitignore` of `*`, so git never sees it; an
773
792
  explicit `dir` is the user's and is left alone. Agents and operators drive it
774
- through the source-agnostic `ab ticket` commands rather than running `mv` by
775
- hand.
793
+ through the source-agnostic `ab ticket` commands rather than running `mv` or
794
+ `mkdir` by hand; `ab ticket state create` makes a new state.
776
795
 
777
796
  **Secrets never live in this file.** `LINEAR_API_KEY` and plugin-declared
778
797
  `requiredEnv` credentials are environment variables (a local `.env` works).
@@ -782,15 +801,21 @@ environment variable instead and say why.
782
801
 
783
802
  ### `[orchestrator]`
784
803
 
785
- Gates the operator-sandbox feature (AUT-340): a persistent, **credential-free**
786
- environment per operator × repository that an operator agent drives through the
787
- `sandbox.*` registry tools (`ab mcp`, and every later binding). The whole table
788
- is **closed to unknown keys** and restart-classified: changing it requires a
789
- dispatcher restart.
804
+ Gates the operator-agent feature (AUT-340, AUT-342): the persistent,
805
+ **credential-free** sandbox environment per operator × repository that an
806
+ operator agent drives through the `sandbox.*` registry tools (`ab mcp`, and
807
+ every later binding), and — when the embedded orchestrator is enabled — the
808
+ turn runner that answers operator sessions and wakes on build attention
809
+ events. The whole table is **closed to unknown keys** and restart-classified:
810
+ changing it requires a dispatcher restart.
790
811
 
791
812
  | Field | Default | Allowed / constraints | Effect |
792
813
  |---|---|---|---|
793
- | `enabled` | `false` | boolean | Master gate. With the table absent or `enabled = false`, the sandbox tools are absent from every binding and no environment is ever provisioned. |
814
+ | `enabled` | `false` | boolean | Master gate. With the table absent or `enabled = false`, the sandbox tools are absent from every binding, no environment is ever provisioned, and no dispatcher tick or message post ever runs orchestrator code — a local dispatcher is inert even when enabled, because the turn runner exists only in the hosted deployment. |
815
+ | `model` | — (required when `enabled = true`) | nonblank provider-qualified string, same vocabulary as `[roles]` | The turn runner's model, resolved through the deployment's gateway credential; a leading `vercel-ai-gateway/` prefix is accepted and stripped. |
816
+ | `invocationBudgetSeconds` | `240` | positive integer, clamped to 300 | Wall-clock budget for one turn invocation. A larger value is clamped — not rejected — to the 300-second hosted function limit the runner executes under. |
817
+ | `approvals` | `["builds.control:abort", "builds.control:discard", "builds.answer:revise-spec", "sandbox.publish", "tickets.move:ready"]` | array of `tool` or `tool:qualifier` strings | Registry tool calls that suspend a turn until the operator answers. Empty (`[]`) means none — autonomous operation. An entry naming an absent or unknown tool is **inert by design**, never an error. A qualified entry applies to one discriminator value only (`tickets.move:ready` approves moving a ticket to `ready`); for a tool without a discriminator, only the bare form matches. |
818
+ | `wake` | inherits the default | array of event-type globs | The attention events a **new** session wakes for. Absent inherits the default attention set (the events `ab watch` filters on by default); an explicit `[]` means never wake — message-only. Each glob must match at least one build event type. Existing sessions never retroactively inherit; an explicit wake update always wins. |
794
819
  | `sandbox` | — | strict subtable | The sandbox behavior knobs; absence reads as the defaults below. |
795
820
 
796
821
  `[orchestrator.sandbox]` fields:
@@ -814,6 +839,20 @@ and archiving an operator's last session are **destructive** (reset re-provision
814
839
  from the current base head); there is at most **one** environment per operator
815
840
  and repository.
816
841
 
842
+ Publication: `sandbox.publish` takes a title (1–200 characters), an optional
843
+ body (≤65,536 characters), and an optional explicit commit, and takes your
844
+ sandbox's current head commit to a pull request. It refuses when the checkout
845
+ has uncommitted changes — tracked or untracked (the sandbox's own provisioning
846
+ marker is excluded) — and when the commit is not a descendant of the base head
847
+ recorded when the sandbox was provisioned or last reset — a pre-existing environment without a recorded
848
+ base head asks you to run `sandbox.reset` first. The rule is PR only, never the
849
+ base branch: the sandbox itself never pushes and holds no credential — the push
850
+ and the PR creation are kernel plumbing through the workspace provider's
851
+ publication capability, to one deterministic operator branch per operator and
852
+ repository, so a later publish updates the same PR's head. A publication is a
853
+ repository-journal fact naming the operator, the session, the branch, the
854
+ commit, and the PR; a refused or failed attempt is journaled too.
855
+
817
856
  ## Setup and upgrades
818
857
 
819
858
  **`ab init <target> [--force]`** runs *outside* build sessions — it takes a
@@ -827,7 +866,7 @@ repo path, needs no `AB_*` environment, and is safe to re-run. It:
827
866
  `[commands]`, no verify or finalize steps, a valid local ticket gate, and one
828
867
  explicit runtime solely as a schema placeholder. It reads no package or
829
868
  language manifest and invents no command.
830
- - Vendors 11 skills before handoff, including the locally editable
869
+ - Vendors 12 skills before handoff, including the locally editable
831
870
  [repository setup reference](references/setup.md) in the installed `ab-guide`
832
871
  tree. On an interactive terminal it starts the selected coding-agent CLI in
833
872
  the target repository with a short prompt to read that reference and
@@ -1030,6 +1069,19 @@ with Linear and the file tracker, without provider-specific API/MCP calls:
1030
1069
  labeled metadata and the body verbatim, so it can read a stored spec back.
1031
1070
  - `ab ticket move <id> <state> [--json]` transitions one ticket and reports its
1032
1071
  post-transition value.
1072
+ - `ab ticket states [--json]` lists every state of the configured source:
1073
+ lifecycle states first (triage, ready, doing, done), then custom states
1074
+ alphabetically. Each row has the state name, the roles it plays under the
1075
+ current config (create, ready, claimed, triage, proposal, done, or none), its
1076
+ ticket count, and its purpose when set. `--json` emits one bare array of
1077
+ `{name, roles, tickets, about?}`.
1078
+ - `ab ticket state create <name> [--about <text>] [--json]` makes a state, for
1079
+ example `icebox`. It is idempotent: an existing state is reported as already
1080
+ existing, and `--about` (one line) records or replaces its purpose. A name is
1081
+ one to forty lowercase letters, digits, and hyphens starting with a letter,
1082
+ and may not match an existing state case-insensitively. There is no rename or
1083
+ delete. Only the file source supports `states` and `state create`; other
1084
+ sources exit nonzero naming the source and the unsupported operation.
1033
1085
 
1034
1086
  State names and ids are source-local. For block and unblock, the first id is the
1035
1087
  ticket being changed and the comma-separated second operand names its blockers.
@@ -1070,6 +1122,37 @@ subcommand accepts `--json`, which emits one bare JSON value and no prose: a
1070
1122
  `Ticket[]` for `list`, and the complete resulting `Ticket` for `create`,
1071
1123
  `update`, `block`, `unblock`, `show`, or `move`.
1072
1124
 
1125
+ ### Ticket assets
1126
+
1127
+ A ticket asset is a file or folder attached to a ticket and stored in the
1128
+ BuildStore, never in the ticket source: a design export, reference screenshots,
1129
+ a sample payload. Each has a kind and a name.
1130
+
1131
+ - `ab ticket attach <id> <kind> <path> [--name <name>] [--json]` stores `<path>`
1132
+ (default name: its basename). Attaching the same kind and name again makes a
1133
+ new revision; earlier ones stay retrievable. Over the store's size limit
1134
+ (25 MiB by default, lower on a hosted deployment) it is refused. Attach and
1135
+ remove leave a note on the ticket naming the asset.
1136
+ - `ab ticket show <id>` lists the current assets; with `--json` the ticket
1137
+ gains an `assets` array.
1138
+ - `ab ticket asset get <id> <kind> <name> <dest> [--rev <n>]` downloads the
1139
+ exact bytes of the latest (or given) revision; `ab ticket asset rm <id> <kind>
1140
+ <name>` drops it from the current assets.
1141
+ - **Frozen at claim.** A claim pins the current revisions into the build. A
1142
+ later attach or remove leaves a running build alone; a human answer with
1143
+ `--revise-spec-from-ticket` pins again. `ab build asset get <slug> <kind>
1144
+ <name> <dest> [--rev <n>]` downloads what the build froze, also after it ends.
1145
+ - **Where phases find them.** `ab context` writes each pinned asset to
1146
+ `.ab/assets/<kind>/<name>/` (a file asset is the single file inside that
1147
+ directory) and lists them under `assets` in `.ab/context.json` with kind,
1148
+ name, revision, layout, size, and path. The key is absent when the build has
1149
+ none.
1150
+
1151
+ By convention the `design` kind is a visual or reference contract, exported to
1152
+ static files when the ticket is groomed, so no build converts a link. The spec
1153
+ cites it by kind and name. See [the lifecycle](#the-lifecycle) for how phases
1154
+ judge against it.
1155
+
1073
1156
  ## Retrieving build artifacts
1074
1157
 
1075
1158
  `ab artifact download <build> <kind>[@rev] --output <file> [--store <ref>]`
@@ -1081,6 +1164,17 @@ Inside a phase, a complete ambient tuple permits only the ambient build; a
1081
1164
  foreign build or malformed/partial identity is rejected. Use the exact pinned
1082
1165
  `@rev` from a PR attachment command.
1083
1166
 
1167
+ Repository-scoped streams have their own form:
1168
+ `ab artifact download stream:<id>[@rev] --output <file> [--store <ref>]`. A
1169
+ stream id is store-assigned and globally unique, so no build argument is
1170
+ needed; the command resolves the stream, requires it to be repository-scoped
1171
+ and owned by this repository, and retrieves the artifact its close finalized
1172
+ (§7.6) — an open stream has no artifact yet. This form is strictly an operator
1173
+ read: under any complete ambient build or Harvest identity it fails closed
1174
+ (§8.2), since repository-scoped targets are never reachable by ambient
1175
+ own-build or own-harvest authority. Find stream ids in the session rows of
1176
+ `ab harvest status`.
1177
+
1084
1178
  In a build session, `ab artifact put <kind> <file> --attach` atomically deposits
1085
1179
  the exact bytes and designates that revision for the PR. A later designation of
1086
1180
  the same kind replaces it; distinct kinds remain distinct attachments. The
@@ -1214,16 +1308,19 @@ on its first paint; clearing the hold removes both on the next poll. The hold
1214
1308
  survives a restart and is not defeated by re-running `ab dispatch`.
1215
1309
 
1216
1310
  `--auto-merge` and `--no-auto-merge` similarly set the durable repository
1217
- claim-time default; omission reuses stored state, falling back to OFF only when
1311
+ auto-merge default; omission reuses stored state, falling back to OFF only when
1218
1312
  no fact exists. The forms are mutually exclusive independently of the intake
1219
1313
  pair. Global-row `m` re-reads current state, appends the opposite value, and
1220
1314
  posts a dispatcher notice. When on, each fresh dispatcher claim records the
1221
1315
  existing human-authored `build.auto-merge-requested` fact immediately after
1222
1316
  `build.created` and before runner launch. The first visible build frame therefore
1223
- carries `auto merge`. This is a creation-time seed, not policy: changes never
1224
- touch existing builds, resumed/adopted logs or other creation paths never sample
1225
- it, and build-row `m` remains independent (a seeded build can be cancelled while
1226
- the global default stays on). Both repository settings are independently
1317
+ carries `auto merge`. The default is also a bulk action over current builds:
1318
+ on each change the dispatcher reconciles every non-terminal build against the
1319
+ newest fact, requesting consent when it turns on and withdrawing it when it
1320
+ turns off, attributed to the toggling operator exactly as a row keypress would
1321
+ be. A per-build toggle overrides its build's state until the global row is
1322
+ toggled again, so a seeded or fanned build can still be cancelled while the
1323
+ global default stays on. Both repository settings are independently
1227
1324
  last-write-wins by event sequence. They are stored in the BuildStore, not in
1228
1325
  `autobuild.toml`; propagation uses polling, not a push channel.
1229
1326
 
@@ -1249,10 +1346,13 @@ settles every exhaustion barrier. Exhaustion and escalation remain terminal.
1249
1346
  Escalation's row is dismissed only by a post-terminal human resume request
1250
1347
  followed by its later kernel acknowledgement, never by the request alone.
1251
1348
 
1252
- A build with auto-merge off has no auto-merge token. Requested, enabled, and
1253
- cancelling states all read `auto merge`: cyan means requested locally but not
1254
- yet applied on GitHub, green means native auto-merge is enabled, and yellow
1255
- means cancellation is in flight. The token disappears when cancellation lands.
1349
+ Every build row renders an explicit `auto merge <state>` token, including off:
1350
+ cyan means requested locally but not yet applied on GitHub, green means native
1351
+ auto-merge is enabled, yellow means cancellation is in flight, and dim means
1352
+ off — turning red while the repository default reads ON, so the
1353
+ consent mismatch is visible without opening the detail view. A build parked on
1354
+ an open PR with no consent carries the reason on its merge step, naming the
1355
+ per-build control that requests it.
1256
1356
  Merge conflicts recover automatically: the gate defers, the pipeline re-enters
1257
1357
  reconcile, and verify re-runs before consent is re-examined — the reconcile
1258
1358
  step on the build row is the only indication, and uncomputed mergeability
@@ -1509,10 +1609,12 @@ stream:<id>` retrieves the closed session's finalized document.
1509
1609
  Harvest sessions stream the same protocol into *repository-scoped* streams:
1510
1610
  the harvest runner's synthesize and review brackets each get one stream
1511
1611
  (`phase` spelled `harvest:<step>`), with `harvest.session.started` carrying
1512
- the stream id. Find them via that `stream` field or the harvest journal
1513
- (`ab watch --repository`); a closed stream finalizes to a repository-scope
1514
- `stream:<id>` artifact. The harvest run id is not on the stream — recover it
1515
- from the journal event that names the stream.
1612
+ the stream id. `ab harvest status` shows each run's sessions the way build
1613
+ status does — one row per session with its stream id and open/closed status
1614
+ (`session hs_… (harvest, synthesize@1) stream st_… (open)`). Once a stream
1615
+ closes, its finalized document is a repository-scoped `stream:<id>` artifact,
1616
+ and `ab artifact download stream:<id>` retrieves it; the stream form is an
1617
+ operator-only read (ambient phase identity fails closed).
1516
1618
 
1517
1619
  **`ab watch [<slug>...] [--repository] [--event <glob>]... [--since <cursor>]
1518
1620
  [--timeout <dur>] [--interval <dur>] [--count <n>] [--json] [--store <ref>]`**
@@ -1553,7 +1655,10 @@ The watch ends with exit 0 when `--timeout` elapses (30 minutes by default;
1553
1655
  gone terminal; with no slugs named, an empty set of active builds does not end
1554
1656
  it. Usage errors, scope violations, unknown slugs, and cursor rejection exit 1
1555
1657
  before any record. A store read that fails after the watch has started is
1556
- reported once on stderr and retried without duplicating or skipping any event.
1658
+ reported on stderr once per failing read source — locally the poll cycle is
1659
+ one source, while against an `http(s)` store each tracked stream and the
1660
+ discovery pass carries its own failure streak — and retried without
1661
+ duplicating or skipping any event.
1557
1662
  Against a local store the default cadence polls every 1 second. Against an
1558
1663
  `http(s)` store each tracked stream long-polls instead — one held request per
1559
1664
  stream waits up to 25 seconds for the next event, requests run concurrently,
@@ -1635,8 +1740,11 @@ condition (one record with `condition` null, showing how the build ended); 3 —
1635
1740
  stdout is a bare `{"cursor": …}` object for the final position, plus one line
1636
1741
  on stderr); 4 — interrupted by SIGINT before any condition was satisfied (same
1637
1742
  cursor behavior as 3). The default poll interval and `--interval` match
1638
- `ab watch`. A store read that fails while waiting is reported once on stderr
1639
- and retried without missing or duplicating a condition. The command is
1743
+ `ab watch`. A store read that fails while waiting is reported on stderr once
1744
+ per failing read source — locally the poll cycle is one source, while
1745
+ against an `http(s)` store each tracked build and the discovery pass carries
1746
+ its own failure streak — and retried without missing or duplicating a
1747
+ condition. The command is
1640
1748
  read-only — it appends no event, takes no lease, and creates no record — and
1641
1749
  deciding what to do about a match stays with the caller's skill.
1642
1750
 
@@ -1760,7 +1868,7 @@ default, when you need to know what this repo's version says).
1760
1868
  | Skill | Place in the lifecycle | Purpose |
1761
1869
  |---|---|---|
1762
1870
  | `ab-spec` | Before a build exists | Design a feature spec-first through conversation, or flesh out a ticket to the spec standard. The human-interactive surface; takes a ticket, not a build slug. **Model-invocable.** |
1763
- | `ab-tickets` | Before a build exists | Drive this repo's local file tracker: create a ticket, report the backlog, groom or move one between `triage/ ready/ doing/ done/`. The agent-facing surface on the tracker — use it instead of `mv`. **Model-invocable.** |
1871
+ | `ab-tickets` | Before a build exists | Drive this repo's local file tracker: create a ticket, report the backlog, groom or move one between the lifecycle directories (`triage/ ready/ doing/ done/`) and any repository-defined states, including the icebox. The agent-facing surface on the tracker — use it instead of `mv`. **Model-invocable.** |
1764
1872
  | `ab-guide` | Outside the pipeline | This skill: reference for the lifecycle, config surface, setup/upgrade behavior, and the installed skills. Its editable `references/setup.md` guides the init handoff. **Model-invocable.** |
1765
1873
  | `ab-harvest` | harvest `synthesize` step | Continue the producer across review rounds: cluster the claimed structured observations and author typed spec-standard create/join/suppress proposals. Runner-only. |
1766
1874
  | `ab-harvest-review` | harvest `review` step | Fresh adversarial reviewer for proposal coverage, semantic dedup, spec quality, and evidence; returns `approve`/`revise`/`escalate`. Runner-only. |
@@ -1770,12 +1878,16 @@ default, when you need to know what this repo's version says).
1770
1878
  | `ab-code-review` | `code-review` phase | Fresh skeptic: review the implementation diff against spec and plan, same verdict vocabulary. |
1771
1879
  | `ab-reconcile` | `reconcile` phase (epilogue) | Resolve a conflicted PR with one merge commit, base merged *into* the build branch. Never rebases. |
1772
1880
  | `ab-finalize` | `finalize` phase | Write the PR description for a green build; the kernel opens the PR. |
1773
-
1774
- Autobuild installs 11 skills. Everything except `ab-spec`, `ab-tickets`, and
1775
- `ab-guide` is **runner-invoked** by the kernel and carries
1776
- `disable-model-invocation: true` — do not invoke a phase skill yourself, and do
1777
- not remove that key to make one convenient to call. A model starting a pipeline
1778
- phase by pattern-matching a description is exactly what the flag prevents. The
1779
- three exceptions drive no phase, which is the criterion for membership (§16.3):
1780
- `ab-spec` and `ab-tickets` are human/agent-facing pre-build surfaces, and
1781
- `ab-guide` is read-only reference including the setup handoff guidance.
1881
+ | `ab-operate` | hosted only | The embedded orchestrator's own operating manual: its role, the registry tool surface with the approval semantics, the attention set, wake-trigger input, budget/approval suspension, and when to escalate to the operator rather than act. Invoked by the embedded orchestrator, never vendored into a build session. |
1882
+
1883
+ Autobuild installs 12 skills. Everything except `ab-spec`, `ab-tickets`, and
1884
+ `ab-guide` carries `disable-model-invocation: true` — do not invoke a phase
1885
+ skill yourself, and do not remove that key to make one convenient to call. A
1886
+ model starting a pipeline phase by pattern-matching a description is exactly
1887
+ what the flag prevents. Those three exceptions drive no build phase, which is
1888
+ the criterion for membership (§16.3): `ab-spec` and `ab-tickets` are
1889
+ human/agent-facing pre-build surfaces, and `ab-guide` is read-only reference
1890
+ including the setup handoff guidance. The fourth skill outside the
1891
+ model-invocable set, `ab-operate`, is not a build-phase skill either: it is the
1892
+ embedded orchestrator's own operating manual, read by the hosted turn runner as
1893
+ its system prompt and never invoked by a model in a build session.
@@ -1,9 +1,9 @@
1
1
  # Operator API v1
2
2
 
3
3
  The hosted service exposes the polling-oriented operator surface at
4
- `/operator/v1/repos/{repo}`. It is versioned with remote store protocol **2**.
4
+ `/operator/v1/repos/{repo}`. It is versioned with remote store protocol **3**.
5
5
  Every request must send `X-Autobuild-Version`,
6
- `X-Autobuild-Protocol-Version: 2`, and `Authorization: Bearer …`. Identity is
6
+ `X-Autobuild-Protocol-Version: 3`, and `Authorization: Bearer …`. Identity is
7
7
  checked before authentication or resource lookup.
8
8
 
9
9
  Mint a token offline; the signed user is used as the human actor on every write: