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 code — pip 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
VOUSSOIR_INTERFACE.md— the full interface reference: Python API, CLI, pytest plugins, MCP tools, wire formatsdocs/layers.md— each layer's role + recursiondocs/naming-conventions.md— charts (the examples) + bring-your-owndocs/trace-lineage.md— propagation, the guardrail, andsingle_tracedocs/migration.md— adopting Voussoir in an existing pytest suitedocs/cross-validation.md— Voussoir as a neutral instrument validating external subjectsdocs/constraints.md— the standing-constraint registry (enforced invariants)docs/schema-versioning.md— what bumps the interchange schemas' version (and what doesn't)docs/adr/— architecture decisions (ADR-001: recursion)
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cafbd4d5a1899fd09dd0e3321297d12acdc4283b2ecbe3520485d27ae9236a03
|
|
| MD5 |
10b099a1c2053c63c93d2bf99ad26179
|
|
| BLAKE2b-256 |
8d23bafc2608205982b2bf9074d0806db86b891f7e07146ca37e63fd49408091
|
Provenance
The following attestation bundles were made for voussoir-0.5.1.tar.gz:
Publisher:
publish.yml on acidblock/voussoir
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voussoir-0.5.1.tar.gz -
Subject digest:
cafbd4d5a1899fd09dd0e3321297d12acdc4283b2ecbe3520485d27ae9236a03 - Sigstore transparency entry: 2257054333
- Sigstore integration time:
-
Permalink:
acidblock/voussoir@6f0f3ae0c34671a99e3d0e1cefbf5e7cc2cfce13 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/acidblock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6f0f3ae0c34671a99e3d0e1cefbf5e7cc2cfce13 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6cbdcd59824795056d49ce2a665892fc35a4e195e0c8a04befcf76bf0cd2619
|
|
| MD5 |
b1117e3b8298661a1fcca56b49810569
|
|
| BLAKE2b-256 |
723b56955ca747f855984b57be32a499915236d7b7ef74c22a1c4cad7e73371b
|
Provenance
The following attestation bundles were made for voussoir-0.5.1-py3-none-any.whl:
Publisher:
publish.yml on acidblock/voussoir
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voussoir-0.5.1-py3-none-any.whl -
Subject digest:
d6cbdcd59824795056d49ce2a665892fc35a4e195e0c8a04befcf76bf0cd2619 - Sigstore transparency entry: 2257054343
- Sigstore integration time:
-
Permalink:
acidblock/voussoir@6f0f3ae0c34671a99e3d0e1cefbf5e7cc2cfce13 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/acidblock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@6f0f3ae0c34671a99e3d0e1cefbf5e7cc2cfce13 -
Trigger Event:
release
-
Statement type: