The incident evidence card¶
When a check breaches into a warning or failure severe enough to open or update an incident,
DataQ assembles a fixed, deterministic snapshot of everything relevant to that failure —
before any narrative is generated over it, not as part of generating one. This page is that
snapshot's contract: what each layer contains, what it is computed from, what a null in it
actually means, and the privacy guarantee it carries.
The card is deterministic on purpose. A narrative generated later (see MCP tool design: honesty & disclosure for the same discipline applied to the rest of the AI-facing surface) can only ever reason over this fixed bundle — it never goes back to a live datasource, and every claim it makes must cite which layer below supports it. The evidence is gathered the same way regardless of whether any narrative is ever requested over it.
What it is a snapshot of¶
The card is captured at the moment an incident opens or re-occurs — it describes that occurrence, not the current live state of the check, the asset, or the pipeline. A check renamed since, or a table that has since recovered, still reads through the card as it was at the time of the breach. Read it as "when this last failed," never as "right now."
The layers¶
| Layer | Contains | Computed from |
|---|---|---|
check |
The failing check's id, name, expectation type, and monitor kind | The check row at capture time |
asset |
The table/file this check runs against | The resolved asset for the run |
failing_result |
Status, the numeric metric, the GX-shaped observed/expected values | The breaching result — no failing sample rows, see below |
kind_detail |
The kind-specific fields (e.g. age_hours for freshness, z_score for anomaly) pulled out of the raw result, so a reader doesn't need to know all four monitor-kind JSON shapes |
failing_result's own observed value, reshaped |
metric_trend |
The 10 most recent readings for this check, newest first | This check's own result history |
sibling_checks |
Every other check's outcome in the same run | The run the failing result belongs to |
same_asset_siblings |
The latest outcome of every other check targeting this same asset, across every suite, from the last 7 days | Every suite's results on this asset, not just this run's |
upstream_pipeline_run |
The orchestration pipeline run that triggered this suite run, its duration, and its delay against that pipeline's own recent history | The linked pipeline_runs row, when one triggered this run |
downstream_blast_radius |
The assets reachable downstream of this one via recorded lineage | The lineage graph, depth-capped |
profile_diff |
Always null |
Not implemented — see below |
The privacy guarantee¶
failing_result never carries a failing sample row — the specific record values that tripped
the check are deliberately excluded from the card at the point it is built, not filtered out
later. A narrative generated over this card, or a client reading it directly, can describe that
a column failed a null check but cannot see which row. Use the run's own results — which
apply the product's standard column-aware redaction — to inspect specific failing values.
failing_result.observed_value is a second, narrower door to real data: some expectation types
(min/max/mean-style rules) carry a literal warehouse cell value there, not just GX metadata. That
value is routed through the same column-policy/warehouse-tag redaction floor every other results
surface applies — but, unlike the live results endpoints, this card computes it once, at the
point the incident opens or gets a fresh occurrence, and stores the result. A suite's column
policy edited afterward does not retroactively re-mask an already-stored card.
same_asset_siblings is assembled once, for the workspace as a whole, with no caller in scope —
it has to be, since it draws from every suite that touches the asset, not just the one that
opened this incident. Reading it back applies a second, per-request narrowing: a caller only
sees the sibling entries on suites they hold at least view access to; entries from suites they
cannot see are withheld and folded into a count instead of being named. An outbound alert
narrows the same layer differently — to entries on the incident's own suite only, since an alert
channel has no per-viewer grant to check it against.
Reading a null layer correctly¶
A null layer does not by itself mean something went wrong — each layer means a different thing
by it, and collapsing them into one "missing data" reading is the mistake this section exists to
prevent:
upstream_pipeline_runisnullfor the ordinary case — a manually-triggered or scheduled run, which is most runs. It means no orchestration pipeline triggered this run, not that one should have and didn't.kind_detailisnullfor an ordinary expectation or comparison check — the common case, where the result's own observed value already is the shape a reader wants, so there is nothing to lift out. For a freshness, volume, schema-drift, or anomaly check, by contrast, the card is only ever captured from a genuinely warned, failed, or critical occurrence — never an operational error or a skip — so anullthere for one of those four kinds means the same rare thing a missingchecklayer means: the check itself is gone.metric_trend,sibling_checks, andsame_asset_siblingsare[], notnull, when there is genuinely nothing to show. Anullin one of these three specifically means the layer could not be built at all (an unexpected failure while assembling it) — distinct from an empty result, which means it was built successfully and found nothing.downstream_blast_radiusbeing[]has three different real causes that read identically: the asset could not be resolved, the asset is a genuine lineage leaf, or the workspace has no lineage recorded at all. An empty list here is a floor, not proof that nothing is affected downstream.profile_diffis alwaysnull. This is a documented placeholder for a comparison DataQ does not yet compute (it would need a live datasource read of both the before and after batch, not just already-captured data) — not a failed attempt, and not specific to any one incident.
The provenance rule a narrative inherits¶
When a narrative is generated over this card, every claim it makes must cite which of a closed,
fixed set of layer names it rests on: failing_result, kind_detail, metric_trend,
sibling_checks, same_asset_siblings, upstream_pipeline_run, downstream_blast_radius, and
check_history — a longer per-check result history fetched alongside the card, not one of the
card's own fields, since the card itself keeps only the last 10 points. check, asset, and the
always-null profile_diff are identifying context, not evidence a hypothesis can be pinned to,
so they are deliberately outside this set. A hypothesis that cannot point to a real layer in it
is refused before it can ever reach a reader. The generation step also computes, deterministically and
without ever asking the model, what the snapshot could not see — a withheld cross-suite sibling
count, an unresolved upstream pipeline, no recorded downstream lineage — because a model cannot
be trusted to notice or disclose a gap it was never shown existed in the first place.