@defrex/autobuild 0.8.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 (251) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +192 -0
  3. package/SPEC.md +2269 -0
  4. package/bin/ab-build-runner.ts +47 -0
  5. package/bin/ab-dev.ts +27 -0
  6. package/bin/ab-dispatch-kernel.ts +92 -0
  7. package/bin/ab-harvest-runner.ts +29 -0
  8. package/bin/ab-init-probe.ts +19 -0
  9. package/bin/ab-process-group-reaper.ts +32 -0
  10. package/bin/ab.ts +6 -0
  11. package/bin/agent/ab +3 -0
  12. package/docs/answering-blocked-builds.md +89 -0
  13. package/docs/architecture.md +726 -0
  14. package/docs/assets/headline-wide.png +0 -0
  15. package/docs/configuration.md +1623 -0
  16. package/docs/hosted-dispatcher-cutover-validation.md +178 -0
  17. package/docs/hosted-dispatcher.md +204 -0
  18. package/docs/mcp.md +119 -0
  19. package/docs/operator-api.md +186 -0
  20. package/docs/remote-store-protocol.md +972 -0
  21. package/docs/remote-ticket-protocol.md +79 -0
  22. package/docs/setup.md +289 -0
  23. package/docs/spec-standard.md +63 -0
  24. package/docs/ticket-dependencies.md +59 -0
  25. package/docs/vercel-sandbox-rollout-probe.md +7 -0
  26. package/package.json +135 -0
  27. package/packages/core/src/cli/args.ts +74 -0
  28. package/packages/core/src/cli/artifact.ts +182 -0
  29. package/packages/core/src/cli/binary.ts +198 -0
  30. package/packages/core/src/cli/build-control.ts +789 -0
  31. package/packages/core/src/cli/build-progress.ts +55 -0
  32. package/packages/core/src/cli/bulk-control.ts +385 -0
  33. package/packages/core/src/cli/context.ts +470 -0
  34. package/packages/core/src/cli/dashboard/actions.ts +59 -0
  35. package/packages/core/src/cli/dashboard/cells.ts +92 -0
  36. package/packages/core/src/cli/dashboard/composer.ts +230 -0
  37. package/packages/core/src/cli/dashboard/detail.ts +100 -0
  38. package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
  39. package/packages/core/src/cli/dashboard/live.ts +152 -0
  40. package/packages/core/src/cli/dashboard/model.ts +1173 -0
  41. package/packages/core/src/cli/dashboard/poll.ts +196 -0
  42. package/packages/core/src/cli/dashboard/render.ts +1568 -0
  43. package/packages/core/src/cli/dashboard/selection.ts +66 -0
  44. package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
  45. package/packages/core/src/cli/dashboard/session-view.ts +506 -0
  46. package/packages/core/src/cli/dashboard/transcript.ts +108 -0
  47. package/packages/core/src/cli/dev.ts +46 -0
  48. package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
  49. package/packages/core/src/cli/dispatch-process.ts +259 -0
  50. package/packages/core/src/cli/dispatch.ts +3692 -0
  51. package/packages/core/src/cli/dotenv.ts +48 -0
  52. package/packages/core/src/cli/env.ts +206 -0
  53. package/packages/core/src/cli/harvest.ts +698 -0
  54. package/packages/core/src/cli/help.ts +713 -0
  55. package/packages/core/src/cli/init-agent.ts +106 -0
  56. package/packages/core/src/cli/init-validation.ts +583 -0
  57. package/packages/core/src/cli/init.ts +756 -0
  58. package/packages/core/src/cli/installation.ts +333 -0
  59. package/packages/core/src/cli/keyboard.ts +261 -0
  60. package/packages/core/src/cli/main.ts +1399 -0
  61. package/packages/core/src/cli/mcp.ts +279 -0
  62. package/packages/core/src/cli/models.ts +65 -0
  63. package/packages/core/src/cli/observe.ts +55 -0
  64. package/packages/core/src/cli/plugin.ts +227 -0
  65. package/packages/core/src/cli/pr-attachments.ts +138 -0
  66. package/packages/core/src/cli/pr-summary.ts +124 -0
  67. package/packages/core/src/cli/publication-settlement.ts +188 -0
  68. package/packages/core/src/cli/repo-state.ts +213 -0
  69. package/packages/core/src/cli/repository-status.ts +80 -0
  70. package/packages/core/src/cli/self-update.ts +443 -0
  71. package/packages/core/src/cli/status.ts +895 -0
  72. package/packages/core/src/cli/store-opening.contract.ts +289 -0
  73. package/packages/core/src/cli/store-opening.ts +167 -0
  74. package/packages/core/src/cli/store-ref.ts +33 -0
  75. package/packages/core/src/cli/terminal-restore.ts +174 -0
  76. package/packages/core/src/cli/terminal.ts +361 -0
  77. package/packages/core/src/cli/terminals.ts +995 -0
  78. package/packages/core/src/cli/testkit.ts +194 -0
  79. package/packages/core/src/cli/ticket.ts +484 -0
  80. package/packages/core/src/cli/upgrade-agent.ts +180 -0
  81. package/packages/core/src/cli/upgrade-commit.ts +468 -0
  82. package/packages/core/src/cli/upgrade-notice.ts +91 -0
  83. package/packages/core/src/cli/upgrade-progress.ts +110 -0
  84. package/packages/core/src/cli/upgrade.ts +888 -0
  85. package/packages/core/src/cli/wait.ts +587 -0
  86. package/packages/core/src/cli/watch.ts +932 -0
  87. package/packages/core/src/config/live.ts +255 -0
  88. package/packages/core/src/config/load.ts +98 -0
  89. package/packages/core/src/config/roles.ts +438 -0
  90. package/packages/core/src/config/schema.ts +774 -0
  91. package/packages/core/src/distribution.ts +24 -0
  92. package/packages/core/src/events/catalog.ts +201 -0
  93. package/packages/core/src/events/envelope.ts +59 -0
  94. package/packages/core/src/events/payloads.ts +618 -0
  95. package/packages/core/src/events/repository.ts +559 -0
  96. package/packages/core/src/events/sessions.ts +180 -0
  97. package/packages/core/src/harvest/schema.ts +147 -0
  98. package/packages/core/src/ids.ts +45 -0
  99. package/packages/core/src/integration/harness.ts +681 -0
  100. package/packages/core/src/kernel/auto-merge.ts +259 -0
  101. package/packages/core/src/kernel/converge.ts +117 -0
  102. package/packages/core/src/kernel/dispatch-settings.ts +60 -0
  103. package/packages/core/src/kernel/dispatch-status.ts +141 -0
  104. package/packages/core/src/kernel/engine.ts +1047 -0
  105. package/packages/core/src/kernel/harvest.ts +823 -0
  106. package/packages/core/src/kernel/origin.ts +69 -0
  107. package/packages/core/src/kernel/phases.ts +196 -0
  108. package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
  109. package/packages/core/src/kernel/pr-attachments.ts +82 -0
  110. package/packages/core/src/kernel/reducer.ts +752 -0
  111. package/packages/core/src/kernel/stall.ts +155 -0
  112. package/packages/core/src/kernel/verify-applicability.ts +52 -0
  113. package/packages/core/src/markdown.ts +67 -0
  114. package/packages/core/src/ontology.ts +288 -0
  115. package/packages/core/src/open-map.ts +203 -0
  116. package/packages/core/src/operator/annotations.ts +187 -0
  117. package/packages/core/src/operator/client.ts +311 -0
  118. package/packages/core/src/operator/control.ts +117 -0
  119. package/packages/core/src/operator/index.ts +62 -0
  120. package/packages/core/src/operator/presentation.ts +22 -0
  121. package/packages/core/src/operator/protocol.ts +98 -0
  122. package/packages/core/src/operator/query.ts +201 -0
  123. package/packages/core/src/operator/registry.ts +1043 -0
  124. package/packages/core/src/operator/requests.ts +138 -0
  125. package/packages/core/src/operator/sandbox.ts +458 -0
  126. package/packages/core/src/operator/server.ts +616 -0
  127. package/packages/core/src/operator/sessions.ts +223 -0
  128. package/packages/core/src/operator/tickets.ts +177 -0
  129. package/packages/core/src/plugin-sdk/index.ts +174 -0
  130. package/packages/core/src/plugins/contract-entry.ts +74 -0
  131. package/packages/core/src/plugins/contract-env.ts +3 -0
  132. package/packages/core/src/plugins/load.ts +259 -0
  133. package/packages/core/src/plugins/manifest.ts +236 -0
  134. package/packages/core/src/plugins/registry.ts +274 -0
  135. package/packages/core/src/plugins/runtimes.ts +75 -0
  136. package/packages/core/src/ports/forge/contract.ts +258 -0
  137. package/packages/core/src/ports/forge/create.ts +74 -0
  138. package/packages/core/src/ports/forge/fake.ts +488 -0
  139. package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
  140. package/packages/core/src/ports/forge/github-transport.ts +352 -0
  141. package/packages/core/src/ports/forge/github.ts +1162 -0
  142. package/packages/core/src/ports/forge/local-git.ts +568 -0
  143. package/packages/core/src/ports/runner/claude.ts +873 -0
  144. package/packages/core/src/ports/runner/cli-stream.ts +90 -0
  145. package/packages/core/src/ports/runner/codex.ts +873 -0
  146. package/packages/core/src/ports/runner/contract.ts +492 -0
  147. package/packages/core/src/ports/runner/fake.ts +213 -0
  148. package/packages/core/src/ports/runner/json-record.ts +2 -0
  149. package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
  150. package/packages/core/src/ports/runner/one-shot.ts +29 -0
  151. package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
  152. package/packages/core/src/ports/runner/pi-bridge.js +6 -0
  153. package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
  154. package/packages/core/src/ports/runner/pi.ts +617 -0
  155. package/packages/core/src/ports/runner/production.ts +74 -0
  156. package/packages/core/src/ports/runner/provider-error.ts +121 -0
  157. package/packages/core/src/ports/runner/routing.ts +234 -0
  158. package/packages/core/src/ports/runner/runtime.ts +214 -0
  159. package/packages/core/src/ports/runner/session-env.ts +34 -0
  160. package/packages/core/src/ports/runner/stream-parts.ts +103 -0
  161. package/packages/core/src/ports/tickets/contract.ts +570 -0
  162. package/packages/core/src/ports/tickets/create.ts +135 -0
  163. package/packages/core/src/ports/tickets/fake.ts +215 -0
  164. package/packages/core/src/ports/tickets/file.ts +559 -0
  165. package/packages/core/src/ports/tickets/linear.ts +811 -0
  166. package/packages/core/src/ports/tickets/operations.ts +161 -0
  167. package/packages/core/src/ports/tickets/remote-index.ts +13 -0
  168. package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
  169. package/packages/core/src/ports/tickets/remote-server.ts +185 -0
  170. package/packages/core/src/ports/tickets/remote.ts +130 -0
  171. package/packages/core/src/ports/tickets/update.ts +51 -0
  172. package/packages/core/src/ports/types.ts +528 -0
  173. package/packages/core/src/ports/workspace/build-execution.ts +123 -0
  174. package/packages/core/src/ports/workspace/contract.ts +136 -0
  175. package/packages/core/src/ports/workspace/create.ts +132 -0
  176. package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
  177. package/packages/core/src/ports/workspace/fake.ts +432 -0
  178. package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
  179. package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
  180. package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
  181. package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
  182. package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
  183. package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
  184. package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
  185. package/packages/core/src/processes/build-child-exit.ts +78 -0
  186. package/packages/core/src/processes/build-child.ts +119 -0
  187. package/packages/core/src/processes/build-execution-state.ts +95 -0
  188. package/packages/core/src/processes/build-parent-watch.ts +35 -0
  189. package/packages/core/src/processes/build-runner.ts +2614 -0
  190. package/packages/core/src/processes/dispatcher.ts +2825 -0
  191. package/packages/core/src/processes/execution-settlement.ts +171 -0
  192. package/packages/core/src/processes/harvest-child.ts +115 -0
  193. package/packages/core/src/processes/harvest-execution-state.ts +121 -0
  194. package/packages/core/src/processes/harvest-runner.ts +1656 -0
  195. package/packages/core/src/processes/harvest.ts +595 -0
  196. package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
  197. package/packages/core/src/processes/phase-failure-budget.ts +52 -0
  198. package/packages/core/src/processes/process-group-reaper.ts +35 -0
  199. package/packages/core/src/processes/process-group.ts +65 -0
  200. package/packages/core/src/processes/publication-loss.ts +62 -0
  201. package/packages/core/src/processes/publication-state.ts +93 -0
  202. package/packages/core/src/processes/sandbox-state.ts +101 -0
  203. package/packages/core/src/registry.ts +24 -0
  204. package/packages/core/src/sessions/reducer.ts +185 -0
  205. package/packages/core/src/skills.ts +7 -0
  206. package/packages/core/src/spec-standard.ts +47 -0
  207. package/packages/core/src/store/adapter.ts +80 -0
  208. package/packages/core/src/store/build-scope.ts +272 -0
  209. package/packages/core/src/store/contract.ts +2581 -0
  210. package/packages/core/src/store/local/blobs.ts +38 -0
  211. package/packages/core/src/store/local/schema.ts +180 -0
  212. package/packages/core/src/store/local/store.ts +1713 -0
  213. package/packages/core/src/store/memory.ts +1138 -0
  214. package/packages/core/src/store/phase-session.ts +425 -0
  215. package/packages/core/src/store/remote/client.ts +780 -0
  216. package/packages/core/src/store/remote/index.ts +12 -0
  217. package/packages/core/src/store/remote/protocol.ts +348 -0
  218. package/packages/core/src/store/remote/server.ts +923 -0
  219. package/packages/core/src/store/remote/token.ts +137 -0
  220. package/packages/core/src/store/remote/version.ts +10 -0
  221. package/packages/core/src/store/retention.ts +91 -0
  222. package/packages/core/src/store/session-handle.ts +265 -0
  223. package/packages/core/src/store/streams/assemble.ts +79 -0
  224. package/packages/core/src/store/streams/lock.ts +42 -0
  225. package/packages/core/src/store/streams/session-writer.ts +306 -0
  226. package/packages/core/src/store/streams/types.ts +171 -0
  227. package/packages/core/src/store/streams/wait.ts +127 -0
  228. package/packages/core/src/store/subscribe.ts +105 -0
  229. package/packages/core/src/store/types.ts +466 -0
  230. package/packages/core/src/testing/fixed.ts +44 -0
  231. package/packages/core/src/testing/store-failures.ts +32 -0
  232. package/packages/core/src/zod-issues.ts +91 -0
  233. package/skills/code-review/SKILL.md +100 -0
  234. package/skills/finalize/SKILL.md +44 -0
  235. package/skills/guide/SKILL.md +1781 -0
  236. package/skills/guide/references/operator-api.md +164 -0
  237. package/skills/guide/references/plugin-authoring.md +354 -0
  238. package/skills/guide/references/remote-store-protocol.md +972 -0
  239. package/skills/guide/references/remote-ticket-protocol.md +79 -0
  240. package/skills/guide/references/setup.md +289 -0
  241. package/skills/guide/references/spec-standard.md +63 -0
  242. package/skills/guide/references/ticket-dependencies.md +59 -0
  243. package/skills/harvest/SKILL.md +66 -0
  244. package/skills/harvest-review/SKILL.md +63 -0
  245. package/skills/implement/SKILL.md +82 -0
  246. package/skills/plan/SKILL.md +123 -0
  247. package/skills/plan-review/SKILL.md +121 -0
  248. package/skills/reconcile/SKILL.md +65 -0
  249. package/skills/spec/SKILL.md +132 -0
  250. package/skills/tickets/SKILL.md +139 -0
  251. package/templates/autobuild.toml +36 -0
