Versioned evidence artifacts
The structured artifact is an opt-in JSONL stream produced from the same normalized evidence as the text renderer:
pytest --receptor=llm --receptor-events=.pytest-receptor/events.jsonl
The destination directory must already exist. The option requires the llm or
ci profile because human is a true passthrough and installs no collector.
No upload, network access, or automatic retention is performed.
The stream is bounded to 50 MiB by default. Set a different explicit ceiling (minimum 64 KiB) when required:
pytest --receptor=ci --receptor-events=events.jsonl \
--receptor-events-max-bytes=104857600
When the next event would consume the space reserved for finalization, the
writer emits evidence_limit, stops storing further event bodies, and still
writes session_finish. Its artifact_policy records the byte ceiling,
whether truncation occurred, and the exact number of dropped records. Thus a
bounded artifact remains integrity-valid but never silently claims full
evidence retention.
Completion and integrity
Every line is a UTF-8 JSON object with schema set to
pytest-receptor.events@1 and a type discriminator. A normal stream contains:
one
session_startrecord;phaseandwarningrecords as pytest emits them;one final
session_finishrecord.
The final record carries pytest’s exit status, normalized outcome, explicit
counts and completeness, plus a SHA-256 digest and count covering every earlier
record exactly as written. Each root_cause event gives a normalized cause a
stable sha256: fingerprint and lists its logical occurrences without growing
the finalization record.
A failed test run can still have a finalized artifact. Conversely, a file
without session_finish is an incomplete event stream even if its last
recorded test passed.
Evidence is streamed, so an interrupted process normally leaves useful partial
records. SIGKILL, host loss, storage failure, and buffered filesystem loss can
still prevent capture; the artifact does not claim otherwise.
Reader API
from pytest_receptor import read_artifact
artifact = read_artifact(".pytest-receptor/events.jsonl")
if not artifact.complete:
print(f"partial stream: {artifact.issue}")
else:
print(artifact.final.data["outcome"])
artifact.complete means that the stream has a validated final record. The
test run’s execution completeness is the separate
artifact.final.data["complete"] field.
The reader:
validates the SHA-256 digest of finalized streams;
rejects unsupported schema major versions;
preserves unknown record types as
ArtifactRecord(known=False)so a newer producer’s data is not silently discarded;classifies a missing or truncated final record as incomplete;
raises
ArtifactFormatErrorfor malformed interior records andArtifactIntegrityErrorfor altered finalized evidence.
Minor evolution within events@1 may add fields and record types. Consumers
must ignore unknown fields and retain unknown records when rewriting an
artifact. An incompatible change requires a new schema major.
Extension producer API
The receptor owns namespaced extension records in the same JSONL stream as pytest evidence. A producer can opt in without importing another diagnostic library:
from pytest_receptor.extensions import current_context, emit
def test_timed_operation(pytestconfig):
context = current_context() # active node, phase, worker, and attempt
reference = emit(
pytestconfig,
"org.example.timer@1",
{"operation": "example", "elapsed_ms": 3.2},
)
emit returns an opaque producer reference when accepted, or None when the
receptor is inactive or the event is rejected. It never changes the pytest
outcome. A namespace must include a major version. The optional
relationships argument accepts a list or tuple of producer references; it
does not deduplicate events or reinterpret pytest outcomes. current_context
returns an immutable phase snapshot inside setup, call, or teardown and None
outside those phases. Background threads and subprocesses need explicit
context propagation; the service never assigns the last test’s node ID to a
session event.
If a producer catches its own recording failure, it can call
mark_incomplete(config) so the final artifact declares the evidence gap
without changing pytest’s result.
The service is active only with --receptor=llm or --receptor=ci and
--receptor-events=PATH. The human profile installs no collector. Events are
bounded to 16 KiB each, 128 per phase or session, and 10,000 per process.
Payloads must be JSON-compatible objects with string keys, at most six nested
levels, 128 entries per container, and strings no longer than 2,048
characters. Invalid or excess events are counted as dropped, with
session_finish.extensions.incomplete=true; they do not fail the test.
The receptor strips control characters and applies its conservative secret
pattern redaction before worker transport and artifact persistence. Each
extension record declares trust=producer_untrusted and
redaction=pattern. Producers must exclude sensitive values that do not match
those patterns. The record carries nodeid, phase, worker_id, attempt,
producer_ref, relationships, and an emitted_during reference to the
receptor’s phase record where applicable. Xdist workers transport phase events
through the corresponding pytest report; worker session events arrive through
worker shutdown metadata. The controller assigns final event IDs and writes
the sole canonical artifact. If a worker is lost, extension evidence is marked
incomplete while already received records remain in the artifact.
Terminal message and captured-section budgets do not mutate the normalized
events. A compact omission marker states original and retained character counts
plus a SHA-256 prefix; .pytest_cache/d/receptor/last-run.txt retains the full
text projection, and JSONL retains the structured source event when enabled.
Security boundary
The file is created owner-only (0600) and symlinks are refused. Text fields
pass through the receptor’s control-character stripping and conservative
credential-pattern redaction before persistence. This catches common
token=..., password, secret, credential, Basic-auth, and bearer-token shapes;
it is not a guarantee that arbitrary sensitive test data cannot appear.
Choose the destination and retention period accordingly. The producer applies the configured hard size limit but deliberately does not delete artifacts; retention remains the responsibility of the local or CI system that owns the destination. Configurable project-specific redaction rules and external blobs may be added compatibly in a future 1.x release.