Skip to main content

Hierarchical test artifact framework — 10 layers, pluggable naming charts, full trace lineage

Project description

Voussoir

Hierarchical test-artifact framework — 10 layers, pluggable naming charts, full trace lineage.

Named for the wedge-shaped stones forming a self-supporting arch. Each voussoir is shaped by its neighbors; the arch holds only when all stones are fully placed — each a keystone.

Voussoir models test artifacts as a hierarchy whose structure carries the weight. The layer topology is the invariant; everything else — the vocabulary you read it in, the depth you nest it to — is surface. Tier 0 means import voussoir pulls in zero third-party packages; the optional extras add pytest, load tooling, OpenTelemetry, and the interactive TUI.

Getting Started

New here? You don't need to install anything to explore Voussoir — clone the repo and run the guided tour. It's Tier 0, so the manual runs from a bare checkout with zero third-party packages. (The demo/ tutorial is clone-and-run; pip install voussoir ships only the importable package.)

git clone https://github.com/acidblock/voussoir.git
cd voussoir
python demo/voussoir_man.py --tour   # 1. the guided tour (non-interactive)
python demo/voussoir_man.py          # 2. the interactive  voussoir-man>  REPL

The tour walks the 10 layers, re-renders them under every shipped chart (testing, time, space, observability, optics, voussoir) so you watch one arch wear different vocabularies, then shows the plugin map, a failing trace-lineage arch, and the keystone principles — top to bottom, nothing to type. The REPL is the same content on demand: type help, then try layers, conv space, conv voussoir, trace, all, q.

Then use it in your own codepip install voussoir (Tier 0, zero deps) and head to Quick start, or Migrating an existing pytest suite to adopt it incrementally.

Install

pip install voussoir            # Tier 0 — pure data model, zero deps
pip install "voussoir[tier1]"   # + pytest, locust, OpenTelemetry
pip install "voussoir[tui]"     # + the Textual explorer (voussoir-tui)
Extra Pulls in For
(none) nothing the pure data model, trace lineage, report generators
test pytest, pytest-xdist, allure-pytest, hypothesis running tests + the pytest plugin
harness locust, faker, factory-boy load / data generation
observe opentelemetry-{api,sdk,exporter-otlp} span export
tui textual the voussoir-tui explorer
sign cryptography Ed25519 signed heads (HMAC signing is dependency-free)
dev all of the above + ruff, mypy, pytest-cov development

Latest development tree: pip install "voussoir @ git+https://github.com/acidblock/voussoir.git" (or pip install -e /path/to/voussoir from a local checkout).

Quick start

from voussoir import (
    Environment, Scenario, Suite, Experiment, Harness, HarnessMode,
    Test, Condition, Validation, Fixture, Monitor, Status,
    TraceLineage, CONVENTIONS,
)

# Compose a hierarchy (leaf up)
val = Validation(name="status_200", expected=200, actual=200, status=Status.PASSED)
test = Test(name="health_check", validations=[val])
harness = Harness(name="smoke", mode=HarnessMode.SMOKE, tests=[test])
# ... Experiment -> Suite -> Scenario -> Environment

# Trace everything — the trace_id propagates down the chain
root = TraceLineage(layer="L0:Environment", name="staging")
child = root.child("L1:Scenario", "login_flow")
assert child.trace_id == root.trace_id

The 10 layers

A spine Environment → Scenario → Suite → Experiment → Harness → Test (L0–L4) with four branch layers attached to each Test: Condition (L4a, ambient), Validation (L4b, judgment), Fixture (L4c, material), Monitor (L4d, observer).

A naming chart relabels those 10 roles without changing the graph or the trace ids — that invariance is the point. The shipped charts are examples, not a fixed set (testing is the default):

Layer testing time space observability optics
L0 Environment Epoch Universe Pipeline Scene
L1 Scenario Era Galaxy Stream Aspect
L2 Suite Period System Probe Exposure
L3 Experiment Phase Orbit Signal Reflection
L3.5 Harness Cadence Trajectory Injector Mirror
L4 Test Moment Point Sample Ray
L4a Condition Window Field Pressure Medium
L4b Validation Deadline Fix Assertion Resolution
L4c Fixture Timestamp Landmark Payload Object
L4d Monitor Clock Sensor Collector Detector

Plus one deliberately meta chart — voussoir (self-referential: the framework testing its own layers) — and bring your own. None is privileged; the integrity lives in the topology, not the names.

Recursion — every keystone has layers

The spine is fixed-depth, but the leaf is not the end: a Test may carry sub_environments, a full nested campaign. Examined closely, the atomic stone is itself an arch — the hierarchy is scale-invariant.

nested = Environment(name="payments_subsystem", scenarios=[...])
leaf = Test(name="charge_card", validations=[ok], sub_environments=[nested])

Nesting is additive (defaults empty → existing trees are byte-identical) and bears load at every scale — nothing hides across the boundary: a failure deep in a sub-arch makes the outer replay() FAILED, describe() renders the nested arch, and verify_lineage(spans, single_trace=True) confirms the sub-arch is wired into one connected campaign. See docs/layers.md and ADR-001.

Trace lineage & the Layer-3 guardrail

Every artifact carries a TraceLineage: a trace_id (one per campaign, propagated unchanged to all descendants — OpenTelemetry traceId semantics), a per-node span_id, and a parent_span_id linking to its parent. A failure at any layer can be reported with the full chain that produced it.

The guardrail (voussoir.verify_lineage, Tier 0) checks a recorded span set and returns violations:

