@sequenceholdings/pipeline-spec 0.1.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 (126) hide show
  1. package/README.md +114 -0
  2. package/dist/authoring/per-table-ingestion.d.ts +74 -0
  3. package/dist/authoring/per-table-ingestion.d.ts.map +1 -0
  4. package/dist/authoring/per-table-ingestion.js +122 -0
  5. package/dist/authoring/per-table-ingestion.js.map +1 -0
  6. package/dist/authoring/yaml.d.ts +6 -0
  7. package/dist/authoring/yaml.d.ts.map +1 -0
  8. package/dist/authoring/yaml.js +15 -0
  9. package/dist/authoring/yaml.js.map +1 -0
  10. package/dist/binding-gaps.d.ts +21 -0
  11. package/dist/binding-gaps.d.ts.map +1 -0
  12. package/dist/binding-gaps.js +74 -0
  13. package/dist/binding-gaps.js.map +1 -0
  14. package/dist/bodies/ingestion.d.ts +359 -0
  15. package/dist/bodies/ingestion.d.ts.map +1 -0
  16. package/dist/bodies/ingestion.js +446 -0
  17. package/dist/bodies/ingestion.js.map +1 -0
  18. package/dist/bodies/serving.d.ts +316 -0
  19. package/dist/bodies/serving.d.ts.map +1 -0
  20. package/dist/bodies/serving.js +290 -0
  21. package/dist/bodies/serving.js.map +1 -0
  22. package/dist/bodies/transformation.d.ts +397 -0
  23. package/dist/bodies/transformation.d.ts.map +1 -0
  24. package/dist/bodies/transformation.js +278 -0
  25. package/dist/bodies/transformation.js.map +1 -0
  26. package/dist/catalog-binding.d.ts +15 -0
  27. package/dist/catalog-binding.d.ts.map +1 -0
  28. package/dist/catalog-binding.js +15 -0
  29. package/dist/catalog-binding.js.map +1 -0
  30. package/dist/columns.d.ts +177 -0
  31. package/dist/columns.d.ts.map +1 -0
  32. package/dist/columns.js +89 -0
  33. package/dist/columns.js.map +1 -0
  34. package/dist/compatibility.d.ts +25 -0
  35. package/dist/compatibility.d.ts.map +1 -0
  36. package/dist/compatibility.js +108 -0
  37. package/dist/compatibility.js.map +1 -0
  38. package/dist/constants.d.ts +95 -0
  39. package/dist/constants.d.ts.map +1 -0
  40. package/dist/constants.js +105 -0
  41. package/dist/constants.js.map +1 -0
  42. package/dist/diff.d.ts +36 -0
  43. package/dist/diff.d.ts.map +1 -0
  44. package/dist/diff.js +300 -0
  45. package/dist/diff.js.map +1 -0
  46. package/dist/documentation.d.ts +22 -0
  47. package/dist/documentation.d.ts.map +1 -0
  48. package/dist/documentation.js +43 -0
  49. package/dist/documentation.js.map +1 -0
  50. package/dist/envelope.d.ts +1241 -0
  51. package/dist/envelope.d.ts.map +1 -0
  52. package/dist/envelope.js +163 -0
  53. package/dist/envelope.js.map +1 -0
  54. package/dist/errors.d.ts +30 -0
  55. package/dist/errors.d.ts.map +1 -0
  56. package/dist/errors.js +47 -0
  57. package/dist/errors.js.map +1 -0
  58. package/dist/graph-references.d.ts +34 -0
  59. package/dist/graph-references.d.ts.map +1 -0
  60. package/dist/graph-references.js +81 -0
  61. package/dist/graph-references.js.map +1 -0
  62. package/dist/graph.d.ts +112 -0
  63. package/dist/graph.d.ts.map +1 -0
  64. package/dist/graph.js +546 -0
  65. package/dist/graph.js.map +1 -0
  66. package/dist/hash.d.ts +28 -0
  67. package/dist/hash.d.ts.map +1 -0
  68. package/dist/hash.js +91 -0
  69. package/dist/hash.js.map +1 -0
  70. package/dist/index.d.ts +27 -0
  71. package/dist/index.d.ts.map +1 -0
  72. package/dist/index.js +27 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/issues.d.ts +14 -0
  75. package/dist/issues.d.ts.map +1 -0
  76. package/dist/issues.js +27 -0
  77. package/dist/issues.js.map +1 -0
  78. package/dist/loader.d.ts +50 -0
  79. package/dist/loader.d.ts.map +1 -0
  80. package/dist/loader.js +241 -0
  81. package/dist/loader.js.map +1 -0
  82. package/dist/migrations/contracts.d.ts +38 -0
  83. package/dist/migrations/contracts.d.ts.map +1 -0
  84. package/dist/migrations/contracts.js +175 -0
  85. package/dist/migrations/contracts.js.map +1 -0
  86. package/dist/migrations/manifest.d.ts +137 -0
  87. package/dist/migrations/manifest.d.ts.map +1 -0
  88. package/dist/migrations/manifest.js +137 -0
  89. package/dist/migrations/manifest.js.map +1 -0
  90. package/dist/operations.d.ts +214 -0
  91. package/dist/operations.d.ts.map +1 -0
  92. package/dist/operations.js +275 -0
  93. package/dist/operations.js.map +1 -0
  94. package/dist/orm-satisfies.d.ts +132 -0
  95. package/dist/orm-satisfies.d.ts.map +1 -0
  96. package/dist/orm-satisfies.js +119 -0
  97. package/dist/orm-satisfies.js.map +1 -0
  98. package/dist/primitives.d.ts +11 -0
  99. package/dist/primitives.d.ts.map +1 -0
  100. package/dist/primitives.js +20 -0
  101. package/dist/primitives.js.map +1 -0
  102. package/dist/resolve.d.ts +124 -0
  103. package/dist/resolve.d.ts.map +1 -0
  104. package/dist/resolve.js +194 -0
  105. package/dist/resolve.js.map +1 -0
  106. package/dist/secret-scan.d.ts +34 -0
  107. package/dist/secret-scan.d.ts.map +1 -0
  108. package/dist/secret-scan.js +98 -0
  109. package/dist/secret-scan.js.map +1 -0
  110. package/dist/serving-compatibility.d.ts +14 -0
  111. package/dist/serving-compatibility.d.ts.map +1 -0
  112. package/dist/serving-compatibility.js +19 -0
  113. package/dist/serving-compatibility.js.map +1 -0
  114. package/dist/snapshot-transport.d.ts +83 -0
  115. package/dist/snapshot-transport.d.ts.map +1 -0
  116. package/dist/snapshot-transport.js +42 -0
  117. package/dist/snapshot-transport.js.map +1 -0
  118. package/dist/suggestions.d.ts +3 -0
  119. package/dist/suggestions.d.ts.map +1 -0
  120. package/dist/suggestions.js +26 -0
  121. package/dist/suggestions.js.map +1 -0
  122. package/dist/types.d.ts +72 -0
  123. package/dist/types.d.ts.map +1 -0
  124. package/dist/types.js +119 -0
  125. package/dist/types.js.map +1 -0
  126. package/package.json +52 -0
