# Dagster integration is an attach-only quality layer on native Dagster primitives

**Status:** accepted (2026-05-29); implementation pending.

## Context

The Dagster Component (`TurbineChecksComponent`) surfaces a Turbine server's Check Definitions as native Dagster asset checks. An early shape carried bespoke fields — `asset_key_map`, `automation_condition`, `extra_tags`, `blocking_dimensions`, `freshness_checks` — and several were not even resolvable by Dagster's component model (`set`, `AutomationCondition`, `timedelta`). The goal: users should customise this integration the *same way* they already customise `dagster-dbt`, leaning on Dagster's own extension points instead of a growing pile of Turbine-specific knobs.

## Decision

- **Attach-only.** The component never emits Dagster assets; something else (dbt, a Python asset, Sling) owns each table asset. Turbine attaches asset checks to those assets and nothing more. Surfacing a contract's schema as a native asset is out of scope.
- **Native translation parity with `dagster-dbt`.** Asset-key targeting and check-spec attributes are configured through a scoped `translation:` block (Dagster's `TranslationFnResolver`) plus a swappable `TurbineTranslator` Python class — the `DagsterDbtTranslator` pattern. Default asset key is `[server.schema, logical_table]`; `translation.key_prefix`/`key` cover the common case; the translator covers arbitrary mapping.
- **`asset_key_map` is removed**, replaced by `translation`.
- **`freshness_checks` is removed.** Turbine already owns data-freshness via the `latency`/`freshness` SLA Property → Check Definition; Dagster materialisation-freshness belongs on the asset's owner via a native `freshness_policy`. `build_last_update_freshness_checks` keys off Dagster materialisation records — a different signal that is not a contract concern and has no place on a check-only component.
- **`automation_condition` stays the native `{{ dg.AutomationCondition.* }}` template** (`eager()`/`on_cron(...)` resolve to real conditions) and moves into the `translation:` block. `extra_tags` folds into `translation.metadata` — `AssetCheckSpec` has no first-class `tags` field, so tags collapse into check metadata.
- **Blocking derives from Check Severity by default** (gating → `AssetCheckSpec.blocking=True`). `blocking_dimensions` stays only as the interim Turbine-specific override until the server exposes Check Severity per check (see `CONTEXT.md` Blocking / Check Severity).
- **One Dagster Component per Server/Datasource**, namespaced by `resource_key` + `translation.key_prefix`. Multiple databases are multiple components, merged natively by Dagster.

## Considered alternatives

- **A typed component field per attribute** (`key_prefix`, `automation: eager|cron`, `tags`, …). Rejected: every unanticipated customisation becomes a component change, and it diverges from dbt muscle-memory. The native `translation:` block already carries key/automation/metadata.
- **Own-the-asset:** emit one `AssetSpec` per contract table carrying the contract's `TableSchema`/owners as native metadata. Deferred: collides with the dbt/Python asset that already owns the table. Revisit as an *additive* metadata-merge onto the existing spec, never a competing asset.
- **Keep `freshness_checks`.** Rejected: redundant with the Turbine latency SLA and less native than a `freshness_policy` on the asset owner.

## Consequences

- The integration mirrors `dagster-dbt`: users change keys, automation, and metadata the same way they already do for dbt — no new mental model.
- `translation.tags` has no home on an `AssetCheckSpec`, so it collapses into check `metadata` — a deliberate asymmetry with dbt, which sets tags on an `AssetSpec`.
- `TurbineServerResource.auth` uses `default_factory=NoAuthConfig`, not `default=NoAuthConfig()`. Surprising but load-bearing: a discriminated-union config field becomes a *required* Dagster selector, and Dagster drops any field equal to its default when it re-initialises the resource at run time — `default_factory` makes pydantic's `field.default` `PydanticUndefined`, so `auth` is never equal-to-default, survives the re-curry, and satisfies the selector.
- Multiple databases mean multiple components; `resource_key` must differ per component or they collide.
- This ADR depends on [ADR-0005](../../../docs/adr/0005-contracts-bind-to-datasources-via-odcs-servers.md): the `[server.schema, logical_table]` default key and the per-Server component both assume contracts bind to Datasources via ODCS `servers:`.
