@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,446 @@
1
+ import { z } from 'zod';
2
+ import { CRON_PATTERN, IDENTIFIER_PATTERN, RESERVED_ENVELOPE_COLUMNS, TIMEZONE_PATTERN, } from '../constants.js';
3
+ import { columnIdentifierSchema, identifierSchema } from '../columns.js';
4
+ import { feedColumnsSchema, feedDocumentationSchema } from '../documentation.js';
5
+ import { operationPolicySchema } from '../operations.js';
6
+ import { isoDurationSchema, repoRelativePathSchema, symbolicRefSchema } from '../primitives.js';
7
+ /**
8
+ * Ingestion stages declare **feeds into a volume landing zone** — never bronze
9
+ * tables, which are platform-derived. This body encodes the append-only-bronze
10
+ * architecture: volumes-first landing, snapshot_mode batch semantics, symbolic
11
+ * worker/credential bindings, and the decode/write split.
12
+ *
13
+ * ## What the platform derives from a feed (not authored)
14
+ *
15
+ * Ingestion lands in **catalog-per-source** catalogs (estate precedent:
16
+ * `src_banksouth_jack_henry`, `src_mock_data`): bronze lives in
17
+ * `src_<source>`, an ACCOUNT-LEVEL asset shared across deployments with a
18
+ * single writing deployment. The deployment catalog (`TargetFacts.catalog`)
19
+ * owns silver/gold/report/serving ONLY and never appears in an ingestion name.
20
+ *
21
+ * - **Source catalog** — `src_<source>` (canonical derivation:
22
+ * `sourceCatalog`; the source identity is underscore-constrained, so the
23
+ * catalog name is deterministic and UC-legal).
24
+ * - **Volume layout** — `/Volumes/src_<source>/bronze/<feed>/batch_id=<ts>/`
25
+ * (UC volumes are `/Volumes/<catalog>/<schema>/<volume>/…`, so the schema
26
+ * is `bronze` and the volume is the feed): every batch lands as raw files
27
+ * under a batch directory keyed by ingestion timestamp. Canonical
28
+ * derivation: `feedVolumePath` (tests pin the documented string to it).
29
+ * - **Bronze table name** — one append-only bronze table per feed:
30
+ * `src_<source>.bronze.<feed>` — the catalog scopes the source, so the
31
+ * table name needs no `<source>_` prefix (canonical derivation:
32
+ * `feedBronzeTable`).
33
+ * - **Envelope columns** — the materializer stamps `batch_id`, `ingested_at`,
34
+ * `source_system`, and `content_hash` onto every row. These names are
35
+ * RESERVED (`RESERVED_ENVELOPE_COLUMNS` in `../constants.js`) — a feed's
36
+ * `natural_key`/`cursor.column` may not name one (`refineFeed` below);
37
+ * the materializer itself rejects a source column collision at runtime,
38
+ * since a feed's full column set is only known at decode time.
39
+ * - **Presence manifest** — for `full_snapshot` feeds the platform records
40
+ * which natural keys appeared in each batch, so downstream deletion
41
+ * detection never needs source-side tombstones.
42
+ *
43
+ * ## The decode/write split
44
+ *
45
+ * Decoders are developer-provided **pure functions (file contents → rows)**
46
+ * invoked by the platform materializer for non-standard formats. Decoders
47
+ * never hold table-write credentials — the platform materializer performs
48
+ * envelope stamping and the append. **Bronze materialization does no semantic
49
+ * work**: no type coercion, dedup, normalization, or filtering — that is
50
+ * cleaning-transformation territory.
51
+ */
52
+ // Re-exported for existing consumers; the definitions live in primitives.ts
53
+ // so operations.ts can share them without an import cycle.
54
+ export { isoDurationSchema, repoRelativePathSchema, symbolicRefSchema };
55
+ export const SNAPSHOT_MODES = ['full_snapshot', 'incremental', 'event_stream'];
56
+ export const snapshotModeSchema = z.enum(SNAPSHOT_MODES);
57
+ /**
58
+ * Feed formats. `xlsx` is RESERVED: present in the enum but rejected at
59
+ * validation until the platform ships its default spreadsheet decoder (sheet
60
+ * selection / header detection stay byte-shape decoding — type coercion stays
61
+ * in cleaning). `custom` requires a `decoder`.
62
+ */
63
+ export const FEED_FORMATS = ['parquet', 'json', 'csv', 'xlsx', 'custom'];
64
+ export const feedFormatSchema = z.enum(FEED_FORMATS);
65
+ /**
66
+ * Incremental cursor declaration. `encoding` is an extensible string (e.g.
67
+ * `yyyyddd` for Jack-Henry-style Julian dates) — the SDK stays
68
+ * provider-neutral and never enumerates vendor encodings.
69
+ */
70
+ export const cursorSchema = z.strictObject({
71
+ /** Source-side cursor column. Source systems may use uppercase names. */
72
+ column: z
73
+ .string()
74
+ .min(1)
75
+ .regex(/^[A-Za-z][A-Za-z0-9_]*$/, 'must be a column identifier'),
76
+ encoding: z.string().min(1).optional(),
77
+ });
78
+ /** Extraction params are scalar hints passed to the extractor (never credentials — the envelope scan enforces it). */
79
+ const extractionValueSchema = z.union([z.string(), z.number(), z.boolean()]);
80
+ export const feedSchema = z.strictObject({
81
+ ...feedDocumentationSchema.shape,
82
+ /** Optional descriptions of observed source columns; not a typed contract. */
83
+ columns: feedColumnsSchema.optional(),
84
+ /**
85
+ * Optional documentation/<name>.documentation.yml. The loader merges it
86
+ * into this feed, preserving inline overrides, before deployment hashing.
87
+ */
88
+ documentation_ref: identifierSchema.optional(),
89
+ /**
90
+ * Batch semantics of the feed:
91
+ * - `full_snapshot` — every batch is the complete current state; the
92
+ * platform writes a presence manifest for deletion detection.
93
+ * - `incremental` — batches carry changed rows selected by `cursor`.
94
+ * - `event_stream` — append-only events; no snapshot semantics.
95
+ */
96
+ snapshot_mode: snapshotModeSchema,
97
+ /** Row identity within the feed. Required (non-empty) for full_snapshot and incremental. */
98
+ natural_key: z.array(columnIdentifierSchema).optional(),
99
+ /** Required iff `snapshot_mode: incremental`; meaningless otherwise. */
100
+ cursor: cursorSchema.optional(),
101
+ pii: z.enum(['none', 'contains_pii']).default('none'),
102
+ /** Wire format of landed files. Standard formats use compiled default decoders. */
103
+ format: feedFormatSchema.optional(),
104
+ /**
105
+ * Repo-relative path of a pure file→rows decoder. Required iff
106
+ * `format: custom`; forbidden otherwise. Decoders never write tables.
107
+ */
108
+ decoder: repoRelativePathSchema.optional(),
109
+ /** Scalar extraction hints (query params, source table names, page sizes). */
110
+ extraction: z.record(z.string().min(1), extractionValueSchema).optional(),
111
+ /** Max staleness for this feed; overrides the spec-wide `freshness_slo`. */
112
+ freshness_slo: isoDurationSchema.optional(),
113
+ /**
114
+ * The source may omit this feed for a deployment-specific schema/version.
115
+ * Optional feeds remain part of the contract, but extraction and
116
+ * materialization may report a successful no-data skip.
117
+ */
118
+ optional: z.boolean().optional(),
119
+ });
120
+ /** Feed documentation changes do not change downstream compatibility. */
121
+ export function feedContract(feed) {
122
+ const { description: _description, columns: _columns, column_descriptions: _columnDescriptions, documentation_ref: _documentationRef, ...contract } = feed;
123
+ return contract;
124
+ }
125
+ /**
126
+ * Ingestion schedule: cron + tz + the shared operation policy vocabulary.
127
+ * Optional by design — the future `upload` runtime is event-driven (file
128
+ * arrival), so a schedule is meaningless there.
129
+ */
130
+ export const scheduleSchema = z.strictObject({
131
+ cron: z.string().regex(CRON_PATTERN, 'must be a cron expression (5–7 fields)'),
132
+ tz: z.string().regex(TIMEZONE_PATTERN, 'must be an IANA timezone (e.g. America/New_York) or UTC'),
133
+ policy: operationPolicySchema.optional(),
134
+ });
135
+ /**
136
+ * Where the ingestion code runs:
137
+ * - `databricks` — a platform-scheduled job in the deployment workspace,
138
+ * pulling from an external system's API (`source` required).
139
+ * - `edge-worker` — an on-prem/VM worker registered in the platform registry
140
+ * (`worker` required): the SEQUENCE2-style extraction posture.
141
+ * - `upload` — RESERVED, rejected at validation ('not yet supported').
142
+ * Future semantics: no entrypoint/schedule/credential; the platform upload
143
+ * surface writes the feed's batch directories; the materializer fires on
144
+ * file arrival (Databricks file-arrival trigger); the batch ledger records
145
+ * the uploading initiator. Covers the spreadsheet→volume→cleaning→ontology
146
+ * expansion (seeds CSVs, iSeries dumps, BSM mock data).
147
+ */
148
+ export const INGESTION_RUNTIMES = ['databricks', 'edge-worker', 'upload'];
149
+ export const ingestionRuntimeSchema = z.enum(INGESTION_RUNTIMES);
150
+ /**
151
+ * Source-identity slug — feeds UC name derivation (`bronze.<source>_<feed>`,
152
+ * volume paths), so it shares the feed names' lowercase-underscore grammar.
153
+ * Hyphens are legal in symbolic refs but not in UC name parts.
154
+ */
155
+ const sourceIdentitySchema = z
156
+ .string()
157
+ .max(63)
158
+ .regex(IDENTIFIER_PATTERN, 'must be a lowercase identifier (a-z, 0-9, _) — it becomes part of derived UC names');
159
+ /** Hard ceiling that keeps a malformed authoring file from stampeding a VM. */
160
+ export const MAX_WORKER_CONCURRENT = 32;
161
+ export const DEFAULT_MATERIALIZER_CONCURRENT = 32;
162
+ export const MAX_MATERIALIZER_CONCURRENT = 64;
163
+ export const MATERIALIZER_STRATEGIES = ['per_feed', 'sharded'];
164
+ export const materializerStrategySchema = z.enum(MATERIALIZER_STRATEGIES);
165
+ export const materializerConcurrencySchema = z.strictObject({
166
+ /**
167
+ * Maximum number of feed materializer notebook tasks that may run at once.
168
+ * The compiler enforces this with dependency lanes (`per_feed`) or shard
169
+ * tasks (`sharded`); this is independent of worker.max_concurrent, which
170
+ * limits edge-worker extraction.
171
+ */
172
+ max_concurrent: z
173
+ .number()
174
+ .int()
175
+ .min(1)
176
+ .max(MAX_MATERIALIZER_CONCURRENT),
177
+ /**
178
+ * How Databricks schedules bronze writes. Omitted = `per_feed` (one
179
+ * notebook task per feed, CON-131 collector when parallel). `sharded`
180
+ * compiles `min(feed_count, max_concurrent)` Spark sessions that each
181
+ * process a disjoint feed subset sequentially (CON-227).
182
+ */
183
+ strategy: materializerStrategySchema.optional(),
184
+ });
185
+ /**
186
+ * Symbolic worker binding — machine and Data Sync family identifiers, never
187
+ * connection strings or managed-secret bindings.
188
+ *
189
+ * Which fields feed physical-name derivation: `data_source` is the source
190
+ * identity (bronze table names + volume paths derive from it, hence the
191
+ * identifier grammar); `machine` is a registry machine id (SEQUENCE2-style),
192
+ * `credential` a Data Sync environment family, and `operation` a machine-local
193
+ * Data Sync operation id — none appears in any derived name.
194
+ */
195
+ export const workerSchema = z.strictObject({
196
+ machine: symbolicRefSchema,
197
+ data_source: sourceIdentitySchema,
198
+ credential: symbolicRefSchema,
199
+ /** Optional machine-local Data Sync operation id used by run-now extraction. */
200
+ operation: z
201
+ .string()
202
+ .regex(/^[a-z][a-z0-9_]*$/, 'operation must be a lowercase snake_case Data Sync operation id')
203
+ .optional(),
204
+ /**
205
+ * How many extraction runs the platform may have in flight against this
206
+ * machine + data source at once. One-stage-per-table means N sibling stages
207
+ * contend for ONE ODBC connection on ONE VM, so capacity is a property of the
208
+ * shared upstream, not of any single stage.
209
+ *
210
+ * Enforcement contract (see `workerCapacityGroup`): every stage sharing a
211
+ * machine + data source also shares one `schedule.policy.exclusion_group`, so
212
+ * a consumer that only understands mutual exclusion serializes them —
213
+ * conservative by default. A consumer that understands budgets (the Data Sync
214
+ * enqueue route) may allow up to `max_concurrent` runs of that operation in the
215
+ * group to run together. Different operations in the group remain serialized.
216
+ * Omitted = 1 = strict serialization.
217
+ */
218
+ max_concurrent: z.number().int().min(1).max(MAX_WORKER_CONCURRENT).optional(),
219
+ });
220
+ /**
221
+ * Symbolic source binding for databricks-runtime API pulls. `system` is the
222
+ * source identity (feeds derived UC names — identifier grammar).
223
+ *
224
+ * `credential` names a binding family when the upstream API is authenticated.
225
+ * Omit it for genuinely keyless / public sources (e.g. ESPN's public site API)
226
+ * — never invent a sentinel binding. Resolution and compiled `credential_*`
227
+ * parameters only occur when this field is present.
228
+ */
229
+ export const sourceSystemSchema = z.strictObject({
230
+ system: sourceIdentitySchema,
231
+ /**
232
+ * Optional symbolic credential family. Omitted = unauthenticated/keyless
233
+ * source. Never a sentinel value and never a secret.
234
+ */
235
+ credential: symbolicRefSchema.optional(),
236
+ });
237
+ /** Feed names are snake_case identifiers (they become volume path segments and bronze table parts). */
238
+ export const feedNameSchema = z
239
+ .string()
240
+ .regex(IDENTIFIER_PATTERN, 'feed names must be lowercase identifiers (a-z, 0-9, _)');
241
+ export const ingestionBodyShape = {
242
+ runtime: ingestionRuntimeSchema,
243
+ /** Required iff `runtime: edge-worker`; forbidden otherwise. */
244
+ worker: workerSchema.optional(),
245
+ /** Required iff `runtime: databricks`; forbidden otherwise. */
246
+ source: sourceSystemSchema.optional(),
247
+ /** Databricks-side feed materializer concurrency, independent of extraction. */
248
+ materializer: materializerConcurrencySchema.optional(),
249
+ feeds: z.record(feedNameSchema, feedSchema),
250
+ schedule: scheduleSchema.optional(),
251
+ /** Spec-wide max staleness; feeds may override per-feed. */
252
+ freshness_slo: isoDurationSchema.optional(),
253
+ /** Raw-file retention window — an explicit replay buffer, not an archive. */
254
+ retention: isoDurationSchema.optional(),
255
+ /** Repo-relative code path. Required for databricks / edge-worker runtimes. */
256
+ entrypoint: repoRelativePathSchema.optional(),
257
+ };
258
+ /**
259
+ * The source identity a stage ingests from — `source.system` for databricks
260
+ * runtimes, `worker.data_source` for edge workers (stage id as the final
261
+ * fallback for future runtimes). This identity determines EVERY derived
262
+ * bronze name, so it is part of the contract surface (`contract_hash`) and
263
+ * the single input to the derivation helpers below.
264
+ */
265
+ export function ingestionSourceIdentity(spec) {
266
+ return spec.source?.system ?? spec.worker?.data_source ?? spec.stage;
267
+ }
268
+ /**
269
+ * Canonical per-source catalog: `src_<source>`. Ingestion never lands in the
270
+ * deployment catalog — per-source catalogs are account-level assets shared
271
+ * across deployments (one writing deployment), per the locked topology.
272
+ *
273
+ * Because the catalog is ACCOUNT-level, the source identity is an
274
+ * account-global namespace: where a source system spans tenants, the author
275
+ * scopes the tenant INTO the identity (`banksouth_candescent`, matching the
276
+ * estate's `src_banksouth_jack_henry`); first-party/global sources need no
277
+ * scope (`src_mock_data`). The registry milestone enforces single-writer per
278
+ * `src_*` FQN account-wide and may introduce registry-owned catalog bindings
279
+ * that supersede this deterministic default.
280
+ */
281
+ export function sourceCatalog(source) {
282
+ return `src_${source}`;
283
+ }
284
+ /**
285
+ * Canonical volume layout for a feed's batches:
286
+ * `/Volumes/src_<source>/bronze/<feed>` — batches land under
287
+ * `batch_id=<ts>/` inside it. The ONE derivation the resolver, docs, and
288
+ * tests share. Derived from the spec's own source identity — never from
289
+ * `TargetFacts.catalog`.
290
+ */
291
+ export function feedVolumePath(args) {
292
+ return `/Volumes/${sourceCatalog(args.source)}/bronze/${args.feed}`;
293
+ }
294
+ /** Canonical bronze table FQN for a feed: `src_<source>.bronze.<feed>`. */
295
+ export function feedBronzeTable(args) {
296
+ return `${sourceCatalog(args.source)}.bronze.${args.feed}`;
297
+ }
298
+ /**
299
+ * Canonical capacity/exclusion group for every stage extracting from ONE
300
+ * machine + data source: `<machine>_<data_source>`.
301
+ *
302
+ * This is the anti-stampede identity. With one stage per table, ~150 sibling
303
+ * stages point at a single VM holding a single ODBC connection to the core; a
304
+ * per-stage exclusion group would declare them all mutually independent and let
305
+ * every one of them fire at once. Grouping by the contended upstream instead
306
+ * means the declared policy matches physical reality, and `worker.max_concurrent`
307
+ * is the budget within the group.
308
+ *
309
+ * Machine ids permit hyphens (symbolic-ref grammar) while group names are
310
+ * identifiers, so hyphens are normalized to underscores.
311
+ */
312
+ export function workerCapacityGroup(worker) {
313
+ return `${worker.machine.replace(/-/g, '_')}_${worker.data_source}`;
314
+ }
315
+ /** Cross-field validation for `type: ingestion` bodies (invoked from the envelope refinement). */
316
+ export function refineIngestionBody(body, ctx) {
317
+ if (body.slos?.freshness !== undefined) {
318
+ // One freshness authority per stage kind: ingestion declares freshness in
319
+ // the body (`freshness_slo`, spec-wide or per-feed) — a second envelope
320
+ // knob would create silent precedence questions.
321
+ ctx.addIssue({
322
+ code: 'custom',
323
+ path: ['slos', 'freshness'],
324
+ message: "ingestion stages declare freshness via the body 'freshness_slo' (spec-wide or per-feed) — remove slos.freshness from the envelope",
325
+ });
326
+ }
327
+ if (body.runtime === 'upload') {
328
+ ctx.addIssue({
329
+ code: 'custom',
330
+ path: ['runtime'],
331
+ message: "runtime 'upload' is reserved, not yet supported — the platform upload surface (file-arrival materialization) has not shipped",
332
+ });
333
+ return;
334
+ }
335
+ if (body.runtime === 'edge-worker' && !body.worker) {
336
+ ctx.addIssue({
337
+ code: 'custom',
338
+ path: ['worker'],
339
+ message: "runtime 'edge-worker' requires a worker binding { machine, data_source, credential }",
340
+ });
341
+ }
342
+ if (body.runtime !== 'edge-worker' && body.worker) {
343
+ ctx.addIssue({
344
+ code: 'custom',
345
+ path: ['worker'],
346
+ message: `worker bindings are only valid for runtime 'edge-worker' (runtime is '${body.runtime}')`,
347
+ });
348
+ }
349
+ if (body.runtime === 'databricks' && !body.source) {
350
+ ctx.addIssue({
351
+ code: 'custom',
352
+ path: ['source'],
353
+ message: "runtime 'databricks' requires a source binding { system } (credential optional for keyless/public APIs)",
354
+ });
355
+ }
356
+ if (body.runtime !== 'databricks' && body.source) {
357
+ ctx.addIssue({
358
+ code: 'custom',
359
+ path: ['source'],
360
+ message: `source bindings are only valid for runtime 'databricks' (runtime is '${body.runtime}')`,
361
+ });
362
+ }
363
+ if (!body.entrypoint) {
364
+ ctx.addIssue({
365
+ code: 'custom',
366
+ path: ['entrypoint'],
367
+ message: `runtime '${body.runtime}' requires a repo-relative entrypoint`,
368
+ });
369
+ }
370
+ if (Object.keys(body.feeds).length === 0) {
371
+ ctx.addIssue({
372
+ code: 'custom',
373
+ path: ['feeds'],
374
+ message: 'an ingestion stage must declare at least one feed',
375
+ });
376
+ }
377
+ for (const [name, feed] of Object.entries(body.feeds)) {
378
+ refineFeed(name, feed, ctx);
379
+ }
380
+ }
381
+ function refineFeed(name, feed, ctx) {
382
+ if (feed.snapshot_mode === 'incremental' && !feed.cursor) {
383
+ ctx.addIssue({
384
+ code: 'custom',
385
+ path: ['feeds', name, 'cursor'],
386
+ message: "snapshot_mode 'incremental' requires a cursor { column, encoding? }",
387
+ });
388
+ }
389
+ if (feed.snapshot_mode !== 'incremental' && feed.cursor) {
390
+ ctx.addIssue({
391
+ code: 'custom',
392
+ path: ['feeds', name, 'cursor'],
393
+ message: `a cursor is only valid for snapshot_mode 'incremental' (snapshot_mode is '${feed.snapshot_mode}')`,
394
+ });
395
+ }
396
+ if ((feed.snapshot_mode === 'full_snapshot' || feed.snapshot_mode === 'incremental') &&
397
+ (!feed.natural_key || feed.natural_key.length === 0)) {
398
+ ctx.addIssue({
399
+ code: 'custom',
400
+ path: ['feeds', name, 'natural_key'],
401
+ message: `snapshot_mode '${feed.snapshot_mode}' requires a non-empty natural_key`,
402
+ });
403
+ }
404
+ if (feed.format === 'xlsx') {
405
+ ctx.addIssue({
406
+ code: 'custom',
407
+ path: ['feeds', name, 'format'],
408
+ message: "format 'xlsx' is reserved, not yet supported — the platform's default spreadsheet decoder has not shipped",
409
+ });
410
+ }
411
+ if (feed.format === 'custom' && !feed.decoder) {
412
+ ctx.addIssue({
413
+ code: 'custom',
414
+ path: ['feeds', name, 'decoder'],
415
+ message: "format 'custom' requires a repo-relative decoder (pure file → rows function)",
416
+ });
417
+ }
418
+ if (feed.format !== 'custom' && feed.decoder) {
419
+ ctx.addIssue({
420
+ code: 'custom',
421
+ path: ['feeds', name, 'decoder'],
422
+ message: 'a decoder is only valid with format custom — standard formats use compiled default decoders',
423
+ });
424
+ }
425
+ // `natural_key`/`cursor.column` are the only source-column names a feed
426
+ // declares (feeds are otherwise schemaless — bronze is raw). The
427
+ // materializer's envelope stamp would silently overwrite and lose a
428
+ // source column sharing one of these reserved names — matched
429
+ // case-insensitively since `cursor.column` (unlike `natural_key`) permits
430
+ // uppercase source-side names. See `RESERVED_ENVELOPE_COLUMNS`.
431
+ const reservedNaturalKeys = (feed.natural_key ?? []).filter((key) => RESERVED_ENVELOPE_COLUMNS.has(key.toLowerCase()));
432
+ const reservedCursorColumn = feed.cursor && RESERVED_ENVELOPE_COLUMNS.has(feed.cursor.column.toLowerCase())
433
+ ? [feed.cursor.column]
434
+ : [];
435
+ const reservedCollisions = [...new Set([...reservedNaturalKeys, ...reservedCursorColumn])].sort();
436
+ if (reservedCollisions.length > 0) {
437
+ ctx.addIssue({
438
+ code: 'custom',
439
+ path: ['feeds', name],
440
+ message: `column name(s) ${reservedCollisions.join(', ')} collide with platform-reserved bronze ` +
441
+ `envelope names (${[...RESERVED_ENVELOPE_COLUMNS].sort().join(', ')}) — the materializer ` +
442
+ 'stamps these onto every bronze row and would silently overwrite and lose this source data',
443
+ });
444
+ }
445
+ }
446
+ //# sourceMappingURL=ingestion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingestion.js","sourceRoot":"","sources":["../../src/bodies/ingestion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,yBAAyB,EACzB,gBAAgB,GACjB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACxE,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAE/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,4EAA4E;AAC5E,2DAA2D;AAC3D,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,CAAA;AAEvE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,eAAe,EAAE,aAAa,EAAE,cAAc,CAAU,CAAA;AACvF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;AAGxD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAA;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;AAGpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IACzC,yEAAyE;IACzE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,KAAK,CAAC,yBAAyB,EAAE,6BAA6B,CAAC;IAClE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAA;AAEF,sHAAsH;AACtH,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;AAE5E,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,YAAY,CAAC;IACvC,GAAG,uBAAuB,CAAC,KAAK;IAChC,8EAA8E;IAC9E,OAAO,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IACrC;;;OAGG;IACH,iBAAiB,EAAE,gBAAgB,CAAC,QAAQ,EAAE;IAC9C;;;;;;OAMG;IACH,aAAa,EAAE,kBAAkB;IACjC,4FAA4F;IAC5F,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE;IACvD,wEAAwE;IACxE,MAAM,EAAE,YAAY,CAAC,QAAQ,EAAE;IAC/B,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACrD,mFAAmF;IACnF,MAAM,EAAE,gBAAgB,CAAC,QAAQ,EAAE;IACnC;;;OAGG;IACH,OAAO,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IAC1C,8EAA8E;IAC9E,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,CAAC,QAAQ,EAAE;IACzE,4EAA4E;IAC5E,aAAa,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IAC3C;;;;OAIG;IACH,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAA;AAIF,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAA;IAC1J,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,YAAY,CAAC;IAC3C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,wCAAwC,CAAC;IAC9E,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,yDAAyD,CAAC;IACjG,MAAM,EAAE,qBAAqB,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAA;AAIF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,YAAY,EAAE,aAAa,EAAE,QAAQ,CAAU,CAAA;AAClF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;AAGhE;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,CAAC;KAC3B,MAAM,EAAE;KACR,GAAG,CAAC,EAAE,CAAC;KACP,KAAK,CACJ,kBAAkB,EAClB,oFAAoF,CACrF,CAAA;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAA;AACvC,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,CAAA;AACjD,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,CAAA;AAE7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,UAAU,EAAE,SAAS,CAAU,CAAA;AACvE,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAA;AAGzE,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,YAAY,CAAC;IAC1D;;;;;OAKG;IACH,cAAc,EAAE,CAAC;SACd,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,2BAA2B,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,EAAE,0BAA0B,CAAC,QAAQ,EAAE;CAChD,CAAC,CAAA;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC;IACzC,OAAO,EAAE,iBAAiB;IAC1B,WAAW,EAAE,oBAAoB;IACjC,UAAU,EAAE,iBAAiB;IAC7B,gFAAgF;IAChF,SAAS,EAAE,CAAC;SACT,MAAM,EAAE;SACR,KAAK,CAAC,mBAAmB,EAAE,iEAAiE,CAAC;SAC7F,QAAQ,EAAE;IACb;;;;;;;;;;;;;OAaG;IACH,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;CAC9E,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,YAAY,CAAC;IAC/C,MAAM,EAAE,oBAAoB;IAC5B;;;OAGG;IACH,UAAU,EAAE,iBAAiB,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAA;AAEF,uGAAuG;AACvG,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC;KAC5B,MAAM,EAAE;KACR,KAAK,CAAC,kBAAkB,EAAE,wDAAwD,CAAC,CAAA;AAEtF,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,OAAO,EAAE,sBAAsB;IAC/B,gEAAgE;IAChE,MAAM,EAAE,YAAY,CAAC,QAAQ,EAAE;IAC/B,+DAA+D;IAC/D,MAAM,EAAE,kBAAkB,CAAC,QAAQ,EAAE;IACrC,gFAAgF;IAChF,YAAY,EAAE,6BAA6B,CAAC,QAAQ,EAAE;IACtD,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,UAAU,CAAC;IAC3C,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,4DAA4D;IAC5D,aAAa,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IAC3C,6EAA6E;IAC7E,SAAS,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IACvC,+EAA+E;IAC/E,UAAU,EAAE,sBAAsB,CAAC,QAAQ,EAAE;CACtB,CAAA;AAEzB;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAIvC;IACC,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,WAAW,IAAI,IAAI,CAAC,KAAK,CAAA;AACtE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,OAAO,MAAM,EAAE,CAAA;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,IAAsC;IACnE,OAAO,YAAY,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,IAAI,EAAE,CAAA;AACrE,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,eAAe,CAAC,IAAsC;IACpE,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,CAAC,IAAI,EAAE,CAAA;AAC5D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAGnC;IACC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,WAAW,EAAE,CAAA;AACrE,CAAC;AAYD,kGAAkG;AAClG,MAAM,UAAU,mBAAmB,CAAC,IAAmB,EAAE,GAAoB;IAC3E,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,KAAK,SAAS,EAAE,CAAC;QACvC,0EAA0E;QAC1E,wEAAwE;QACxE,iDAAiD;QACjD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC;YAC3B,OAAO,EACL,mIAAmI;SACtI,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC9B,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,SAAS,CAAC;YACjB,OAAO,EACL,8HAA8H;SACjI,CAAC,CAAA;QACF,OAAM;IACR,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,KAAK,aAAa,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACnD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,QAAQ,CAAC;YAChB,OAAO,EACL,sFAAsF;SACzF,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,KAAK,aAAa,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAClD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,QAAQ,CAAC;YAChB,OAAO,EAAE,yEAAyE,IAAI,CAAC,OAAO,IAAI;SACnG,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,KAAK,YAAY,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAClD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,QAAQ,CAAC;YAChB,OAAO,EACL,yGAAyG;SAC5G,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,OAAO,KAAK,YAAY,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACjD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,QAAQ,CAAC;YAChB,OAAO,EAAE,wEAAwE,IAAI,CAAC,OAAO,IAAI;SAClG,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,YAAY,CAAC;YACpB,OAAO,EAAE,YAAY,IAAI,CAAC,OAAO,uCAAuC;SACzE,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,CAAC;YACf,OAAO,EAAE,mDAAmD;SAC7D,CAAC,CAAA;IACJ,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAA;IAC7B,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,IAAc,EAAE,GAAoB;IACpE,IAAI,IAAI,CAAC,aAAa,KAAK,aAAa,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACzD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC;YAC/B,OAAO,EAAE,qEAAqE;SAC/E,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,aAAa,KAAK,aAAa,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACxD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC;YAC/B,OAAO,EAAE,6EAA6E,IAAI,CAAC,aAAa,IAAI;SAC7G,CAAC,CAAA;IACJ,CAAC;IACD,IACE,CAAC,IAAI,CAAC,aAAa,KAAK,eAAe,IAAI,IAAI,CAAC,aAAa,KAAK,aAAa,CAAC;QAChF,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,EACpD,CAAC;QACD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,aAAa,CAAC;YACpC,OAAO,EAAE,kBAAkB,IAAI,CAAC,aAAa,oCAAoC;SAClF,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;QAC3B,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC;YAC/B,OAAO,EACL,2GAA2G;SAC9G,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAC9C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC;YAChC,OAAO,EAAE,8EAA8E;SACxF,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC;YAChC,OAAO,EACL,6FAA6F;SAChG,CAAC,CAAA;IACJ,CAAC;IAED,wEAAwE;IACxE,iEAAiE;IACjE,oEAAoE;IACpE,8DAA8D;IAC9D,0EAA0E;IAC1E,gEAAgE;IAChE,MAAM,mBAAmB,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAClE,yBAAyB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CACjD,CAAA;IACD,MAAM,oBAAoB,GACxB,IAAI,CAAC,MAAM,IAAI,yBAAyB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAC5E,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QACtB,CAAC,CAAC,EAAE,CAAA;IACR,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,mBAAmB,EAAE,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACjG,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;YACrB,OAAO,EACL,kBAAkB,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,yCAAyC;gBACxF,mBAAmB,CAAC,GAAG,yBAAyB,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,uBAAuB;gBAC1F,2FAA2F;SAC9F,CAAC,CAAA;IACJ,CAAC;AACH,CAAC"}