@cat-factory/contracts 0.6.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 (166) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accounts.d.ts +103 -0
  3. package/dist/accounts.d.ts.map +1 -0
  4. package/dist/accounts.js +102 -0
  5. package/dist/accounts.js.map +1 -0
  6. package/dist/agent-config.d.ts +77 -0
  7. package/dist/agent-config.d.ts.map +1 -0
  8. package/dist/agent-config.js +78 -0
  9. package/dist/agent-config.js.map +1 -0
  10. package/dist/api-keys.d.ts +44 -0
  11. package/dist/api-keys.d.ts.map +1 -0
  12. package/dist/api-keys.js +49 -0
  13. package/dist/api-keys.js.map +1 -0
  14. package/dist/auth.d.ts +24 -0
  15. package/dist/auth.d.ts.map +1 -0
  16. package/dist/auth.js +15 -0
  17. package/dist/auth.js.map +1 -0
  18. package/dist/board-scan.d.ts +89 -0
  19. package/dist/board-scan.d.ts.map +1 -0
  20. package/dist/board-scan.js +122 -0
  21. package/dist/board-scan.js.map +1 -0
  22. package/dist/bootstrap.d.ts +168 -0
  23. package/dist/bootstrap.d.ts.map +1 -0
  24. package/dist/bootstrap.js +148 -0
  25. package/dist/bootstrap.js.map +1 -0
  26. package/dist/clarity.d.ts +75 -0
  27. package/dist/clarity.d.ts.map +1 -0
  28. package/dist/clarity.js +66 -0
  29. package/dist/clarity.js.map +1 -0
  30. package/dist/companion.d.ts +32 -0
  31. package/dist/companion.d.ts.map +1 -0
  32. package/dist/companion.js +43 -0
  33. package/dist/companion.js.map +1 -0
  34. package/dist/consensus.d.ts +195 -0
  35. package/dist/consensus.d.ts.map +1 -0
  36. package/dist/consensus.js +164 -0
  37. package/dist/consensus.js.map +1 -0
  38. package/dist/documents.d.ts +197 -0
  39. package/dist/documents.d.ts.map +1 -0
  40. package/dist/documents.js +161 -0
  41. package/dist/documents.js.map +1 -0
  42. package/dist/entities.d.ts +1691 -0
  43. package/dist/entities.d.ts.map +1 -0
  44. package/dist/entities.js +853 -0
  45. package/dist/entities.js.map +1 -0
  46. package/dist/environments.d.ts +426 -0
  47. package/dist/environments.d.ts.map +1 -0
  48. package/dist/environments.js +190 -0
  49. package/dist/environments.js.map +1 -0
  50. package/dist/events.d.ts +98 -0
  51. package/dist/events.d.ts.map +1 -0
  52. package/dist/events.js +2 -0
  53. package/dist/events.js.map +1 -0
  54. package/dist/fragment-library.d.ts +123 -0
  55. package/dist/fragment-library.d.ts.map +1 -0
  56. package/dist/fragment-library.js +88 -0
  57. package/dist/fragment-library.js.map +1 -0
  58. package/dist/github.d.ts +215 -0
  59. package/dist/github.d.ts.map +1 -0
  60. package/dist/github.js +204 -0
  61. package/dist/github.js.map +1 -0
  62. package/dist/index.d.ts +41 -0
  63. package/dist/index.d.ts.map +1 -0
  64. package/dist/index.js +41 -0
  65. package/dist/index.js.map +1 -0
  66. package/dist/iteration-cap.d.ts +17 -0
  67. package/dist/iteration-cap.d.ts.map +1 -0
  68. package/dist/iteration-cap.js +25 -0
  69. package/dist/iteration-cap.js.map +1 -0
  70. package/dist/localModels.d.ts +54 -0
  71. package/dist/localModels.d.ts.map +1 -0
  72. package/dist/localModels.js +79 -0
  73. package/dist/localModels.js.map +1 -0
  74. package/dist/merge.d.ts +106 -0
  75. package/dist/merge.d.ts.map +1 -0
  76. package/dist/merge.js +129 -0
  77. package/dist/merge.js.map +1 -0
  78. package/dist/model-defaults.d.ts +23 -0
  79. package/dist/model-defaults.d.ts.map +1 -0
  80. package/dist/model-defaults.js +34 -0
  81. package/dist/model-defaults.js.map +1 -0
  82. package/dist/notifications.d.ts +136 -0
  83. package/dist/notifications.d.ts.map +1 -0
  84. package/dist/notifications.js +125 -0
  85. package/dist/notifications.js.map +1 -0
  86. package/dist/observability.d.ts +271 -0
  87. package/dist/observability.d.ts.map +1 -0
  88. package/dist/observability.js +152 -0
  89. package/dist/observability.js.map +1 -0
  90. package/dist/personal-subscriptions.d.ts +66 -0
  91. package/dist/personal-subscriptions.d.ts.map +1 -0
  92. package/dist/personal-subscriptions.js +70 -0
  93. package/dist/personal-subscriptions.js.map +1 -0
  94. package/dist/primitives.d.ts +57 -0
  95. package/dist/primitives.d.ts.map +1 -0
  96. package/dist/primitives.js +66 -0
  97. package/dist/primitives.js.map +1 -0
  98. package/dist/provisioning.d.ts +46 -0
  99. package/dist/provisioning.d.ts.map +1 -0
  100. package/dist/provisioning.js +107 -0
  101. package/dist/provisioning.js.map +1 -0
  102. package/dist/recurring.d.ts +117 -0
  103. package/dist/recurring.d.ts.map +1 -0
  104. package/dist/recurring.js +99 -0
  105. package/dist/recurring.js.map +1 -0
  106. package/dist/release.d.ts +60 -0
  107. package/dist/release.d.ts.map +1 -0
  108. package/dist/release.js +75 -0
  109. package/dist/release.js.map +1 -0
  110. package/dist/requests.d.ts +451 -0
  111. package/dist/requests.d.ts.map +1 -0
  112. package/dist/requests.js +231 -0
  113. package/dist/requests.js.map +1 -0
  114. package/dist/requirements.d.ts +127 -0
  115. package/dist/requirements.d.ts.map +1 -0
  116. package/dist/requirements.js +137 -0
  117. package/dist/requirements.js.map +1 -0
  118. package/dist/runners.d.ts +387 -0
  119. package/dist/runners.d.ts.map +1 -0
  120. package/dist/runners.js +117 -0
  121. package/dist/runners.js.map +1 -0
  122. package/dist/sandbox.d.ts +300 -0
  123. package/dist/sandbox.d.ts.map +1 -0
  124. package/dist/sandbox.js +243 -0
  125. package/dist/sandbox.js.map +1 -0
  126. package/dist/service-fragment-defaults.d.ts +16 -0
  127. package/dist/service-fragment-defaults.d.ts.map +1 -0
  128. package/dist/service-fragment-defaults.js +23 -0
  129. package/dist/service-fragment-defaults.js.map +1 -0
  130. package/dist/services.d.ts +81 -0
  131. package/dist/services.d.ts.map +1 -0
  132. package/dist/services.js +77 -0
  133. package/dist/services.js.map +1 -0
  134. package/dist/slack.d.ts +104 -0
  135. package/dist/slack.d.ts.map +1 -0
  136. package/dist/slack.js +98 -0
  137. package/dist/slack.js.map +1 -0
  138. package/dist/snapshot.d.ts +522 -0
  139. package/dist/snapshot.d.ts.map +1 -0
  140. package/dist/snapshot.js +104 -0
  141. package/dist/snapshot.js.map +1 -0
  142. package/dist/spec.d.ts +174 -0
  143. package/dist/spec.d.ts.map +1 -0
  144. package/dist/spec.js +173 -0
  145. package/dist/spec.js.map +1 -0
  146. package/dist/tasks.d.ts +150 -0
  147. package/dist/tasks.d.ts.map +1 -0
  148. package/dist/tasks.js +146 -0
  149. package/dist/tasks.js.map +1 -0
  150. package/dist/testing.d.ts +67 -0
  151. package/dist/testing.d.ts.map +1 -0
  152. package/dist/testing.js +64 -0
  153. package/dist/testing.js.map +1 -0
  154. package/dist/tracker.d.ts +18 -0
  155. package/dist/tracker.d.ts.map +1 -0
  156. package/dist/tracker.js +24 -0
  157. package/dist/tracker.js.map +1 -0
  158. package/dist/vendor-credentials.d.ts +37 -0
  159. package/dist/vendor-credentials.d.ts.map +1 -0
  160. package/dist/vendor-credentials.js +40 -0
  161. package/dist/vendor-credentials.js.map +1 -0
  162. package/dist/workspace-settings.d.ts +36 -0
  163. package/dist/workspace-settings.d.ts.map +1 -0
  164. package/dist/workspace-settings.js +41 -0
  165. package/dist/workspace-settings.js.map +1 -0
  166. package/package.json +31 -0
