Declarative lifecycle state-machine specs: one enforcement path, policy identity digests, semantic diffs
Project description
statespec
Declarative lifecycle policy for entities whose status moves through reviewable steps.
A spec is plain data — states, named transitions (roles + guards), invariants — and the
same artifact is human-readable (Mermaid / plain-English table), machine-checkable
(validate), enforced through a single interpreter (apply / fire), and identified
by content digests (identity) so that behavioural change is mechanically
distinguishable from wording change.
statespec.core— the engine:StateSpec,Transition,Invariant,apply,fire,validate.statespec.expr— closed guard/invariant expression grammar (compare + boolean), with a versionedOpaqueescape hatch whose body is hashed into the policy identity.statespec.identity—canonical(),semantic_digest/presentation_digest,change_kind,diff, andpolicy_record()(the versioned.policy.jsonenvelope — seedocs/policy-artifact-contract.md, the authoritative copy of the contract).statespec.render— Mermaid, tables, and the generated lifecycle doc.
Pure Python, stdlib-only, no dependencies.
Quick start
pip install statespec
from statespec import StateSpec, Transition, apply, validate
from statespec.expr import field
spec = StateSpec(
name="review", title="Review",
states={"draft": "Being written", "submitted": "Awaiting review",
"approved": "Done"},
fields={"amount": "int"},
initial="draft", terminal=frozenset({"approved"}),
transitions=(
Transition("submit", ("draft",), "submitted",
roles=frozenset({"author"})),
Transition("approve", ("submitted",), "approved",
roles=frozenset({"reviewer"}),
guard=field("amount").le(100)),
),
)
assert validate(spec, known_roles=frozenset({"author", "reviewer"})) == []
apply(spec, "approve", "submitted", frozenset({"reviewer"}), {"amount": 40})
# -> "approved". Wrong role -> PermissionDenied; amount 500 -> GuardRejected;
# fire() returns the same decision plus structured evidence per condition.
Every claim above is exercised by the test suite: the refusal taxonomy and
its precedence in tests/test_core.py, the expression grammar and
typechecking in tests/test_expr.py, digests/diff in tests/test_identity.py,
and canonicalisation byte-stability against a golden artifact in
tests/test_contract_golden.py.
Boundaries (what this does not do)
statespec sits above concurrency and persistence. It decides whether a
transition is legal; it does not make the write atomic or durable. It will
not stop a race between two concurrent transitions (pair it with optimistic
locking or a version column at the persistence layer), a forgotten lock, an
out-of-band mutation that sets status around the interpreter, or a
cross-system inconsistency. The "single enforcement path" property holds
only if every status write in the application routes through apply/fire
— enforce that with a repo-level convention or CI gate, not by trust. The
digests identify policy change; they do not verify that deployed code
matches the artifact — that check belongs to CI or a control plane.
Provenance
Extracted from ConceptPending/baseplate
branch example/state-machine at tag statespec-v0.3.0 (commit 2aca24e, 2026-06-10),
where the kernel was developed and frozen. The only changes at extraction were the import
rename app.statespec → statespec and doc-path adjustments. This repository is the
kernel's authoritative home from statespec-v0.4.0 onward; the in-tree copy in baseplate
remains frozen at v0.3.0 as part of the reference example.
Versioning and freeze discipline
Kernel tags form their own series (statespec-vX.Y.Z), separate from any consuming
product's versions. The canonicalisation and digest rules are a frozen contract:
any change to the policy-artifact envelope bumps schema_version and the kernel tag
together, and is recorded in docs/policy-artifact-contract.md's changelog. Changes
that alter existing digests are breaking and require explicit re-binding by consumers
(e.g. approval planes). Exceptions to the freeze: genuine bugs, security issues, or
contract defects.
Known consumers: baseplate (reference example, v0.3.0), baseplate-control
(approval plane; vendors this kernel byte-identically and verifies digests independently).
Development
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
.venv/bin/ruff check src tests
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 statespec-0.4.0.tar.gz.
File metadata
- Download URL: statespec-0.4.0.tar.gz
- Upload date:
- Size: 33.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1866b4ea5d51c59446866ab1aef24f30289b4aff87d7e9592565bdf9b4858ce
|
|
| MD5 |
94f8dc935a763d643419103e8a2743da
|
|
| BLAKE2b-256 |
4ef0a5e0fb5bac218ea865436d5022de9d3413b6f3cd005dccacecb2b2c62a4a
|
File details
Details for the file statespec-0.4.0-py3-none-any.whl.
File metadata
- Download URL: statespec-0.4.0-py3-none-any.whl
- Upload date:
- Size: 20.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c6a3e0d4c5ac4b18e1087f40a8e664d782a5ff68d0f0e56971b218b5076aff1
|
|
| MD5 |
84c6b1f10d929a3be176ea83937c9993
|
|
| BLAKE2b-256 |
6cff6e53c015a2e96fa9d90e59537891484a01056c34166750c849019377405a
|