Six lines that matter for safety review. READ vs. WRITE (did anything change?), WRITE vs. NETWORK (did anything cross a trust boundary?), NETWORK vs. EXECUTE (did we hand control to another process?), EXECUTE vs. SPAWN (did the child outlive the call?), SPAWN vs. DESTRUCTIVE (can we undo it?). Anything finer is manifest territory.
A taxonomy that grows at runtime is a taxonomy that drifts. Adding a class would require updating the precedence ordering, the parity tests, the porting-notes log, every consumer that exhaustively matches on Effect, and every receipt that hashed under the old ordering. The cost is real; the benefit (better fit for one new use case) is local. So: closed by default, with an explicit project-level process for extension.
The runtime's job is to classify and decide. Calling a model to second-guess that decision turns the hook into a non-deterministic component, which breaks receipt replayability, which breaks the audit story. If you want a model in the loop, run it outside the hook and feed its decision in as part of the manifest or posture configuration.
Receipts are content-addressable. Two operators replaying the same session see byte-identical receipts. That property only holds if the runtime is deterministic — same input → same output, every time. The cost is that wall-clock time can only enter at the I/O boundary (the hook); the benefit is that "what happened?" has a SHA-stable answer.
MacFall7/M87-Spine-lite is a sibling project with a different scope: a governance framework for Claude Code shell commands, with a six-class taxonomy drawn on shell-vs-file lines and numeric risk-delta scores. spine-lite-python is broader (any LLM tool call, not just bash) and uses ordinal precedence rather than numeric scores. The two designs are categorically different, not relabelings of each other. See Porting Notes for the full story.
Pydantic v2 is the de facto standard for typed Python schemas, has fast Rust-backed validation, and round-trips JSON cleanly. The frozen + extra-forbid model config gives us schema strictness without writing a custom validator.
ManifestError. Tools without declared effects can't be classified, and an unclassified call is a refused call. The manifest is the contract; declare the effects explicitly.
ManifestError at validation time. Unknown effect strings fail Pydantic validation; the call doesn't reach the classifier.
Yes. The hook adapter implements the Claude Code PreToolUse contract specifically, but the underlying pipeline is just a function from inputs to a Decision. Wire it into any process that can invoke a subprocess and parse JSON.
Posture members live in the closed Posture enum (Phase 3). Adding a member is an additive change that needs project-level sign-off, similar to Effect. The transition function itself is pure — defining new transitions for existing postures is a code change in posture.py and a parity test update.
See SECURITY.md. Don't open a public issue for a vulnerability.
Phase 1 ships only the taxonomy, the exception hierarchy, and a CLI. None of those need a dataclass. Phase 2 introduces the manifest and decision types where the dataclass discipline applies.
The lockfile (uv.lock) is committed to the repo. CI uses it directly. See Design Rationale / uv.lock is committed. Refresh with uv lock --upgrade and commit the result.
Strict typing catches more bugs than it costs. The codebase has zero Any today; the constraint forces you to model the data clearly rather than waving at it. If a third-party stub forces an Any, justify it with an inline comment.
Repo-level governance files want to be read in one sitting. A 150-line cap forces priorities; everything else lives in docs/.
Open an issue with a written rationale. Wait for a HALT-format response from the project lead. PRs that modify __all__ without prior agreement will be closed back to the issue.
Phase 3 will add --receipt-dir to spine-lite hook. Each receipt is a content-addressable file; same inputs produce identical bytes. Capture them somewhere durable for audit.
- Architecture — the overall shape.
- Invariants — the rules.
- Design Rationale — past decisions.
- Glossary — vocabulary.