Data Models
The engine's data models are Pydantic v2 classes under src/tractusx_testlab/models/,
organized into four sub-packages:
| Sub-package | Contains |
|---|---|
authoring/ |
the shapes of the YAML documents an author writes (tests, TCK manifests, steps, variables, services) |
runtime/ |
what execution produces (results, events, jobs, inspection metadata) |
primitives/ |
enums and exceptions shared by everything else |
domain/ |
feature-specific domain models: package security and server state |
All public models are re-exported from the package root, so
from tractusx_testlab.models import StepDefinition always works regardless of the
internal file layout.
The YAML document structure
The engine compiles two document kinds, discriminated by an explicit kind: field
(the Kubernetes convention) and pinned to the single syntax version v1-alpha.
Tests (kind: test)
A test is the executable authoring unit. Its steps are grouped into three phases —
setup:, execution:, teardown: — and every step uses the verb-form keys
uses: / with: / returns::
kind: test
syntax: v1-alpha
id: catalog-smoke
namespace: my-tck
metadata:
name: "Catalog smoke test"
version: "1.0"
execution:
- id: query
uses: connector/consumer/query_catalog
name: Query the SUT catalog
with:
counter_party_address: ${{ env.sut_dsp_url }}
counter_party_id: ${{ env.sut_bpn }}
returns:
datasets:
type: array
validate:
- uses: validate/assert
name: the SUT published at least one offer # optional
with: { input: datasets, operator: not_empty }
This maps onto TestDefinition and StepDefinition
(models/authoring/definitions.py):
class StepDefinition(BaseModel):
"""Step definition using ``uses`` and ``with`` verb-form keys."""
id: Optional[str] = Field(default=None, pattern=r"^[a-z][a-z0-9_]{0,49}$")
uses: str
name: Optional[str] = None
with_: Optional[dict[str, Any]] = Field(default=None, alias="with")
returns: Optional[dict[str, ReturnFieldDefinition]] = None
validate: Optional[list[Assertion]] = None
timeout_s: Optional[float] = None
if_condition: Optional[str] = Field(default=None, alias="if")
usesis the canonical step id (<category>/<module>/<function>), the key the step registry resolves to a Python executor class.withcarries the parameters, validated into the executor's declaredparams_modelbefore it runs.returnsdeclares the output fields the test reads; each entry is aReturnFieldDefinition(type, optionalclass). Assertions resolve against these declared returns, and later steps reference them as${{ steps.<id>.<field> }}.validateis a list ofAssertionentries, themselves in verb form (uses: validate/assert,with: {input, operator, expected}), each with an optionalname. Nothing in the engine reads the name; the run report calls the check by it, so a step carrying fourvalidate/assertentries says which requirement each one covers instead of listing the same id four times.
When any of this does not hold up, the author is told so by
syntax/diagnostics.py rather than by Pydantic: the finding names the step by
its id, the line the key sits on, and — for a rejected key — the keys that
would have been accepted, with a near-miss called out as the likely typo. The
same renderer serves the compiler, the player, the server's compile endpoint and a
step binding its with: block at runtime, so one wrong key reads the same
wherever it is caught.
TCK manifests (kind: tck)
A TCK groups tests into a certification package. TckDefinition carries
certification metadata (authors, standards, license, dataspace_version),
an env: block, and the ordered tests: list:
class TckDefinition(BaseModel):
kind: Literal["tck"] = "tck"
syntax: Literal["v1-alpha"]
id: str
metadata: TckMetadataDefinition
env: Optional[EnvDefinition] = None
tests: list[TckTestEntry] = Field(default_factory=list)
Each TckTestEntry names a test file relative to the package's tests/ folder,
with an optional human-readable name and a skippable flag the operator can act
on at runtime.
EnvDefinition is the shared environment: variables, services, schemas
(each a SchemaDefinition with id + source), and testdata entries.
Variables
Variables are declared in the TCK's env.variables block, in the same verb-form
syntax as steps:
env:
variables:
- id: provider_bpn
description: BPN-L of the SUT certificate provider.
uses: variable/type/string
with:
source: input
scope: sut # SUT operator provides this value
returns:
value:
type: string
- id: certificate_type
description: Certificate type (default iso9001).
uses: variable/type/string
with:
source: value
value: iso9001 # static default — scope not required
returns:
value:
type: string
The source is a VariableSource: value (static default), input
(operator-supplied at runtime), or generated (produced by a named generator such
as uuid). For source: input the scope (VariableScope) is required and
names the participant responsible for the value — engine or sut — enforced by
the compiler per
ADR-0023.
Tests reference variables as ${{ env.<id> }}, step outputs as
${{ steps.<id>.<field> }}, and deployment facts as ${{ infrastructure.* }}.
Services
ServiceDefinition declares an external service a run talks to:
class ServiceDefinition(BaseModel):
name: str
type: ServiceType
base_url: str
auth: dict = Field(default_factory=dict)
params: Optional[dict] = None
Steps never name a service in their with: block — connector services are seeded
into the run context at runtime, and the executor picks the right one through the
StepContext accessors (get_consumer_service(), get_provider_service(), …).
Enums (models/primitives/enums.py)
The primitives every other model shares. The most load-bearing ones:
| Enum | Values | Used for |
|---|---|---|
StepPhase |
SETUP, EXECUTION, TEARDOWN |
which phase a step belongs to |
StepStatus |
PENDING, RUNNING, WAITING, PASSED, FAILED, SKIPPED |
per-step execution status |
TestStatus |
IDLE, RUNNING, COMPLETED, FAILED, CANCELLED, SKIPPED |
per-test and per-TCK status |
JobStatus |
QUEUED, RUNNING, WAITING, PAUSED, COMPLETED, FAILED, CANCELLED, TIMED_OUT |
overall job lifecycle |
AssertionSeverity |
HARD, SOFT |
whether a failed assertion aborts or warns |
VariableSource |
value, input, generated |
how a declared variable obtains its value |
VariableScope |
engine, sut |
who provides a source: input variable |
DefinitionKind |
test, tck |
the document kind: discriminator |
EventKind |
job_started, step_completed, … |
discriminator on every execution event |
Runtime results (models/runtime/results.py)
What a run produces, nested top-down:
TckResult
└── tests: list[TestResult]
├── execution: list[StepResult]
│ ├── request / response: HttpRequest / HttpResponse
│ ├── exchanges: list[HttpExchange]
│ └── assertions: list[AssertionResult]
├── assertion_summary: AssertionSummary
└── callback_results: list[CallbackResult]
StepResult is the workhorse: step_name, step_type, phase (StepPhase),
status (StepStatus), timing (started_at / finished_at / duration_s), the
captured request / response, the serialised output, an optional
error / error_traceback, and the evaluated assertions. exchanges holds
every call the step made - both the engine's own httpx calls and the ones
tractusx-sdk made on its behalf, each naming in context the method that sent
it - while request / response name the one the test is about
(ADR-0016). CallbackResult
records a callback received (or timed out) on a mock listener.
Execution events (models/runtime/events.py)
Frozen event models the execution monitor publishes while a job runs —
JobStartedEvent, TestStartedEvent, StepCompletedEvent,
AssertionResultEvent, and so on — each carrying its EventKind so consumers
(the CLI, the server's SSE stream and its clients) can dispatch on kind directly. The SSE wire
name is derived from the kind by turning its underscore into a dot
(step_completed → step.completed). See
Execution Events for the full catalogue.
Jobs (models/runtime/jobs.py)
Job tracks one submitted execution (job_id, status, timing, the current
test and step), with JobMemory as its mutable key-value store and JobEvent
entries as its event log.
Inspection models (models/runtime/inspection.py)
Frozen Pydantic v2 models returned by Tck.inspect(). They contain static metadata
extracted from a compiled TCK without executing any steps.
StepMeta
Metadata for a single step.
class StepMeta(BaseModel):
model_config = ConfigDict(frozen=True)
step_name: str # step.name if set, otherwise falls back to step.uses
uses: str # step identifier, e.g. "connector/consumer/query_catalog"
phase: StepPhase # SETUP | EXECUTION | TEARDOWN
validation_count: int # number of validate: entries on this step
TestInspection
Metadata for one test within a TCK.
class TestInspection(BaseModel):
model_config = ConfigDict(frozen=True)
name: str
steps: tuple[StepMeta, ...]
TckInspectionResult
Top-level result returned by Tck.inspect().
class TckInspectionResult(BaseModel):
model_config = ConfigDict(frozen=True)
name: str
total_steps: int
total_validations: int
tests: tuple[TestInspection, ...]
Usage
from tractusx_testlab.authoring import Loader
loader = Loader()
tck = loader.load("my-test.tck")
result = tck.inspect()
print(result.name) # "Certificate Management Conformity"
print(result.total_steps) # 12
print(result.total_validations) # 8
for test in result.tests:
for step in test.steps:
print(step.uses, step.phase.value) # "connector/consumer/query_catalog" "EXECUTION"
See ADR-0022: TCK Static Inspection for the full architectural rationale.
How data flows between steps
A step's declared contract is the only channel data moves through:
- Declared returns. Assertions and
${{ steps.<id>.<field> }}references read the fields the step'sreturns:block declares, which the executor'soutput_modelpromises. See Step Contracts. - Published outputs. Every step publishes all of its return outputs: each
top-level output field becomes a context variable of the same name —
negotiatereturnsnegotiation_id,do_dspreturns thedataplane_url/edr_tokenpair, and downstream steps read exactly those. - Explicit capture. When a test needs a value under a name of its own
choosing, the util steps (
util/json_path_extract,util/base64,util/parse_kv) accept astore_in_variableparameter naming the context variable to write.