| title | Validation Pattern |
|---|---|
| anchor | validation |
| type | pattern |
| status | standard |
| version | 2.1 |
| normative | true |
| Attribute | Value |
|---|---|
| Status | STANDARD |
| Type | Pattern (cross-cutting, platform-agnostic) |
| Scope | Machine-readable validation results and the per-area trust map every product publishes |
| Extends | Master Design |
| Module home | Observability: validation results are operational evidence |
| Notation | Design Language |
| Wire schema | 2.1 (canonical, additive over 2.0); 1.0 registered as a legacy binding |
| Implementations | implementation/teradata/patterns/validation/ |
This pattern defines the validation result contract and the trust map an agent reads before using a product. Each module and pattern contributes conformance checks, its invariants, the temporal TLM-01..17 rules, the Semantic primary-object validations, which validators execute and publish as results in this contract. Results are append-only operational evidence in the Observability module (temporal profile EVENT_APPEND_ONLY).
An agent needs published validation evidence, resolved per area, so it can judge how far to trust the part of the product it is about to use. This pattern defines that evidence and how to read it.
- One results contract, many producers. A unit-test harness, a simple validator, or a full trust engine all publish the same record shape, distinguished by
producer_id. - Only a validator computes trust. Consumers are read-only: they act on published results and never re-derive a verdict from raw evidence.
- The map informs; it does not block. Trust is published per area, and no value in this contract withholds permission to use the product. A consumer proceeds and discloses: it states the confidence and the open gaps for every area it used. What a failure costs is bounded by the area it belongs to, so a defect in one module no longer withdraws a product an agent needed for another.
- Coverage is part of the answer. An area no check reached is unknown, never fine. The map records which checks existed, which ran, and what would raise confidence, so an agent can say how far it trusts an answer and a product owner can see what to build next.
- Validation results are operational evidence: append-only event records in Observability.
Provides:
| Capability | Made available to |
|---|---|
QualityScore |
Agents and reviewers, as the per-area trust map, the readiness scores, and the status vocabularies this pattern defines over validation evidence. |
Requires:
| Capability | Strength | Provider | Why |
|---|---|---|---|
RichMetadata |
[hard] |
self / platform |
Validation results are self-describing, so an agent can interpret a run without external narration. |
Two related records: one run record summarising the whole run, and one area record for each area the run covers. Both are append-only, and both bind their physical types per implementation.
The result entity is ValidationRun, bound to validation_run. The name is part of the contract, not a designer's choice: the standard conformance queries and the latest-run projection resolve it by name, so a product that names it something else does not fail loudly. The queries find no rows, count no failures, and report clean. A design brief that proposes a different name is corrected rather than accommodated.
One logical record per product per producer per run; consumers read the latest per (product, producer).
| Field | Meaning |
|---|---|
product_prefix |
Product identity the run evaluated |
producer_id, producer_version |
Identity and version of the producing validator/harness |
profile_id, profile_version |
Check profile evaluated: which checks it defines, and their scopes (nullable for simple harnesses) |
source_format |
Provenance: NATIVE, or the interchange format it was ingested from |
payload_schema_version |
Wire schema version of this record |
run_id |
Deterministic run identifier |
started_dts, completed_dts |
Run instants (typed timestamps, persisted UTC) |
trust_status |
Advisory product-level summary of the map: TRUSTED | DEGRADED | UNTRUSTED |
agent_use_allowed |
Deprecated at schema 2.1: retained for wire compatibility, no longer a decision (§4.4) |
total_checks, passed_count, failed_count, error_count |
Check totals by status |
critical_failure_count, error_failure_count |
Counts by severity among failed/errored checks |
data_product_trust_score |
Conformance score, 0-100 or null |
performance_readiness_score, operational_readiness_score |
Other score dimensions, 0-100 or null |
repair_candidate_count |
True (uncapped) number of repair candidates |
failed_checks_json |
Machine-readable failure detail, capped |
repair_candidates_json |
Machine-readable repair proposals, capped |
evidence_expires_dts |
Producer-declared expiry of this evidence (nullable) |
A simple test harness populates the identity, status, and count fields and leaves scores, JSON blobs, and profile fields null: a fully conformant result. Runs are appended, never overwritten.
The trust-map entity is ValidationArea, bound to validation_area. One logical record per run per area; consumers read the latest per (product, producer, area). Its name is part of the contract for the same reason validation_run is.
| Field | Meaning |
|---|---|
product_prefix, producer_id, run_id |
Parentage: the run this entry belongs to |
scope_kind, scope_id |
The area this entry describes (§4.1) |
checks_expected |
How many checks the profile defines for this area; 0 means none is defined |
checks_ran |
How many of them executed in this run |
passed_count, failed_count, error_count |
Check outcomes within the area, by status |
critical_failure_count, error_failure_count |
Counts by severity among the area's failed/errored checks |
area_status |
pass | fail | partial | not-validated | no-evidence (§4.3) |
confidence |
strong | partial | weak | unknown (§4.3) |
open_gaps |
What is uncovered or unproven here; required unless confidence is strong |
recommended_action |
What would raise confidence in this area; required unless confidence is strong |
completed_dts |
Inherited from the run, so the latest-per-area projection is deterministic |
Coverage is checks_ran / checks_expected. It is derived on read, not stored, so there is no second copy of it to drift from the counts.
A run publishes an entry for every area its profile covers, including the areas it could not check. An area left out of the map is invisible, and an invisible area reads as an area with nothing wrong: the opposite of what this pattern is for.
A producer that publishes no area records is at wire schema 2.0. Consumers read its run record as a single PRODUCT-scope entry (§10), so the map has one shape everywhere.
An area is the unit the map resolves trust to. Its key is (scope_kind, scope_id):
scope_kind |
scope_id is |
Example |
|---|---|---|
MODULE |
The module anchor | domain |
ENTITY |
The catalogued entity, qualified by its module anchor | domain.Ticket |
PATTERN |
The pattern anchor | temporal-lifecycle-metadata |
CAPABILITY |
The capability name from the catalogue | NearestNeighbors |
PRODUCT |
The product prefix: whole-product entries that belong to no narrower area | CALLCENTRE |
Identities come from the corpus and the product's own Semantic catalogue, never from a convention applied to an object name. Areas may overlap by design: an ENTITY entry says something narrower than the MODULE entry above it, and a consumer reading both takes the narrowest entry that covers what it is about to query.
Coverage separates proven sound from never looked at, which a pass rate alone cannot express. checks_expected is what the producer's profile defines for the area; checks_ran is what executed. An area whose only check is unwritten reports checks_expected = 0, which is no-evidence: an honest statement that nothing is known, and a coverage gap for the product owner to close.
area_status — what happened to this area in this run:
| Condition | area_status |
|---|---|
checks_expected = 0 |
no-evidence |
Checks defined, checks_ran = 0 |
not-validated |
Checks ran, no failures, checks_ran = checks_expected |
pass |
Checks ran, no failures, checks_ran < checks_expected |
partial |
| Any failed or errored check in the area | fail |
confidence — how far the entry supports use of the area, severity-weighted and coverage-aware:
| Condition | confidence |
|---|---|
Nothing ran (no-evidence or not-validated) |
unknown |
| Any CRITICAL or ERROR-severity failure in the area, or coverage below half | weak |
| Failures only at WARNING/INFO severity, or full pass on partial coverage | partial |
| Every defined check ran and passed | strong |
Rules apply in order, first match wins. The vocabulary is deliberately the reviewer's (roles/review.md): a review that becomes a deployed product's first published map should not have to be retyped in a different language.
An area at weak or unknown is not an area an agent may not use. It is an area an agent must be honest about when it uses it (§9).
trust_status has exactly three values: TRUSTED, DEGRADED, UNTRUSTED. It is an advisory summary of the map, useful for a dashboard or a first glance, and it is not permission and not a decision. A consumer that needs to know whether to rely on something reads the entries for the areas it is about to use.
Default summary profile (rules in order):
- Any execution error (
error_count > 0), any CRITICAL-severity failure, or any ERROR-severity failure →UNTRUSTED. No score can rescue this rule. - Else
data_product_trust_score < 70→UNTRUSTED. - Else any failed check, or
data_product_trust_score < 90→DEGRADED. - Else →
TRUSTED.
Producers that compute no scores skip the score clauses. Implementation profiles may tighten the summary toward caution but never loosen it.
agent_use_allowed is deprecated at schema 2.1. The field remains so a 2.0 reader still parses a 2.1 record, and a 2.1 producer publishes go in it. It carries no authority at any schema version, and a consumer must not branch on it: an area that needs disclosure gets disclosure, and nothing in this contract withholds use. Consumers reading 1.0 or 2.0 records ignore the field for the same reason.
Two independent axes:
- Status: what happened when the check ran:
PASSED|FAILED|ERROR(could not execute). - Severity: how much a failure matters:
INFO|WARNING|ERROR|CRITICAL.
| Field | Counts |
|---|---|
error_count |
Checks with status ERROR |
critical_failure_count |
Failed/errored checks with severity CRITICAL |
error_failure_count |
Failed/errored checks with severity ERROR |
failed_count |
Checks with status FAILED, any severity |
Both axes are counted twice over: once for the whole run on the run record, and once per area on that area's entry. WARNING/INFO failures feed failed_count but not the severity counts: they can hold an area at partial confidence, never drive it to weak. Producers whose native format carries no severity default failed checks to ERROR. The counts are authoritative; the JSON blobs are capped and must never be counted by consumers.
Scores are optional: null means not assessed, never perfect. Where computed, each score is a severity-weighted pass rate over its check family: round(earned / total × 100) with weights CRITICAL = 40, ERROR = 25, WARNING = 10, INFO = 5; earned sums the weights of passed checks.
| Score | Check categories |
|---|---|
data_product_trust_score |
STRUCTURAL, SEMANTIC, QUERY, CAPABILITY, DATA_QUALITY, FREE_TEXT |
performance_readiness_score |
PERFORMANCE |
operational_readiness_score |
OPERATIONAL |
Only data_product_trust_score participates in the default summary profile's thresholds. The three scores are reported separately and must not be blended. A score is a run-level figure: it summarises, it does not locate, which is what the map is for.
failed_checks_json is an array of failed/errored check records, capped at 20 items; each item's sample_rows is capped at 3 rows. Item shape:
{
"test_id": "CALLCENTRE-SEM-004",
"name": "Curated column metadata covers deployed columns",
"category": "SEMANTIC",
"severity": "CRITICAL",
"status": "FAILED",
"scope_kind": "MODULE",
"scope_id": "semantic",
"row_count": 39,
"sample_rows": [
{
"entity_name": "Agent",
"column_name": "agent_status",
"issue_code": "MISSING_COLUMN_METADATA",
"repair_hint": "Register the column in the Semantic column metadata with a business description."
}
],
"error_message": null,
"repair_strategy": "Backfill column metadata for every deployed column of the entity."
}Rules: the check-level identifier is test_id (issue_code exists only inside sample_rows); scope_kind / scope_id name the area the check belongs to (§4.1, §12), so a consumer reading a failure knows which map entry it landed on; every sample_rows element carries issue_code, repair_hint, and the object-identifying keys; row_count is the true total, sample_rows the first ≤ 3: consumers render the remainder as + (row_count − shown) more, never by counting the blob; error_message is non-null only for status ERROR; every issue code is catalogued in the producer's documentation; the blob is optional for count-only producers.
repair_candidates_json is an array of repair proposals, capped at 20 items; the true total is repair_candidate_count. Item shape:
{
"candidate_id": "CALLCENTRE-STRUCT-001-COLUMN-TYPE-DRIFT",
"issue_code": "COLUMN_TYPE_DRIFT",
"summary": "Align datatype, length, precision and scale for same/similar columns.",
"mode": "proposal",
"requires_approval": true,
"sql": "-- review and align column datatypes"
}mode ∈ detect | proposal | safe-auto. requires_approval = true candidates must never be executed autonomously: a candidate is a proposal, not an instruction; a consumer executing repair does so under its own change-management controls. Optional when no repairs are proposed.
Read, select, proceed, disclose. A consumer works through the map in four steps:
- Read the map before analytical use, discovering its location and the authoritative producer through product orientation.
- Select the entries covering the areas the work will touch: the modules, entities, patterns, and capabilities the query plan reaches, taking the narrowest entry that covers each. Areas the work does not touch place no constraint on it.
- Proceed. Nothing in this contract withholds use, at any confidence.
- Disclose, proportionally. State the confidence for every area used. An area at
weak, or any CRITICAL/ERROR failure in an area used, is surfaced with its consequence and itsrecommended_action. An area atunknownis reported as unknown: never as sound, never silently. An answer drawn only fromstrongareas says that too, because the consumer has earned the right to say it.
Trust authority. Multiple producers may publish for one product. Each product designates exactly one trust-authoritative producer in its orientation metadata; that producer's latest entries are the map. Other producers' results are evidence: surfaced, especially where they disagree, but not map-defining. Absent a designation, consumers take the most cautious entry per area across producers and say that they did so, because a product with two maps and no designation has not told anyone which one it means.
The designation is made at design time. Everything above is written from the consumer's side, and a consumer can only read a designation that already exists. VAL-13 is checked at runtime; the fact it checks has to be established while the product is being designed, because by deploy time the manifest is already written. So it is a designer's obligation, stated here rather than left to be inferred from the consumer rule:
Name the producer whose trust map is the product's authoritative one. Record it as a design decision, and carry it into the orientation manifest as
trust_authoritative_producer(the Semantic module owns the field). Where the product has exactly one producer it is authoritative by definition, and must still be named: an implicit designation is not readable.
Further consumer rules. Never re-derive a status or a confidence, and never recount capped blobs: only a validator computes trust. Treat unknown JSON keys as additive extension (ignore, don't fail). Apply the staleness rules (§11).
Consumer-side policy is out of contract. A consumer may hold its own rule about which confidence it will act on unsupervised, and a high-consequence autonomous action is a reasonable place to hold one. That rule belongs to the consumer and its operator, and it is theirs to state and log. This pattern's job is to make the confidence legible and located, so a policy has something honest to act on; it is not to decide, on a product owner's behalf, what an agent may not read.
Every record carries payload_schema_version; the canonical version is 2.1. Incompatible changes bump the major version; additive optional fields are compatible within a major version.
Wire schema 2.1 adds the area record, the scope_kind / scope_id keys on failed-check items, and the deprecation of agent_use_allowed. It is additive: a 2.0 reader parses a 2.1 run record unchanged.
Reading a 2.0 or 1.0 producer. A producer that publishes no area records still has a readable map: consumers project its run record as one PRODUCT-scope entry, with checks_expected and checks_ran both taken from total_checks and the §4.3 rules applied to the run counts. A derived entry is capped at partial confidence, because a run-level pass says nothing about which areas it covered, and it is marked as derived rather than published so a consumer can tell the difference. The map then covers one area, the whole product, which is exactly as much as such a producer knows.
Selecting records by version. payload_schema_version is a version string, not an ordered number, so a reader must never select records by comparing it lexically. '10.0' sorts below '2.1', so a reader written as "at least 2.1" silently stops covering the schema it was written for the moment a two-digit major version exists. Registered legacy versions are enumerated explicitly, and every other version is canonical-or-later by exclusion: a new major version is then covered by every check the day it appears, and registering a new legacy binding is a single edit per reader. This binds consumers and conformance checks alike.
Wire schema 1.0 remains the registered legacy binding (the same status/count/score/JSON fields without the producer-identity, source_format, payload_schema_version, or evidence_expires_dts fields); consumers treat a 1.0 record as an implied single producer. Producer and consumer are held together by a shared golden fixture: both build gates fail on drift.
Age and absence are coverage facts: they change what the map claims, not whether the product may be read.
- Evidence window. A producer may declare per-record expiry; a product may declare a maximum evidence age in orientation. Absent both, the default window is 7 days from
completed_dts. - Stale evidence (past expiry / older than window): every entry from that run reads at
confidence=unknown, whatever it recorded, because a passed check proves the state of a product as it was. Consumers surface the staleness and its date, andrecommended_actionis to re-run the validator. - No evidence: the area is unvalidated rather than sound. An area with no published entry reads as
no-evidence/unknown, and a product with no published run has a map of one such entry. - Incomplete evidence (
total_checks = 0or unparseable): treat as no evidence.
Staleness can only downgrade confidence, never raise it. It does not block: an agent may answer from a stale product, and says that it did.
test_idscheme:{PRODUCT-PREFIX}-{FAMILY}-{NNN}(e.g.CALLCENTRE-SEM-008); parameterised checks may extend the suffix. Stable across runs. Ingested results map their native identity into this scheme deterministically.- Categories (drive score families):
STRUCTURAL,SEMANTIC,QUERY,CAPABILITY,PERFORMANCE,OPERATIONAL,DATA_QUALITY,FREE_TEXT. - Scope (drives the map): every check belongs to exactly one area. By ownership, a check's scope is the module or pattern that owns it — the checks shipped under a module's own directory are that module's area, and a pattern's conformance queries are that pattern's — so an existing check suite acquires its scope without being rewritten. A check that resolves a single entity or a single capability declares the narrower scope instead, and a check about the product as a whole declares
PRODUCT. A category is what a check tests; a scope is what it tests about, and the two are independent: oneSTRUCTURALcheck can belong to Domain and the next to Search. - Validators prove the product's self-describing metadata (semantic catalogue, orientation manifest, relationships, cookbook) against what is physically deployed. The temporal pattern's
TLM-01..17rules (blocking → CRITICAL/ERROR) and the Semantic module's primary-object validations lift directly into validator profiles: as do each module's ownINV-*invariant checks. Each lifts with the scope of the document that states it.
The result is mappable from/to established open formats; source_format records provenance. Ingest mappings are implemented by validation tooling, not by this pattern.
| Standard | Layer | Mapping |
|---|---|---|
| JUnit XML | Test results | testsuite/testcase totals → status counts; failures default to severity ERROR. source_format = 'JUNIT-XML' |
| CTRF | Test results | summary + tests[] → counts and optional detail. source_format = 'CTRF' |
| Open Test Reporting | Test results | Ingest as consumer adoption matures. source_format = 'OTR' |
| SARIF 2.1.0 | Analysis results | ruleId ↔ test_id, level ↔ severity, fixes[] ↔ repair candidates. source_format = 'SARIF' |
| OpenLineage quality facet | Emission | Runs may additionally emit per-assertion facets on lineage run events |
| ODCS / ODPS | Check definitions | Contract-side quality rules define checks; results land here, linked through test_id |
| Rule | Check |
|---|---|
| VAL-01 | trust_status is exactly one of the three vocabulary values. |
| VAL-02 | agent_use_allowed is published as go on every 2.1 record, and no consumer branches on it at any version. |
| VAL-03 | The default summary and confidence profiles are tightened toward caution or not at all, never loosened. |
| VAL-04 | total_checks = passed_count + failed_count + error_count. |
| VAL-05 | Severity counts are consistent with the severity model, on the run record and on every area entry. |
| VAL-06 | Scores are 0-100 integers or null; null only when not assessed. |
| VAL-07 | JSON blobs respect their caps; true totals live in row_count / repair_candidate_count. |
| VAL-08 | Every sample_rows element carries issue_code and repair_hint; every issue code is catalogued. |
| VAL-09 | Runs are appended; the latest-per-(product, producer) and latest-per-area projections are deterministic (completed_dts, then run_id). |
| VAL-10 | Consumers apply staleness as a confidence downgrade, and disclose every area they used at weak or unknown. |
| VAL-11 | Producer and consumer build gates verify the shared golden fixture at the declared schema version. |
| VAL-12 | Every record carries non-null producer_id and payload_schema_version. |
| VAL-13 | The map is taken from the designated producer; absent designation, the most cautious entry per area applies and the consumer says so. |
| VAL-14 | scope_kind, area_status, and confidence come from their vocabularies, and scope_id resolves to a real module, entity, pattern, capability, or the product. Runtime-checkable for PRODUCT, MODULE, and ENTITY against deployed catalogue metadata; PATTERN and CAPABILITY have no deployed catalogue to resolve against, so the producer's build-time assertion against its own validator profile is the enforcement point. |
| VAL-15 | Per area: checks_ran = passed_count + failed_count + error_count, and checks_ran never exceeds checks_expected. |
| VAL-16 | pass and strong require checks that ran at full coverage; no-evidence and not-validated carry confidence = unknown. |
| VAL-17 | Every entry below strong carries open_gaps and recommended_action. |
| VAL-18 | Every area a run's profile covers has an entry, including uncovered ones; every failed check's scope resolves to an entry in the same run. |
- Observability module: the module home for the run record and the trust map, alongside its other run/event evidence.
- Temporal & lifecycle metadata pattern: both results relations declare profile
EVENT_APPEND_ONLY;TLMblocking rules are canonical CRITICAL/ERROR checks, scoped to that pattern's area. - Semantic module: its primary-object validations are canonical STRUCTURAL/SEMANTIC checks; product orientation declares the results location and the trust-authoritative producer, so the map is read before analytical resource use. Its catalogue is also where an
ENTITYscope resolves. roles/review.md: a reviewer builds this same map by hand, in this vocabulary, before a validator exists to publish it. The two are the same artefact at different stages of a product's life.- Implementation: the Teradata binding (results table, DBC/data checks, wire-schema bindings) lives in
implementation/teradata/patterns/validation/.
End of Validation Pattern