showcase · tier 2 · ops & support
ETL quarantine
A schema gate splits good rows from bad. Rejects land in a quarantine file and the pipeline keeps going.
The whole file
7 tasks · 5 waves · the plan falls out of the bindings, nothing is scheduled by hand.
t2-etl-quarantine.nika.yamlsource
nika: v1workflow: id: etl-quarantine description: "CSV batch → schema gate → quarantine the bad · aggregate the good"vars: batch_csv: "./data/incoming/orders.csv"tasks: # A deterministic empty fallback · the recover target when parsing dies. empty_batch: invoke: tool: "nika:jq" args: { input: [], expression: "." } raw: invoke: tool: "nika:read" args: { path: "${{ vars.batch_csv }}" } rows: with: csv: ${{ tasks.raw.output }} invoke: tool: "nika:convert" args: input: "${{ with.csv }}" from: csv to: json has_header: true on_error: recover: ${{ tasks.empty_batch.output }} # malformed CSV → empty batch · pipeline lives check: with: rows: ${{ tasks.rows.output }} invoke: tool: "nika:validate" args: data: "${{ with.rows }}" format: json schema: type: array items: type: object required: [order_id, amount, currency] properties: order_id: { type: string } amount: { type: string } currency: { type: string, enum: [EUR, USD, GBP] } good: with: rows: ${{ tasks.rows.output }} valid: ${{ tasks.check.output.valid }} when: ${{ with.valid == true }} invoke: tool: "nika:jq" args: input: "${{ with.rows }}" expression: 'group_by(.currency) | map({currency: .[0].currency, orders: length, total: (map(.amount | tonumber) | add)})' quarantine: with: valid: ${{ tasks.check.output.valid }} errors: ${{ tasks.check.output.errors }} when: ${{ with.valid == false }} invoke: tool: "nika:write" args: path: "./data/quarantine/orders-rejected.json" content: "${{ with.errors }}" create_dirs: true report: with: totals: ${{ tasks.good.output }} when: ${{ with.totals != null && size(with.totals) > 0 }} # good is SKIPPED (null) on the quarantine path · guard before size() invoke: tool: "nika:write" args: path: "./data/reports/daily-totals.json" content: "${{ with.totals }}" create_dirs: trueoutputs: # Deliberately untyped · on the quarantine path `good` is SKIPPED and # this is null — a declared `type: array` would refuse the branch the # file exists to demonstrate. totals: ${{ tasks.good.output }}nika inspect · engine 0.105.0 · vendored graph, never re-derived
8 declared edges · 5 waves · the plan falls out of the bindings