@olivervea/pipelines 2.0.0-beta.3 → 2.0.0-beta.31

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 (65) hide show
  1. package/README.md +165 -78
  2. package/manifest.schema.json +300 -16
  3. package/package.json +6 -4
  4. package/src/assets.d.ts +2 -2
  5. package/src/build/build.ts +98 -64
  6. package/src/build/cli.ts +33 -23
  7. package/src/build/elf.ts +16 -7
  8. package/src/build/hashes.ts +40 -51
  9. package/src/build/project.ts +55 -52
  10. package/src/builder/pipeline.ts +348 -89
  11. package/src/builder/refs.ts +15 -5
  12. package/src/core/brands.ts +6 -5
  13. package/src/core/config-refs.ts +103 -0
  14. package/src/core/config.ts +224 -0
  15. package/src/core/external.ts +86 -0
  16. package/src/core/handlers.ts +62 -15
  17. package/src/core/matrix.ts +150 -0
  18. package/src/core/models.ts +37 -15
  19. package/src/core/secrets.ts +2 -2
  20. package/src/core/steps.ts +113 -38
  21. package/src/core/types.ts +5 -1
  22. package/src/core/webhooks.ts +21 -7
  23. package/src/ctx/files.ts +119 -42
  24. package/src/ctx/index.ts +110 -39
  25. package/src/ctx/links.ts +30 -0
  26. package/src/ctx/sh.ts +106 -44
  27. package/src/ctx/types.ts +24 -4
  28. package/src/describe/config-schema.ts +98 -0
  29. package/src/describe/context.ts +187 -0
  30. package/src/describe/describe.ts +63 -355
  31. package/src/describe/failure-handlers.ts +34 -0
  32. package/src/describe/manifest-schema.ts +304 -61
  33. package/src/describe/mapper-source.ts +252 -105
  34. package/src/describe/record.ts +67 -37
  35. package/src/describe/schemas.ts +71 -0
  36. package/src/describe/stages.ts +166 -0
  37. package/src/describe/step.ts +544 -0
  38. package/src/describe/triggers.ts +72 -0
  39. package/src/dev/cli.ts +62 -35
  40. package/src/harness/env.ts +47 -25
  41. package/src/harness/execute.ts +74 -30
  42. package/src/harness/exit.ts +1 -1
  43. package/src/harness/index.ts +19 -8
  44. package/src/harness/main.ts +245 -99
  45. package/src/harness/mask.ts +98 -0
  46. package/src/harness/result.ts +45 -24
  47. package/src/harness/validate.ts +82 -15
  48. package/src/index.ts +139 -10
  49. package/src/library/checks.ts +11 -6
  50. package/src/library/compose-deploy.ts +115 -0
  51. package/src/library/github-source.ts +100 -47
  52. package/src/library/helm-deploy-ssh.ts +426 -112
  53. package/src/library/helm-deploy.ts +529 -0
  54. package/src/library/http-health.ts +7 -5
  55. package/src/library/image-build.ts +510 -162
  56. package/src/library/index.ts +48 -14
  57. package/src/library/models.ts +35 -14
  58. package/src/library/ssh.ts +44 -18
  59. package/src/testing/index.ts +100 -35
  60. package/src/util/canonical.ts +7 -4
  61. package/src/util/code-hash.ts +17 -0
  62. package/src/util/hash.ts +5 -5
  63. package/src/util/tar.ts +83 -38
  64. package/src/library/script.ts +0 -85
  65. package/src/util/handler-hash.ts +0 -22
@@ -1,138 +1,378 @@
1
1
  // The manifest format, defined once in zod. `manifest.schema.json` is generated from this
2
2
  // (bun scripts/gen-manifest-schema.ts) and describe validates every manifest against it.
3
- import { z } from "zod";
3
+ import { z } from 'zod';
4
4
 
5
- export const API_VERSION = "2.0";
5
+ export const API_VERSION = '2.0';
6
6
 
