@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,618 @@
1
+ /**
2
+ * The event vocabulary (SPEC §15.3), frozen as schemas — this closes open
3
+ * thread §18.1. It is simultaneously the store schema, the kernel's I/O, the
4
+ * UI API, and the resume format, so payloads are strict objects: unknown keys
5
+ * are deposit errors, and schema errors are agent feedback (D6), not build
6
+ * failures.
7
+ *
8
+ * Conventions (SPEC §15.2): closed vocabularies live in type names
9
+ * (`plan.completed`); open ones live in payloads (`verify.completed {step}`).
10
+ * Events carry facts and refs `{kind, rev}` — never derived state, never
11
+ * blobs, never code (D3: code travels through the Forge; events carry SHAs).
12
+ */
13
+ import { z } from 'zod'
14
+ import {
15
+ artifactRefSchema,
16
+ buildOutcomeSchema,
17
+ commitRangeSchema,
18
+ prAttachmentFilenameSchema,
19
+ prImageHostSchema,
20
+ escalationResolutionSchema,
21
+ escalationSourceSchema,
22
+ escalationTargetSchema,
23
+ feedbackSchema,
24
+ findingSchema,
25
+ hostedPrAttachmentAssetSchema,
26
+ mediaTypeSchema,
27
+ observationKindSchema,
28
+ phaseSchema,
29
+ policyEscalationCauseSchema,
30
+ reviewVerdictKindSchema,
31
+ ticketRefSchema,
32
+ workspaceBaseSchema,
33
+ type ArtifactRef,
34
+ type VerifyOutcome,
35
+ } from '../ontology'
36
+
37
+ const empty = z.strictObject({})
38
+ const reasonOnly = z.strictObject({ reason: z.string().optional() })
39
+ const round = z.number().int().positive()
40
+ const attempt = z.number().int().positive()
41
+ const dispatchStage = z.enum(['create', 'workspace', 'spec', 'comment', 'launch'])
42
+ const infrastructureOperation = z.enum([
43
+ 'provision',
44
+ 'start',
45
+ 'wait',
46
+ 'stop',
47
+ 'delete',
48
+ 'reconcile',
49
+ ])
50
+ const infrastructureCause = z.enum([
51
+ 'missing',
52
+ 'session-expired',
53
+ 'timeout',
54
+ 'provider-limit',
55
+ 'provider-error',
56
+ 'unknown-outcome',
57
+ ])
58
+
59
+ export const agentFailureCauseSchema = z.enum([
60
+ 'availability',
61
+ 'exhaustion',
62
+ 'credentials',
63
+ 'configuration',
64
+ ])
65
+
66
+ /** One configured execution target that failed within a single phase attempt. */
67
+ export const providerAttemptSchema = z.strictObject({
68
+ index: z.number().int().nonnegative(),
69
+ session: z.string().min(1),
70
+ runner: z.string().min(1),
71
+ model: z.string().optional(),
72
+ error: z.string().min(1),
73
+ cause: agentFailureCauseSchema.optional(),
74
+ })
75
+
76
+ export const providerAttemptsSchema = z
77
+ .array(providerAttemptSchema)
78
+ .min(1)
79
+ .superRefine((attempts, ctx) => {
80
+ attempts.forEach((attempt, index) => {
81
+ if (attempt.index !== index) {
82
+ ctx.addIssue({
83
+ code: 'custom',
84
+ path: [index, 'index'],
85
+ message: `provider attempt index must be ${index} at this ordered position`,
86
+ })
87
+ }
88
+ })
89
+ })
90
+
91
+ export const providerSubstitutionSchema = z
92
+ .strictObject({
93
+ failed: providerAttemptSchema,
94
+ selectedIndex: z.number().int().positive(),
95
+ })
96
+ .superRefine((value, ctx) => {
97
+ if (value.selectedIndex !== value.failed.index + 1) {
98
+ ctx.addIssue({
99
+ code: 'custom',
100
+ path: ['selectedIndex'],
101
+ message: 'selectedIndex must name the configured entry immediately after failed.index',
102
+ })
103
+ }
104
+ })
105
+
106
+ const verifyStepSelectionSchema = z
107
+ .array(
108
+ z
109
+ .string()
110
+ .min(1, 'verify step names must be nonempty')
111
+ .refine((step) => step.trim().length > 0, 'verify step names must not be blank'),
112
+ )
113
+ .superRefine((steps, ctx) => {
114
+ const seen = new Set<string>()
115
+ steps.forEach((step, index) => {
116
+ if (seen.has(step)) {
117
+ ctx.addIssue({
118
+ code: 'custom',
119
+ path: [index],
120
+ message: `duplicate verify step ${JSON.stringify(step)}`,
121
+ })
122
+ }
123
+ seen.add(step)
124
+ })
125
+ })
126
+
127
+ const verifyCompletionBase = {
128
+ step: z.string().min(1),
129
+ attempt,
130
+ }
131
+
132
+ /**
133
+ * `verify.completed` is a durable protocol. The boolean branch remains
134
+ * readable for historical logs; every current writer uses the canonical,
135
+ * three-outcome branch. Keeping the branches strict prevents a producer from
136
+ * smuggling a skip through `pass: true` or recording contradictory facts.
137
+ */
138
+ const verifyCompletedPayloadSchema = z.union([
139
+ z.strictObject({
140
+ ...verifyCompletionBase,
141
+ pass: z.boolean(),
142
+ report: artifactRefSchema.optional(),
143
+ }),
144
+ z.discriminatedUnion('outcome', [
145
+ z.strictObject({
146
+ ...verifyCompletionBase,
147
+ outcome: z.literal('pass'),
148
+ report: artifactRefSchema.optional(),
149
+ }),
150
+ z.strictObject({
151
+ ...verifyCompletionBase,
152
+ outcome: z.literal('fail'),
153
+ report: artifactRefSchema.optional(),
154
+ }),
155
+ z.strictObject({
156
+ ...verifyCompletionBase,
157
+ outcome: z.literal('skipped'),
158
+ reason: z.string().trim().min(1, 'a skipped verification requires a non-blank reason'),
159
+ }),
160
+ ]),
161
+ ])
162
+
163
+ /** A pushed head is a success checkpoint, never a claim attached to failure. */
164
+ const finalizeStepCompletedPayloadSchema = z.discriminatedUnion('ok', [
165
+ z.strictObject({
166
+ step: z.string().min(1),
167
+ ok: z.literal(true),
168
+ note: z.string().optional(),
169
+ headSha: z.string().trim().min(1, 'a finalize publication head must be non-blank').optional(),
170
+ }),
171
+ z.strictObject({
172
+ step: z.string().min(1),
173
+ ok: z.literal(false),
174
+ note: z.string().optional(),
175
+ }),
176
+ ])
177
+
178
+ /** Shared by `plan-review.verdict` and `code-review.verdict` (symmetric by design). */
179
+ const reviewVerdictPayload = z.strictObject({
180
+ round,
181
+ verdict: reviewVerdictKindSchema,
182
+ findings: z.array(findingSchema),
183
+ artifact: artifactRefSchema,
184
+ /** Present when `verdict` is `escalate`. */
185
+ reason: z.string().optional(),
186
+ })
187
+
188
+ export const eventPayloadSchemas = {
189
+ // ── Build lifecycle ────────────────────────────────────────────────────────
190
+ 'build.created': z.strictObject({
191
+ ticket: ticketRefSchema,
192
+ repo: z.string().min(1),
193
+ baseBranch: z.string().min(1),
194
+ /** Claim-time auto-merge intent retained across dispatch recovery until the
195
+ * ordinary human-authored command fact can be materialized. */
196
+ autoMergeRequestedBy: z.string().min(1).optional(),
197
+ /** Frozen at claim time. Historical logs and disabled installs omit it. */
198
+ pr: z
199
+ .strictObject({
200
+ imageHost: prImageHostSchema.optional(),
201
+ })
202
+ .optional(),
203
+ }),
204
+ 'build.completed': z.strictObject({ outcome: buildOutcomeSchema }),
205
+ 'runner.attached': z.strictObject({
206
+ instance: z.string().min(1),
207
+ host: z.string().min(1),
208
+ resumedFromSeq: z.number().int().nonnegative().optional(),
209
+ }),
210
+ /** Pre-phase setup infrastructure failed. `null` explicitly means the
211
+ * command could not produce an exit status (for example, Exec threw). */
212
+ 'runner.setup-failed': z.strictObject({
213
+ command: z.string().min(1),
214
+ attempt,
215
+ exitStatus: z.number().int().nullable(),
216
+ output: z.string(),
217
+ }),
218
+ 'workspace.provisioned': z.strictObject({
219
+ provider: z.string().min(1),
220
+ ref: z.string().min(1),
221
+ /** Working-copy path in the provider execution environment. Optional only
222
+ * for historical event replay. */
223
+ path: z.string().min(1).optional(),
224
+ /** Dispatcher-reachable path. Remote providers intentionally omit it. */
225
+ localPath: z.string().min(1).optional(),
226
+ branch: z.string().min(1),
227
+ base: workspaceBaseSchema,
228
+ }),
229
+ 'workspace.released': z.union([
230
+ empty,
231
+ z.strictObject({
232
+ ref: z.string().min(1),
233
+ reason: z.enum(['completion', 'abort', 'pause', 'blocked', 'replacement', 'discard']),
234
+ }),
235
+ ]),
236
+ /** Durable marker that background provisioning of this build's remote
237
+ * workspace has begun. The marker's liveness is the build's execution lease:
238
+ * a holder heartbeats it while provisioning, so a dead provisioner's marker
239
+ * is adopted within one lease TTL. */
240
+ 'workspace.provision-started': z.strictObject({
241
+ provider: z.string().min(1),
242
+ branch: z.string().min(1),
243
+ generation: z.number().int().nonnegative(),
244
+ }),
245
+ /** Dispatcher-owned identity and provider lifecycle evidence. These facts do
246
+ * not carry pipeline progress; they make disposable execution observable. */
247
+ 'execution.started': z.strictObject({
248
+ provider: z.string().min(1),
249
+ workspaceRef: z.string().min(1),
250
+ instance: z.string().min(1),
251
+ environmentId: z.string().min(1).optional(),
252
+ sessionId: z.string().min(1).optional(),
253
+ /** Provider-native detached command id, recorded at launch so a later
254
+ * process can re-observe the execution without process memory. Optional
255
+ * only so historical journals replay without migration. */
256
+ commandId: z.string().min(1).optional(),
257
+ }),
258
+ /** Confirmed executor completion is the infrastructure retry-epoch boundary. */
259
+ 'execution.ended': z.strictObject({
260
+ instance: z.string().min(1),
261
+ workspaceRef: z.string().min(1),
262
+ outcome: z.enum(['completed', 'stopped', 'lost']),
263
+ exitCode: z.number().int().nullable().optional(),
264
+ }),
265
+ 'infrastructure.failed': z.strictObject({
266
+ provider: z.string().min(1),
267
+ workspaceRef: z.string().min(1),
268
+ instance: z.string().min(1),
269
+ environmentId: z.string().min(1).optional(),
270
+ sessionId: z.string().min(1).optional(),
271
+ operation: infrastructureOperation,
272
+ cause: infrastructureCause,
273
+ attempt,
274
+ retryable: z.boolean(),
275
+ cleanupPending: z.boolean(),
276
+ error: z.string().min(1),
277
+ }),
278
+ 'infrastructure.cleanup-attempted': z.strictObject({
279
+ provider: z.string().min(1),
280
+ workspaceRef: z.string().min(1),
281
+ operation: z.enum(['stop', 'delete', 'reconcile']),
282
+ attempt,
283
+ outcome: z.enum(['confirmed', 'absent', 'unknown']),
284
+ error: z.string().min(1).optional(),
285
+ /** Snapshot-purge evidence for environments whose provider leaves automatic
286
+ * snapshots behind. Absence is provable from Store events without querying
287
+ * the provider; emitters without snapshots omit the field. */
288
+ snapshots: z
289
+ .strictObject({
290
+ outcome: z.enum(['confirmed', 'absent', 'unknown']),
291
+ deleted: z.number().int().min(0).optional(),
292
+ error: z.string().min(1).optional(),
293
+ })
294
+ .optional(),
295
+ }),
296
+ /** Remote phases deposit their output and park before trusted publication.
297
+ * The dispatcher settles the request only after the VM session and execution
298
+ * lease are gone, then appends the ordinary phase completion fact. */
299
+ 'publication.requested': z.discriminatedUnion('operation', [
300
+ z.strictObject({
301
+ operation: z.literal('implement'),
302
+ branch: z.string().min(1),
303
+ sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
304
+ round,
305
+ base: z.string().regex(/^[0-9a-f]{40,64}$/i),
306
+ artifact: artifactRefSchema,
307
+ }),
308
+ z.strictObject({
309
+ operation: z.literal('reconcile'),
310
+ branch: z.string().min(1),
311
+ sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
312
+ artifact: artifactRefSchema,
313
+ }),
314
+ z.strictObject({
315
+ operation: z.literal('finalize'),
316
+ branch: z.string().min(1),
317
+ sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
318
+ description: artifactRefSchema,
319
+ }),
320
+ z.strictObject({
321
+ operation: z.literal('finalize-step'),
322
+ branch: z.string().min(1),
323
+ sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
324
+ step: z.string().min(1),
325
+ }),
326
+ ]),
327
+ /** Durable loss record: the dispatcher released (or is about to release) the
328
+ * workspace while the referenced `publication.requested` (named by `request`,
329
+ * its seq) could not be completed. Appended by the last-chance release guard
330
+ * (settlePublicationBeforeRelease) BEFORE `workspace.released`, so an
331
+ * operator reading the log alone can distinguish pending (a request with no
332
+ * settling successor), done (the operation's completion fact), and lost
333
+ * (this event). Deliberately no competing status field — this fact plus the
334
+ * request's seq is the whole surface. Recovery continues through the
335
+ * existing abandoned-publication flow, which re-runs the phase. */
336
+ 'publication.lost': z.strictObject({
337
+ /** seq of the lost `publication.requested` event. */
338
+ request: z.number().int().positive(),
339
+ operation: z.enum(['implement', 'reconcile', 'finalize', 'finalize-step']),
340
+ branch: z.string().min(1),
341
+ sha: z.string().regex(/^[0-9a-f]{40,64}$/i),
342
+ reason: z.string().trim().min(1),
343
+ }),
344
+ /** Checkpoints in the dispatcher-owned, retry-safe abort cleanup saga. */
345
+ 'abort.remote-branch-deleted': z.strictObject({ branch: z.string().min(1) }),
346
+ 'abort.local-branch-deleted': z.strictObject({ branch: z.string().min(1) }),
347
+ 'abort.ticket-returned': z.strictObject({
348
+ ticket: ticketRefSchema,
349
+ state: z.string().min(1),
350
+ label: z.string().min(1),
351
+ }),
352
+ /** A post-creation dispatch attempt stopped before runner attachment. */
353
+ 'dispatch.failed': z.strictObject({
354
+ stage: dispatchStage,
355
+ attempt,
356
+ error: z.string().min(1),
357
+ }),
358
+ /** Durable boundary preventing duplicate ticket notifications on recovery. */
359
+ 'dispatch.comment-posted': empty,
360
+
361
+ // ── Operator commands (D2: commands are events in the same log) ───────────
362
+ 'build.pause-requested': reasonOnly,
363
+ 'build.resume-requested': reasonOnly,
364
+ 'build.abort-requested': reasonOnly,
365
+ 'build.discard-requested': empty,
366
+ 'build.auto-merge-requested': empty,
367
+ 'build.auto-merge-cancelled': empty,
368
+ 'build.paused': empty,
369
+ 'build.resumed': empty,
370
+ 'build.aborted': empty,
371
+
372
+ // ── Spec (SPEC §6.3) ───────────────────────────────────────────────────────
373
+ 'spec.imported': z.strictObject({
374
+ artifact: artifactRefSchema,
375
+ ticket: ticketRefSchema,
376
+ }),
377
+ 'spec.authored': z.strictObject({
378
+ artifact: artifactRefSchema,
379
+ session: z.string().min(1),
380
+ }),
381
+ 'spec.revised': z.strictObject({
382
+ artifact: artifactRefSchema,
383
+ /** seq of the `escalation.raised` event that forced the revision. */
384
+ escalation: z.number().int().positive(),
385
+ }),
386
+
387
+ // ── Sessions (every agent run is bracketed by these) ──────────────────────
388
+ 'session.started': z.strictObject({
389
+ session: z.string().min(1),
390
+ role: z.string().min(1),
391
+ runner: z.string().min(1),
392
+ model: z.string().optional(),
393
+ /** Optional only so historical events replay without migration. */
394
+ args: z.array(z.string()).optional(),
395
+ phase: phaseSchema,
396
+ round: round.optional(),
397
+ /** Stream id this session's live view writes to (SPEC §9, session
398
+ * streams). Optional only so historical events replay unchanged. */
399
+ stream: z.string().min(1).optional(),
400
+ /** Present only when this target substituted for the preceding failed one. */
401
+ substitution: providerSubstitutionSchema.optional(),
402
+ }),
403
+ 'session.ended': z.union([
404
+ /** Ordinary completion retains the historical transcript-bearing shape. */
405
+ z.strictObject({
406
+ session: z.string().min(1),
407
+ transcript: artifactRefSchema,
408
+ usage: z.strictObject({
409
+ inputTokens: z.number().int().nonnegative(),
410
+ outputTokens: z.number().int().nonnegative(),
411
+ turns: z.number().int().nonnegative(),
412
+ }),
413
+ }),
414
+ /** A recovering runner closes work whose transcript died with its prior
415
+ * instance. Takeover evidence replaces, rather than fabricates, a corpus
416
+ * artifact. */
417
+ z.strictObject({
418
+ session: z.string().min(1),
419
+ outcome: z.literal('reclaimed'),
420
+ reclaimedBy: z.strictObject({
421
+ instance: z.string().min(1),
422
+ resumedFromSeq: z.number().int().nonnegative(),
423
+ }),
424
+ }),
425
+ ]),
426
+
427
+ // ── Plan loop / code loop (symmetric by design) ────────────────────────────
428
+ 'plan.started': z.strictObject({
429
+ round,
430
+ /** Symmetric with `implement.started` (§15.6-B): guidance answered on a
431
+ * plan-loop escalation must reach the producer even when the runner
432
+ * parked and re-attached with a fresh session. The start is the carrier
433
+ * `ab context` materializes from; a later matching `session.started` is
434
+ * the durable consumption boundary. */
435
+ feedback: feedbackSchema.optional(),
436
+ }),
437
+ 'plan.completed': z.strictObject({
438
+ round,
439
+ artifact: artifactRefSchema,
440
+ /** Effective plan-selected verify set in config order. Optional only so
441
+ * historical logs retain their default-all meaning. */
442
+ verifySteps: verifyStepSelectionSchema.optional(),
443
+ }),
444
+ 'plan-review.started': z.strictObject({ round }),
445
+ 'plan-review.verdict': reviewVerdictPayload,
446
+ 'implement.started': z.strictObject({
447
+ round,
448
+ /** Carries routed feedback for this producer occurrence. Guidance remains
449
+ * recoverable until a matching `session.started` records launch. */
450
+ feedback: feedbackSchema.optional(),
451
+ }),
452
+ 'implement.completed': z.strictObject({
453
+ round,
454
+ commits: commitRangeSchema,
455
+ artifact: artifactRefSchema,
456
+ }),
457
+ 'code-review.started': z.strictObject({ round }),
458
+ 'code-review.verdict': reviewVerdictPayload,
459
+
460
+ // ── Verify / finalize ──────────────────────────────────────────────────────
461
+ 'verify.started': z.strictObject({
462
+ step: z.string().min(1),
463
+ attempt,
464
+ /** An answered escalation raised by this agent verifier returns to the
465
+ * same step. The start citation is the durable delivery carrier; the
466
+ * subsequent matching `session.started` is the exactly-once consumption
467
+ * boundary. Optional for historical logs and guidance-free check/skip
468
+ * starts. */
469
+ feedback: feedbackSchema.optional(),
470
+ }),
471
+ 'verify.completed': verifyCompletedPayloadSchema,
472
+ 'finalize.started': empty,
473
+ 'finalize.completed': z.strictObject({
474
+ pr: z.strictObject({
475
+ number: z.number().int().positive(),
476
+ url: z.string().min(1),
477
+ headSha: z.string().min(1),
478
+ }),
479
+ }),
480
+ 'finalize.step-completed': finalizeStepCompletedPayloadSchema,
481
+
482
+ // Any agent session may explicitly designate an exact deposited revision.
483
+ // Successful external uploads are recorded immediately so retries can adopt
484
+ // them and terminal cleanup never depends on a workspace or current config.
485
+ 'pr-attachment.designated': z.strictObject({
486
+ artifact: artifactRefSchema,
487
+ filename: prAttachmentFilenameSchema,
488
+ mediaType: mediaTypeSchema,
489
+ }),
490
+ 'pr-attachment.hosted': z.strictObject({
491
+ /** seq of the correlated pr-attachment.designated fact. */
492
+ designationSeq: z.number().int().positive(),
493
+ asset: hostedPrAttachmentAssetSchema,
494
+ }),
495
+ 'pr-attachment.reclaimed': z.strictObject({
496
+ /** seq of the correlated pr-attachment.hosted fact. */
497
+ hostedSeq: z.number().int().positive(),
498
+ }),
499
+ 'pr-attachment.reclaim-failed': z.strictObject({
500
+ hostedSeq: z.number().int().positive(),
501
+ attempt,
502
+ error: z.string().min(1),
503
+ }),
504
+
505
+ // ── Post-PR (D1: janitor duty of the dispatcher — SPEC §15.7) ─────────────
506
+ 'pr.auto-merge-enabled': z.strictObject({
507
+ /** seq of the human auto-merge command this forge mutation applied. */
508
+ commandSeq: z.number().int().positive(),
509
+ }),
510
+ 'pr.auto-merge-disabled': z.strictObject({
511
+ /** seq of the human auto-merge command this forge mutation applied. */
512
+ commandSeq: z.number().int().positive(),
513
+ }),
514
+ 'pr.merged': z.strictObject({ sha: z.string().min(1) }),
515
+ 'pr.closed': empty,
516
+ 'pr.conflicted': z.strictObject({ baseSha: z.string().min(1) }),
517
+ 'reconcile.progress-checked': z.strictObject({
518
+ /** seq of the repeat pr.conflicted fact this authoritative read answers. */
519
+ conflictSeq: z.number().int().positive(),
520
+ /** Completed reconcile whose merge target is being compared. */
521
+ attempt,
522
+ baseSha: z.string().min(1),
523
+ }),
524
+ 'reconcile.started': z.strictObject({ attempt, baseSha: z.string().min(1) }),
525
+ 'reconcile.completed': z.strictObject({
526
+ mergeCommit: z.string().min(1),
527
+ artifact: artifactRefSchema,
528
+ }),
529
+
530
+ // ── Cross-cutting ──────────────────────────────────────────────────────────
531
+ 'observation.recorded': z.strictObject({
532
+ id: z.string().min(1),
533
+ kind: observationKindSchema,
534
+ summary: z.string().min(1),
535
+ files: z.array(z.string()).optional(),
536
+ refs: z.array(z.string()).optional(),
537
+ }),
538
+ 'escalation.raised': z.strictObject({
539
+ id: z.string().min(1),
540
+ phase: escalationTargetSchema,
541
+ round: round.optional(),
542
+ source: escalationSourceSchema,
543
+ /** Optional only so cause-less historical policy facts remain replayable.
544
+ * validateEventWrite requires it on every current policy append. */
545
+ policyCause: policyEscalationCauseSchema.optional(),
546
+ question: z.string().min(1),
547
+ refs: z.array(z.string()).optional(),
548
+ }),
549
+ 'escalation.answered': z.strictObject({
550
+ id: z.string().min(1),
551
+ answer: z.string().min(1),
552
+ resolution: escalationResolutionSchema,
553
+ /** Exact replacement spec authorized by a `revise-spec` answer. */
554
+ artifact: artifactRefSchema.optional(),
555
+ /** Absolute per-loop/current-spec review budget selected by the operator. */
556
+ reviewRoundCeiling: z.number().int().positive().optional(),
557
+ }),
558
+ /** Infra failure — distinct from verdicts (a verdict is a fact, not a failure). */
559
+ 'phase.failed': z.strictObject({
560
+ phase: phaseSchema,
561
+ round: round.optional(),
562
+ attempt,
563
+ error: z.string().min(1),
564
+ willRetry: z.boolean(),
565
+ /** Ordered configured targets tried inside this one phase attempt. */
566
+ providerAttempts: providerAttemptsSchema.optional(),
567
+ }),
568
+ } as const
569
+
570
+ export type EventType = keyof typeof eventPayloadSchemas
571
+
572
+ export const EVENT_TYPES = Object.keys(eventPayloadSchemas) as EventType[]
573
+
574
+ export type EventPayload<T extends EventType> = z.infer<(typeof eventPayloadSchemas)[T]>
575
+
576
+ /** Canonical read shape shared by the reducer, engine, and query surfaces. */
577
+ export interface NormalizedVerifyCompletion {
578
+ step: string
579
+ attempt: number
580
+ outcome: VerifyOutcome
581
+ report?: ArtifactRef
582
+ reason?: string
583
+ }
584
+
585
+ /**
586
+ * Normalize exactly once at each event-consumer boundary. Legacy booleans keep
587
+ * their historical meaning; `skipped` is never represented as either boolean.
588
+ */
589
+ export function normalizeVerifyCompletion(
590
+ payload: EventPayload<'verify.completed'>,
591
+ ): NormalizedVerifyCompletion {
592
+ if ('pass' in payload) {
593
+ return {
594
+ step: payload.step,
595
+ attempt: payload.attempt,
596
+ outcome: payload.pass ? 'pass' : 'fail',
597
+ ...(payload.report !== undefined ? { report: payload.report } : {}),
598
+ }
599
+ }
600
+ if (payload.outcome === 'skipped') {
601
+ return {
602
+ step: payload.step,
603
+ attempt: payload.attempt,
604
+ outcome: 'skipped',
605
+ reason: payload.reason,
606
+ }
607
+ }
608
+ return {
609
+ step: payload.step,
610
+ attempt: payload.attempt,
611
+ outcome: payload.outcome,
612
+ ...(payload.report !== undefined ? { report: payload.report } : {}),
613
+ }
614
+ }
615
+
616
+ export function isEventType(value: string): value is EventType {
617
+ return Object.hasOwn(eventPayloadSchemas, value)
618
+ }