@@ -0,0 +1,853 @@
1
+ import * as v from 'valibot';
2
+ import { subscriptionVendorSchema } from './vendor-credentials.js';
3
+ import { agentConfigValuesSchema } from './agent-config.js';
4
+ import { testReportSchema } from './testing.js';
5
+ import { consensusStepConfigSchema, stepGatingSchema, taskEstimateSchema } from './consensus.js';
6
+ import { cloudProviderSchema, instanceSizeSchema } from './provisioning.js';
7
+ import { releaseSignalSchema } from './release.js';
8
+ import { agentKindSchema, agentStateSchema, blockLevelSchema, blockStatusSchema, blockTypeSchema, positionSchema, sizeSchema, taskTypeFieldsSchema, taskTypeSchema, } from './primitives.js';
9
+ // ---------------------------------------------------------------------------
10
+ // Entity schemas: the single source of truth for the data shapes that travel
11
+ // over the wire. Domain types in @cat-factory/kernel are derived from these, and
12
+ // the worker validates responses against them, so frontend, core and facade can
13
+ // never silently drift apart.
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * A lightweight link from a block to the pull request an implementation agent
17
+ * opened for it. Distinct from the richer {@link GitHubPullRequest} projection
18
+ * (synced from GitHub): this is just enough to display the PR on the board and
19
+ * navigate to it. Recorded on a task when its container ("implementer") agent
20
+ * pushes a branch and opens a PR.
21
+ */
22
+ export const pullRequestRefSchema = v.object({
23
+ /** The PR's web URL, opened when the user clicks through from the board. */
24
+ url: v.string(),
25
+ /** The PR number within the repo, shown as `#<number>` when known. */
26
+ number: v.optional(v.number()),
27
+ /** The head branch the agent pushed its work to, when known. */
28
+ branch: v.optional(v.string()),
29
+ });
30
+ export const blockSchema = v.object({
31
+ id: v.string(),
32
+ title: v.string(),
33
+ type: blockTypeSchema,
34
+ description: v.string(),
35
+ position: positionSchema,
36
+ /**
37
+ * An explicit, user-set pixel size for the block (service frames are resizable
38
+ * by dragging their borders). Absent means the board auto-sizes the frame from
39
+ * its contents; present is the dragged size (the client never shrinks it below
40
+ * the content's natural extent). Only frames carry this today.
41
+ */
42
+ size: v.optional(sizeSchema),
43
+ status: blockStatusSchema,
44
+ progress: v.number(),
45
+ dependsOn: v.array(v.string()),
46
+ executionId: v.nullable(v.string()),
47
+ level: blockLevelSchema,
48
+ parentId: v.nullable(v.string()),
49
+ confidence: v.optional(v.number()),
50
+ /**
51
+ * The `task-estimator` agent's triage of this task (complexity / risk / impact,
52
+ * each 0..1, + rationale). Written by a `task-estimator` pipeline step once it
53
+ * runs; surfaced in the UI and used to gate consensus steps. Absent until a
54
+ * task-estimator step has run. Only meaningful on `task`-level blocks.
55
+ */
56
+ estimate: v.optional(v.nullable(taskEstimateSchema)),
57
+ moduleName: v.optional(v.string()),
58
+ /**
59
+ * The kind of work this task represents (feature / bug / document / spike / recurring),
60
+ * chosen by the human at creation. Drives the card's icon/badge, the per-type creation
61
+ * fields, and the per-service running-task limit's optional per-type bucketing. Only
62
+ * meaningful on `task`-level blocks; absent ⇒ treated as `feature`.
63
+ */
64
+ taskType: v.optional(taskTypeSchema),
65
+ /**
66
+ * Small per-type fields collected on the create-task form (see {@link taskTypeFieldsSchema}),
67
+ * e.g. a bug's severity / repro steps, a spike's time-box. Only meaningful on `task`-level
68
+ * blocks; absent ⇒ none collected.
69
+ */
70
+ taskTypeFields: v.optional(v.nullable(taskTypeFieldsSchema)),
71
+ /**
72
+ * Ids of curated best-practice prompt fragments selected for this block. Their
73
+ * bodies are composed into the agent system prompt at run time. The catalog
74
+ * itself lives in @cat-factory/prompt-fragments and is served separately.
75
+ */
76
+ fragmentIds: v.optional(v.array(v.string())),
77
+ /**
78
+ * Service-level (frame-only): ids of the best-practice / guideline prompt fragments
79
+ * selected as this service's programming standards (drawn from the universal pool in
80
+ * @cat-factory/prompt-fragments). At run time the execution engine folds their bodies
81
+ * into the system prompt of every agent under this service that carries the
82
+ * `code-aware` trait. Seeded from the workspace default on new services; absent ⇒ no
83
+ * service-level fragments (only the block's own `fragmentIds` apply).
84
+ */
85
+ serviceFragmentIds: v.optional(v.array(v.string())),
86
+ /**
87
+ * Id of the LLM model selected for this block from the shared model catalog
88
+ * (see MODEL_CATALOG in @cat-factory/kernel). When set it overrides the agent
89
+ * routing's default model at run time; absent means "use the routing default".
90
+ */
91
+ modelId: v.optional(v.string()),
92
+ /**
93
+ * Task-level configuration values contributed by the agents in the task's
94
+ * pipeline (see {@link agentConfigValuesSchema}) — a sparse id→value map. Each
95
+ * value is editable until its contributing agent's step starts, then freezes.
96
+ * Used e.g. for the Tester's `tester.environment` (local vs ephemeral) choice.
97
+ * Only meaningful on `task`-level blocks.
98
+ */
99
+ agentConfig: v.optional(agentConfigValuesSchema),
100
+ /**
101
+ * Service-level (frame-only): path to the service's docker-compose file used to
102
+ * stand up the Tester's local infra dependencies, relative to the repo root
103
+ * (e.g. `docker-compose.yml`). Autodiscovered when the service is added but may
104
+ * be set later. Mutually exclusive with {@link noInfraDependencies}; a Tester
105
+ * pipeline cannot start until one of the two is set.
106
+ */
107
+ testComposePath: v.optional(v.string()),
108
+ /**
109
+ * Service-level (frame-only): the service has no infra dependencies to stand up,
110
+ * so the Tester spins nothing up. When true {@link testComposePath} is ignored.
111
+ */
112
+ noInfraDependencies: v.optional(v.boolean()),
113
+ /**
114
+ * Service-level (frame-only): the cloud provider this service's container jobs
115
+ * run on. Absent means the owning account's `defaultCloudProvider`.
116
+ */
117
+ cloudProvider: v.optional(cloudProviderSchema),
118
+ /**
119
+ * Service-level (frame-only): the abstract instance size for this service's
120
+ * container jobs, resolved to a provider-specific id at dispatch. Absent means
121
+ * the built-in default size.
122
+ */
123
+ instanceSize: v.optional(instanceSizeSchema),
124
+ /**
125
+ * The pull request the block's implementation ("implementer") agent opened for
126
+ * its work. Set on a task once its container agent pushes a branch and opens a
127
+ * PR; surfaced on the board so the PR can be opened from the selected task.
128
+ */
129
+ pullRequest: v.optional(pullRequestRefSchema),
130
+ /**
131
+ * Id of the merge threshold preset selected for this task (see
132
+ * {@link mergeThresholdPresetSchema}). Drives the `merger` step's auto-merge
133
+ * decision and the CI-fixer attempt budget. Absent means "use the workspace's
134
+ * default preset".
135
+ */
136
+ mergePresetId: v.optional(v.string()),
137
+ /**
138
+ * Id of the pipeline chosen for this task at creation (see {@link pipelineSchema}).
139
+ * The task's "Start"/"Run" controls default to it; absent means the user picks a
140
+ * pipeline at run time (the board falls back to the first defined pipeline).
141
+ */
142
+ pipelineId: v.optional(v.string()),
143
+ /**
144
+ * Internal user id (`usr_*`) of the person who created this block, captured from
145
+ * the authenticated session at creation (tasks today). Drives "notify the task
146
+ * creator" routing for notifications. Absent/null on blocks created before this
147
+ * was recorded, or with auth disabled (local/dev), where there is no user.
148
+ */
149
+ createdBy: v.optional(v.nullable(v.string())),
150
+ /**
151
+ * Internal user id (`usr_*`) of the account member (a `product` role-holder) made
152
+ * responsible for this task. They are notified when requirement review flags findings.
153
+ * Absent/null when no responsible product person is assigned.
154
+ */
155
+ responsibleProductUserId: v.optional(v.nullable(v.string())),
156
+ });
157
+ /**
158
+ * A curated best-practice "prompt fragment" (e.g. Node performance, React state
159
+ * management). The catalog is authored in @cat-factory/prompt-fragments and
160
+ * surfaced to the frontend read-only so a user can pick which apply to a block.
161
+ */
162
+ export const promptFragmentSchema = v.object({
163
+ /** Stable id, e.g. `node.performance`. Selection persists this. */
164
+ id: v.string(),
165
+ /** Semver of the body content, for display and future version pinning. */
166
+ version: v.string(),
167
+ /** Human title shown in the picker, e.g. `Node.js performance`. */
168
+ title: v.string(),
169
+ /** Grouping label for the picker, e.g. `Node`, `React`. */
170
+ category: v.string(),
171
+ /** One-line description shown in the picker. */
172
+ summary: v.string(),
173
+ /** The guidance injected into the agent system prompt. */
174
+ body: v.string(),
175
+ /** Optional hints for filtering which blocks/agents a fragment suits. */
176
+ appliesTo: v.optional(v.object({
177
+ blockTypes: v.optional(v.array(blockTypeSchema)),
178
+ agentKinds: v.optional(v.array(agentKindSchema)),
179
+ })),
180
+ /**
181
+ * Free-form tags used by the relevance selector to decide whether a fragment
182
+ * is pertinent to a given run (e.g. `backend`, `frontend`, `db`). Optional and
183
+ * absent on the built-in catalog tier; managed fragments may set them.
184
+ */
185
+ tags: v.optional(v.array(v.string())),
186
+ /**
187
+ * Provenance for a fragment sourced from a repo: which {@link FragmentSource}
188
+ * it came from, the file path within that source, and the blob sha last synced
189
+ * (so a "changed?" check is a cheap comparison). Absent for hand-authored
190
+ * fragments and the built-in catalog.
191
+ */
192
+ source: v.optional(v.object({
193
+ sourceId: v.string(),
194
+ path: v.string(),
195
+ sha: v.string(),
196
+ })),
197
+ });
198
+ /** The full catalog as served by `GET /prompt-fragments`. */
199
+ export const promptFragmentCatalogSchema = v.array(promptFragmentSchema);
200
+ /** Informational list price for a model, surfaced in the picker. */
201
+ export const modelCostSchema = v.object({
202
+ /** List price per 1M input tokens. */
203
+ inputPerMillion: v.number(),
204
+ /** List price per 1M output tokens. */
205
+ outputPerMillion: v.number(),
206
+ /** ISO 4217 currency the prices are expressed in (e.g. `EUR`). */
207
+ currency: v.string(),
208
+ });
209
+ /**
210
+ * A selectable LLM model, resolved to the flavour actually in use for this
211
+ * deployment (`GET /models`). `flavor` is `direct` when the model's own provider
212
+ * API key is configured, `cloudflare` for the Workers AI fallback, or
213
+ * `subscription` for a Claude Code / Codex model run via a stored subscription
214
+ * token. `provider`/`model` are the effective {@link ModelRef} parts the agent
215
+ * will run with; the picker stores only `id`.
216
+ */
217
+ export const modelOptionSchema = v.object({
218
+ /** Stable id persisted on a block (`Block.modelId`). */
219
+ id: v.string(),
220
+ /** Model-family label, e.g. `Qwen3`. */
221
+ label: v.string(),
222
+ /** One-line description shown in the picker. */
223
+ description: v.string(),
224
+ /** Which flavour is active for this deployment. */
225
+ flavor: v.picklist(['cloudflare', 'direct', 'subscription']),
226
+ /**
227
+ * Whether this model is actually selectable given what the workspace has
228
+ * configured: a direct key for its provider, a subscription token for its vendor,
229
+ * or the opt-in Cloudflare lib enabled. The picker disables an unavailable model.
230
+ */
231
+ available: v.optional(v.boolean()),
232
+ /** Short provider label for the active flavour, e.g. `Cloudflare`, `DashScope`. */
233
+ providerLabel: v.string(),
234
+ /** Effective provider id the agent runs with. */
235
+ provider: v.string(),
236
+ /** Effective model id within the provider. */
237
+ model: v.string(),
238
+ /**
239
+ * For a `subscription` model, the vendor whose pooled token authenticates it;
240
+ * the frontend enables the option only when the workspace has a token for it.
241
+ */
242
+ vendor: v.optional(subscriptionVendorSchema),
243
+ /** Informational list price for the model, when known. */
244
+ cost: v.optional(modelCostSchema),
245
+ /** The model's context window at the effective provider, when known. */
246
+ contextTokens: v.optional(v.number()),
247
+ /**
248
+ * True when the effective flavour runs on a flat-rate subscription. Its `cost`
249
+ * is illustrative of quota burn rate only — quota-based usage does NOT draw on
250
+ * the monetary spend budget.
251
+ */
252
+ quotaBased: v.optional(v.boolean()),
253
+ /**
254
+ * An alternative subscription flavour for a model that ALSO has a Cloudflare /
255
+ * direct base (e.g. GLM-5.2, Kimi). The frontend renders ONLY this flavour when
256
+ * the workspace has a token for `vendor` (hiding the base), and the executor
257
+ * always prefers it at dispatch. Absent for subscription-only models (whose base
258
+ * IS the subscription) and for models with no subscription path.
259
+ */
260
+ subscription: v.optional(v.object({
261
+ vendor: subscriptionVendorSchema,
262
+ providerLabel: v.string(),
263
+ provider: v.string(),
264
+ model: v.string(),
265
+ cost: v.optional(modelCostSchema),
266
+ contextTokens: v.optional(v.number()),
267
+ })),
268
+ });
269
+ /** The full catalog as served by `GET /models`. */
270
+ export const modelCatalogSchema = v.array(modelOptionSchema);
271
+ export const pipelineSchema = v.object({
272
+ id: v.string(),
273
+ name: v.string(),
274
+ agentKinds: v.array(agentKindSchema),
275
+ /**
276
+ * Per-step human approval gates, parallel to {@link agentKinds}: when
277
+ * `gates[i]` is true the run pauses after step `i` completes so a human can
278
+ * review (and edit) its proposal before the next step runs. Absent / shorter
279
+ * than `agentKinds` means "no gate" for the missing indices, so legacy
280
+ * pipelines run straight through unchanged.
281
+ */
282
+ gates: v.optional(v.array(v.boolean())),
283
+ /**
284
+ * Per-step companion quality thresholds, parallel to {@link agentKinds}: when step
285
+ * `i` is a companion kind, `thresholds[i]` is the minimum rating (0..1) its review
286
+ * must reach for the run to proceed; below it the preceding producer is re-run, and
287
+ * once the rework budget is spent the step parks for a human (the iteration-cap gate).
288
+ * `null`/absent on a companion step means "use the companion's default threshold";
289
+ * ignored on non-companion steps.
290
+ */
291
+ thresholds: v.optional(v.array(v.nullable(v.pipe(v.number(), v.minValue(0), v.maxValue(1))))),
292
+ /**
293
+ * Per-step enable flags, parallel to {@link agentKinds}: when `enabled[i]` is
294
+ * explicitly `false` the step is kept in the pipeline (so it can be toggled back
295
+ * on) but SKIPPED at run start — the execution instance is built only from the
296
+ * enabled steps. Absent / shorter than `agentKinds`, or `true`, means the step
297
+ * runs, so legacy pipelines run every step unchanged.
298
+ */
299
+ enabled: v.optional(v.array(v.boolean())),
300
+ /**
301
+ * Per-step consensus configuration, parallel to {@link agentKinds}: when
302
+ * `consensus[i]` is set and its `enabled` is true AND step `i`'s kind carries a
303
+ * consensus capability trait, the step runs through the multi-model consensus
304
+ * mechanism (specialist panel / debate / ranked voting) instead of a single LLM
305
+ * call — optionally gated on the task estimate (sub-threshold ⇒ standard agent).
306
+ * `null`/absent means "standard single-actor agent" (the default). Copied onto
307
+ * the run's step at start, like {@link gates}. See {@link consensusStepConfigSchema}.
308
+ */
309
+ consensus: v.optional(v.array(v.nullable(consensusStepConfigSchema))),
310
+ /**
311
+ * Per-step gating, parallel to {@link agentKinds}: when `gating[i]` is set and its
312
+ * `enabled` is true, step `i` runs only if the task estimate meets the threshold
313
+ * (OR across the supplied axes); otherwise it is transparently SKIPPED at runtime.
314
+ * `null`/absent means "always run" (the default). Copied onto the run's step at
315
+ * start, like {@link gates}. A pipeline with any enabled gating requires a
316
+ * `task-estimator` step earlier in the chain. See {@link stepGatingSchema}.
317
+ */
318
+ gating: v.optional(v.array(v.nullable(stepGatingSchema))),
319
+ /**
320
+ * Free-form organizational labels for the saved-pipeline library (filter/search).
321
+ * Absent ⇒ no labels. Applies to built-in and custom pipelines alike.
322
+ */
323
+ labels: v.optional(v.array(v.string())),
324
+ /**
325
+ * When true the pipeline is archived: kept but hidden from the default library view
326
+ * (a "show archived" toggle reveals it). Organizational only — an archived built-in
327
+ * is still read-only for structure. Absent / false ⇒ active.
328
+ */
329
+ archived: v.optional(v.boolean()),
330
+ /**
331
+ * True for the curated built-in catalog pipelines (`seedPipelines()`). Built-ins
332
+ * are read-only templates: they can be cloned (into an editable copy) but not
333
+ * edited in place. Absent / false on user-created and cloned pipelines.
334
+ */
335
+ builtin: v.optional(v.boolean()),
336
+ });
337
+ export const decisionSchema = v.object({
338
+ id: v.string(),
339
+ question: v.string(),
340
+ options: v.array(v.string()),
341
+ chosen: v.nullable(v.string()),
342
+ });
343
+ /** One entry of a running step's todo list — its label and current status. */
344
+ export const stepSubtaskItemSchema = v.object({
345
+ /** The task's human-readable subject, as the agent wrote it. */
346
+ label: v.string(),
347
+ status: v.picklist(['pending', 'in_progress', 'completed']),
348
+ });
349
+ /**
350
+ * Live subtask counts for a running step, reported by the container agent from
351
+ * the coding tool's own todo list (e.g. "3/8 done, 1 in progress"). Present only
352
+ * while an async job is in flight and the agent maintains a todo list; the board
353
+ * renders it as a finer-grained progress indicator than `progress` alone.
354
+ *
355
+ * `items` carries the individual todo entries (label + status) so a zoomed-in
356
+ * card can render the actual task list, not just the count. It is optional — an
357
+ * older agent/poll that only reported counts, or the simpler `todos[].done`
358
+ * fallback shape, still validates without it.
359
+ */
360
+ export const stepSubtasksSchema = v.object({
361
+ completed: v.number(),
362
+ inProgress: v.number(),
363
+ total: v.number(),
364
+ items: v.optional(v.array(stepSubtaskItemSchema)),
365
+ });
366
+ /**
367
+ * One GitHub-review-style comment left on a specific block or item of an agent's
368
+ * proposal — either by a human reviewing an approval gate, or by a quality
369
+ * companion (e.g. the Spec Reviewer) grading a structured output. `quotedSource`
370
+ * is the verbatim raw markdown of the block the comment targets (sliced from the
371
+ * proposal by its source line range), so a "request changes" re-run can quote the
372
+ * agent's own text back to it rather than a re-rendered approximation. It is
373
+ * OPTIONAL because a comment may instead anchor to a structured item via
374
+ * {@link anchorId} (e.g. a spec requirement / acceptance-criterion id), where the
375
+ * reviewed output is rendered as discrete items rather than free prose and there is
376
+ * no quoted source range — the shape a companion returns.
377
+ */
378
+ export const stepReviewCommentSchema = v.object({
379
+ /**
380
+ * Verbatim raw-markdown source of the commented prose block. Optional: a comment
381
+ * may instead anchor to a structured item via {@link anchorId}, where there is no
382
+ * prose source to quote.
383
+ */
384
+ quotedSource: v.optional(v.string()),
385
+ /**
386
+ * 0-based source line range [start, end) of the commented prose block, for
387
+ * best-effort re-anchoring. Optional: a comment may instead anchor to a structured
388
+ * item via {@link anchorId} (e.g. a spec requirement/acceptance-criterion id), where
389
+ * there is no prose line range.
390
+ */
391
+ srcStart: v.optional(v.number()),
392
+ srcEnd: v.optional(v.number()),
393
+ /**
394
+ * Stable id of the structured item the comment targets (e.g. a spec
395
+ * requirement/criterion id), when the reviewed output is rendered as structured
396
+ * items rather than free prose. Absent for prose-range comments.
397
+ */
398
+ anchorId: v.optional(v.string()),
399
+ /** The reviewer's note on this block / item. */
400
+ body: v.string(),
401
+ });
402
+ /**
403
+ * The standardized, stored verdict a quality companion produced for an output it
404
+ * graded — shared by every companion site (the pipeline companion step and the
405
+ * requirements-rework gate). The raw model response is {@link companionAssessmentSchema}
406
+ * (rating + summary + comments); this is the persisted, self-describing record of how
407
+ * that assessment was applied: the `rating`, the `threshold` it was judged against,
408
+ * whether it `passed`, and the `feedback` surfaced to the human / fed into a rework.
409
+ */
410
+ export const companionVerdictSchema = v.object({
411
+ /** Overall quality of the graded output (0..1, higher = better). */
412
+ rating: v.pipe(v.number(), v.minValue(0), v.maxValue(1)),
413
+ /** The quality bar the rating had to reach to pass. */
414
+ threshold: v.pipe(v.number(), v.minValue(0), v.maxValue(1)),
415
+ /** Whether the rating met the threshold. */
416
+ passed: v.boolean(),
417
+ /** The companion's challenge / justification (its assessment summary). */
418
+ feedback: v.string(),
419
+ });
420
+ /**
421
+ * A human approval gate raised after a step whose pipeline marked it
422
+ * `requiresApproval`. Unlike a {@link Decision} (which an agent raises and which
423
+ * re-runs the same step on resolution), an approval gate fires once the step has
424
+ * already produced its `proposal`; approving advances the run (carrying the —
425
+ * possibly edited — proposal forward as context), requesting changes re-runs the
426
+ * same step with the human's `feedback` (+ per-block `comments`), and rejecting
427
+ * stops the run entirely (a terminal `rejected` failure the board can retry).
428
+ */
429
+ export const stepApprovalSchema = v.object({
430
+ /** Unique id of this gate; the durable run parks on it like a decision. */
431
+ id: v.string(),
432
+ /** `pending` while awaiting the human; terminal `approved`/`rejected`; `changes_requested` re-runs the step. */
433
+ status: v.picklist(['pending', 'approved', 'changes_requested', 'rejected']),
434
+ /** The agent's output the human is reviewing (editable before approval). */
435
+ proposal: v.string(),
436
+ /** When changes were requested, the human's freeform guidance fed into the re-run. */
437
+ feedback: v.optional(v.string()),
438
+ /** When changes were requested, per-block review comments fed into the re-run. */
439
+ comments: v.optional(v.array(stepReviewCommentSchema)),
440
+ });
441
+ /**
442
+ * The agent flows that produce an "agent run" (a container-backed job whose
443
+ * lifecycle, progress and failure the board surfaces uniformly):
444
+ * - `bootstrap` — a "bootstrap repo" run that scaffolds/adapts a new repo.
445
+ * - `execution` — a task pipeline run that implements a board task.
446
+ */
447
+ export const agentRunKindSchema = v.picklist(['bootstrap', 'execution']);
448
+ /**
449
+ * How an agent run faulted, so the board can classify the failure (and hint
450
+ * whether a retry is likely to help). The union spans both flows; a given flow
451
+ * only ever produces a subset:
452
+ * - `preflight` — rejected before dispatch (repo missing/not empty, not connected). [bootstrap]
453
+ * - `dispatch` — the container accept-request itself failed (HTTP / network). [bootstrap]
454
+ * - `evicted` — the container vanished mid-run (eviction/crash). Retrying spins a fresh one.
455
+ * - `timeout` — a container watchdog fired (inactivity or max-duration).
456
+ * - `agent` — the agent / git push reported a failure.
457
+ * - `job_failed` — an async container job came back failed. [execution]
458
+ * - `rejected` — a human rejected a gated proposal, stopping the run. [execution]
459
+ * - `cancelled` — the user (or an orphan sweep) explicitly stopped the run.
460
+ * - `unknown` — anything not otherwise classified.
461
+ */
462
+ export const agentFailureKindSchema = v.picklist([
463
+ 'preflight',
464
+ 'dispatch',
465
+ 'evicted',
466
+ 'timeout',
467
+ 'agent',
468
+ 'job_failed',
469
+ 'rejected',
470
+ // A companion agent could not return a parseable quality assessment (truncated /
471
+ // malformed) even after a repair retry, so the run was failed for human attention.
472
+ // (Exhausting the automatic rework budget no longer fails the run — it parks on the
473
+ // companion iteration-cap gate for a human; see `companion.exceeded`.)
474
+ 'companion_rejected',
475
+ 'cancelled',
476
+ 'unknown',
477
+ ]);
478
+ /**
479
+ * Structured diagnostics captured when an agent run fails, stored on the run and
480
+ * surfaced on the board so a crash isn't just a one-line message. The container's
481
+ * stdout/stderr can't always be pulled into this record (an evicted container is
482
+ * gone), so for `evicted`/`timeout` failures the `hint` points at where to look.
483
+ */
484
+ export const agentFailureSchema = v.object({
485
+ kind: agentFailureKindSchema,
486
+ /** Human-readable summary (mirrors the run's `error` for back-compat). */
487
+ message: v.string(),
488
+ /** Extended detail when available (the harness's reason, an HTTP body, …). */
489
+ detail: v.nullable(v.string()),
490
+ /** Where to look next (e.g. "check the container logs for this job id"). */
491
+ hint: v.nullable(v.string()),
492
+ /** Epoch ms the failure was recorded. */
493
+ occurredAt: v.number(),
494
+ /** Last subtask counts seen before the failure, for context (null if none). */
495
+ lastSubtasks: v.nullable(stepSubtasksSchema),
496
+ });
497
+ /**
498
+ * State a polling **gate** step carries (today `ci` and `conflicts`). A gate is
499
+ * special (like a `deployer` step): it is NOT itself an LLM/container agent. It
500
+ * runs a programmatic precheck against a provider (CI check runs / PR mergeability)
501
+ * for the PR head commit and only escalates to a helper container agent (`ci-fixer`
502
+ * / `conflict-resolver`) on a negative verdict, looping until the precheck passes or
503
+ * the attempt budget is spent. Which gate a step is comes from its `agentKind`, so it
504
+ * is not duplicated here. See the engine's `GateDefinition` registry.
505
+ * - `phase: 'checking'` — running the precheck / waiting for the provider.
506
+ * - `phase: 'working'` — a helper agent is in flight (tracked via the step's
507
+ * `jobId`); on completion the gate returns to `checking`.
508
+ */
509
+ /** One failing check the CI gate's precheck saw, flattened for display. */
510
+ export const gateFailingCheckSchema = v.object({
511
+ name: v.string(),
512
+ /** GitHub conclusion (e.g. `failure`, `timed_out`), or null when not reported. */
513
+ conclusion: v.nullable(v.string()),
514
+ });
515
+ export const gateStepStateSchema = v.object({
516
+ phase: v.picklist(['checking', 'working']),
517
+ /** How many helper-agent attempts have been dispatched so far. */
518
+ attempts: v.number(),
519
+ /** Ceiling on attempts, resolved from the task's merge preset at step start. */
520
+ maxAttempts: v.number(),
521
+ /** The PR head commit being gated, once resolved. */
522
+ headSha: v.optional(v.nullable(v.string())),
523
+ /**
524
+ * The most recent precheck verdict, so the UI can show why the gate is looping
525
+ * (failing → a helper is fixing) vs idle-passing. Set on every probe.
526
+ */
527
+ lastVerdict: v.optional(v.nullable(v.picklist(['pass', 'pending', 'fail']))),
528
+ /**
529
+ * Human-readable summary of the latest failing precheck (the failing CI checks /
530
+ * the conflict reason) — the conclusion detail that used to be fed only to the
531
+ * helper agent and then discarded. Carried across the helper dispatch so the
532
+ * window keeps showing what is being fixed. Null when the last probe passed.
533
+ */
534
+ lastFailureSummary: v.optional(v.nullable(v.string())),
535
+ /**
536
+ * Structured failing checks behind {@link lastFailureSummary} for the CI gate, so
537
+ * the UI can list each red check by name + conclusion. Absent for the conflicts
538
+ * gate (GitHub reports no file-level detail) and when the last probe passed.
539
+ */
540
+ failingChecks: v.optional(v.nullable(v.array(gateFailingCheckSchema))),
541
+ /**
542
+ * Epoch ms of the release marker for a time-windowed gate (post-release-health) — the
543
+ * moment it began watching the deployed release. The gate keeps polling `pending`
544
+ * until this + the preset's watch window has elapsed (then a clean run passes) or a
545
+ * monitor/SLO regresses (then it escalates to the on-call agent). Absent for the
546
+ * CI/conflicts gates.
547
+ */
548
+ watchSince: v.optional(v.nullable(v.number())),
549
+ /**
550
+ * The watch-window length (minutes) for a time-windowed gate (post-release-health),
551
+ * resolved from the task's merge preset ONCE on first entry (alongside `maxAttempts`)
552
+ * so the probe doesn't re-load the block + re-resolve the preset on every poll. Absent
553
+ * for the CI/conflicts gates.
554
+ */
555
+ watchWindowMinutes: v.optional(v.nullable(v.number())),
556
+ /**
557
+ * The regressed signals captured when the post-release-health gate escalated to the
558
+ * on-call agent, so the agent's completion handler can build the `release_regression`
559
+ * notification + incident enrichment from the SAME evidence the agent investigated
560
+ * — rather than re-reading Datadog (a third round-trip that could also disagree with
561
+ * what the agent saw if the window moved). Absent for the CI/conflicts gates.
562
+ */
563
+ regressedSignals: v.optional(v.nullable(v.array(releaseSignalSchema))),
564
+ });
565
+ /**
566
+ * State a `tester` step carries while it runs the Tester → Fixer loop. Unlike `ci`,
567
+ * the gate's own work IS a container job (the Tester); on a withheld greenlight the
568
+ * engine loops a `fixer` container agent and re-tests.
569
+ * - `phase: 'testing'` — a Tester job is in flight (tracked via the step's `jobId`).
570
+ * - `phase: 'fixing'` — a Fixer job is in flight; on completion the step returns to
571
+ * `testing` and a fresh Tester job is dispatched.
572
+ */
573
+ export const testerStepStateSchema = v.object({
574
+ phase: v.picklist(['testing', 'fixing']),
575
+ /** How many `fixer` attempts have been dispatched so far. */
576
+ attempts: v.number(),
577
+ /** Ceiling on fixer attempts, resolved from the task's merge preset at step start. */
578
+ maxAttempts: v.number(),
579
+ /** The most recent Tester report (what was tested, outcomes, concerns, greenlight). */
580
+ lastReport: v.optional(v.nullable(testReportSchema)),
581
+ });
582
+ /**
583
+ * Per-step LLM observability rollup: a compact aggregate over every model call the
584
+ * step's container made, recorded by the LLM proxy and summed by the engine for the
585
+ * board. It surfaces, at a glance, token usage, how close the step ran to its
586
+ * output-token limit (truncation), the latency split between transport/proxy
587
+ * overhead and actual model execution, and any errors/warnings. The full per-call
588
+ * detail (prompts + responses) is fetched on demand for the drill-down panel.
589
+ * Absent when the observability sink is not wired.
590
+ */
591
+ export const stepMetricsSchema = v.object({
592
+ /** Number of model calls recorded for this step. */
593
+ calls: v.number(),
594
+ /** Sum of prompt (input) tokens across the step's calls. */
595
+ promptTokens: v.number(),
596
+ /** Sum of completion (output) tokens across the step's calls. */
597
+ completionTokens: v.number(),
598
+ /** Largest single completion the model produced (closest approach to the limit). */
599
+ peakCompletionTokens: v.number(),
600
+ /** The output ceiling in effect (max requested `max_tokens`), or null when unknown. */
601
+ maxOutputTokens: v.nullable(v.number()),
602
+ /** Calls cut short by the output limit (`finish_reason === 'length'`). */
603
+ truncatedCalls: v.number(),
604
+ /** Sum of model execution time (ms) — the "actual prompt/tool execution" slice. */
605
+ upstreamMs: v.number(),
606
+ /** Sum of transport/proxy overhead (ms) — the interim-layer cost. */
607
+ overheadMs: v.number(),
608
+ /** Calls that failed (non-2xx / refused / in-process error). */
609
+ errors: v.number(),
610
+ /** Successful calls that warned (truncated or content-filtered). */
611
+ warnings: v.number(),
612
+ });
613
+ export const pipelineStepSchema = v.object({
614
+ /**
615
+ * Id of the execution run (the {@link executionInstanceSchema} `id`) this step
616
+ * belongs to — surfaced on every step so a lone step in a log line or a detail view
617
+ * can name its run, for easier debugging. A projection that always equals the parent
618
+ * instance's `id`: stamped from the enclosing instance when the run is read or
619
+ * emitted, not persisted independently. Absent only on steps not yet round-tripped.
620
+ */
621
+ runId: v.optional(v.string()),
622
+ agentKind: agentKindSchema,
623
+ state: agentStateSchema,
624
+ progress: v.number(),
625
+ /** LLM observability rollup for this step; see {@link stepMetricsSchema}. */
626
+ metrics: v.optional(v.nullable(stepMetricsSchema)),
627
+ /**
628
+ * Live gate state while a polling gate step (`ci` / `conflicts`) runs its
629
+ * precheck-or-escalate loop; see {@link gateStepStateSchema}. The gate kind is
630
+ * `agentKind`.
631
+ */
632
+ gate: v.optional(v.nullable(gateStepStateSchema)),
633
+ /** Live Tester→Fixer loop state while a `tester` step runs/fixes; see {@link testerStepStateSchema}. */
634
+ test: v.optional(v.nullable(testerStepStateSchema)),
635
+ /** Live subtask counts while an async (container) step runs; see {@link stepSubtasksSchema}. */
636
+ subtasks: v.optional(stepSubtasksSchema),
637
+ /**
638
+ * True while a container-backed step is being dispatched and its per-run
639
+ * container is cold-booting — i.e. before the container is up and the agent has
640
+ * begun executing. Set the moment the job is dispatched (the dispatch blocks
641
+ * until the container accepts the job, so it covers the whole boot window) and
642
+ * cleared on the first successful poll, when the container is provably up. Lets
643
+ * the board show an explicit "Spinning up container…" phase instead of a blank
644
+ * "working" state. Only ever set on async (container) steps.
645
+ */
646
+ startingContainer: v.optional(v.boolean()),
647
+ decision: v.nullable(decisionSchema),
648
+ /**
649
+ * Whether a human approval gate fires after this step completes. Copied from
650
+ * the pipeline's `gates` at run start; absent means no gate.
651
+ */
652
+ requiresApproval: v.optional(v.boolean()),
653
+ /**
654
+ * The live approval gate for this step (see {@link stepApprovalSchema}). Set
655
+ * once the step's proposal is ready and `requiresApproval` is true; null/absent
656
+ * otherwise.
657
+ */
658
+ approval: v.optional(v.nullable(stepApprovalSchema)),
659
+ /**
660
+ * Live state of a companion step that reviews a preceding producer step. Set when
661
+ * this step's `agentKind` is a companion kind. `threshold` is the quality bar the
662
+ * companion's latest rating (the last `verdicts` entry) must reach; `attempts`
663
+ * counts only the AUTOMATIC reworks performed, and once it reaches `maxAttempts` the
664
+ * step parks on the iteration-cap gate (`exceeded`) for a human rather than failing.
665
+ * A human "request changes" on the companion's gate also re-runs the producer but does
666
+ * NOT consume `attempts` (only the automatic loop is budgeted). Absent for non-companion steps.
667
+ */
668
+ companion: v.optional(v.nullable(v.object({
669
+ /** The quality bar (0..1) the latest verdict's rating must reach; seeded from the pipeline. */
670
+ threshold: v.number(),
671
+ /** The automatic rework budget: once `attempts` reaches this the gate parks for a human (`exceeded`). */
672
+ maxAttempts: v.number(),
673
+ /**
674
+ * How many AUTOMATIC reworks the companion has driven so far (the producer is
675
+ * looped back once per failed verdict). Human "request changes" cycles are not
676
+ * counted. Defaults to 0; once it reaches `maxAttempts` the step parks on the
677
+ * iteration-cap gate (`exceeded`) — an "extra round" raises `maxAttempts` by one.
678
+ */
679
+ attempts: v.optional(v.number(), 0),
680
+ /**
681
+ * One standardized {@link companionVerdictSchema} per grading cycle, in order —
682
+ * the full sequence of correction iterations (the producer is re-run after each
683
+ * rejected verdict), including any human-driven ones. Empty before the first
684
+ * grade; the last entry is the latest.
685
+ */
686
+ verdicts: v.array(companionVerdictSchema),
687
+ /**
688
+ * Set true when the automatic rework budget (`maxAttempts`) was spent with the
689
+ * rating still below the bar: instead of failing the run, the step parks on its
690
+ * approval gate for a human to resolve via the shared iteration-cap surface
691
+ * (one more round / proceed anyway / stop & reset). Cleared once the human grants
692
+ * an extra round (the loop resumes). Absent until/unless the cap is hit.
693
+ */
694
+ exceeded: v.optional(v.boolean()),
695
+ }))),
696
+ /**
697
+ * Transient rework feedback carried on a PRODUCER step while it is being re-run by
698
+ * a downstream companion (the analogue of an approval's `changes_requested`
699
+ * feedback for the automatic path). Folded into the agent's revision context on the
700
+ * re-run, then cleared. Absent when no companion rework is in flight.
701
+ */
702
+ rework: v.optional(v.nullable(v.object({
703
+ /** The producer's previous proposal the companion challenged. */
704
+ previousProposal: v.string(),
705
+ /** The companion's prose feedback driving the rework. */
706
+ feedback: v.string(),
707
+ /** Optional per-item / per-block challenges to address. */
708
+ comments: v.optional(v.array(stepReviewCommentSchema)),
709
+ }))),
710
+ /**
711
+ * Transient incorporation intent carried on a parked `requirements-review` gate step.
712
+ * Set when the human answers the findings and asks to incorporate: the run is signalled
713
+ * to wake and the durable driver, on re-entering the gate, folds the answers into a
714
+ * document and re-reviews it (the LLM work that used to block the HTTP request). Cleared
715
+ * once that async cycle completes. `feedback` is the human's optional "do it differently"
716
+ * direction (a redo). Absent when no incorporation is pending.
717
+ */
718
+ pendingIncorporation: v.optional(v.nullable(v.object({ feedback: v.optional(v.string()) }))),
719
+ /**
720
+ * Consensus configuration for this step, copied from the pipeline's `consensus`
721
+ * array at run start. Present (with `enabled: true`) when this step should run
722
+ * through the multi-model consensus mechanism; read by the consensus executor
723
+ * (and to decide gating against the block estimate). Absent ⇒ standard agent.
724
+ * See {@link consensusStepConfigSchema}.
725
+ */
726
+ consensus: v.optional(v.nullable(consensusStepConfigSchema)),
727
+ /**
728
+ * Estimate-based gating for this step, copied from the pipeline's `gating` array at
729
+ * run start. When present (with `enabled: true`) the step is skipped at runtime unless
730
+ * the block's task estimate meets the threshold. Absent ⇒ always run. See
731
+ * {@link stepGatingSchema}.
732
+ */
733
+ gating: v.optional(v.nullable(stepGatingSchema)),
734
+ /**
735
+ * True when this step was skipped at runtime because its `gating` was not satisfied
736
+ * (the task estimate fell below the threshold). The step's `state` is `done` with no
737
+ * output; the UI renders it as "skipped (gated)". Absent ⇒ the step ran normally.
738
+ */
739
+ skipped: v.optional(v.boolean()),
740
+ /** Text the agent produced for this step (when LLM execution is enabled). */
741
+ output: v.optional(v.string()),
742
+ /** Identifier of the model that produced `output`, for transparency. */
743
+ model: v.optional(v.string()),
744
+ /**
745
+ * Ids of the prompt-fragment library entries that were folded into this step's
746
+ * system prompt — the manual selection on the block unioned with the relevance
747
+ * selector's pick. Recorded for observability and replay-stability; absent when
748
+ * the fragment-library module is not configured.
749
+ */
750
+ selectedFragmentIds: v.optional(v.array(v.string())),
751
+ /**
752
+ * Identifier of an in-flight asynchronous agent job (a container run polled by
753
+ * the durable driver). Set while the step is dispatched-but-not-yet-finished so
754
+ * a Workflows replay re-attaches to the running job instead of starting a new
755
+ * one; cleared once the job's result is recorded.
756
+ */
757
+ jobId: v.optional(v.string()),
758
+ /**
759
+ * Epoch ms the step first began executing (transitioned to `working`). Set once
760
+ * and never overwritten on subsequent state changes, so a re-run/replay keeps the
761
+ * original start. Absent until the step starts.
762
+ */
763
+ startedAt: v.optional(v.nullable(v.number())),
764
+ /**
765
+ * Epoch ms the step finished (transitioned to `done`). With {@link startedAt}
766
+ * this yields the step's execution duration. Absent until the step completes.
767
+ */
768
+ finishedAt: v.optional(v.nullable(v.number())),
769
+ /**
770
+ * Epoch ms the step parked on a human (an approval gate, a raised decision, or an
771
+ * iteration-cap gate), freezing its duration clock: while parked, elapsed time stops
772
+ * accruing — the symmetric counterpart of {@link finishedAt}'s terminal freeze, so a
773
+ * step waiting on input is not billed for the human's deliberation. Set once on park,
774
+ * cleared (null) when the step resumes working or finishes. Absent until first parked.
775
+ */
776
+ pausedAt: v.optional(v.nullable(v.number())),
777
+ /**
778
+ * How many times this step's container was evicted/crashed and recovered by
779
+ * automatically re-dispatching a fresh container (bounded by
780
+ * `MAX_EVICTION_RECOVERIES`). Once spent, a further eviction fails the run as
781
+ * `evicted` rather than looping. Absent/0 until the first eviction.
782
+ */
783
+ evictionRecoveries: v.optional(v.number()),
784
+ /**
785
+ * How many times this step's container was evicted by *transient infrastructure
786
+ * churn* — an event the runtime facade flags as not-a-crash (e.g. a deploy
787
+ * draining the sandbox) — and recovered by re-dispatching a fresh container.
788
+ * Counted separately from {@link evictionRecoveries} and bounded by a larger
789
+ * `MAX_TRANSIENT_EVICTION_RECOVERIES`, since such churn can recur several times in
790
+ * a short window, unlike a crash. Absent/0 until the first transient eviction.
791
+ */
792
+ transientEvictionRecoveries: v.optional(v.number()),
793
+ });
794
+ export const executionStatusSchema = v.picklist(['running', 'blocked', 'done', 'paused', 'failed']);
795
+ export const executionInstanceSchema = v.object({
796
+ id: v.string(),
797
+ blockId: v.string(),
798
+ pipelineId: v.string(),
799
+ pipelineName: v.string(),
800
+ steps: v.array(pipelineStepSchema),
801
+ currentStep: v.number(),
802
+ status: executionStatusSchema,
803
+ /**
804
+ * Structured failure diagnostics when `status` is `failed`; absent/null
805
+ * otherwise. Lets a failed task surface the same failure banner + retry as a
806
+ * failed bootstrap (shared {@link agentFailureSchema}).
807
+ */
808
+ failure: v.optional(v.nullable(agentFailureSchema)),
809
+ /**
810
+ * Internal user id (`usr_*`) of whoever started this run (or retried it). Recorded
811
+ * so the individual-usage restricted mode can use the initiator's OWN personal
812
+ * subscription (e.g. Claude) for the run's steps — a personal credential is never
813
+ * shared, so only its owner's runs may use it. Absent for runs started without a
814
+ * signed-in user (auth-disabled/local dev) and for legacy runs.
815
+ */
816
+ initiatedBy: v.optional(v.nullable(v.string())),
817
+ });
818
+ export const workspaceSchema = v.object({
819
+ id: v.string(),
820
+ name: v.string(),
821
+ /** Optional free-text description (null when unset). */
822
+ description: v.nullable(v.string()),
823
+ createdAt: v.number(),
824
+ /** The account this board belongs to, or null for a legacy/unscoped board. */
825
+ accountId: v.nullable(v.string()),
826
+ });
827
+ /**
828
+ * The spend safeguard's view of the current billing period. Token usage is
829
+ * tracked per LLM call and priced into a single currency; once `costSpent`
830
+ * reaches `costLimit` the engine pauses runs and the frontend shows a warning.
831
+ * Global across all workspaces (an operator's budget is org-wide), attached to
832
+ * every snapshot by the worker so the client can render the warning anywhere.
833
+ */
834
+ export const spendStatusSchema = v.object({
835
+ /** Start of the current billing period (epoch ms; calendar month, UTC). */
836
+ periodStart: v.number(),
837
+ /** Input (prompt) tokens consumed this period. */
838
+ inputTokens: v.number(),
839
+ /** Output (completion) tokens produced this period. */
840
+ outputTokens: v.number(),
841
+ /** Estimated cost of this period's usage, in `currency`. */
842
+ costSpent: v.number(),
843
+ /** Configured budget for one period, in `currency`. */
844
+ costLimit: v.number(),
845
+ /** ISO 4217 currency the costs are expressed in (e.g. `EUR`). */
846
+ currency: v.string(),
847
+ /** True once `costSpent >= costLimit`: runs are paused until the period rolls over. */
848
+ exceeded: v.boolean(),
849
+ });
850
+ // The workspace snapshot schema lives in ./snapshot — it references
851
+ // `bootstrapJobSchema` from ./bootstrap, which itself imports from this file, so
852
+ // keeping it here would be a circular import.
853
+ //# sourceMappingURL=entities.js.map