7
- const stepName = z.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/).describe("Step name: lower-case kebab, 1-63 chars. The step's identity.");
8
- const modelName = z.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/).describe("Model name (key of `models`).");
9
- const secretName = z.string().regex(/^[A-Z_][A-Z0-9_]*$/).describe("Secret name; also the env var name in the step container.");
7
+ const stepName = z
8
+ .string()
9
+ .regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/)
10
+ .describe("Step name: lower-case kebab, 1-63 chars. The step's identity.");
11
+ const modelName = z
12
+ .string()
13
+ .regex(/^[A-Za-z][A-Za-z0-9_]*$/)
14
+ .describe('Model name (key of `models`).');
15
+ const secretName = z
16
+ .string()
17
+ .regex(/^[A-Z_][A-Z0-9_]*$/)
18
+ .describe('Secret name; also the env var name in the step container.');
19
+ const configName = z
20
+ .string()
21
+ .regex(/^[a-z][a-zA-Z0-9]*$/)
22
+ .describe("Config entry name; a key of the pipeline's config document.");
10
23
  const hash = z.string().regex(/^sha256:[0-9a-f]{64}$/);
11
- const Json = z.json().meta({ id: "Json", description: "Any JSON value." });
12
- const jsonSchema = z.record(z.string(), z.unknown()).describe("A JSON Schema (draft 2020-12) object, canonical (sorted keys, no $schema).");
24
+ const Json = z.json().meta({ id: 'Json', description: 'Any JSON value.' });
25
+ const jsonSchema = z
26
+ .record(z.string(), z.unknown())
27
+ .describe('A JSON Schema (draft 2020-12) object, canonical (sorted keys, no $schema).');
13
28
 
14
29
  export const RefMapping = z
15
30
  .strictObject({
16
31
  ref: z
17
32
  .string()
18
33
  .regex(/^(?:input|failure|[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.[^.]+)*$/)
19
- .describe("'<root>' or '<root>.<field>.<field>…'. Root: 'input' (pipeline input), a step name (that step's output) or 'failure' (FailureContext, failure handlers only)."),
34
+ .describe(
35
+ "'<root>' or '<root>.<field>.<field>…'. Root: 'input' (pipeline input), a step name (that step's output) or 'failure' (FailureContext, failure handlers only).",
36
+ ),
20
37
  })
21
- .describe("Value taken from the run pool. A ref whose root step was skipped yields no value (only allowed for optional inputs).");
22
- export const ConstMapping = z.strictObject({ const: Json }).describe("A JSON constant.");
38
+ .describe(
39
+ 'Value taken from the run pool. A ref whose root step was skipped yields no value (only allowed for optional inputs).',
40
+ );
41
+ export const ConstMapping = z.strictObject({ const: Json }).describe('A JSON constant.');
23
42
 
24
43
  export type Mapping = { ref: string } | { const: unknown } | { [field: string]: Mapping };
25
44
  export const Mapping: z.ZodType<Mapping> = z
26
45
  .union([
27
46
  RefMapping,
28
47
  ConstMapping,
29
- z.record(z.string(), z.lazy(() => Mapping)).describe(
30
- "An object built field by field. Discrimination: an object whose only key is 'ref' (string value) is a ref, whose only key is 'const' is a constant, anything else is a nested object (describe never emits a nested object whose only key is 'ref' or 'const').",
31
- ),
48
+ z
49
+ .record(
50
+ z.string(),
51
+ z.lazy(() => Mapping),
52
+ )
53
+ .describe(
54
+ "An object built field by field. Discrimination: an object whose only key is 'ref' (string value) is a ref, whose only key is 'const' is a constant, anything else is a nested object (describe never emits a nested object whose only key is 'ref' or 'const').",
55
+ ),
32
56
  ])
33
- .meta({ id: "Mapping" });
57
+ .meta({ id: 'Mapping' });
34
58
 
35
59
  export const ModelSchemas = z
36
60
  .strictObject({
37
- output: jsonSchema.describe("z.output view: validates step outputs, trigger results and the pool. Every object has additionalProperties: false."),
38
- input: jsonSchema.optional().describe("z.input view (defaults make fields optional; `default` values are listed). Present only when it differs from `output`."),
61
+ output: jsonSchema.describe(
62
+ 'z.output view: validates step outputs, trigger results and the pool. Every object has additionalProperties: false.',
63
+ ),
64
+ input: jsonSchema
65
+ .optional()
66
+ .describe(
67
+ 'z.input view (defaults make fields optional; `default` values are listed). Present only when it differs from `output`.',
68
+ ),
39
69
  })
40
- .meta({ id: "ModelSchemas" });
70
+ .meta({ id: 'ModelSchemas' });
41
71
 
42
72
  export const InputType = z
43
73
  .union([
44
- z.strictObject({ model: modelName, optional: z.boolean().describe("optional(Model): may be absent because its producer was skipped.") }),
45
- z.strictObject({ schema: ModelSchemas.describe("A plain (unnamed) zod schema. Such inputs always have an explicit mapping.") }),
74
+ z.strictObject({
75
+ model: modelName,
76
+ optional: z
77
+ .boolean()
78
+ .describe('optional(Model): may be absent because its producer was skipped.'),
79
+ cells: z
80
+ .literal(true)
81
+ .optional()
82
+ .describe(
83
+ 'cells(Model): every cell of a matrix step whose output is the model, mapped with a whole-step ref; the value is [{cell, output}, …] in cell order. Omitted otherwise.',
84
+ ),
85
+ }),
86
+ z.strictObject({
87
+ schema: ModelSchemas.describe(
88
+ 'A plain (unnamed) zod schema. Such inputs always have an explicit mapping.',
89
+ ),
90
+ }),
46
91
  ])
47
- .meta({ id: "InputType" });
92
+ .meta({ id: 'InputType' });
93
+
94
+ const axisName = z.string().regex(/^[a-z][a-zA-Z0-9]{0,30}$/);
95
+ const axisValue = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/);
96
+
97
+ export const Matrix = z
98
+ .strictObject({
99
+ axes: z
100
+ .array(
101
+ z.strictObject({
102
+ name: axisName,
103
+ values: z.array(axisValue).min(1).describe('Unique per axis.'),
104
+ }),
105
+ )
106
+ .min(1)
107
+ .max(4)
108
+ .describe(
109
+ 'In order: the cells are the cross product, first axis outermost, minus the excludes (1 to 64 cells).',
110
+ ),
111
+ exclude: z
112
+ .array(z.record(axisName, axisValue))
113
+ .optional()
114
+ .describe(
115
+ 'Each entry names one or more axes with one of their values; a cell matching every named axis is left out (omitted when none).',
116
+ ),
117
+ failure: z
118
+ .strictObject({ kind: z.enum(['keepRunning', 'cascadeFailure']) })
119
+ .describe(
120
+ "keepRunning: every cell runs to the end. cascadeFailure: a cell whose last allowed attempt failed cancels the step's other scheduled or running cells.",
121
+ ),
122
+ maxParallel: z
123
+ .int()
124
+ .min(1)
125
+ .optional()
126
+ .describe("At most this many of the step's cells running at once in a run."),
127
+ })
128
+ .describe(
129
+ "A matrix step runs once per cell, named '<step>[<axis>=<value>,…]' in axis order; OLVE_CELL holds the cell as compact JSON.",
130
+ )
131
+ .meta({ id: 'Matrix' });
48
132
 
