Skip to content

Commit 111f34c

Browse files
committed
chore: phase 2 blueprint correction — sibling, not parity target
Probe of MacFall7/M87-Spine-lite found a sibling Python project with a categorically different six-class taxonomy (SAFE_READ/SHELL_*/SCOPED_WRITE/ RESTRICTED_WRITE/SHELL_DANGEROUS on shell-vs-file lines with numeric risk scores and a 5-step pipeline). spine-lite-python's taxonomy stays canonical: state × boundary × reversibility axes with ordinal precedence, authored from spec. CLAUDE.md mission reworded; halt + operator resolution appended verbatim to RECEIPTS.md as the Phase 2 day 1 opening entry; porting-notes reframed from translation log to design history; surgical edits across nine docs pages drop the stale TS-reference framing. Posture enum members pinned in posture-and-hooks.md ahead of the Phase 2 ship.
1 parent e5f7fb9 commit 111f34c

13 files changed

Lines changed: 199 additions & 64 deletions

File tree

CLAUDE.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Operating manual for Claude Code sessions in this repo.
44

55
## Mission
66

7-
Python port of M87-Spine-lite (TypeScript). Deterministic policy and effects runtime for LLM tool calls. Public API and observable semantics must mirror the TypeScript reference within the closed six-class taxonomy.
7+
Deterministic policy and effects runtime for LLM tool calls. Public API and observable semantics are defined by the architectural invariants below; the sibling project at [MacFall7/M87-Spine-lite](https://github.com/MacFall7/M87-Spine-lite) is informational and not a parity target. See `docs/explanation/porting-notes.md` for the relationship.
88

99
## Authority
1010

@@ -19,7 +19,6 @@ Mac decides — halt and ask:
1919
- Anything in `src/spine_lite/__init__.py`'s `__all__`.
2020
- New dependencies beyond `pyproject.toml`.
2121
- Phase boundary transitions (1→2, 2→3).
22-
- Semantic divergence from the TypeScript reference.
2322
- PyPI publish, repo visibility, GitHub Pages enablement.
2423

2524
## Architectural invariants
@@ -56,7 +55,7 @@ All three green or no commit. Before any push, also `coverage` and `docs`. Cover
5655
Halt and report when:
5756

5857
- A phase exit gate item is unclear.
59-
- Python and TS reference diverge semantically and you can't tell which is right.
58+
- The architectural invariants above conflict with new code or new decisions.
6059
- A test fails you can't explain in 15 minutes.
6160
- You're about to add a dependency.
6261
- You're about to modify `__all__`.
@@ -75,14 +74,14 @@ Awaiting: <decision needed>
7574
## Phase plan
7675

7776
- **Phase 1** — scaffold + CI + docs deploy. Tags `v0.1.0a0`.
78-
- **Phase 2**`manifest` and `classifier` complete. Pydantic v2 models, parity tests against TS reference fixtures, `hypothesis` for invariants. Tags `v0.2.0a0`.
77+
- **Phase 2**`manifest`, `classifier`, and the closed `Posture` enum complete. Pydantic v2 models, round-trip parity tests against authored fixtures, `hypothesis` for invariants. Tags `v0.2.0a0`.
7978
- **Phase 3**`posture`, `receipt`, `hook`, `cli` complete. End-to-end PreToolUse integration with Claude Code. Tags `v0.3.0a0`.
8079

8180
Phase exit gates and receipts live in `RECEIPTS.md`.
8281

8382
## Scope
8483

85-
- In-repo only. Don't touch the TypeScript reference (read-only spec).
84+
- In-repo only. Don't touch the sibling project at MacFall7/M87-Spine-lite (informational, not a parity target).
8685
- Don't invoke Patronus, Braintrust, or Arize SDKs (operator-decision pending).
8786
- No network calls in tests. No LLM calls anywhere in the runtime.
8887

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
Deterministic policy and effects runtime for LLM tool calls.
99

10-
A Python port of [M87-Spine-lite](https://github.com/MacFall7/M87-Spine-lite). Same closed effects taxonomy, same precedence rules, same posture state machine — typed, tested, packaged.
10+
Six-class effects taxonomy on state × boundary × reversibility axes. Ordinal precedence. Content-addressable receipts. Wires into Claude Code as a PreToolUse hook (Phase 3); usable anywhere you can shell out to a subprocess. Sibling project to [M87-Spine-lite](https://github.com/MacFall7/M87-Spine-lite) — see [Porting Notes](docs/explanation/porting-notes.md) for the relationship.
1111

1212
## What it does
1313

RECEIPTS.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,125 @@ All 10 items clear. **Phase 1 closed.**
112112
- None. Phase 2 (manifest + classifier, ~5 working days, target `v0.2.0a0`) is gated on operator go per `CLAUDE.md`.
113113

114114
**Next:** Halt for Mac at the Phase 1 → Phase 2 transition. Per blueprint §11, completion of Phase 1 unblocks the Braintrust application thread on the operator's side.
115+
116+
---
117+
118+
### Phase 2 Day 1 Opening — 2026-05-08
119+
120+
Phase 2 opened with a §9 halt. The halt and the operator's resolution are recorded here verbatim for the run-registry trail.
121+
122+
**§9 HALT (verbatim):**
123+
124+
```
125+
HALT: Reference repo's spec diverges from Phase 1's invariants in ways that make
126+
a straight parity port mechanically impossible.
127+
128+
Context:
129+
- The blueprint and CLAUDE.md describe a "TS reference" at MacFall7/M87-Spine-lite.
130+
- WebFetch confirms the repo exists and is public, but it's implemented in Python
131+
(hooks/guard.py, hooks/governor.py), not TypeScript. The "TS reference" framing
132+
in the blueprint is stale.
133+
- Bigger issue: the reference's six-class taxonomy is *categorically different*
134+
from what shipped in Phase 1.
135+
136+
Reference (M87-Spine-lite, README verbatim): │ Phase 1 (spine-lite-python):
137+
138+
SAFE_READ (risk 0.00, auto-approve) │ READ
139+
SHELL_SAFE (risk 0.01, auto-approve) │ WRITE
140+
SHELL_MUTATING (risk 0.04, auto-approve) │ NETWORK
141+
SCOPED_WRITE (risk 0.02, auto-approve) │ EXECUTE
142+
RESTRICTED_WRITE (risk 0.08, manual approval) │ SPAWN
143+
SHELL_DANGEROUS (risk 0.10, blocked) │ DESTRUCTIVE
144+
145+
+ numeric risk scores │ + ordinal precedence
146+
+ per-class default disposition │ + most_restrictive collapse
147+
+ 5-step deny→network→safe→mutating→default pipeline │ + classifier returns Decision
148+
+ 7 invariants about governance philosophy │ + 7 invariants about implementation
149+
150+
- These are not "the same six things with different labels." The reference draws axes
151+
on shell-vs-file and risk-magnitude; Phase 1 draws axes on state-vs-boundary-vs-
152+
reversibility. There's no NETWORK in the reference; there's no SAFE_READ vs
153+
SHELL_SAFE distinction in Phase 1.
154+
- Phase 1 shipped 2026-05-08 as v0.1.0a0, merged via PR #1, repo public, Pages live.
155+
Mac signed off on the action-centric taxonomy and the 7 implementation invariants
156+
in CLAUDE.md.
157+
- This is exactly the §9 stop condition: "TS reference and Python implementation
158+
diverge semantically and you cannot determine which is correct." Except now
159+
it's clear the divergence isn't fixable by translation — it's a different design.
160+
161+
Options:
162+
1. Treat the reference as a sibling project, not a parity target. Phase 2 implements
163+
manifest + classifier from the contract document already drafted in
164+
docs/concepts/posture-and-hooks.md and docs/how-to/wire-claude-code.md (author-
165+
from-spec, same pattern as Phase 1). Add a porting-notes entry recording that
166+
the reference informed the design but is not the byte-equality target — its
167+
taxonomy and ours are categorically different. Parity tests become "round-trip
168+
our own fixtures" rather than "byte-equal against external fixtures."
169+
170+
2. Retract Phase 1's taxonomy. Re-tag v0.1.0a0 as deprecated, bump to v0.1.1a0
171+
with the reference's SAFE_READ/SHELL_*/SCOPED_WRITE/RESTRICTED_WRITE/SHELL_DANGEROUS,
172+
then resume Phase 2 against that baseline. Brings byte-for-byte parity back
173+
on the table.
174+
175+
3. Both taxonomies, with a translation layer. Phase 1 names stay public; manifest
176+
schema accepts reference-style names and maps them. Mapping is judgment-heavy
177+
(does SHELL_SAFE map to EXECUTE or to READ depending on the call?), so parity
178+
becomes case-by-case rather than mechanical.
179+
180+
4. The reference repo isn't actually the spec — CLAUDE.md is, and you confirmed
181+
that in Phase 1 by directing "FLIP TO OPTION 1: author scaffold from blueprint
182+
spec." Phase 2 follows the same pattern. The reference is consulted as
183+
inspiration, cited in porting-notes, but isn't a parity gate.
184+
185+
Recommendation: Option 1 (or equivalently Option 4 — they're the same path with
186+
different labels).
187+
188+
Awaiting:
189+
- Pick 1, 2, 3, or 4 for the parity question.
190+
- (a) or (b) for the Posture enum question.
191+
- Anything else worth logging in porting-notes before Phase 2 starts.
192+
```
193+
194+
**Operator resolution (verbatim):**
195+
196+
```
197+
DECISION ON HALT:
198+
199+
Parity question: Option 1. Reference (MacFall7/M87-Spine-lite) is a sibling project,
200+
not a parity target. Phase 1's taxonomy is correct and final. spine-lite-python
201+
authors from spec, same pattern as Phase 1.
202+
203+
Posture question: sub-option (a). Ship Posture enum in Phase 2 with manifest
204+
validation. Closed-enum + one-line __all__ addition.
205+
206+
BLUEPRINT CORRECTION (do this in commit 1 of Phase 2 before code lands):
207+
208+
Update CLAUDE.md and docs/explanation/porting-notes.md:
209+
- Reference repo is a Python sibling, not "TS reference"
210+
- Reference is informational, not a parity target
211+
- spine-lite-python's design rationale: broader taxonomy (state × boundary ×
212+
reversibility axes), ordinal precedence over risk scores, suitable for any LLM
213+
tool call not just bash
214+
- Record that the reference's taxonomy (SAFE_READ / SHELL_SAFE / SHELL_MUTATING /
215+
SCOPED_WRITE / RESTRICTED_WRITE / SHELL_DANGEROUS + numeric risk scores +
216+
5-step pipeline) was reviewed and explicitly not adopted
217+
218+
Coverage 100% on manifest.py, classifier.py, posture.py (enum scope only).
219+
Six-commit Conventional Commits shape preserved (the blueprint correction is
220+
commit 1, then 5 functional commits).
221+
222+
Begin from blueprint correction commit. Halt at exit gate.
223+
```
224+
225+
**What landed in commit 1 of Phase 2:**
226+
227+
- `CLAUDE.md` mission rewritten to drop "TS reference" framing; sibling project recorded as informational, not a parity target.
228+
- `docs/explanation/porting-notes.md` reframed from "translation log" to "design history" with a Sibling Project section, a Phase 2 opening entry recording this halt and resolution, and a Phase 1 entry pinning the taxonomy as `spine-lite-python`'s spec.
229+
- `docs/concepts/posture-and-hooks.md` updated to remove the "subject to refinement" caveat from the posture table and pin the four members (`INTERACTIVE`, `AUTONOMOUS`, `DRY_RUN`, `LOCKED`) with their string values.
230+
- `docs/explanation/architecture.md` reference-implementation paragraph rewritten as a sibling-project note.
231+
- `docs/explanation/faq.md` "Why Python after TypeScript?" question replaced with "How does this relate to M87-Spine-lite?", plus three other in-place corrections.
232+
- `docs/concepts/effects-taxonomy.md`, `docs/concepts/overview.md`, `docs/how-to/contribute.md`, `docs/how-to/use-the-api.md`, `docs/reference/glossary.md`: surgical edits to drop TS-reference framing.
233+
- `README.md` and `docs/index.md` headlines reworded: `spine-lite-python` is described directly, sibling project credited but not framed as a port target.
234+
- This receipt entry.
235+
236+
**Next:** Phase 2 functional commits begin (Posture → manifest → classifier → fixtures+tests → release+exit-receipt).

docs/concepts/effects-taxonomy.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Anything finer is a manifest concern (the *which* file, the *which* host), not a
5757
A taxonomy that grows at runtime is a taxonomy that drifts. Adding a seventh class would require:
5858

5959
1. Updating `Effect` and `PRECEDENCE` in `effects.py`.
60-
2. Updating the parity tests against the TypeScript reference.
60+
2. Updating the parity tests and the porting-notes log.
6161
3. Updating every consumer that exhaustively matches on `Effect`.
6262
4. A migration note in `CHANGELOG.md` and `docs/explanation/porting-notes.md`.
6363
5. Project-level sign-off — recorded as a HALT against [`CLAUDE.md`](https://github.com/MacFall7/spine-lite-python/blob/main/CLAUDE.md).
@@ -75,4 +75,4 @@ The `effects` module contains zero I/O, zero clocks, zero randomness. This is en
7575
- [Concepts / Overview](overview.md) — where the taxonomy fits in the pipeline.
7676
- [Reference / API](../reference/api.md#effects) — the auto-generated `Effect`, `PRECEDENCE`, `most_restrictive` reference.
7777
- [Reference / Glossary](../reference/glossary.md) — term-by-term definitions.
78-
- [Explanation / Porting Notes](../explanation/porting-notes.md)how the Python form relates to the TypeScript reference.
78+
- [Explanation / Porting Notes](../explanation/porting-notes.md)design history and the relationship to the sibling project.

docs/concepts/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ The PreToolUse **hook** is just an I/O wrapper that maps stdin/stdout to and fro
3838

3939
## What's closed
4040

41-
The effects taxonomy is closed at six classes. Adding a class is a project-level decision that requires updating the precedence ordering, the parity tests against the TypeScript reference, and the docstrings. It is not a runtime extension point.
41+
The effects taxonomy is closed at six classes. Adding a class is a project-level decision that requires updating the precedence ordering, the parity tests, the porting-notes log, and the docstrings. It is not a runtime extension point.
4242

4343
This is a feature, not a limitation. A taxonomy that grows at runtime is a taxonomy that drifts. The six classes were chosen to cover every observable side effect a tool call can produce. If something looks like a seventh class, it's probably an existing class with new arguments.
4444

docs/concepts/posture-and-hooks.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A manifest is the policy document for a tool. It declares:
1010
- The set of effects each invocation can produce.
1111
- Posture constraints — under which postures the tool may be invoked, and under which it must be refused.
1212

13-
Manifests are validated as Pydantic v2 models and round-trip the TypeScript reference fixtures byte-for-byte after JSON normalisation.
13+
Manifests are validated as Pydantic v2 models and round-trip authored fixtures byte-for-byte after JSON normalisation.
1414

1515
## Classifier (Phase 2)
1616

@@ -27,18 +27,18 @@ Pure function. Given a tool call and a manifest, returns a `Decision` carrying:
2727

2828
No I/O. No clocks. Same input → same output, every time.
2929

30-
## Posture state machine (Phase 3)
30+
## Posture state machine
3131

32-
A posture is the current operational mode. Transitions are pure value-in-value-out functions; no hidden state.
32+
The closed `Posture` enum lands in Phase 2 (manifest validation depends on it). Transition functions land in Phase 3.
3333

34-
Postures (planned, subject to refinement against the TypeScript reference):
34+
Posture is the current operational mode. Transitions (Phase 3) are pure value-in-value-out functions; no hidden state.
3535

36-
| Posture | Meaning |
37-
|---|---|
38-
| `INTERACTIVE` | Operator is at the keyboard; ambiguous calls escalate to a prompt. |
39-
| `AUTONOMOUS` | No operator in the loop; ambiguous calls fail closed. |
40-
| `DRY_RUN` | Classification only; no `WRITE`/`NETWORK`/`EXECUTE`/`SPAWN`/`DESTRUCTIVE` effects fire. |
41-
| `LOCKED` | Refuse everything except explicitly allow-listed read-only calls. |
36+
| Posture | Value | Meaning |
37+
|---|---|---|
38+
| `INTERACTIVE` | `"interactive"` | Operator is at the keyboard; ambiguous calls escalate to a prompt. |
39+
| `AUTONOMOUS` | `"autonomous"` | No operator in the loop; ambiguous calls fail closed. |
40+
| `DRY_RUN` | `"dry_run"` | Classification only; no `WRITE`/`NETWORK`/`EXECUTE`/`SPAWN`/`DESTRUCTIVE` effects fire. |
41+
| `LOCKED` | `"locked"` | Refuse everything except explicitly allow-listed read-only calls. |
4242

4343
Transitions are total — every `(posture, decision)` pair has a defined next posture or a `PostureError`. There are no implicit transitions.
4444

@@ -81,4 +81,4 @@ echo $? # 0 = allow, non-zero = deny
8181

8282
- [Concepts / Overview](overview.md) — pipeline shape.
8383
- [How-To / Wire into Claude Code](../how-to/wire-claude-code.md) — operator runbook.
84-
- [Explanation / Porting Notes](../explanation/porting-notes.md)how this maps to the TypeScript reference.
84+
- [Explanation / Porting Notes](../explanation/porting-notes.md)design history and the relationship to the sibling project.

docs/explanation/architecture.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,11 @@ Adding a class is permitted but expensive. The process is in [Effects Taxonomy /
7070

7171
The architecture stays the same across phases. Each phase fills in pure modules that adhere to the same purity contract.
7272

73-
## Reference implementation
73+
## Sibling project
7474

75-
The TypeScript reference lives at [MacFall7/M87-Spine-lite](https://github.com/MacFall7/M87-Spine-lite). Treat it as the spec for semantic behaviour. Where Python idiom diverges from TypeScript (typing, dataclass shape, exception names), prefer the Python form and document the call in [Porting Notes](porting-notes.md). Anything that changes observable behaviour is a divergence and needs project-level sign-off.
75+
[MacFall7/M87-Spine-lite](https://github.com/MacFall7/M87-Spine-lite) is a Python sibling project: a governance framework for Claude Code shell commands with a different six-class taxonomy drawn on shell-vs-file lines. It's informational and citable, not a parity target. See [Porting Notes](porting-notes.md) for the full relationship.
76+
77+
`spine-lite-python`'s spec is canonical and lives in this repository: the architectural invariants in `CLAUDE.md`, the [Invariants](invariants.md) page, and the design rationale recorded as decisions are made.
7678

7779
## See also
7880

docs/explanation/faq.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Six lines that matter for safety review. `READ` vs. `WRITE` (did anything change
66

77
## Why is the taxonomy closed?
88

9-
A taxonomy that grows at runtime is a taxonomy that drifts. Adding a class would require updating the precedence ordering, the parity tests against the TypeScript reference, 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.
9+
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.
1010

1111
## Why no LLM calls inside the runtime?
1212

@@ -16,13 +16,13 @@ The runtime's job is to classify and decide. Calling a model to second-guess tha
1616

1717
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.
1818

19-
## Why Python after TypeScript?
19+
## How does this relate to M87-Spine-lite?
2020

21-
The TypeScript reference is the spec. The Python port is what you install in environments where Python is already the language of choice — Claude Code agents written in Python, internal CLIs, CI pipelines, Jupyter sessions. Both implementations target the same observable behaviour.
21+
[MacFall7/M87-Spine-lite](https://github.com/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](porting-notes.md) for the full story.
2222

2323
## Why Pydantic v2 for the manifest?
2424

25-
Pydantic v2 is the de facto standard for typed Python schemas, has fast Rust-backed validation, and round-trips JSON cleanly. The TypeScript reference uses Zod; Pydantic v2 is the closest match in the Python ecosystem.
25+
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.
2626

2727
## What happens when a tool call has no declared effects?
2828

0 commit comments

Comments
 (0)