@@ -0,0 +1,528 @@
1
+ /**
2
+ * Ports (SPEC §3.2): interfaces to the world, each with swappable adapters.
3
+ * The kernel and processes depend only on these types; adapters live in
4
+ * `ports/<port>/` and fakes for seam tests in `ports/fakes/`.
5
+ */
6
+ import type {
7
+ HostedPrAttachmentAsset,
8
+ PrAttachment,
9
+ PrImageHostTarget,
10
+ TicketRef,
11
+ WorkspaceBase,
12
+ } from '../ontology'
13
+ import type { StreamOutcome, StreamPart } from '../store/streams/types'
14
+ import type { BuildExecution } from './workspace/build-execution'
15
+ import type { HarvestExecution } from './workspace/harvest-execution'
16
+ import type { OperatorSandboxExecution } from './workspace/operator-sandbox'
17
+
18
+ // ── Session streams (SPEC §9) ─────────────────────────────────────────────
19
+ //
20
+ // The live, protocol-shaped view of a build session: a runner that holds the
21
+ // optional streaming capability translates its harness's native output into
22
+ // AI SDK UI Message Stream parts and appends them as they happen. Streams
23
+ // are presentation, never routing; outcomes still travel only the typed CLI.
24
+
25
+ /**
26
+ * The per-turn write side of a session stream. `append` NEVER throws: a
27
+ * failing stream path must never fail a turn. Adapters call it with the
28
+ * part vocabulary of `ai-ui-message-stream/v1` plus the `data-ab-*`
29
+ * extension parts.
30
+ */
31
+ export interface SessionStreamEmitter {
32
+ append(parts: StreamPart[]): void
33
+ }
34
+
35
+ /**
36
+ * The bracket-scoped sink a runtime registration's `openSessionStream`
37
+ * capability receives: `open` creates the store stream and returns its id,
38
+ * after which the sink is also an emitter; `close` is idempotent (first
39
+ * outcome wins) and best-effort.
40
+ */
41
+ export interface SessionStreamSink extends SessionStreamEmitter {
42
+ open(label: string): Promise<string>
43
+ close(outcome: StreamOutcome): Promise<void>
44
+ }
45
+
46
+ // ── TicketSource (SPEC §3.2, §13) ────────────────────────────────────────────
47
+ //
48
+ // Policy (§13): the TicketSource initiates and receives projections; it is
49
+ // never consulted mid-build and never used as artifact storage. A file-based
50
+ // adapter with nowhere to put blobs must be fully workable.
51
+
52
+ export interface Ticket {
53
+ ref: TicketRef
54
+ /** Stable source-native key used to correlate an externally created ticket
55
+ * with Autobuild's durable creation reservation. It is separate from the
56
+ * operator-facing `ref.id` and optional for legacy/plugin projections. */
57
+ creationKey?: string
58
+ title: string
59
+ /** The ticket body — pre-build, the spec lives here (§6.3). */
60
+ body: string
61
+ state?: string
62
+ labels: string[]
63
+ /** Source-local ids of tickets declared as blockers. Absent ≡ none. */
64
+ blockedBy?: string[]
65
+ }
66
+
67
+ /** A partial source scan. Diagnostics name source records that were excluded
68
+ * from eligibility; infrastructure failures and source-wide invariants still
69
+ * reject `listReady` rather than appearing here. */
70
+ export interface TicketListing {
71
+ tickets: Ticket[]
72
+ diagnostics: string[]
73
+ }
74
+
75
+ export interface TicketDraft {
76
+ title: string
77
+ body: string
78
+ /** Labels applied at creation. Names need not already exist in the source;
79
+ * adapters own any provider-side label-registry reconciliation. */
80
+ labels?: string[]
81
+ /** Source-local blocker ids, recorded natively at creation (§13). An
82
+ * adapter must record every one or throw — never discard silently. */
83
+ blockedBy?: string[]
84
+ }
85
+
86
+ /** One node of the dependency graph, as this source sees it. */
87
+ export interface DependencyState {
88
+ /** The requested source-local id, echoed back. */
89
+ id: string
90
+ /** False = no such ticket in this source. */
91
+ exists: boolean
92
+ /**
93
+ * Complete per this source's NATIVE lifecycle semantics — the adapter owns
94
+ * this meaning and callers must not re-derive it from `state` (§13: the
95
+ * provider's taxonomy never leaks above the port). Linear: `state.type` in
96
+ * `completed | canceled`. File: state equals the source's done state.
97
+ */
98
+ resolved: boolean
99
+ /** This node's own declared blockers, so callers can close the graph. */
100
+ blockedBy: string[]
101
+ }
102
+
103
+ export interface TicketCreateOptions {
104
+ /** Explicit workflow state for this create (harvest always targets Triage). */
105
+ state?: string
106
+ /** Source-level adoption key for crash-safe external creation. File and fake
107
+ * sources treat it as opaque; Linear requires a durably reserved UUID v4,
108
+ * sends it verbatim as the issue id, and queries that id when adopting. */
109
+ idempotencyKey?: string
110
+ }
111
+
112
+ /** Editable ticket fields. Omitted fields are preserved; state is deliberately
113
+ * absent because `transition()` exclusively owns lifecycle changes. */
114
+ export interface TicketUpdate {
115
+ /** Required when named: an update cannot clear the ticket's title. */
116
+ title?: string
117
+ /** Required when named: pre-build, this is the spec (§6.3). */
118
+ body?: string
119
+ /** A complete label replacement. An explicit empty array clears labels.
120
+ * Names need not already exist in the source; adapters own any
121
+ * provider-side label-registry reconciliation. */
122
+ labels?: string[]
123
+ }
124
+
125
+ export interface TicketSource {
126
+ readonly name: string
127
+ /** Ready tickets matching the dispatch criteria (label/state — §3.3), plus
128
+ * actionable diagnostics for individual source records excluded as invalid.
129
+ * Fatal source failures and invariants still reject the call. */
130
+ listReady(criteria: { labels?: string[]; state?: string }): Promise<TicketListing>
131
+ get(id: string): Promise<Ticket | null>
132
+ /** Claim-before-launch (§12): acquires nonterminal work and returns true.
133
+ * Returns false for an unknown, already-claimed/contested, or terminal ticket.
134
+ * Refusing a terminal ticket leaves its state unchanged; claim never reopens
135
+ * completed work. An explicit transition to a nonterminal state permits a
136
+ * later claim. */
137
+ claim(id: string): Promise<boolean>
138
+ comment(id: string, body: string): Promise<void>
139
+ transition(id: string, state: string): Promise<void>
140
+ create(draft: TicketDraft, opts?: TicketCreateOptions): Promise<Ticket>
141
+ /** Partially replace editable fields. The patch must be nonempty and strict;
142
+ * omitted fields (including unmodeled provider metadata) remain untouched. */
143
+ update(id: string, patch: TicketUpdate): Promise<void>
144
+ /** Add one same-source blocker. Retries are idempotent; the target and
145
+ * blocker must exist, and a ticket may not block itself. */
146
+ addBlocker(id: string, blockerId: string): Promise<void>
147
+ /** Remove one same-source blocker. Missing relations are successful no-ops;
148
+ * only the target ticket is required to still exist. */
149
+ removeBlocker(id: string, blockerId: string): Promise<void>
150
+ /**
151
+ * Dependency-graph nodes for `ids`. The result covers EVERY requested id, in
152
+ * request order; a missing id comes back `{exists: false, resolved: false,
153
+ * blockedBy: []}`. Read at ticket creation and at dispatch time only — both
154
+ * are initiation, so §13's "never consulted mid-build" is untouched.
155
+ */
156
+ dependencyStates(ids: string[]): Promise<DependencyState[]>
157
+ }
158
+
159
+ // ── Workspace (SPEC §3.2, §7) ────────────────────────────────────────────────
160
+
161
+ export interface WorkspaceHandle {
162
+ provider: string
163
+ /** Provider-scoped identifier (worktree path or sandbox name). */
164
+ ref: string
165
+ /** Absolute working-copy path in the provider's execution environment. It
166
+ * is not necessarily reachable from the dispatcher. */
167
+ path: string
168
+ /** Dispatcher-reachable path when one exists. Remote providers omit it. */
169
+ localPath?: string
170
+ branch: string
171
+ }
172
+
173
+ export interface WorkspaceProvisionResult extends WorkspaceHandle {
174
+ /** Durable evidence for the commit this provision selected or reused. */
175
+ base: WorkspaceBase
176
+ }
177
+
178
+ export interface WorkspacePublication {
179
+ /** Observe whether the exact requested commit already reached the durable
180
+ * remote branch after an interrupted acknowledgement. */
181
+ isPublished?(input: { sha: string; branch: string }): Promise<boolean>
182
+ /** Publish exactly one commit to exactly one branch; no arbitrary command
183
+ * surface is exposed to the dispatcher or build child. */
184
+ publish(input: { ref: string; sha: string; branch: string }): Promise<void>
185
+ }
186
+
187
+ /** Snapshot-purge evidence recorded with a workspace cleanup fact. Absence of
188
+ * leftover snapshots can be proven from these durable events without querying
189
+ * the provider. */
190
+ export interface WorkspaceReapSnapshots {
191
+ outcome: 'confirmed' | 'absent' | 'unknown'
192
+ deleted?: number
193
+ error?: string
194
+ }
195
+
196
+ export interface WorkspaceReapOutcome {
197
+ outcome: 'confirmed' | 'absent'
198
+ snapshots: WorkspaceReapSnapshots
199
+ }
200
+
201
+ export interface WorkspaceRecovery {
202
+ /** Fence an unavailable environment. Unknown outcomes must reject so the
203
+ * dispatcher retains the lease/workspace fact and retries after its fence. */
204
+ reap(handle: WorkspaceHandle): Promise<WorkspaceReapOutcome>
205
+ }
206
+
207
+ export interface WorkspaceProvider {
208
+ readonly name: string
209
+ /** Optional workspace-adjacent executor. Remote providers substitute here;
210
+ * locally reachable providers use the shipped subprocess capability. */
211
+ readonly buildExecution?: BuildExecution
212
+ /** Optional hosted-harvest executor. Present on providers that can
213
+ * provision a disposable environment for harvest sessions; its absence
214
+ * keeps harvest running locally in the dispatcher's workspace. */
215
+ readonly harvestExecution?: HarvestExecution
216
+ /** Optional operator-sandbox host (AUT-340): one persistent,
217
+ * credential-free environment per operator × repository behind registry
218
+ * tools. Present on providers that can provision it; absent means the
219
+ * provider cannot host an operator sandbox, and sandbox tools stay
220
+ * unavailable over that provider. */
221
+ readonly orchestratorSandbox?: OperatorSandboxExecution
222
+ /** Trusted dispatcher-only publication capability for remote workspaces. */
223
+ readonly publication?: WorkspacePublication
224
+ /** Optional only for remote/disposable providers. */
225
+ readonly recovery?: WorkspaceRecovery
226
+ provision(opts: {
227
+ repo: string
228
+ baseBranch: string
229
+ branch: string
230
+ /** Immutable recovery checkpoint, never a moved base branch. */
231
+ revision?: string
232
+ /** Monotonic environment generation used in provider idempotency keys. */
233
+ generation?: number
234
+ }): Promise<WorkspaceProvisionResult>
235
+ release(handle: WorkspaceHandle): Promise<void>
236
+ }
237
+
238
+ // ── Forge (SPEC §3.2, §8.6, §15.7) ───────────────────────────────────────────
239
+ //
240
+ // D7: agents never touch the remote — push, PR creation, and forge API calls
241
+ // are plumbing triggered by terminal commands. D1: PR→main is squash merge,
242
+ // main→feature is a merge commit, rebase is banned.
243
+
244
+ export interface PrRef {
245
+ number: number
246
+ /** Provider locator. HTTP(S) when the forge has a web UI; otherwise a
247
+ * meaningful provider-native identifier such as a local Git ref. */
248
+ url: string
249
+ headSha: string
250
+ }
251
+
252
+ export type PrState =
253
+ | { state: 'open'; mergeable: boolean | null }
254
+ | { state: 'merged'; sha: string }
255
+ | { state: 'closed' }
256
+
257
+ /** Abort cleanup may race landing. A merged result is terminal and must never
258
+ * be overwritten by a close attempt. */
259
+ export type ClosePrResult = { state: 'closed' } | { state: 'merged'; sha: string }
260
+
261
+ /** Result of reconciling durable auto-merge intent with the forge. Native
262
+ * state is acknowledged only by `applied`; the other results deliberately
263
+ * leave the command pending for a later janitor poll. */
264
+ export type AutoMergeDeferralCode =
265
+ | 'github-plan-limitation'
266
+ | 'repository-auto-merge-disabled'
267
+ | 'unproven-gate-state'
268
+ /** A persistent, definite state — GitHub has computed merge conflicts with
269
+ * the base branch. Recovery is updating the branch or resolving the
270
+ * conflicts, not gate-state investigation. */
271
+ | 'merge-conflicts'
272
+ /** Mergeability not yet computed — transient, resolves on retry. */
273
+ | 'mergeability-uncomputed'
274
+ | 'local-base-checkout-dirty'
275
+ | 'local-git-identity-missing'
276
+
277
+ /** How a deferral resolves. `pipeline-resolved` codes are recovered by the
278
+ * pipeline itself — reconcile for merge conflicts, a later poll for
279
+ * not-yet-computed mergeability — and record no operator observation.
280
+ * `human-actionable` codes need a person to fix the named condition and keep
281
+ * the one-observation-per-PR-and-command rule. Adding a code without choosing
282
+ * a class is a compile error. */
283
+ export type AutoMergeDeferralClass = 'pipeline-resolved' | 'human-actionable'
284
+
285
+ export const autoMergeDeferralClasses = {
286
+ 'github-plan-limitation': 'human-actionable',
287
+ 'repository-auto-merge-disabled': 'human-actionable',
288
+ 'unproven-gate-state': 'human-actionable',
289
+ 'merge-conflicts': 'pipeline-resolved',
290
+ 'mergeability-uncomputed': 'pipeline-resolved',
291
+ 'local-base-checkout-dirty': 'human-actionable',
292
+ 'local-git-identity-missing': 'human-actionable',
293
+ } as const satisfies Record<AutoMergeDeferralCode, AutoMergeDeferralClass>
294
+
295
+ export interface AutoMergeDeferralReason {
296
+ /** Stable machine-readable family used by durable operator observations. */
297
+ code: AutoMergeDeferralCode
298
+ /** Provider detail. It must identify auto-merge inspection/application, not PR creation. */
299
+ detail: string
300
+ }
301
+
302
+ export type AutoMergeResult =
303
+ | { kind: 'applied' }
304
+ | { kind: 'ungated'; headSha: string }
305
+ | { kind: 'deferred'; reason?: AutoMergeDeferralReason }
306
+
307
+ /** One explicitly designated image artifact to copy to a review-window host. */
308
+ export interface PrAttachmentUploadRequest {
309
+ workspacePath: string
310
+ target: PrImageHostTarget
311
+ prUrl: string
312
+ attachment: PrAttachment
313
+ content: Uint8Array
314
+ /** Full lowercase SHA-256 / BuildStore blobRef for `content`. */
315
+ sha256: string
316
+ }
317
+
318
+ export interface PrAttachmentReclaimRequest {
319
+ /** Cleanup deliberately runs from the main repository after workspace removal. */
320
+ workspacePath: string
321
+ asset: HostedPrAttachmentAsset
322
+ }
323
+
324
+ /** Narrow optional Forge capability. Its absence is a supported text-only
325
+ * path, not a verification outcome. */
326
+ export interface PrAttachmentHosting {
327
+ upload(request: PrAttachmentUploadRequest): Promise<HostedPrAttachmentAsset>
328
+ reclaim(request: PrAttachmentReclaimRequest): Promise<void>
329
+ }
330
+
331
+ export interface Forge {
332
+ readonly name: string
333
+ /** Optional because another forge may support PRs but not public image assets. */
334
+ readonly prAttachments?: PrAttachmentHosting
335
+ /** Publish the workspace's current HEAD to this remote destination branch. */
336
+ pushBranch(workspacePath: string, branch: string): Promise<void>
337
+ openPr(opts: {
338
+ workspacePath: string
339
+ head: string
340
+ base: string
341
+ title: string
342
+ body: string
343
+ /** Untouched deposited pr-description. Providers may use it as the
344
+ * landing message; title/body remain the portable review projection. */
345
+ mergeMessage?: string
346
+ }): Promise<PrRef>
347
+ /** Optional authoritative base resolver. Capability-less adapters retain
348
+ * the legacy origin fetch performed by the caller. The destination is a
349
+ * build-private ref in the workspace's shared Git database. */
350
+ snapshotBase?(opts: {
351
+ workspacePath: string
352
+ base: string
353
+ destinationRef: string
354
+ }): Promise<string>
355
+ /** Janitor poll (§15.7): merged/closed/mergeability for one PR. */
356
+ getPrState(workspacePath: string, number: number): Promise<PrState>
357
+ /** Optional additive abort-cleanup capabilities. Older plugins remain
358
+ * loadable, but cleanup stays pending with an actionable diagnostic. */
359
+ closePr?(workspacePath: string, number: number): Promise<ClosePrResult>
360
+ deleteBranch?(workspacePath: string, branch: string): Promise<void>
361
+ /**
362
+ * Reconcile GitHub-native auto-merge desired state. Enabling returns an
363
+ * ungated candidate only after proving the base branch has no merge-blocking
364
+ * gate. Transient merge states defer quietly; non-transient inability to
365
+ * inspect or apply consent returns a reason-bearing deferral. The operation
366
+ * is idempotent and safe to retry across the forge-call/event-append crash window.
367
+ */
368
+ setAutoMerge(workspacePath: string, number: number, enabled: boolean): Promise<AutoMergeResult>
369
+ /**
370
+ * Perform a normal squash merge, guarded by the inspected PR head. This is
371
+ * never an admin/force operation: forge-side protection still applies.
372
+ */
373
+ squashMerge(workspacePath: string, number: number, expectedHeadSha: string): Promise<void>
374
+ /** Post the build's summary comment (§7.5). */
375
+ commentOnPr(workspacePath: string, number: number, body: string): Promise<void>
376
+ /** Optional checkout-less capability: the current tip of a remote branch.
377
+ * Throws when the branch does not exist (including 404); callers decide
378
+ * whether absence is an error or an `undefined`. Its absence retains the
379
+ * legacy host `git ls-remote` base resolution. */
380
+ remoteBranchSha?(branch: string): Promise<string>
381
+ /** Optional checkout-less capability: raw file bytes from the repository.
382
+ * Ref omitted → the repository's default branch. Throws when the path does
383
+ * not exist. Its absence retains reading configuration from a checkout. */
384
+ readFile?(path: string, ref?: string): Promise<string>
385
+ }
386
+
387
+ // ── AgentRunner (SPEC §9) ────────────────────────────────────────────────────
388
+ //
389
+ // Session-based, because review loops need memory: the producer *continues*
390
+ // its session across rounds; the reviewer gets a fresh one each round (§10).
391
+ // Every production adapter also exposes this distribution's `ab` launcher at
392
+ // the front of the tool environment's PATH; CLI availability is a runner
393
+ // guarantee and does not depend on a global installation or host ordering.
394
+
395
+ export interface AgentSessionHandle {
396
+ id: string
397
+ runner: string
398
+ model?: string
399
+ }
400
+
401
+ interface AgentTurnResultBase {
402
+ /** Final text of the turn — informational only; outcomes travel the typed
403
+ * channel (`ab done|verdict|escalate`), never parsed from output (§8.4). */
404
+ text: string
405
+ usage: { inputTokens: number; outputTokens: number; turns: number }
406
+ }
407
+
408
+ /** A turn whose provider/runtime completed normally. This does not mean the
409
+ * phase completed: only a typed terminal event can express that outcome. */
410
+ export interface AgentTurnCompleted extends AgentTurnResultBase {
411
+ kind: 'completed'
412
+ }
413
+
414
+ export type AgentFailureCause = 'availability' | 'exhaustion' | 'credentials' | 'configuration'
415
+
416
+ export interface AgentTurnFailure {
417
+ /** Provider/runner text, preserved verbatim when one was supplied. */
418
+ message: string
419
+ /** False means "apply the existing bounded retry policy"; it does not claim
420
+ * the failure is transient. True remains the compatibility signal used by
421
+ * retry policy. Provider exhaustion is permanent for retry purposes while
422
+ * still being eligible for a configured alternate. */
423
+ permanent: boolean
424
+ /** Additive structured routing cause. Legacy plugin runners may omit it;
425
+ * their existing permanent bit then supplies the conservative behavior. */
426
+ cause?: AgentFailureCause
427
+ }
428
+
429
+ /** A turn that reached a provider/runtime-declared failure while retaining an
430
+ * endable session handle, so transcript deposition remains guaranteed. */
431
+ export interface AgentTurnFailed extends AgentTurnResultBase {
432
+ kind: 'failed'
433
+ failure: AgentTurnFailure
434
+ }
435
+
436
+ /** SDK/provider-declared failures must use the failed variant. A completed turn
437
+ * with no typed terminal is the distinct `no-terminal` condition (§8.4). */
438
+ export type AgentTurnResult = AgentTurnCompleted | AgentTurnFailed
439
+
440
+ /** Transcripts come back through the interface, not scraped from disk (§9). */
441
+ export interface Transcript {
442
+ content: string
443
+ metadata: {
444
+ runner: string
445
+ model?: string
446
+ usage: { inputTokens: number; outputTokens: number; turns: number }
447
+ }
448
+ }
449
+
450
+ export interface AgentStartOpts {
451
+ /** Installed skill to invoke, e.g. `ab-plan`. */
452
+ skill: string
453
+ /** Opaque argument passed to the skill (build slug or harvest run id). */
454
+ invocation?: string
455
+ /** Backward-compatible build name; new callers should also set invocation. */
456
+ buildSlug?: string
457
+ workspacePath: string
458
+ model?: string
459
+ /** Extra CLI argv tokens for the selected runtime, in declared order. */
460
+ args?: readonly string[]
461
+ /** Scoped ambient auth (D8): AB_STORE, AB_BUILD, AB_PHASE, AB_SESSION,
462
+ * AB_TOKEN. Adapters merge it over their process environment and then apply
463
+ * the runner-controlled Autobuild CLI PATH prefix. */
464
+ env: Record<string, string>
465
+ /** Caller-owned cancellation for this turn. Aborting must leave any created
466
+ * session endable so its transcript can still be deposited. */
467
+ signal?: AbortSignal
468
+ /** Live session stream, present only when the resolved runtime declared
469
+ * the streaming capability (SPEC §9). A turn without it emits no parts. */
470
+ stream?: SessionStreamEmitter
471
+ }
472
+
473
+ /** Per-turn refresh for a continued session (§10): each continue turn is a
474
+ * NEW session bracket (next round, fresh session id), so the runner
475
+ * re-issues ambient auth for the turn (D8) — merged over the start env. A
476
+ * turn left on round 1's env would have the CLI resolve the stale round and
477
+ * reject the continued round's terminal as a D5 second call (§8.4). */
478
+ export function agentInvocation(opts: AgentStartOpts): string {
479
+ const invocation = opts.invocation ?? opts.buildSlug
480
+ if (invocation === undefined || invocation === '') {
481
+ throw new Error('AgentStartOpts requires a non-empty invocation')
482
+ }
483
+ return invocation
484
+ }
485
+
486
+ export interface AgentContinueOpts {
487
+ env?: Record<string, string>
488
+ /** Caller-owned cancellation for this continued turn. */
489
+ signal?: AbortSignal
490
+ /** Live session stream, present only when the resolved runtime declared
491
+ * the streaming capability (SPEC §9). A turn without it emits no parts. */
492
+ stream?: SessionStreamEmitter
493
+ }
494
+
495
+ export interface AgentRunner {
496
+ readonly name: string
497
+ start(opts: AgentStartOpts): Promise<{ session: AgentSessionHandle; result: AgentTurnResult }>
498
+ /** Review-loop rounds (§10). Adapters without native resumption implement
499
+ * this as start-with-rehydrate-from-store (§9). */
500
+ continue(
501
+ session: AgentSessionHandle,
502
+ message: string,
503
+ opts?: AgentContinueOpts,
504
+ ): Promise<AgentTurnResult>
505
+ /** Always called; the transcript goes to the store — the corpus is
506
+ * guaranteed complete (§9). */
507
+ end(session: AgentSessionHandle): Promise<Transcript>
508
+ }
509
+
510
+ // ── TelemetrySource (SPEC §3.2, §12) ─────────────────────────────────────────
511
+
512
+ export interface Signal {
513
+ source: string
514
+ id: string
515
+ title: string
516
+ url?: string
517
+ /** Evidence for the proposal this signal may become (spec standard §6.1). */
518
+ evidence: Record<string, unknown>
519
+ occurrences?: number
520
+ usersAffected?: number
521
+ firstSeen?: string
522
+ lastSeen?: string
523
+ }
524
+
525
+ export interface TelemetrySource {
526
+ readonly name: string
527
+ fetchSignals(since?: string): Promise<Signal[]>
528
+ }
@@ -0,0 +1,123 @@
1
+ /** Immutable identity passed when a workspace-adjacent executor is asked to
2
+ * start a build. Build state, workspace location, config, progress, and outcome
3
+ * are deliberately absent: both sides obtain those only from the BuildStore. */
4
+ export interface BuildExecutionStart {
5
+ slug: string
6
+ storeRef: string
7
+ instance: string
8
+ /** Provider-native locator from the current workspace.provisioned fact. */
9
+ workspaceRef: string
10
+ }
11
+
12
+ /** Serialized private-child envelope. Host PIDs are meaningful only to the
13
+ * local executor; an environment-owned VM is supervised as one unit by its
14
+ * provider. Keeping the modes discriminated prevents accidental cross-namespace
15
+ * signalling. */
16
+ export type BuildChildLaunch = BuildExecutionStart &
17
+ (
18
+ | { supervision: { kind: 'local-parent'; parentPid: number } }
19
+ | { supervision: { kind: 'environment' } }
20
+ )
21
+
22
+ export function parseBuildChildLaunch(value: unknown): BuildChildLaunch | undefined {
23
+ if (value === null || typeof value !== 'object') return undefined
24
+ const candidate = value as Partial<BuildChildLaunch> & { supervision?: Record<string, unknown> }
25
+ if (
26
+ typeof candidate.slug !== 'string' ||
27
+ candidate.slug.length === 0 ||
28
+ typeof candidate.storeRef !== 'string' ||
29
+ candidate.storeRef.length === 0 ||
30
+ typeof candidate.instance !== 'string' ||
31
+ candidate.instance.length === 0 ||
32
+ typeof candidate.workspaceRef !== 'string' ||
33
+ candidate.workspaceRef.length === 0 ||
34
+ candidate.supervision === undefined
35
+ )
36
+ return undefined
37
+ const supervision = candidate.supervision
38
+ if (supervision.kind === 'environment') {
39
+ if (Object.hasOwn(supervision, 'parentPid')) return undefined
40
+ return candidate as BuildChildLaunch
41
+ }
42
+ if (
43
+ supervision.kind === 'local-parent' &&
44
+ Number.isInteger(supervision.parentPid) &&
45
+ (supervision.parentPid as number) > 0
46
+ )
47
+ return candidate as BuildChildLaunch
48
+ return undefined
49
+ }
50
+
51
+ export const BUILD_EXECUTION_LEASE_TTL_MS = 60_000
52
+
53
+ /** Child exit is liveness evidence only. It carries no pipeline outcome. */
54
+ export interface BuildExecutionExit {
55
+ exitCode: number | null
56
+ signal?: string
57
+ }
58
+
59
+ /** Immutable provider identity recorded as soon as an execution starts. */
60
+ export interface BuildExecutionIdentity {
61
+ provider: string
62
+ workspaceRef: string
63
+ /** Provider-native VM/container identity when it differs from workspaceRef. */
64
+ environmentId?: string
65
+ /** Provider-native session identity. A resumed persistent VM gets a new one. */
66
+ sessionId?: string
67
+ /** Provider-native detached command identity, recorded durably at launch so
68
+ * a later process can re-observe the execution without process memory. */
69
+ commandId?: string
70
+ }
71
+
72
+ /** Teardown must distinguish proof from an interrupted/ambiguous acknowledgement. */
73
+ export type BuildExecutionTeardownResult =
74
+ | { outcome: 'confirmed' | 'absent' }
75
+ | { outcome: 'unknown'; error: string }
76
+
77
+ /** How a handle's lifetime is tied to its supervising process. `local-parent`
78
+ * means the kernel owns the process tree and must reap it at teardown;
79
+ * `environment` means the guest environment owns the execution and the kernel
80
+ * supervises only its local wait — teardown detaches instead of stopping, so
81
+ * the guest outlives the supervising process and a later invocation settles
82
+ * the execution from the Store plus provider liveness. */
83
+ export type BuildExecutionSupervision = 'local-parent' | 'environment'
84
+
85
+ export interface BuildExecutionHandle {
86
+ /** Available for supervision/tests, never used as build state. */
87
+ readonly pid?: number
88
+ readonly identity?: BuildExecutionIdentity
89
+ /** Explicit supervision kind. Never inferred from `environmentId` — a local
90
+ * pid and a remote VM id are different namespaces. */
91
+ readonly supervision: BuildExecutionSupervision
92
+ /** Resolves only after the execution environment has reaped everything the
93
+ * build started. The exit projection still describes the build leader. */
94
+ readonly completion: Promise<BuildExecutionExit>
95
+ /** Idempotent shutdown: graceful, bounded force escalation, then full reap. */
96
+ stop(): Promise<BuildExecutionTeardownResult | void>
97
+ /** Stop supervising without touching the environment: abort the local wait
98
+ * only. No command kill, no environment stop, no lease release, no
99
+ * `execution.ended`, no publication settlement. The teardown mode when the
100
+ * guest must outlive the supervising process. */
101
+ detach(): Promise<void>
102
+ }
103
+
104
+ /** Provider liveness observation of one recorded execution, derived from
105
+ * provider state rather than process memory. `ended` carries the observed
106
+ * exit code; `lost` means the provider proved the environment/command no
107
+ * longer exists without an exit code. */
108
+ export type ExecutionObservation =
109
+ | { state: 'running' }
110
+ | { state: 'ended'; exitCode?: number }
111
+ | { state: 'lost' }
112
+
113
+ /** The substitutable seam at the workspace boundary. A remote workspace
114
+ * provider may implement this by starting the fixed build process beside its
115
+ * checkout; the kernel need not know where that is. Every provider owns full
116
+ * teardown of the environment and descendants it starts before completion. */
117
+ export interface BuildExecution {
118
+ start(input: BuildExecutionStart): Promise<BuildExecutionHandle>
119
+ /** Optional liveness observation of a previously recorded execution, from
120
+ * provider state alone. Absent = this provider cannot observe; callers
121
+ * supervise exactly as before ("cannot observe" never means "act"). */
122
+ observe?(identity: BuildExecutionIdentity): Promise<ExecutionObservation>
123
+ }