49
133
  export const StepDescriptor = z
50
134
  .strictObject({
51
135
  name: stepName,
52
- description: z.string().describe("What the step does, shown in the UI (may be empty)."),
53
- kind: z.enum(["custom", "script", "gitHubSource", "imageBuild", "helmDeployOverSsh"]).describe("'custom' = handlers/<name>.ts; others are SDK library steps."),
54
- image: z.string().min(1).describe("Container image the step runs in."),
55
- inputTypes: z.record(z.string(), InputType).describe("Declared input entries, keyed like input.json."),
56
- input: z.record(z.string(), Mapping).describe("How the service assembles input.json: one mapping per input key (same keys as inputTypes)."),
57
- output: modelName.nullable().describe("Output model, or null if the step produces no output."),
58
- secrets: z.array(secretName).describe("Declared secrets (sorted). Mount exactly these, as env vars of the same name."),
59
- handlerHash: hash.describe("Implementation identity: handler bundle (contract files excluded) + harness (bun + sdkHash); for library steps kind + config + harness."),
136
+ description: z
137
+ .string()
138
+ .describe('What the step does, shown in the UI (may be empty).'),
139
+ kind: z
140
+ .string()
141
+ .min(1)
142
+ .describe("A label for the step's kind: 'custom' unless the step names one."),
143
+ image: z.string().min(1).describe('Container image the step runs in.'),
144
+ inputTypes: z
145
+ .record(z.string(), InputType)
146
+ .describe('Declared input entries, keyed like input.json.'),
147
+ input: z
148
+ .record(z.string(), Mapping)
149
+ .describe(
150
+ 'How the service assembles input.json: one mapping per input key (same keys as inputTypes).',
151
+ ),
152
+ output: modelName
153
+ .nullable()
154
+ .describe('Output model, or null if the step produces no output.'),
155
+ secrets: z
156
+ .array(secretName)
157
+ .describe(
158
+ 'Declared secrets (sorted). Mount exactly these, as env vars of the same name.',
159
+ ),
60
160
  timeoutSeconds: z.number().int().positive().optional(),
61
161
  retries: z.number().int().nonnegative().optional(),
62
- config: z.record(z.string(), Json).optional().describe("Library-step configuration (display only; already covered by handlerHash)."),
162
+ matrix: Matrix.optional().describe(
163
+ 'Set on a matrix step (never on a failure handler); omitted otherwise.',
164
+ ),
165
+ config: z
166
+ .record(z.string(), Json)
167
+ .optional()
168
+ .describe(
169
+ 'The step\'s settings (display only). A config reference is recorded as {"$config": "<entry>.<field>…"}; the entry is in configEntries.',
170
+ ),
171
+ configEntries: z
172
+ .array(configName)
173
+ .optional()
174
+ .describe(
175
+ "Config entries the step receives (sorted; omitted when none). The service delivers them in input.json under '$config'.",
176
+ ),
177
+ clusterAccess: z
178
+ .literal(true)
179
+ .optional()
180
+ .describe(
181
+ "The step pod gets the instance's step ServiceAccount token (Kubernetes:StepServiceAccount), if the instance grants one. Omitted = no token.",
182
+ ),
183
+ externalInputs: z
184
+ .record(
185
+ z.string(),
186
+ z.strictObject({
187
+ model: modelName.describe(
188
+ "The value is validated against this model's input view.",
189
+ ),
190
+ title: z.string().min(1).describe('What is asked, shown to whoever answers.'),
191
+ description: z.string(),
192
+ from: z
193
+ .array(
194
+ z.discriminatedUnion('kind', [
195
+ z.strictObject({ kind: z.literal('person') }),
196
+ z.strictObject({ kind: z.literal('callback') }),
197
+ z.strictObject({
198
+ kind: z.literal('poll'),
199
+ url: z
200
+ .string()
201
+ .describe(
202
+ 'GET while waiting; {run.id} and {run.number} are filled in.',
203
+ ),
204
+ valuePath: z
205
+ .string()
206
+ .min(1)
207
+ .describe("Dot path to the value; '.' is the whole body."),
208
+ intervalSeconds: z.int().min(10),
209
+ }),
210
+ ]),
211
+ )
212
+ .min(1)
213
+ .describe('Who or what may answer.'),
214
+ }),
215
+ )
216
+ .optional()
217
+ .describe(
218
+ 'Inputs answered from outside the run (omitted when none): the step waits until every one has a value, which the service writes into input.json under its key.',
219
+ ),
63
220
  })
64
- .meta({ id: "Step" });
221
+ .meta({ id: 'Step' });
65
222
 
66
- export type Stage = { steps: z.infer<typeof StepDescriptor>[] } | { when: Record<string, (string | number | boolean)[]>; stages: Stage[] };
223
+ export type Stage =
224
+ | { steps: z.infer<typeof StepDescriptor>[] }
225
+ | { when: Record<string, (string | number | boolean)[]>; stages: Stage[] }
226
+ | { select: string; discriminator: string; cases: Record<string, Stage[]> };
67
227
  export const Stage: z.ZodType<Stage> = z
68
228
  .union([
69
- z.strictObject({ steps: z.array(StepDescriptor).min(1).describe("Steps run in parallel; the stage is a barrier.") }),
70
229
  z.strictObject({
71
- when: z.record(z.string(), z.array(z.union([z.string(), z.number(), z.boolean()])).min(1)).describe(
72
- "Pipeline-input field → accepted values (all fields must match). Empty object = always.",
73
- ),
230
+ steps: z
231
+ .array(StepDescriptor)
232
+ .min(1)
233
+ .describe('Steps run in parallel; the stage is a barrier.'),
234
+ }),
235
+ z.strictObject({
236
+ when: z
237
+ .record(
238
+ z.string(),
239
+ z.array(z.union([z.string(), z.number(), z.boolean()])).min(1),
240
+ )
241
+ .describe(
242
+ 'Pipeline-input field → accepted values (all fields must match). Empty object = always.',
243
+ ),
74
244
  stages: z.array(z.lazy(() => Stage)),
75
245
  }),
246
+ z.strictObject({
247
+ select: z
248
+ .string()
249
+ .min(1)
250
+ .describe(
251
+ "Config reference (`<entry>.<field>…`) to a discriminated union; the run's config snapshot picks the case.",
252
+ ),
253
+ discriminator: z
254
+ .string()
255
+ .min(1)
256
+ .describe("The union's literal field whose value names the case."),
257
+ cases: z
258
+ .record(z.string(), z.array(z.lazy(() => Stage)))
259
+ .describe(
260
+ 'Discriminator value → stages. Exactly one case runs; a step name may repeat across cases, nowhere else.',
261
+ ),
262
+ }),
76
263
  ])
