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:

  1. one session_start record;

  2. phase and warning records as pytest emits them;

  3. one final session_finish record.

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 ArtifactFormatError for malformed interior records and ArtifactIntegrityError for 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.

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.