Kind Meaning
orphan-span a span whose parent_span_id was never recorded
trace-divergence a child whose trace_id differs from its parent's
duplicate-span the same span_id recorded more than once
disconnected-subtrace (under single_trace=True) a span not part of the campaign's one connected trace

single_trace=True is a per-campaign assertion — use it when a span set is meant to be one connected lineage (e.g. a nested arch). It is not a session-wide switch: a pytest session legitimately holds many traces (one per test), so applying it globally would mis-fire. See docs/trace-lineage.md for the full explanation.

In a pytest suite

Add the fixtures to your conftest.py:

from voussoir.conftest_base import *  # noqa: F401, F403

Every test then gets voussoir_trace, test_trace, and make_condition / make_validation / make_fixture / make_monitor / make_test. The package also registers a pytest11 plugin, so the guardrail's lineage_registry fixture is available with no wiring:

def test_login(lineage_registry, test_trace):
    lineage_registry.record(test_trace)
    ...

Run with --voussoir-strict-lineage (or voussoir_strict_lineage = true in pytest config) and a broken chain fails the session; without it, the same violations are an advisory summary.

Interactive manual — beyond the tour

Getting Started covers the guided tour and the REPL. Beyond those:

python demo/voussoir_man.py --markdown # emit the manual as markdown
python demo/voussoir_man.py --no-color # plain output (no ANSI)
voussoir-tui                           # the Textual explorer  (needs the [tui] extra)
bash demo/voussoir_canvas.sh           # open the manual in a claude-canvas document
python -m voussoir.readiness           # deployment-readiness matrix + coverage map

The manual's content is structured data in voussoir.manual (Tier 0) — both the CLI man-page and the TUI render the same source, and its sample-hierarchy builders (build_sample_lineage, build_self_test_lineage) are importable for your own demos and tests. voussoir.readiness is the companion catalog: every practical executor / source / sink integration point (CATALOG), what it costs to stand up (Readiness tiers), and what trace artifact it emits.

voussoir-tui is the one installed entry point (via the [tui] extra); the demo/ scripts run from a clone. The TUI re-renders the layer table and trace arch as you switch charts (sidebar or number keys), and maps Voussoir onto the 5-layer Claude Code extensibility stack (Memory → Skills → Hooks → Subagents → Plugins).

┌ Voussoir ─────────────────── convention: voussoir ─┐
│ Conventions │ ▏Layers▕  Trace   Plugins   Princ.   │
│  Testing    │  Layer  Role     Name                 │
│  Time       │  L0     root     Framework            │
│  Space      │  L1     scope    Capability           │
│  Observ.    │  L2     group    Module               │
│ ▶ Voussoir  │  L3     intent   TestFile             │
│             │  L4     leaf     Case                 │
└─────────────┴──────────────────────────────────────┘
 q Quit  1 Testing  2 Time  3 Space  4 Observ.  5 Voussoir

(Rendered screenshot: demo/voussoir_tui.svg.)

Examples

Runnable, Tier-0 scripts in examples/ — each exits 0 and is kept green by tests/test_examples.py:

Script Shows
build_hierarchy.py the full L0→L4 tree, describe() + replay()
charts.py one topology rendered under every shipped chart
lineage_guardrail.py verify_lineage() clean vs. a broken chain
recursion.py a nested sub-arch; failure surfaces at the outer root
cross_validation.py Voussoir as the neutral instrument validating external subjects under bring-your-own charts

Docs

Project layout & quality

voussoir/      the package (model, trace/, lineage, reporting, pytest_plugin, …)
tests/         the suite (~1,130 tests)
examples/      runnable Tier-0 showcases
docs/          layers / naming / trace-lineage / migration / adr
demo/          the interactive manual (CLI + Textual TUI + canvas wrapper)
plugin/        the Claude Code plugin (marketplace, /voussoir command, validator agent, voussoir-naming skill, MCP server)
scripts/       dev tooling — git hooks (pre-push main-guard) + installer

CI (GitHub Actions) gates main on three checks: the test suite (Python 3.11 / 3.12 / 3.13), ruff lint + format, and a Tier-0 import job that installs the package with no extras and proves import voussoir works.

License

Apache-2.0. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

voussoir-0.5.1.tar.gz (206.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

voussoir-0.5.1-py3-none-any.whl (74.6 kB view details)

Uploaded Python 3

File details

Details for the file voussoir-0.5.1.tar.gz.

File metadata

  • Download URL: voussoir-0.5.1.tar.gz
  • Upload date:
  • Size: 206.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for voussoir-0.5.1.tar.gz
Algorithm Hash digest
SHA256 cafbd4d5a1899fd09dd0e3321297d12acdc4283b2ecbe3520485d27ae9236a03
MD5 10b099a1c2053c63c93d2bf99ad26179
BLAKE2b-256 8d23bafc2608205982b2bf9074d0806db86b891f7e07146ca37e63fd49408091

See more details on using hashes here.

Provenance

The following attestation bundles were made for voussoir-0.5.1.tar.gz:

Publisher: publish.yml on acidblock/voussoir

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file voussoir-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: voussoir-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 74.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for voussoir-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 d6cbdcd59824795056d49ce2a665892fc35a4e195e0c8a04befcf76bf0cd2619
MD5 b1117e3b8298661a1fcca56b49810569
BLAKE2b-256 723b56955ca747f855984b57be32a499915236d7b7ef74c22a1c4cad7e73371b

See more details on using hashes here.

Provenance

The following attestation bundles were made for voussoir-0.5.1-py3-none-any.whl:

Publisher: publish.yml on acidblock/voussoir

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page