77
- .meta({ id: "Stage" });
264
+ .meta({ id: 'Stage' });
78
265
 
79
266
  export const Trigger = z
80
- .discriminatedUnion("kind", [
81
- z.strictObject({ kind: z.literal("push"), name: z.literal("push") }).describe("GitHub push. Event = the webhook payload."),
267
+ .discriminatedUnion('kind', [
268
+ z
269
+ .strictObject({ kind: z.literal('push'), name: z.literal('push') })
270
+ .describe('GitHub push. Event = the webhook payload.'),
82
271
  z
83
272
  .strictObject({
84
- kind: z.literal("poll"),
273
+ kind: z.literal('poll'),
85
274
  name: z.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/),
86
275
  url: z.string(),
87
- headers: z.record(z.string(), z.string()).describe("Values may contain '$SECRET:NAME' (NAME ∈ secrets)."),
276
+ headers: z
277
+ .record(z.string(), z.string())
278
+ .describe("Values may contain '$SECRET:NAME' (NAME ∈ secrets)."),
88
279
  secrets: z.array(secretName),
89
- valuePath: z.string().describe("Dot path into the JSON response; a change triggers map-trigger with {\"value\": <string>}."),
280
+ valuePath: z
281
+ .string()
282
+ .describe(
283
+ 'Dot path into the JSON response; a change triggers map-trigger with {"value": <string>}.',
284
+ ),
90
285
  intervalSeconds: z.number().int().positive(),
91
286
  })
92
- .describe("Background poller."),
287
+ .describe('Background poller.'),
93
288
  z
94
289
  .strictObject({
95
- kind: z.literal("webhook"),
290
+ kind: z.literal('webhook'),
96
291
  name: z.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/),
97
- verify: z.discriminatedUnion("scheme", [
292
+ verify: z.discriminatedUnion('scheme', [
98
293
  z.strictObject({
99
- scheme: z.literal("hmac-sha256"),
294
+ scheme: z.literal('hmac-sha256'),
100
295
  secret: secretName,
101
296
  header: z.string().min(1),
102
- prefix: z.string().describe("Text before the hex digest in the header, e.g. 'sha256='."),
297
+ prefix: z
298
+ .string()
299
+ .describe("Text before the hex digest in the header, e.g. 'sha256='."),
103
300
  }),
104
- z.strictObject({ scheme: z.literal("bearer"), secret: secretName }),
301
+ z.strictObject({ scheme: z.literal('bearer'), secret: secretName }),
105
302
  ]),
106
303
  })
107
- .describe("Inbound HTTP delivery to /api/hooks/<pipeline id>/<name>. Event = {headers, body}; a null mapping ignores it."),
304
+ .describe(
305
+ 'Inbound HTTP delivery to /api/hooks/<pipeline id>/<name>. Event = {headers, body}; a null mapping ignores it.',
306
+ ),
108
307
  ])
109
- .meta({ id: "Trigger" });
308
+ .meta({ id: 'Trigger' });
110
309
 
111
310
  export const Manifest = z
112
311
  .strictObject({
113
312
  apiVersion: z.literal(API_VERSION),
114
313
  name: z.string().min(1),
115
314
  description: z.string().optional(),
116
- input: modelName.describe("Pipeline input model."),
315
+ input: modelName.describe('Pipeline input model.'),
117
316
  harness: z
118
317
  .strictObject({
119
- bun: z.string().describe("Bun runtime version compiled into the binary."),
120
- sdk: z.string().describe("@olivervea/pipelines package version."),
121
- sdkHash: hash.describe("Hash of the SDK runtime sources + zod version."),
318
+ bun: z.string().describe('Bun runtime version compiled into the binary.'),
319
+ sdk: z.string().describe('@olivervea/pipelines package version.'),
320
+ sdkHash: hash.describe('Hash of the SDK runtime sources + zod version.'),
122
321
  })
123
- .describe("Harness identity. Already folded into every handlerHash."),
124
- models: z.record(modelName, ModelSchemas).describe("Every model referenced by the pipeline (input, step inputs/outputs)."),
125
- secrets: z.array(z.strictObject({ name: secretName, description: z.string() })).describe("All declared secrets (steps + poll triggers), sorted by name."),
322
+ .describe('Harness identity. Already folded into codeHash.'),
323
+ codeHash: hash.describe(
324
+ 'Hash of the whole build: the pipeline and its code bundled, plus the harness. Any code change makes a new pipeline version.',
325
+ ),
326
+ models: z
327
+ .record(modelName, ModelSchemas)
328
+ .describe('Every model referenced by the pipeline (input, step inputs/outputs).'),
329
+ secrets: z
330
+ .array(z.strictObject({ name: secretName, description: z.string() }))
331
+ .describe('All declared secrets (steps + poll triggers), sorted by name.'),
332
+ config: z
333
+ .array(
334
+ z.strictObject({
335
+ name: configName,
336
+ description: z.string(),
337
+ required: z
338
+ .boolean()
339
+ .describe(
340
+ 'False when the schema accepts a missing value (optional, or a default).',
341
+ ),
342
+ schema: jsonSchema.describe(
343
+ "z.input view: validates the config document's value for this entry (defaults listed).",
344
+ ),
345
+ }),
346
+ )
347
+ .optional()
348
+ .describe(
349
+ 'Config entries used by steps, sorted by name (omitted when none). Fields of kind secretRef ({secret: NAME}) name a secret.',
350
+ ),
351
+ retention: z
352
+ .strictObject({
353
+ keepLatestRuns: z.int().min(0).optional(),
354
+ minRunAgeDays: z.int().min(0).optional(),
355
+ buildGraceDays: z.int().min(0).optional(),
356
+ })
357
+ .optional()
358
+ .describe(
359
+ 'Retention overrides for this pipeline (omitted when none). Each field replaces the instance setting of the same meaning.',
360
+ ),
126
361
  triggers: z.array(Trigger),
127
362
  stages: z.array(Stage),
128
363
  failureHandlers: z.array(
129
364
  z.strictObject({
130
- step: StepDescriptor.describe("The handler step. Not part of any stage; its inputs are all FailureContext ({ref: 'failure'})."),
131
- steps: z.array(stepName).nullable().describe("Steps whose failure triggers the handler (null = any step)."),
365
+ step: StepDescriptor.describe(
366
+ "The handler step. Not part of any stage; its inputs are all FailureContext ({ref: 'failure'}).",
367
+ ),
368
+ steps: z
369
+ .array(stepName)
370
+ .nullable()
371
+ .describe('Steps whose failure triggers the handler (null = any step).'),
132
372
  }),
133
373
  ),
134
374
  })
135
- .meta({ id: "Manifest", title: "Olve.Pipelines v2 manifest" });
375
+ .meta({ id: 'Manifest', title: 'Olve.Pipelines v2 manifest' });
136
376
 
137
377
  export type ManifestT = z.infer<typeof Manifest>;
138
378
  export type StepDescriptorT = z.infer<typeof StepDescriptor>;
@@ -141,6 +381,9 @@ export type ModelSchemasT = z.infer<typeof ModelSchemas>;
141
381
 
142
382
  /** The JSON Schema document written to manifest.schema.json. */
143
383
  export function manifestJsonSchema(): Record<string, unknown> {
144
- const js = z.toJSONSchema(Manifest, { target: "draft-2020-12" }) as Record<string, unknown>;
145
- return { ...js, $id: "https://olve.dev/schemas/pipelines/manifest-2.0.json" };
384
+ const js = z.toJSONSchema(Manifest, { target: 'draft-2020-12' }) as Record<
385
+ string,
386
+ unknown
387
+ >;
388
+ return { ...js, $id: 'https://olve.dev/schemas/pipelines/manifest-2.0.json' };
146
389
  }