@olivervea/pipelines 2.0.0-beta.18 → 2.0.0-beta.19

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