@@ -0,0 +1,397 @@
1
+ import { z } from 'zod';
2
+ import { type CatalogBinding } from '../catalog-binding.js';
3
+ import { type ColumnsOrRef } from '../columns.js';
4
+ /**
5
+ * Transformation stages are where typing begins: inputs by logical asset
6
+ * reference (including the `lakebase_cdf` kind for reverse-sync), engine
7
+ * declaration, and strictly typed outputs whose contracts later compile to
8
+ * DLT expectations and Delta constraints.
9
+ *
10
+ * ## Quarantine semantics
11
+ *
12
+ * Rows failing a `quarantine`-posture check land in a platform-derived
13
+ * `<output>__quarantine` sibling table (see `QUARANTINE_SUFFIX`). The name
14
+ * derivation is defined HERE (M1) so later compilation matches — e.g.
15
+ * `silver.players` quarantines into `silver.players__quarantine`.
16
+ */
17
+ export declare const TRANSFORMATION_ENGINES: readonly ["dlt", "spark-job", "polars"];
18
+ export declare const transformationEngineSchema: z.ZodEnum<{
19
+ dlt: "dlt";
20
+ "spark-job": "spark-job";
21
+ polars: "polars";
22
+ }>;
23
+ export type TransformationEngine = z.infer<typeof transformationEngineSchema>;
24
+ export declare const outputAssetNameSchema: z.ZodString;
25
+ /**
26
+ * Reference to another stage's asset: an ingestion feed (`feed`) or a
27
+ * transformation output (`output`) — exactly one. `columns` narrows the input
28
+ * to the subset actually consumed (checked against the producer's declared
29
+ * columns by graph validation). `declared_loose` opts out of the column-subset
30
+ * check for schema-fluid sources.
31
+ */
32
+ declare const stageInputSchema: z.ZodObject<{
33
+ stage: z.ZodString;
34
+ feed: z.ZodOptional<z.ZodString>;
35
+ output: z.ZodOptional<z.ZodString>;
36
+ columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
37
+ freshness_requirement: z.ZodOptional<z.ZodString>;
38
+ declared_loose: z.ZodOptional<z.ZodBoolean>;
39
+ }, z.core.$strict>;
40
+ export type StageInput = z.infer<typeof stageInputSchema>;
41
+ /**
42
+ * Platform-defined CDC envelope columns present on every Lakebase CDF history
43
+ * table, IN ADDITION to the declared business columns. Authors never
44
+ * re-declare these; the platform guarantees them.
45
+ *
46
+ * `_pg_change_type` values: `insert`, `update_preimage`, `update_postimage`,
47
+ * `delete`; the commit columns order and deduplicate events.
48
+ */
49
+ export declare const LAKEBASE_CDF_ENVELOPE_COLUMNS: {
50
+ readonly _pg_change_type: "string";
51
+ readonly _pg_commit_lsn: "string";
52
+ readonly _pg_commit_ts: "timestamp";
53
+ };
54
+ /**
55
+ * A CDF-enabled Lakebase table input — the reverse-sync path (Postgres manual
56
+ * overlay → Delta history → transformation).
57
+ *
58
+ * The contract is an **envelope, not a row schema**: the input declares the
59
+ * business columns it consumes PLUS the platform-defined CDC envelope
60
+ * (`LAKEBASE_CDF_ENVELOPE_COLUMNS`), and a `fold` semantic:
61
+ *
62
+ * - `latest_state` — the platform-documented fold to current rows by key
63
+ * (last `update_postimage`/`insert` per key, `delete` removes).
64
+ * - `events` — the raw CDC event stream.
65
+ *
66
+ * Business-column types come from the declared schema — never inferred.
67
+ * `manual.*` sources are Atlas-owned tables with no ORM YAML, so columns are
68
+ * ALWAYS declared explicitly. (ORM-YAML-derived typing is deferred until an
69
+ * ORM→Delta type mapping exists — not assumed.)
70
+ */
71
+ declare const lakebaseCdfInputSchema: z.ZodObject<{
72
+ kind: z.ZodLiteral<"lakebase_cdf">;
73
+ table: z.ZodString;
74
+ columns: z.ZodUnion<readonly [z.ZodObject<{
75
+ schema_ref: z.ZodString;
76
+ }, z.core.$strict>, z.ZodRecord<z.ZodString, z.ZodObject<{
77
+ type: z.ZodUnion<readonly [z.ZodEnum<{
78
+ string: "string";
79
+ boolean: "boolean";
80
+ uuid: "uuid";
81
+ int: "int";
82
+ long: "long";
83
+ numeric: "numeric";
84
+ double: "double";
85
+ timestamp: "timestamp";
86
+ date: "date";
87
+ json: "json";
88
+ }>, z.ZodEnum<{
89
+ float: "float";
90
+ timestamp_ntz: "timestamp_ntz";
91
+ array: "array";
92
+ map: "map";
93
+ struct: "struct";
94
+ binary: "binary";
95
+ }>, z.ZodString]>;
96
+ nullable: z.ZodDefault<z.ZodBoolean>;
97
+ key: z.ZodDefault<z.ZodBoolean>;
98
+ domain: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
99
+ description: z.ZodOptional<z.ZodString>;
100
+ on_violation: z.ZodDefault<z.ZodEnum<{
101
+ fail: "fail";
102
+ quarantine: "quarantine";
103
+ warn: "warn";
104
+ }>>;
105
+ }, z.core.$strict>>]>;
106
+ fold: z.ZodEnum<{
107
+ latest_state: "latest_state";
108
+ events: "events";
109
+ }>;
110
+ }, z.core.$strict>;
111
+ export type LakebaseCdfInput = z.infer<typeof lakebaseCdfInputSchema>;
112
+ /**
113
+ * Discriminated input union: a stage-asset reference or a Lakebase CDF table.
114
+ * (`kind` discriminates; stage references have no `kind`.)
115
+ */
116
+ export declare const transformationInputSchema: z.ZodUnion<readonly [z.ZodObject<{
117
+ kind: z.ZodLiteral<"lakebase_cdf">;
118
+ table: z.ZodString;
119
+ columns: z.ZodUnion<readonly [z.ZodObject<{
120
+ schema_ref: z.ZodString;
121
+ }, z.core.$strict>, z.ZodRecord<z.ZodString, z.ZodObject<{
122
+ type: z.ZodUnion<readonly [z.ZodEnum<{
123
+ string: "string";
124
+ boolean: "boolean";
125
+ uuid: "uuid";
126
+ int: "int";
127
+ long: "long";
128
+ numeric: "numeric";
129
+ double: "double";
130
+ timestamp: "timestamp";
131
+ date: "date";
132
+ json: "json";
133
+ }>, z.ZodEnum<{
134
+ float: "float";
135
+ timestamp_ntz: "timestamp_ntz";
136
+ array: "array";
137
+ map: "map";
138
+ struct: "struct";
139
+ binary: "binary";
140
+ }>, z.ZodString]>;
141
+ nullable: z.ZodDefault<z.ZodBoolean>;
142
+ key: z.ZodDefault<z.ZodBoolean>;
143
+ domain: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
144
+ description: z.ZodOptional<z.ZodString>;
145
+ on_violation: z.ZodDefault<z.ZodEnum<{
146
+ fail: "fail";
147
+ quarantine: "quarantine";
148
+ warn: "warn";
149
+ }>>;
150
+ }, z.core.$strict>>]>;
151
+ fold: z.ZodEnum<{
152
+ latest_state: "latest_state";
153
+ events: "events";
154
+ }>;
155
+ }, z.core.$strict>, z.ZodObject<{
156
+ stage: z.ZodString;
157
+ feed: z.ZodOptional<z.ZodString>;
158
+ output: z.ZodOptional<z.ZodString>;
159
+ columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
160
+ freshness_requirement: z.ZodOptional<z.ZodString>;
161
+ declared_loose: z.ZodOptional<z.ZodBoolean>;
162
+ }, z.core.$strict>]>;
163
+ export type TransformationInput = z.infer<typeof transformationInputSchema>;
164
+ export declare function isLakebaseCdfInput(input: TransformationInput): input is LakebaseCdfInput;
165
+ /**
166
+ * How the output relation is written — consumed by writer-pattern validation
167
+ * when compilation lands (M6):
168
+ * - `overwrite` — full recompute each run (materialized-view-style).
169
+ * - `append` — append-only (event/log outputs).
170
+ * - `auto_cdc` — merge by key with CDC semantics.
171
+ */
172
+ export declare const WRITE_PATTERNS: readonly ["overwrite", "append", "auto_cdc"];
173
+ export declare const writePatternSchema: z.ZodEnum<{
174
+ overwrite: "overwrite";
175
+ append: "append";
176
+ auto_cdc: "auto_cdc";
177
+ }>;
178
+ export type WritePattern = z.infer<typeof writePatternSchema>;
179
+ /** A writer owns one disjoint value of a non-null string discriminator. */
180
+ export declare const writeScopeSchema: z.ZodObject<{
181
+ column: z.ZodString;
182
+ value: z.ZodString;
183
+ }, z.core.$strict>;
184
+ export declare const transformationOutputSchema: z.ZodObject<{
185
+ description: z.ZodOptional<z.ZodString>;
186
+ columns: z.ZodUnion<readonly [z.ZodObject<{
187
+ schema_ref: z.ZodString;
188
+ }, z.core.$strict>, z.ZodRecord<z.ZodString, z.ZodObject<{
189
+ type: z.ZodUnion<readonly [z.ZodEnum<{
190
+ string: "string";
191
+ boolean: "boolean";
192
+ uuid: "uuid";
193
+ int: "int";
194
+ long: "long";
195
+ numeric: "numeric";
196
+ double: "double";
197
+ timestamp: "timestamp";
198
+ date: "date";
199
+ json: "json";
200
+ }>, z.ZodEnum<{
201
+ float: "float";
202
+ timestamp_ntz: "timestamp_ntz";
203
+ array: "array";
204
+ map: "map";
205
+ struct: "struct";
206
+ binary: "binary";
207
+ }>, z.ZodString]>;
208
+ nullable: z.ZodDefault<z.ZodBoolean>;
209
+ key: z.ZodDefault<z.ZodBoolean>;
210
+ domain: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
211
+ description: z.ZodOptional<z.ZodString>;
212
+ on_violation: z.ZodDefault<z.ZodEnum<{
213
+ fail: "fail";
214
+ quarantine: "quarantine";
215
+ warn: "warn";
216
+ }>>;
217
+ }, z.core.$strict>>]>;
218
+ on_type_violation: z.ZodDefault<z.ZodEnum<{
219
+ fail: "fail";
220
+ quarantine: "quarantine";
221
+ warn: "warn";
222
+ }>>;
223
+ compatibility: z.ZodDefault<z.ZodEnum<{
224
+ "additive-only": "additive-only";
225
+ versioned: "versioned";
226
+ }>>;
227
+ write_pattern: z.ZodEnum<{
228
+ overwrite: "overwrite";
229
+ append: "append";
230
+ auto_cdc: "auto_cdc";
231
+ }>;
232
+ write_scope: z.ZodOptional<z.ZodObject<{
233
+ column: z.ZodString;
234
+ value: z.ZodString;
235
+ }, z.core.$strict>>;
236
+ }, z.core.$strict>;
237
+ export type TransformationOutput = z.infer<typeof transformationOutputSchema>;
238
+ /** Manual-only, or cron / after (both allowed for cron floor + dependency chaining). */
239
+ export declare const transformationTriggerSchema: z.ZodObject<{
240
+ manual: z.ZodOptional<z.ZodLiteral<true>>;
241
+ cron: z.ZodOptional<z.ZodString>;
242
+ after: z.ZodOptional<z.ZodArray<z.ZodString>>;
243
+ refresh_serving: z.ZodOptional<z.ZodArray<z.ZodString>>;
244
+ }, z.core.$strict>;
245
+ export type TransformationTrigger = z.infer<typeof transformationTriggerSchema>;
246
+ export declare const transformationBodyShape: {
247
+ engine: z.ZodEnum<{
248
+ dlt: "dlt";
249
+ "spark-job": "spark-job";
250
+ polars: "polars";
251
+ }>;
252
+ /**
253
+ * The stage's lineage edges — what it consumes. An empty array is legal
254
+ * only as a scaffold state (`seq-studio pipeline init`); graph validation
255
+ * emits a `no_inputs_declared` warning until real inputs are wired.
256
+ */
257
+ inputs: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
258
+ kind: z.ZodLiteral<"lakebase_cdf">;
259
+ table: z.ZodString;
260
+ columns: z.ZodUnion<readonly [z.ZodObject<{
261
+ schema_ref: z.ZodString;
262
+ }, z.core.$strict>, z.ZodRecord<z.ZodString, z.ZodObject<{
263
+ type: z.ZodUnion<readonly [z.ZodEnum<{
264
+ string: "string";
265
+ boolean: "boolean";
266
+ uuid: "uuid";
267
+ int: "int";
268
+ long: "long";
269
+ numeric: "numeric";
270
+ double: "double";
271
+ timestamp: "timestamp";
272
+ date: "date";
273
+ json: "json";
274
+ }>, z.ZodEnum<{
275
+ float: "float";
276
+ timestamp_ntz: "timestamp_ntz";
277
+ array: "array";
278
+ map: "map";
279
+ struct: "struct";
280
+ binary: "binary";
281
+ }>, z.ZodString]>;
282
+ nullable: z.ZodDefault<z.ZodBoolean>;
283
+ key: z.ZodDefault<z.ZodBoolean>;
284
+ domain: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
285
+ description: z.ZodOptional<z.ZodString>;
286
+ on_violation: z.ZodDefault<z.ZodEnum<{
287
+ fail: "fail";
288
+ quarantine: "quarantine";
289
+ warn: "warn";
290
+ }>>;
291
+ }, z.core.$strict>>]>;
292
+ fold: z.ZodEnum<{
293
+ latest_state: "latest_state";
294
+ events: "events";
295
+ }>;
296
+ }, z.core.$strict>, z.ZodObject<{
297
+ stage: z.ZodString;
298
+ feed: z.ZodOptional<z.ZodString>;
299
+ output: z.ZodOptional<z.ZodString>;
300
+ columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
301
+ freshness_requirement: z.ZodOptional<z.ZodString>;
302
+ declared_loose: z.ZodOptional<z.ZodBoolean>;
303
+ }, z.core.$strict>]>>;
304
+ trigger: z.ZodObject<{
305
+ manual: z.ZodOptional<z.ZodLiteral<true>>;
306
+ cron: z.ZodOptional<z.ZodString>;
307
+ after: z.ZodOptional<z.ZodArray<z.ZodString>>;
308
+ refresh_serving: z.ZodOptional<z.ZodArray<z.ZodString>>;
309
+ }, z.core.$strict>;
310
+ outputs: z.ZodRecord<z.ZodString, z.ZodObject<{
311
+ description: z.ZodOptional<z.ZodString>;
312
+ columns: z.ZodUnion<readonly [z.ZodObject<{
313
+ schema_ref: z.ZodString;
314
+ }, z.core.$strict>, z.ZodRecord<z.ZodString, z.ZodObject<{
315
+ type: z.ZodUnion<readonly [z.ZodEnum<{
316
+ string: "string";
317
+ boolean: "boolean";
318
+ uuid: "uuid";
319
+ int: "int";
320
+ long: "long";
321
+ numeric: "numeric";
322
+ double: "double";
323
+ timestamp: "timestamp";
324
+ date: "date";
325
+ json: "json";
326
+ }>, z.ZodEnum<{
327
+ float: "float";
328
+ timestamp_ntz: "timestamp_ntz";
329
+ array: "array";
330
+ map: "map";
331
+ struct: "struct";
332
+ binary: "binary";
333
+ }>, z.ZodString]>;
334
+ nullable: z.ZodDefault<z.ZodBoolean>;
335
+ key: z.ZodDefault<z.ZodBoolean>;
336
+ domain: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
337
+ description: z.ZodOptional<z.ZodString>;
338
+ on_violation: z.ZodDefault<z.ZodEnum<{
339
+ fail: "fail";
340
+ quarantine: "quarantine";
341
+ warn: "warn";
342
+ }>>;
343
+ }, z.core.$strict>>]>;
344
+ on_type_violation: z.ZodDefault<z.ZodEnum<{
345
+ fail: "fail";
346
+ quarantine: "quarantine";
347
+ warn: "warn";
348
+ }>>;
349
+ compatibility: z.ZodDefault<z.ZodEnum<{
350
+ "additive-only": "additive-only";
351
+ versioned: "versioned";
352
+ }>>;
353
+ write_pattern: z.ZodEnum<{
354
+ overwrite: "overwrite";
355
+ append: "append";
356
+ auto_cdc: "auto_cdc";
357
+ }>;
358
+ write_scope: z.ZodOptional<z.ZodObject<{
359
+ column: z.ZodString;
360
+ value: z.ZodString;
361
+ }, z.core.$strict>>;
362
+ }, z.core.$strict>>;
363
+ entrypoint: z.ZodString;
364
+ config: z.ZodOptional<z.ZodObject<{
365
+ catalog: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
366
+ default: z.ZodString;
367
+ by_target: z.ZodRecord<z.ZodString, z.ZodString>;
368
+ }, z.core.$strict>]>>;
369
+ history_catalog: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
370
+ default: z.ZodString;
371
+ by_target: z.ZodRecord<z.ZodString, z.ZodString>;
372
+ }, z.core.$strict>]>>;
373
+ silver_catalog: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
374
+ default: z.ZodString;
375
+ by_target: z.ZodRecord<z.ZodString, z.ZodString>;
376
+ }, z.core.$strict>]>>;
377
+ python_environment_version: z.ZodOptional<z.ZodEnum<{
378
+ 3: "3";
379
+ 4: "4";
380
+ }>>;
381
+ }, z.core.$catchall<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>>;
382
+ };
383
+ /** Structural slice of a stage spec the transformation refinement needs. */
384
+ export interface TransformationBody {
385
+ readonly engine: TransformationEngine;
386
+ readonly inputs: readonly TransformationInput[];
387
+ readonly trigger: TransformationTrigger;
388
+ readonly outputs: Record<string, {
389
+ columns: ColumnsOrRef;
390
+ write_scope?: z.infer<typeof writeScopeSchema>;
391
+ }>;
392
+ readonly config?: Readonly<Record<string, string | number | boolean | CatalogBinding>>;
393
+ }
394
+ /** Cross-field validation for `type: transformation` bodies (invoked from the envelope refinement). */
395
+ export declare function refineTransformationBody(body: TransformationBody, ctx: z.RefinementCtx): void;
396
+ export {};
397
+ //# sourceMappingURL=transformation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transformation.d.ts","sourceRoot":"","sources":["../../src/bodies/transformation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ import { z } from 'zod';
2
+ import { catalogBindingSchema } from '../catalog-binding.js';
3
+ import { CRON_PATTERN, OUTPUT_ASSET_PATTERN, QUALIFIED_TABLE_PATTERN, REPORT_ASSET_PATTERN, QUARANTINE_SUFFIX, STAGE_ID_PATTERN, } from '../constants.js';
4
+ import { columnIdentifierSchema, columnsOrRefSchema, identifierSchema, isSchemaRef, nullableKeyColumns, } from '../columns.js';
5
+ import { isoDurationSchema, repoRelativePathSchema } from './ingestion.js';
6
+ /**
7
+ * Transformation stages are where typing begins: inputs by logical asset
8
+ * reference (including the `lakebase_cdf` kind for reverse-sync), engine
9
+ * declaration, and strictly typed outputs whose contracts later compile to
10
+ * DLT expectations and Delta constraints.
11
+ *
12
+ * ## Quarantine semantics
13
+ *
14
+ * Rows failing a `quarantine`-posture check land in a platform-derived
15
+ * `<output>__quarantine` sibling table (see `QUARANTINE_SUFFIX`). The name
16
+ * derivation is defined HERE (M1) so later compilation matches — e.g.
17
+ * `silver.players` quarantines into `silver.players__quarantine`.
18
+ */
19
+ export const TRANSFORMATION_ENGINES = ['dlt', 'spark-job', 'polars'];
20
+ export const transformationEngineSchema = z.enum(TRANSFORMATION_ENGINES);
21
+ const stageRefSchema = z
22
+ .string()
23
+ .regex(STAGE_ID_PATTERN, 'must be a stage id (kebab-case slug)');
24
+ export const outputAssetNameSchema = z
25
+ .string()
26
+ .regex(OUTPUT_ASSET_PATTERN, "output assets must match '^(silver|gold|report)\\.[a-z][a-z0-9_]*$'");
27
+ /**
28
+ * Reference to another stage's asset: an ingestion feed (`feed`) or a
29
+ * transformation output (`output`) — exactly one. `columns` narrows the input
30
+ * to the subset actually consumed (checked against the producer's declared
31
+ * columns by graph validation). `declared_loose` opts out of the column-subset
32
+ * check for schema-fluid sources.
33
+ */
34
+ const stageInputSchema = z.strictObject({
35
+ stage: stageRefSchema,
36
+ feed: identifierSchema.optional(),
37
+ output: outputAssetNameSchema.optional(),
38
+ columns: z.array(columnIdentifierSchema).min(1).optional(),
39
+ /** Max acceptable staleness of the consumed asset. */
40
+ freshness_requirement: isoDurationSchema.optional(),
41
+ /** Skip column-subset validation — the input consumes whatever the producer emits. */
42
+ declared_loose: z.boolean().optional(),
43
+ });
44
+ /**
45
+ * Platform-defined CDC envelope columns present on every Lakebase CDF history
46
+ * table, IN ADDITION to the declared business columns. Authors never
47
+ * re-declare these; the platform guarantees them.
48
+ *
49
+ * `_pg_change_type` values: `insert`, `update_preimage`, `update_postimage`,
50
+ * `delete`; the commit columns order and deduplicate events.
51
+ */
52
+ export const LAKEBASE_CDF_ENVELOPE_COLUMNS = {
53
+ _pg_change_type: 'string',
54
+ _pg_commit_lsn: 'string',
55
+ _pg_commit_ts: 'timestamp',
56
+ };
57
+ /**
58
+ * A CDF-enabled Lakebase table input — the reverse-sync path (Postgres manual
59
+ * overlay → Delta history → transformation).
60
+ *
61
+ * The contract is an **envelope, not a row schema**: the input declares the
62
+ * business columns it consumes PLUS the platform-defined CDC envelope
63
+ * (`LAKEBASE_CDF_ENVELOPE_COLUMNS`), and a `fold` semantic:
64
+ *
65
+ * - `latest_state` — the platform-documented fold to current rows by key
66
+ * (last `update_postimage`/`insert` per key, `delete` removes).
67
+ * - `events` — the raw CDC event stream.
68
+ *
69
+ * Business-column types come from the declared schema — never inferred.
70
+ * `manual.*` sources are Atlas-owned tables with no ORM YAML, so columns are
71
+ * ALWAYS declared explicitly. (ORM-YAML-derived typing is deferred until an
72
+ * ORM→Delta type mapping exists — not assumed.)
73
+ */
74
+ const lakebaseCdfInputSchema = z.strictObject({
75
+ kind: z.literal('lakebase_cdf'),
76
+ /** `<schema>.<table>` of the CDF-enabled Lakebase source (e.g. `manual.retail_customers`). */
77
+ table: z
78
+ .string()
79
+ .regex(QUALIFIED_TABLE_PATTERN, "must be '<schema>.<table>' (e.g. manual.retail_customers)"),
80
+ /** Business columns consumed, with types — REQUIRED, never inferred. */
81
+ columns: columnsOrRefSchema,
82
+ fold: z.enum(['latest_state', 'events']),
83
+ });
84
+ /**
85
+ * Discriminated input union: a stage-asset reference or a Lakebase CDF table.
86
+ * (`kind` discriminates; stage references have no `kind`.)
87
+ */
88
+ export const transformationInputSchema = z.union([lakebaseCdfInputSchema, stageInputSchema]);
89
+ export function isLakebaseCdfInput(input) {
90
+ return 'kind' in input && input.kind === 'lakebase_cdf';
91
+ }
92
+ /**
93
+ * How the output relation is written — consumed by writer-pattern validation
94
+ * when compilation lands (M6):
95
+ * - `overwrite` — full recompute each run (materialized-view-style).
96
+ * - `append` — append-only (event/log outputs).
97
+ * - `auto_cdc` — merge by key with CDC semantics.
98
+ */
99
+ export const WRITE_PATTERNS = ['overwrite', 'append', 'auto_cdc'];
100
+ export const writePatternSchema = z.enum(WRITE_PATTERNS);
101
+ /** A writer owns one disjoint value of a non-null string discriminator. */
102
+ export const writeScopeSchema = z.strictObject({
103
+ column: columnIdentifierSchema,
104
+ value: z.string().min(1),
105
+ });
106
+ export const transformationOutputSchema = z.strictObject({
107
+ description: z.string().optional(),
108
+ /** The output's typed contract — inline columns or a `schema_ref`. */
109
+ columns: columnsOrRefSchema,
110
+ /**
111
+ * Row posture when a value violates its column type: `fail` the batch
112
+ * (default), `quarantine` into `<output>__quarantine`, or `warn`.
113
+ */
114
+ on_type_violation: z.enum(['fail', 'quarantine', 'warn']).default('fail'),
115
+ /**
116
+ * Contract evolution posture: `additive-only` (default) — breaking changes
117
+ * fail validation; `versioned` — breaking changes allowed with an explicit
118
+ * version bump (enforced by the CI gate via `diffContracts`).
119
+ */
120
+ compatibility: z.enum(['additive-only', 'versioned']).default('additive-only'),
121
+ write_pattern: writePatternSchema,
122
+ /** Every write, including snapshot deletions, must stay inside this scope. */
123
+ write_scope: writeScopeSchema.optional(),
124
+ });
125
+ /** Manual-only, or cron / after (both allowed for cron floor + dependency chaining). */
126
+ export const transformationTriggerSchema = z.strictObject({
127
+ /** Explicit opt-in to run-now only; useful for isolated parity and backfill jobs. */
128
+ manual: z.literal(true).optional(),
129
+ cron: z.string().regex(CRON_PATTERN, 'must be a cron expression (5–7 fields)').optional(),
130
+ after: z
131
+ .array(stageRefSchema)
132
+ .min(1)
133
+ .refine((after) => new Set(after).size === after.length, 'after must not declare the same upstream stage twice')
134
+ .optional(),
135
+ /** Refresh already provisioned serving stages that read this writer's outputs. */
136
+ refresh_serving: z.array(stageRefSchema).min(1)
137
+ .refine((stages) => new Set(stages).size === stages.length, 'refresh_serving must not repeat a stage')
138
+ .optional(),
139
+ });
140
+ /** Opaque per-engine scalar config plus platform-owned catalog bindings. */
141
+ const engineConfigValueSchema = z.union([z.string(), z.number(), z.boolean()]);
142
+ const engineConfigSchema = z
143
+ .object({
144
+ catalog: catalogBindingSchema.optional(),
145
+ history_catalog: catalogBindingSchema.optional(),
146
+ silver_catalog: catalogBindingSchema.optional(),
147
+ /** Explicit serverless Python environment; omitted preserves version 3. */
148
+ python_environment_version: z.enum(['3', '4']).optional(),
149
+ })
150
+ .catchall(engineConfigValueSchema);
151
+ export const transformationBodyShape = {
152
+ engine: transformationEngineSchema,
153
+ /**
154
+ * The stage's lineage edges — what it consumes. An empty array is legal
155
+ * only as a scaffold state (`seq-studio pipeline init`); graph validation
156
+ * emits a `no_inputs_declared` warning until real inputs are wired.
157
+ */
158
+ inputs: z.array(transformationInputSchema),
159
+ trigger: transformationTriggerSchema,
160
+ outputs: z.record(outputAssetNameSchema, transformationOutputSchema),
161
+ entrypoint: repoRelativePathSchema,
162
+ config: engineConfigSchema.optional(),
163
+ };
164
+ const CATALOG_CONFIG_KEYS = ['bronze_catalog'];
165
+ function refineCatalogConfigKeys(config, ctx) {
166
+ if (!config)
167
+ return;
168
+ for (const key of CATALOG_CONFIG_KEYS) {
169
+ if (!(key in config))
170
+ continue;
171
+ const parsed = identifierSchema.safeParse(config[key]);
172
+ if (parsed.success)
173
+ continue;
174
+ ctx.addIssue({
175
+ code: 'custom',
176
+ path: ['config', key],
177
+ message: parsed.error.issues[0]?.message ?? 'must be a lowercase identifier (a-z, 0-9, _)',
178
+ });
179
+ }
180
+ }
181
+ /** Cross-field validation for `type: transformation` bodies (invoked from the envelope refinement). */
182
+ export function refineTransformationBody(body, ctx) {
183
+ refineCatalogConfigKeys(body.config, ctx);
184
+ if (body.config?.python_environment_version !== undefined && body.engine !== 'polars') {
185
+ ctx.addIssue({ code: 'custom', path: ['config', 'python_environment_version'], message: 'python_environment_version requires a polars producer' });
186
+ }
187
+ if (body.trigger.refresh_serving && body.engine !== 'spark-job' && body.engine !== 'polars') {
188
+ ctx.addIssue({ code: 'custom', path: ['trigger', 'refresh_serving'], message: 'refresh_serving requires a spark-job or polars producer' });
189
+ }
190
+ if (body.trigger.manual && (body.trigger.cron || body.trigger.after || body.trigger.refresh_serving)) {
191
+ ctx.addIssue({ code: 'custom', path: ['trigger'], message: 'manual cannot be combined with cron, after, or refresh_serving' });
192
+ }
193
+ if (!body.trigger.manual && !body.trigger.cron && (!body.trigger.after || body.trigger.after.length === 0)) {
194
+ ctx.addIssue({
195
+ code: 'custom',
196
+ path: ['trigger'],
197
+ message: 'a transformation must declare a trigger: cron, after (upstream stages), both, or manual',
198
+ });
199
+ }
200
+ body.inputs.forEach((input, index) => {
201
+ if (isLakebaseCdfInput(input))
202
+ return;
203
+ const hasFeed = input.feed !== undefined;
204
+ const hasOutput = input.output !== undefined;
205
+ if (hasFeed === hasOutput) {
206
+ ctx.addIssue({
207
+ code: 'custom',
208
+ path: ['inputs', index],
209
+ message: "a stage input must reference exactly one of 'feed' (ingestion feed) or 'output' (transformation output)",
210
+ });
211
+ }
212
+ });
213
+ // Inventory-driven raw passthroughs (FIS/JH report plants) write table
214
+ // names from code. Declaring each report.* output here is what OOM'd
215
+ // pipeline-deploy during bundle deploy.
216
+ if (Object.keys(body.outputs).length === 0 && body.config?.raw_passthrough !== true) {
217
+ ctx.addIssue({
218
+ code: 'custom',
219
+ path: ['outputs'],
220
+ message: 'a transformation must declare at least one output',
221
+ });
222
+ }
223
+ for (const [asset, output] of Object.entries(body.outputs)) {
224
+ if (REPORT_ASSET_PATTERN.test(asset) && body.config?.raw_passthrough !== true) {
225
+ ctx.addIssue({
226
+ code: 'custom',
227
+ path: ['config', 'raw_passthrough'],
228
+ message: `report output '${asset}' requires config.raw_passthrough: true`,
229
+ });
230
+ }
231
+ if (!REPORT_ASSET_PATTERN.test(asset) && body.config?.raw_passthrough === true) {
232
+ ctx.addIssue({
233
+ code: 'custom',
234
+ path: ['outputs', asset],
235
+ message: `raw passthrough stages may only declare report.* outputs (got '${asset}')`,
236
+ });
237
+ }
238
+ refineWriteScope({ engine: body.engine, asset, output, ctx });
239
+ if (asset.endsWith(QUARANTINE_SUFFIX)) {
240
+ ctx.addIssue({
241
+ code: 'custom',
242
+ path: ['outputs', asset],
243
+ message: `'${QUARANTINE_SUFFIX}' names are platform-derived quarantine tables and cannot be declared`,
244
+ });
245
+ }
246
+ if (isSchemaRef(output.columns))
247
+ continue;
248
+ const badKeys = nullableKeyColumns(output.columns);
249
+ for (const column of badKeys) {
250
+ ctx.addIssue({
251
+ code: 'custom',
252
+ path: ['outputs', asset, 'columns', column, 'nullable'],
253
+ message: `key column '${column}' must be nullable: false`,
254
+ });
255
+ }
256
+ }
257
+ }
258
+ /** Scope declarations are valid only for explicit atomic job writers. */
259
+ function refineWriteScope({ engine, asset, output, ctx }) {
260
+ if (!output.write_scope)
261
+ return;
262
+ if (engine !== 'spark-job' && engine !== 'polars') {
263
+ ctx.addIssue({ code: 'custom', path: ['outputs', asset, 'write_scope'],
264
+ message: 'scoped writers require spark-job or polars with source-scoped atomic writes' });
265
+ }
266
+ if (isSchemaRef(output.columns))
267
+ return;
268
+ const column = output.columns[output.write_scope.column];
269
+ if (!column || column.type !== 'string' || column.nullable) {
270
+ ctx.addIssue({ code: 'custom', path: ['outputs', asset, 'write_scope'],
271
+ message: 'write_scope.column must name a non-nullable string output column' });
272
+ }
273
+ else if (column.domain && !column.domain.includes(output.write_scope.value)) {
274
+ ctx.addIssue({ code: 'custom', path: ['outputs', asset, 'write_scope', 'value'],
275
+ message: 'write_scope.value must belong to the discriminator column domain' });
276
+ }
277
+ }
278
+ //# sourceMappingURL=transformation.js.map