Metadata-Version: 2.4
Name: srmech
Version: 0.6.0
Summary: Stored-Relationship Mechanism. 14-class A-N primitive vocabulary in native C + Python - substrate-native 28-dim chiral hyper-loop = so(8) adjoint (14 g_2 derivations + 14 L+R octonion-multiplications; Spin(8) triality) made hardware-callable. Full cascade-catalog C/Python parity (content-addressing, cyclic-group, graph-Laplacian, primes, HDC, rational, dispatch, catalog, templating, Kepler). Canonical QM/QFT/SM operations. Runtime spectral decomposition; dual-path signal-processing; AMSC (MPR v1).
Keywords: stored-relationship,mechanism,attested,provenance,ndjson,ground-proof,research
Author: Steven Kirkland
License-Expression: GPL-3.0-or-later
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Project-URL: Homepage, https://github.com/lemonforest/mlehaptics/tree/main/docs/srmech/python
Project-URL: Repository, https://github.com/lemonforest/mlehaptics
Project-URL: Issues, https://github.com/lemonforest/mlehaptics/issues
Project-URL: Changelog, https://github.com/lemonforest/mlehaptics/blob/main/docs/srmech/python/CHANGELOG.md
Project-URL: Research notebook, https://github.com/lemonforest/mlehaptics/blob/main/docs/srmech/srmech_research_notebook.md
Requires-Python: >=3.10
Requires-Dist: tomli>=2.0; python_version < "3.11"
Requires-Dist: numpy>=1.24
Provides-Extra: validation
Requires-Dist: jsonschema>=4.0; extra == "validation"
Provides-Extra: collectors
Requires-Dist: requests>=2.28; extra == "collectors"
Requires-Dist: beautifulsoup4>=4.11; extra == "collectors"
Provides-Extra: tests
Requires-Dist: pytest>=7.0; extra == "tests"
Provides-Extra: crypto
Requires-Dist: cryptography>=41.0; extra == "crypto"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40.0; extra == "anthropic"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: scikit-build-core>=0.10; extra == "dev"
Requires-Dist: cmake>=3.23; extra == "dev"
Requires-Dist: hatchling>=1.18; extra == "dev"
Requires-Dist: jsonschema>=4.0; extra == "dev"
Requires-Dist: requests>=2.28; extra == "dev"
Requires-Dist: beautifulsoup4>=4.11; extra == "dev"
Description-Content-Type: text/markdown

# srmech

**Status:** **v0.6.0** — 14-class A–N primitive vocabulary with native C parity; canonical QM/QFT/SM operations + a callable so(8)/Spin(8) triality surface (octonion `L`/`R`-mult + the 28-generator adjoint + the order-3 automorphism `τ`, `Fix(τ) = g₂ = 14`, plus the so(4) = su(2) ⊕ su(2) `quaternion_subalgebra_stabilizer` and the order-3 `lean_isa_seventh_primitive`); runtime spectral decomposition JSON-callable by-reference (the `$srmech_handle` grammar); a reentrant C core; dual-path signal-processing surface; Attested Multi-Source Collector/Catalog (AMSC, MPR v1) provenance framework; the Class-M HDC variant ladder (`polar` `{-1,0,+1}`, `Klein-4` `(ℤ₂)²`), a `coupling` composition score (Class K∘L), `symmetric_eigendecompose` (real-symmetric Class L), `rfft` (real-input half-spectrum dual-path op, Class A∘I∘K), and the foundational cross-domain `cascade` catalog — now a **two-tier lean-ISA split** (`cascade.atoms` primitives + `cascade.compose` composites): `pin_slot_at_zero` K / `reorient` C / `magnitude` K / `best_rational_signed` K∘N∘C / `cyclic_gcd` I, the chirality mini-set `chiral_flip` / `chiral_dual` / `net_chirality` C, the Klein-4 four-sector `parallel_sector_dispatch` (C peer `srmech_cascade_parallel_sector_dispatch`), and the native Kuramoto coupled-oscillator `kuramoto_step` (C peer `srmech_cascade_kuramoto_step_f64`) — a named cascade is the default, a math-library call the exception. *(The package also bundles the `siona` co-name alias — `pip install srmech` also gives `import siona`, same objects. The standalone `siona` package on PyPI is a metapackage that depends on `srmech`, so `pip install siona` resolves here too.)*

`srmech` (Stored-Relationship Mechanism) is a research package shipping five load-bearing surfaces:

1. **14-class primitive vocabulary** (`srmech.amsc.*`) — content-addressing, streaming, cyclic-group, graph-Laplacian, prime-factorisation, TLV, search, dispatch, catalog, templating, rational-approximation, equation-of-centre/Kepler, hyperdimensional-computing (HDC). Each class has both a Python wrapper and a native C symbol in `libsrmech.{so,dll,dylib}`.
2. **Canonical QM/QFT/SM operations layer** (`srmech.qm.*`) — TDSE/TISE, Pauli + Clifford, hydrogen radial, Dirac γ-matrices, Feynman propagators, η-deformed pseudo-Hermitian inner products, SU(2)/SU(3) gauge generators + Wilson loops, Higgs/W/Z/CKM Standard-Model operations, and the **so(8)/Spin(8) triality engine** (`srmech.qm.{octonion, so8, triality}`): the MPR-attested octonion multiplication table, the 28-generator `so(8)` adjoint, and the order-3 outer automorphism `τ` whose fixed subalgebra is exactly the 14 `g₂` derivations (the `D4 → G2` Z3 fold = the A-N `1+3+7+3` partition).
3. **Runtime spectral decomposition** (`srmech.spectral`) — eigenbasis projection, HDC delta encoding, spectral prediction, prediction-error gating, sparse-truncate compression.
4. **Dual-path signal-processing surface** (`srmech.signal_processing`) — 38 closed-form algebra ops (Path A) + an RBS-HDC bound-vector instrument at D=8192 (Path B), with a cascade dispatcher routing per call.
5. **AMSC provenance framework** (`srmech.amsc.format`, `srmech.amsc.catalog`, `srmech.amsc.adapters`) — every ground-proof datum carries a mandatory attestation block (`source_doi`, `source_url`, `license`, `retrieved_at`, `response_sha256`, `parser_version`, `parser_rule_hash`, `collector_descriptor_path`, `collector_descriptor_hash`).

Implementation is JPL Power-of-Ten compliant on the C side; cibuildwheel matrix covers Linux / macOS / Windows × Python 3.10–3.14; a `py3-none-any` pure-Python wheel ships for Pyodide / WASM environments where the C surface can't load.

## Companion textbook

**The Metric Field and Its Primitives** — the framework textbook accompanying this package. Lays out the substrate-vs-excitation ontology (MFO), the 14-class primitive vocabulary at substrate level, and the cascade-composition discipline that `srmech` implements computationally.

- [PDF (GitHub)](https://github.com/lemonforest/mlehaptics/blob/main/docs/srmech/metric-field-and-its-primitives.pdf) — renders inline in the GitHub viewer
- [PDF (ReadTheDocs)](https://mlehaptics.readthedocs.io/srmech/metric-field-and-its-primitives.pdf) — served as a static asset alongside the [research notebook](https://mlehaptics.readthedocs.io/srmech/srmech_research_notebook/)
- [Technical Disclosure Commons](https://www.tdcommons.org/dpubs_series/10243/) — the textbook as a timestamped defensive publication (Kirkland, 2026-05-25)
- [MFO research notebook](https://mlehaptics.readthedocs.io/antikythera-maths/mfo_spectral_research_notebook/) — working draft the textbook is consolidated from

## Install

```bash
pip install srmech                  # core (numpy + stdlib; no jsonschema, no network adapters)
pip install srmech[validation]      # adds jsonschema for strict data-block validation
pip install srmech[collectors]      # adds requests + beautifulsoup4 for fetched adapters
pip install srmech[dev]             # everything
```

## Quick start

Decompose a real signal onto a graph-Laplacian eigenbasis, take an HDC delta against a reference, and recompose:

```python
import numpy as np
from srmech import spectral
from srmech.amsc import laplacian

# Substrate: cycle-graph Laplacian on 8 nodes (any Hermitian L works).
A = np.roll(np.eye(8), 1, axis=1)
A = A + A.T
L = laplacian.dense_laplacian(A.astype(np.complex128))

# Project two states onto the eigenbasis.
state_ref = np.array([1.0, 0, 0, 0, 0, 0, 0, 0], dtype=np.complex128)
state_now = np.array([0.9, 0.1, 0, 0, 0, 0, 0, 0], dtype=np.complex128)

h_ref = spectral.decompose(state_ref, L)
h_now = spectral.decompose(state_now, L)

# HDC XOR delta on encoded coefficient bytes.
delta_bytes = spectral.delta(h_ref, h_now)

# Predict one substrate-natural tick ahead.
h_pred = spectral.predict(h_now, L, steps=1, dt=0.1)

# Recover the node-domain state.
state_back = spectral.recompose(h_pred, L)
```

## Public surface

### The 14 classes in substrate-native ordering — `1 + 3 + 7 + 3 = 14`

The 14 classes are presented in alphabetical order in the table below (matching the import paths). The **substrate-native ordering is not alphabetical** — it is the cyclic-algebra-path partition `1 + 3 + 7 + 3 = 14`:

| Slot | Classes | Role |
|---|---|---|
| **1** — foundational content-anchor | `{A}` | The content-address every cascade begins from |
| **3** — substrate-projection triad | `{I, C, J}` | Cyclic-group + cascade-orientation + prime-period (the projection-triad that maps substrate-content to observable structure) |
| **7** — cascade-detection heptad | `{D, E, F, G, K, L, M}` | Pattern-match + catalog + render + byte-search + pin-slot + Laplacian + HDC-bind (the detection-and-rendering layer) |
| **+3** — meta-cascade language-translation triad | `{B, H, N}` | TLV-framing + self-introspection + rational-approximation (the operators that translate between continuous-Hopf-quantum and discrete-cyclic-algebra descriptions) |

**Why this ordering matters.** Per [PR #680 (R30 walking-path closure)](https://github.com/lemonforest/mlehaptics/pull/680), the substrate admits **two co-equal bit-exact substrate-native mathematical languages**:

- the **11D quantum-Hopf-language** (continuous-DOF, parallelizable-sphere ladder `1 + 3 + 7`)
- the **`1 + 3 + 7 + 3 = 14` cyclic-algebra-path** (discrete-DOF, A–N cascade-operator class enumeration)

Under Class C chirality the cyclic-algebra-path further admits a **`14 + 14 = 28`-dim chiral-hyper-loop reading = 𝔰𝔬(8) adjoint** (per MFO §VIII.31.11): `14 𝔤₂ derivations + 14 L⊕R octonion-multiplications` = the chirality-dual pair. As of v0.5.0 this is **exposed as a callable, bit-exact-tested surface** (`srmech.qm.{octonion, so8, triality}`): the τ-fixed subalgebra of `so(8)` is exactly the 14 `g₂` derivations (the `D4 →(Z3 fold) G2` theorem) — the same 14 as the A-N partition's `1 + 3 + 7 + 3`. Endianness is the byte-axis instance of the same Class C orientation primitive; the scope hierarchy is `endianness ⊂ Class C ⊂ Klein-4 ⊂ Spin(8) triality`.

Modern physics uses the first; antiquity 9 of 9 traditions canvassed (Antikythera + Pythagoreans + Plato Timaeus + Stoics + Lucretius + Apollonius + Ptolemy + Heron + Archimedes) used the second. We had been using the cyclic-algebra path in `srmech` from the beginning without ever stating why — because antiquity had, and it worked. The R30 closure provides the answer: bit-exact cross-substrate confirmation rules out projection-reading; both languages are substrate-native; the `+3 = {B, H, N}` are substrate-native language-translation operators bridging them. The k=3 fingerprint observed across substrates (planet multipole axes, codon alphabet, 3-jet QCD, 3-generation Yukawa, the antiquity meta-op triads) is the `{B, H, N}` triad showing up wherever continuous↔discrete encoding happens.

**About the A–N alphabet.** The labels A through N record the **chronological order** in which each operation was named during this framework's evolution — they are discovery-fingerprint, not substrate-ordering. Re-sorted by substrate-native role, the partition above (`{A}` + `{I, C, J}` + `{D, E, F, G, K, L, M}` + `{B, H, N}`) is the substrate-side grouping. The alphabetical table below is the lookup convenience.

Full context: [substrate-native-maths research notebook](https://mlehaptics.readthedocs.io/substrate-native-maths/substrate_native_research_notebook/) (PR #680 SSoT).

### `srmech.amsc.*` — 14-class primitive vocabulary (alphabetical lookup)

Each class is importable as `srmech.amsc.<module>` with native C dispatch and a Python fallback. The C surface is loaded once at import time; if loading fails (Pyodide, ABI mismatch), the package transparently falls back to pure Python.

To check the backend state, call `srmech.native_status()` (top-level; equivalently `describe()['native']`) — `{has_native, dispatching, abi_version, expected_abi, native_version, load_error}`. `dispatching` is `True` iff `libsrmech` loaded **and** its ABI matched, so native ops really run; otherwise `load_error` carries the reason and the pure-Python fallback is used. (The native shim is `srmech.amsc._native`; `srmech._native` is the data dir that merely *holds* the binary.)

```python
import srmech
srmech.native_status()
# {'has_native': True, 'dispatching': True, 'abi_version': 3,
#  'expected_abi': 3, 'native_version': '0.6.0', 'load_error': None}
```

| Module | Class | Primitive operation |
|---|---|---|
| `format`, `_native` | A | Content-addressing via SHA-256 (`sha256_bytes` -> 64-char lowercase hex digest `str`) |
| `tlv` | B | Byte-canonical TLV pack (`tlv_pack`) |
| `format` | C | Streaming NDJSON iterator (`read_ndjson`) |
| `dispatch` | D | Multi-needle byte-pattern dispatch (`match`) |
| `naming` | E | Catalog sorted-key lookup (`lookup`) |
| `template` | F | Template `{key}` substitution (`render`) |
| `search` | G | Byte-pattern search (`byte_search`) |
| `_native` | H | Self-introspection (`srmech_version`, `srmech_abi_version`) |
| `cyclic` | I | Modular arithmetic — `gcd`, `lcm`, `mod_add`, `mod_mul`, `mod_pow`, `mod_inv` |
| `primes` | J | Prime testing + factorisation + multiplicative order — `is_prime`, `factor`, `cyclic_period` |
| `kepler` | K | Equation-of-centre / pin-slot — `pin_slot`, `kepler_solve`, `equation_of_centre` |
| `laplacian` | L | Graph Laplacian — `dense_adjacency`, `dense_laplacian`, `normalized_laplacian`, `jacobi_eigvals`, `hermitian_eigendecompose`, `symmetric_eigendecompose`, `elementwise_transcendental` (pi-free Jacobi in C; n ≤ 256 native bound) |
| `hdc` | M | HDC spatter codes — binary `bind`, `bundle`, `permute`, `similarity`; `polar_*` `{-1,0,+1}` and `klein4_*` `(ℤ₂)²` variants |
| `rational` | N | Continued-fraction convergents — `continued_fraction`, `best_rational` |

### `srmech.qm.*` — canonical QM/QFT/SM operations

Each operation cites canonical physics literature in its docstring (Schrödinger / Heisenberg / Pauli / Dirac / Klein-Gordon / Feynman / Yang-Mills / Gell-Mann / Wilson / Glashow-Weinberg-Salam / Higgs / Cabibbo-Kobayashi-Maskawa / Bender-Boettcher / Mostafazadeh). Modules:

- `single_particle` — TDSE, TISE, Heisenberg-picture evolution, lattice momentum, density matrix, Liouville–von Neumann equation, commutators.
- `spin` — Pauli matrices, Clifford `Cl(0,3)` residual products, Pauli spin operators.
- `potentials` — hydrogen radial wavefunction, harmonic oscillator ladder + Hamiltonian.
- `relativistic` — Dirac γ-matrices, γ⁵, Weyl left/right projectors, charge conjugation, Dirac operator in momentum space, Klein–Gordon equation.
- `propagators` — Feynman scalar / fermion / photon / massive-vector propagators.
- `pseudo_hermitian` — η-deformed inner product, ⟨·⟩_η expectation, pseudo-Hermitian check, η construction from eigendecomposition.
- `gauge` — SU(2) and SU(3) generators (Gell-Mann basis), structure constants, Casimir operator, Wilson loops from segment data.
- `sm` — Higgs vev, weak mixing angle, W/Z boson masses, Weinberg relation residual, Yukawa coupling, CKM matrix construction.
- `octonion` — the MPR-attested Cayley-Dickson-from-H convention: `octonion_mult_table` (the attested `(8,8,8)` int8 structure constants), `octonion_left_mult` / `octonion_right_mult` (the `8×8` `L_a` / `R_a` binders), `octonion_conjugate`, `octonion_norm` (Class K ∘ C, never `abs()`). `octonion_table_attestation` content-addresses the table bytes via `sha256_bytes`. Cites Baez (2002), *The Octonions* (arXiv:math/0105155).
- `so8` — the 28-generator `so(8)` adjoint partitioned **14 (g₂ = Der O) + 7 (L-type) + 7 (R-type)**: `so8_adjoint_basis`, `g2_subalgebra` (the 14 derivations; deterministic rank-revealing numpy subset, no RNG), `so7_subalgebra` (the 21; the `D4 → B3` Z2 fold), and `an_embedding` — the bit-exact **su(3) ⊕ 3 ⊕ 3̄** Lie branching of the 14 g₂ generators (su(3) = the stabiliser of an imaginary octonion unit; the genuine fundamental `3` is the `+i` eigenspace of the su(3)-invariant complex structure `J`, `J² = −I`, so a real 3-span cannot carry it). The `8 + 3 + 3̄` decomposition is the op's own self-attesting bit-exact computation (Baez §4.1 cited for `g₂ = Der O` / dim 14 only, the build input); the 14 A-N class names are surfaced only as a documented `framework_an_reading` label ("framework-reading, not derived"), distinct from this su(3) partition.
- `triality` — the Spin(8) triality engine: `triality_automorphism` (the `28×28` order-3 outer automorphism `τ`, `τ³ = I`, `Fix(τ) = g₂` dim 14), `triality_swap` (the Z2 — with `τ` generates `S3 = Out(Spin(8))`), `triality_cycle` (the Class-I `8v → 8s → 8c` rep-permutation), `triality_apply`, `triality_companions`, `triality_relation_residual` (Cartan's `g_v(x·y) = g_s(x)·y + x·g_c(y)`, 0 when correct). Cites Cartan (1925) + Baez (2002).

### `srmech.spectral` — runtime spectral decomposition

Class-composition layer above `srmech.amsc.{laplacian, hdc, format}`. No new primitive class is introduced; every operation is a composition over the 14-class A–N vocabulary.

```python
from srmech.spectral import (
    decompose,          # state + Hermitian L → SpectralHandle (V.conj().T @ state)
    delta,              # XOR delta between two encoded coefficient byte vectors
    recompose,          # SpectralHandle + L → node-domain state (V @ coeffs)
    similarity,         # HDC similarity in [-1, +1]
    predict,            # cascade-extrapolate via per-mode exp(-i·λ_k·steps·dt)
    prediction_error,   # XOR delta with popcount-density threshold gating
    truncate_sparse,    # keep top-k or above-threshold modes; zero the rest
    SpectralHandle,     # opaque (substrate_descriptor_hash, coefficients_bytes, content_sha, n_modes)
    clear_eigenbasis_cache,
    N_MAX_EIGENBASES,   # module-level LRU bound (default 8)
)
```

Eigenbasis is O(n³) one-time per substrate (cached by `substrate_descriptor_hash`); coefficients are O(n²) per state; deltas are O(D) per step. `predict` preserves magnitudes (unitary phase rotation per eigenmode); `truncate_sparse` produces best k-term approximations per Mallat (2008) §9.2.

#### By-reference handle grammar — the `$srmech_handle` id (rc16)

A `SpectralHandle` is an opaque, frozen, bytes-bearing dataclass that JSON-RPC cannot carry **by value**. Over the MCP / Anthropic boundary the 7 `srmech.spectral.*` tools therefore exchange a small **by-reference id**: a producer returns

```json
{"$srmech_handle": {"uuid": "…", "name": "spectral:<sha12>", "kind": "spectral"}}
```

(the literal sentinel key is `HANDLE_ENVELOPE_KEY = "$srmech_handle"`), the caller copies it verbatim into the next tool's input, and `srmech._handles.get_handle_registry()` resolves it back to the live in-process object. The id carries a **dual grammar**: `uuid` is the position-encoded (silicon / cyclic-algebra) address, `name` is the meaning-encoded (biology / continuous-Hopf) address auto-derived from the handle's Class-A `content_sha` (`"spectral:" + content_sha[:12]`); resolution tries `uuid` then `name` — the registry is the **B/H/N continuous↔discrete translation locus**. With the grammar landed, **all 7 `srmech.spectral.*` operations are MCP-callable** (`describe()` reports `handle_pending: 0`).

### `srmech.amsc.cascade` — foundational cross-domain cascade catalog

The cascades that recur across **every / most** domains, promoted so a named cascade is the default and a math-library call the exception (*being forced to reach for a math library is the signal that a cascade is waiting to be found*). Compositions over the 14-class A–N vocabulary — **no new primitive class.** Each cascade ships with a **dedicated C symbol** in `libsrmech.{so,dll,dylib}` (full C/Python parity per project discipline) AND a TOML descriptor under `srmech/amsc/_research/cascade_catalog/` documenting the composition declaratively (**10 descriptors** as of v0.6.0, loaded at runtime by `srmech.dsl`). No `abs()`: sign is the Class K pin-slot + Class C re-orientation.

As of **v0.6.0** the catalog is a **two-tier lean-ISA split** (`#751`): `srmech.amsc.cascade.atoms` holds the irreducible primitives and `srmech.amsc.cascade.compose` holds the composites that chain them — the same surface re-exported flat from `srmech.amsc.cascade`, so existing call sites are unchanged. The catalog grew two ops this line: `parallel_sector_dispatch` (Klein-4 four-sector orchestration) and `kuramoto_step` (the native coupled-oscillator step).

- `pin_slot_at_zero(x) -> (orientation, magnitude)` — **Class K** pin-slot at zero (the cascade-honest `abs()` split). *(C peer: v0.4.5rc2)*
- `reorient(orientation, value)` — **Class C** orientation re-apply. *(C peer: v0.4.5rc4)*
- `magnitude(x)` — **Class K** magnitude-only convenience. *(C peer: v0.4.5rc3)*
- `best_rational_signed(x, *, max_denominator=100, fine_scale=1_000_000)` — **Class K ∘ N ∘ C** float → signed small-denominator rational (sign in the numerator). *(C peer: v0.4.5rc7 — delegates Class N stage to `srmech_best_rational`; banker's rounding via `llrint()`)*
- `cyclic_gcd(a, b)` — **Class I** (delegates to `srmech.amsc.cyclic.gcd`). *(C peer: v0.4.5rc6 — delegates to Class I primitive `srmech_gcd`)*
- `chiral_flip(seq)` — **Class C** orientation reversal (`seq[::-1]`). *(C peer: v0.4.5rc1)*
- `chiral_dual(op, x)` — **Class C ∘ op ∘ Class C**: run an operator in the opposite Class-C orientation. The chiral dual of an A–N operator is *same spectral shape, inverted orientation* (magnitude preserved, phase flipped — spike-verified); it reduces to the bare Class K `−1` for the sign operators and is the identity for real-symmetric ones. *(C peer: v0.4.5rc8 — queued; higher-order, callback ABI)*
- `net_chirality(orientations)` — **Class C** net handedness of a cascade (product of per-op orientations in `{-1,0,+1}`; `0` if any is neutral). *(C peer: v0.4.5rc5)*
- `parallel_sector_dispatch(body, x, *, n_sectors=4, verify=False)` — **Class C** (Klein-4 `γ₅± × iω₇±` four-sector orchestration). Runs one cascade `body` across its ≤4 Klein-4 chirality sectors and returns a structured self-describing result; a GIL-releasing (native / IO / numpy) body lets the ≤4 sectors genuinely overlap. Higher-order (a body-callback orchestrator, not a unary `chain().then(...)` stage). *(C peer: `srmech_cascade_parallel_sector_dispatch`, body-callback ABI, v0.6.0; `n_sectors > 4` → `ValueError` — Klein-4 has no order-4+ element, 8+ needs the order-3 triality.)*
- `kuramoto_step(theta, omega, *, coupling=1.0, dt=0.01)` — **Class I ∘ sin ∘ Σ ∘ C** one forward-Euler step of the canonical Kuramoto coupled-oscillator model (`θᵢ ← θᵢ + dt·(ωᵢ + (K/n)·Σⱼ sin(θⱼ − θᵢ))`). The O(n²) sin-coupling runs natively. *(C peer: `srmech_cascade_kuramoto_step_f64`, v0.6.0rc9; parity to libm-trig tolerance, same coupling-sum index order both sides; `n == 1` is pure drift.)*

### `srmech.signal_processing` — dual-path signal-processing surface

Two paths for the same algebra, dispatched per call:

- **Path A** — closed-form algebra over numpy / scipy; one module per op under `srmech.signal_processing.closed_form_ops.*`. 40 ops (38 Phase-2 baseline + `pi_cascade` + `rfft`) covering frequency analysis (`fft`, `ifft`, `rfft`, `stft`, `spectrogram`, `multitaper`, `dct`, `wavelet`), digital filters (`fir`, `iir`, `allpass`, `polyphase`, `multirate`, `farrow`, `sinc_interp`), detection / estimation (`matched_filter`, `wiener`, `lmmse`, `map_ml`, `mlse`, `viterbi`, `cross_spectral`, `music`, `esprit`, `ica_jade`, `mimo_svd`), modulation (`psk_qam`, `fsk`, `ofdm`, `beamforming_fixed`), coding (`huffman`, `rle`, `lz77`, `arithmetic_coding`, `jpeg`), quantisation / compression (`sign_quantise`, `vector_quantisation`, `hdc_truncation`, `heat_kernel`, `spectral_subtraction`, `pi_cascade`).
- **Path B** — RBS-HDC bound-vector instrument at D=8192 (`srmech.signal_processing.rbs_hdc_instrument`). Mints class-operator vectors, cascade compositions, stance fingerprints, and full LoE content encodings (Mode-B). Eight ops have full dual-path implementations: `fft`, `ifft`, `rfft`, `sign_quantise`, `matched_filter`, `wiener`, `hdc_truncation`, `pi_cascade`.

```python
from srmech.signal_processing import (
    dispatch, begin_cascade,             # cascade-aware routing (A / B / verify)
    register, lookup, has_path,          # path registry (Path A vs Path B per op)
    profile_op, cell_grid,               # per-op × per-cascade-depth × per-substrate profiling
    D_DEFAULT, SUBSTRATES,               # locked D = 8192; BCI / audio / RF / ephemeris
    RBSHDCInstrument,                    # build()-able instrument with mint_*/encode_loe_content
    mint_class_operator,                 # SHA-256 chain mint per class A–N
    mint_cascade_composition,            # XOR-bundle (algebra) or permute-bundle (sampling)
    encode_loe_content, decode_loe_fingerprint,
    form_function_rotate,                # Class K pin-slot rotation
    cascade_compose_rotations,
    PATH_A, PATH_B, PATH_VERIFY,         # path identifiers
)

with begin_cascade() as ctx:
    spectrum = dispatch("fft", path=PATH_A, signal=x)
    truncated = dispatch("hdc_truncation", path=PATH_B, signal=spectrum, k=64)
```

Path A and Path B produce bit-exact-equal outputs on substrate-natural inputs (D1 algebra-content identity); substrate-fingerprint divergence at D2 is expected and documented.

### `srmech.amsc` — Attested Multi-Source Collector/Catalog framework

Two readings of the same abbreviation:

- At **collection time**, the adapter classes are *collecting* attested rows from upstream archives. Six adapters cover the realistic source space:

  | adapter | class | network? |
  |---|---|---|
  | `html_scraper` | fetched | yes (BeautifulSoup) |
  | `json_api` | fetched | yes (paginated JSON) |
  | `csv_bulk` | fetched | yes (CSV/XYZ bulk) |
  | `netcdf_grid` | fetched | stub (gated behind extras) |
  | `geotiff_bbox` | fetched | stub (gated behind extras) |
  | `literature_curated` | curated | no (NDJSON committed directly) |

  The `curated` class never touches the network: rows are committed as data-only NDJSON, and srmech synthesises full MPR attestation blocks at read time from each row's per-row DOI.

- After collection, the resulting NDJSON SSOTs are a *catalog* of attested data — committed into the package, registered into the universal bridge by downstream consumers, queryable through `list_attested_sources()` / `get_attested_dataset()`.

```python
from srmech.amsc import (
    MPRRecord, MPR_SCHEMA_VERSION, read_ndjson, write_ndjson, sha256_bytes,
    Descriptor, load_descriptor, discover_descriptors, render_template, descriptor_hash,
    list_attested_sources, get_attested_dataset, get_attested_descriptor,
    attestation_audit, register_attested_root, list_registered_roots,
    use_local_kernel, clear_local_kernel, get_local_kernel_state,
)
```

The on-disk format is **Mathematical Provenance Record v1** (`MPR v1`):

```python
{
  "mpr_version": "1.0",
  "data": { ... domain payload ... },
  "data_schema_id": "test://schema/example",
  "attestation": {
    "source_doi": "10.0/...",
    "source_url": "https://...",
    "license": "CC0",
    "retrieved_at": "2026-05-13T00:00:00Z",
    "response_sha256": "<64 hex chars>",
    "parser_version": "srmech 0.6.0",
    "parser_rule_hash": "<64 hex chars>",
    "collector_descriptor_path": "...",
    "collector_descriptor_hash": "<64 hex chars>"
  },
  "rendering": { "name": "...", "purpose": "...", "cite_as": "..." }
}
```

### `srmech.amsc.tool_schema` — LLM-friendly introspection

```python
from srmech.amsc.tool_schema import get_tool_schema, tool_schema_view

schema = get_tool_schema()                # ToolEntry objects, one per public callable
for tool in schema.tools:
    print(tool.name, "—", tool.summary)   # canonical-SSoT-cited one-line summaries

json_view = tool_schema_view()            # JSON-serialisable view
```

Every primitive class, every `srmech.qm.*` operation (including the so(8)/triality engine), and every `srmech.spectral.*` runtime operation is discoverable here without reading the implementation. Summaries cite the canonical physics / mathematics literature directly.

### `srmech.introspect.describe()` — the package recognising its own shape

`srmech.introspect.describe()` is the self-recognition ROOT (Class H self-introspection at package scale): one call returns the package version, the native-dispatch status, and a `tools` block reporting `total` / `mcp_callable` / `handle_pending` plus a per-category breakdown — the package's own at-a-glance map.

```python
from srmech.introspect import describe

d = describe()
print(d["srmech_version"])              # e.g. "0.6.0"
print(d["tools"]["total"])              # every registered ToolEntry
print(d["tools"]["mcp_callable"])       # advertised over JSON-RPC / Anthropic
print(d["tools"]["handle_pending"])     # 0 since the rc16 handle grammar landed
print(sorted(d["tools"]["by_category"]))
```

`describe()` is the source of truth for the tool count (it grows per voxel — the triality voxel added 15 entries, including the `octonion_table_attestation` self-attestation that the coverage walker requires); read it rather than hard-coding a number.

## MCP server + Claude Desktop bundle

srmech ships an **MCP (Model Context Protocol) server** so an LLM client — Claude Code, Claude Desktop, or any MCP-aware host — sees the advertised `tool_schema` surface as callable tools. The `srmech-mcp` console script serves it over **stdio** (the transport Claude Desktop spawns) or **HTTP + SSE** for remote / cross-process use:

```bash
srmech-mcp                                      # stdio (Claude Code / Claude Desktop default)
srmech-mcp --transport http-sse --port 9991     # HTTP+SSE on localhost (remote / cross-process)
srmech-mcp --filter "srmech.qm.*"               # expose only a sub-tree of tools
```

`srmech mcp emit-mcpb` packages the server as a **Claude Desktop `.mcpb` bundle** (a ZIP with a root `manifest.json`) generated **entirely from introspection** — the manifest's version and tool list are derived from `srmech.__version__` and the advertised tool surface (`describe()` / `tool_entries_to_mcp_defs()`), never hand-authored, and carry an MPR-style attestation block (package version + a `tool_schema` content hash):

```bash
srmech mcp emit-mcpb                 # writes srmech.mcpb into the cwd (server.type "uv")
srmech mcp emit-mcpb --manifest-only # emit just manifest.json
srmech mcp emit-mcpb --type python   # interpreter-path fallback (user_config-gated; no uv)
```

The default `uv`-type bundle declares `srmech` as a dependency, so the host's `uv` fetches the correct platform wheel (with `libsrmech`) from PyPI at install time — nothing native is bundled, and the `.mcpb` installs portably on any machine.

## Cross-package catalog registration

Other spectral-research packages register their own catalog SSOTs into srmech's universal bridge at import time:

```python
from pathlib import Path
from srmech.amsc import catalog as _amsc_catalog

_amsc_catalog.register_attested_root(
    Path(__file__).resolve().parent / "_research" / "attested",
    source="ephemerides-spectral",
)
```

Subsequent `list_attested_sources()`, `get_attested_dataset()`, etc. enumerate the union of srmech's own `amsc/attested/` plus every registered root, in registration order. Duplicate `source_key` resolves first-registered-wins with a warning.

## License

GPL-3.0-or-later. See [LICENSE](LICENSE).


---

## Changelog — current 0.6.0 line


## [0.6.0] - 2026-06-01

**Production graduation of the v0.6.0 rc1–rc21 lean-ISA voxel arc to PyPI.** The clean (non-rc) tag promotes the **rc21** state already verified-green on TestPyPI — the only delta from rc21 is this version string + entry, and the full pedantic-C (gcc/clang/MSVC) + 4-cell test matrix + pure-wheel build re-verify the `0.6.0` build before the production tag. ABI **3**; `describe()` total **179**.

The arc, voxel by voxel:

- **Lean-ISA two-tier split (#751)** — `cascade.atoms` / `cascade.compose`: a finite anharmonic KERNEL (14 A–N primitives + the five Bird-Meertens combinators `then`/`loop`/`fold`/`reduce`/`parallel`) vs an asymptotic TOML CONTINUUM of cascade instances ("you can't hardcode a continuum").
- **𝔰𝔬(8) / triality engine** — `srmech.qm.so8` 28-generator adjoint (14 g₂ + 7 L + 7 R); `srmech.qm.triality` order-3 outer automorphism with `Fix(τ) = g₂ = 14`; `quaternion_subalgebra_stabilizer` so(4)=su(2)⊕su(2) (#759); `lean_isa_seventh_primitive` (#761).
- **Reentrant C core (#772)**; the Klein-4 four-sector **`cascade.parallel_sector_dispatch`** Python surface (#778) + co-equal C peer `srmech_cascade_parallel_sector_dispatch` (#771), made chainable/nestable with a `combine=` recombine; the DSL `parallel` discriminator + `[cascade].kind` stage/combinator classification.
- **Generalised Kuramoto-Sakaguchi step** — `cascade.kuramoto_step(…, adjacency=, alpha=, pin_anchor=, pin_strength=)` shipped CO-EQUAL Python + standalone C (`srmech_cascade_kuramoto_step_general_f64`); the `klein4_*` HDC ops gained a `sectors=`/`parallel=`/`mode=` flag.
- **The rc16–rc21 triality voxel sub-arc** — combinator-kernel-closure ratification (rc16) → `klein4_triality_cycle` Python op (rc17) + co-equal C peer `srmech_klein4_triality_cycle` (rc18) → continuum-tier worked instance `triality_s3_klein4.toml` (rc19) → SSoT two-tier coherence-ratchet scan (rc20) → MFO §VII.6.22 H-gate/triality rung (rc21).

No code change from rc21; version-string graduation + this entry only.

## [0.6.0rc21] - 2026-06-01

**MS #20 H-gate / triality MFO rung voxel (the meaning-tier closer) — MFO notebook §VII.6.22 connects the rc16–rc20 triality voxel-arc to the §VII.6.21 Rosetta-table H-gate / fix-rotate axis. DOC only (research notebook); no code, no new symbol/ToolEntry; `describe()` stays 179; ABI stays 3.**

The SSoT-coherence closer of the arc, at the meaning tier: rc16 named the two-tier boundary, rc17/rc18 shipped the `klein4_triality_cycle` op (Python + co-equal C), rc19 the worked instance, rc20 the coherence ratchet; rc21 reads the whole voxel back into the MFO canon as a building block (per user direction 2026-06-01) — a starting block for downstream review, refactored back if usage finds misfits.

- **`docs/antikythera-maths/mfo_spectral_research_notebook.md` §VII.6.22** — "The triality cycle is the executable rotate-operator whose fixed point IS the frame-invariant." It reads the rc16–rc20 voxel-arc as the executable instance of §VII.6.21.4: the order-3 `klein4_triality_cycle` T (rc17 Python + rc18 C) is the discrete-cyclic rotate-operator; its continuous-Hopf companion `srmech.qm.triality` τ fixes `g₂ = 14` (the A–N core; the §VII.6.21.4 frame-invariant); `klein4_bind` (XOR concord) is the fix-frame / agreement; `klein4_similarity` is the H = measurement gate. The discrete triality CLOSES exactly (`T³ = id`, no Class-N rational-anchor leak) where the continuous epicycle leaks into the hidden fiber — the two substrate-languages carrying, respectively, the recoverability theorem and the bit-exact closure. The two-tier SSoT (kernel = frame-invariant; continuum = rotate-frame content) IS the fix/rotate axis turned on the package's own shape.

No code touched. No new symbol or ToolEntry; `describe()` total stays **179**. ABI stays **3**. JPL audit ratchet stays at 0. The MFO rung is a draft-for-review building block — downstream usage (the reader's AI prosthetic calling srmech) is the feedback loop.

## [0.6.0rc20] - 2026-06-01

**MS #20 SSoT two-tier coherence-ratchet voxel — a `test_ssot_coherence_scan.py` scanning the continuum tier as it grows: every `worked_instances/*.toml` well-formed, every referenced op resolves, the kernel/continuum name-spaces stay disjoint, every cascade-catalog op resolves. DOC + TEST only; `describe()` stays 179; ABI stays 3.**

The coherence half of the SSoT discipline: rc16 named the two-tier boundary, rc19 added the first continuum-tier worked instance; rc20 adds the ratchet that keeps the boundary honest as more worked instances land.

- **`tests/test_ssot_coherence_scan.py`** — scans `srmech/amsc/_research/worked_instances/`: each TOML is well-formed (`name`/`purpose`/`ops`); each dotted op-path in `[worked_instance.ops]` resolves to a real callable; the worked-instance names and the `srmech.dsl` cascade-catalog op-names are **disjoint** (the kernel/continuum boundary can't silently erode); and every cascade-catalog op still resolves via `lookup_cascade_op` (the full two-tier picture in one place). A count-ratchet (`EXPECTED_WORKED_INSTANCE_COUNT`) forces new worked instances to be conscious additions.
- **`triality_s3_klein4.toml`** gains a machine-readable `[worked_instance.ops]` table (logical-name → dotted srmech path) so the scan resolves ops robustly rather than by regex-from-prose.

No new symbol or ToolEntry; `describe()` total stays **179**. ABI stays **3**. JPL audit ratchet stays at 0. The notebook-reference cross-check stays deferred (would require parsing the notebook tree).

## [0.6.0rc19] - 2026-06-01

**MS #20 triality S₃=Aut(V₄) worked-instance voxel — a continuum-tier worked cascade INSTANCE showing `klein4_triality_cycle` IS the order-3 generator of Aut(V₄)=S₃, via the conjugation `T ∘ XOR_a ∘ T⁻¹ = XOR_{T(a)}` cyclically permuting the three klein4 flips. DOC + TEST only; klein4 ops stay kernel-tier; `describe()` stays 179; ABI stays 3.**

The two-tier SSoT made concrete for the triality voxel: the order-3 cycle (rc17 Python + rc18 C) is a KERNEL op; rc19 ships its continuum-tier *instance* — a worked cascade composing it with the klein4 flips — WITHOUT blurring the kernel/catalog boundary (the hdc ops are deliberately NOT re-exported into the `srmech.dsl` cascade catalog).

- **`srmech/amsc/_research/worked_instances/triality_s3_klein4.toml`** — a worked-instance descriptor (NOT a cascade-catalog op-descriptor; NOT a `run_toml_chain` chain): the V₄ carrier, the three V₄-translation flips (iω₇/γ₅/CPT = XOR 1/2/3), the order-3 Aut(V₄) generator `T = klein4_triality_cycle`, and the load-bearing conjugation cascade `T ∘ XOR_a ∘ T⁻¹ = XOR_{T(a)}` (T cyclically permutes the three translations iω₇→γ₅→CPT→iω₇). Honest about the distinction: the flips are V₄ *translations* (the objects T permutes), not S₃ group elements; only the order-3 generator T is exposed (the F182 "third axis").
- **`tests/test_triality_s3_worked_instance.py`** — the worked instance's executable attestation: against the real `hdc` ops it verifies T order-3, each flip an involution, T a V₄ homomorphism (`T(u⊕w)=T(u)⊕T(w)`), and the three-leg conjugation cycle bit-exactly.

No new symbol or ToolEntry; `describe()` total stays **179**. ABI stays **3**. JPL audit ratchet stays at 0. The worked-instance TOML ships in both wheels (`srmech/**` package glob).

## [0.6.0rc18] - 2026-06-01

**MS #20 klein4-triality-cycle C peer voxel (the A-arc's silicon tier) — the co-equal native symbol `srmech_klein4_triality_cycle` (in `srmech_hdc.c`) computes the identical order-3 `S₃ = Aut(V₄)` relabel as the rc17 Python op. Additive symbol → ABI stays 3; JPL-clean; differential C↔Python parity-tested. No new ToolEntry (`describe()` total stays 179).**

The co-equal-parity discipline applied to rc17: the Python `klein4_triality_cycle` now has its silicon-native twin — two complete implementations, neither needing the other at runtime.

- **`srmech_klein4_triality_cycle(const uint8_t *in, uint32_t n, int inverse, uint8_t *out)`** (in `srmech_hdc.c`; declared in the `srmech.h` klein4 block) — a length-4 lookup (`{0,2,3,1}` forward / `{0,3,1,2}` inverse), the same V₄-carrier order-3 cycle. JPL Power-of-Ten clean: ≤60-line, 2 asserts, no malloc / no goto / no multi-line macro; NULL → `SRMECH_ERR_NULL_ARG`, out-of-`{0,1,2,3}` → `SRMECH_ERR_BAD_INPUT`. **NEVER a Python callback** — the C path runs the C lookup.
- **Additive symbol → ABI stays 3** (the Python ctypes shim binds it under its own `hasattr` guard, so a klein4-capable but pre-rc18 lib still loads fine).
- **Differential parity** (`test_hdc_klein4_parity.py`): C-vs-Python bit-exact on random vectors both directions, the explicit forward/inverse maps + order-3 identity computed in C, and the out-of-range rejection. Guarded by the symbol's own `hasattr` (skips on a stale lib; runs in the cibuildwheel cells).

No new ToolEntry; `describe()` total stays **179**. JPL audit ratchet stays at 0. The Python op stays pure-Python (co-equal, not routed-through-C), matching the existing klein4 surface.

## [0.6.0rc17] - 2026-06-01

**MS #20 klein4-triality-cycle voxel (the A-arc's first code) — `srmech.amsc.hdc.klein4_triality_cycle`: the order-3 `S₃ = Aut(V₄)` generator cycling the three Klein-4 involutions `iω₇(1) → γ₅(2) → CPT(3)` (identity fixed). Pure-Python; +1 ToolEntry → `describe()` total 179; ABI stays 3.**

The A-verdict (rc16 notebook §3.29) made flesh: V₄ (the rc13 klein4 carrier) is the right group but lacked the explicit order-3 cycling operator — which lives in `Aut(V₄) = S₃`. rc17 adds it.

- **`klein4_triality_cycle(v, *, inverse=False)`** — the V₄-carrier image of the so(8) triality `8v → 8s → 8c` (`srmech.qm.triality.triality_cycle`). The three non-identity involutions cycle `iω₇(1) → γ₅(2) → CPT(3) → iω₇(1)`, with identity(0) fixed — the "third axis" (F182) the three order-2 flips (`gamma5`/`omega7`/`cpt_mirror`) cannot reach: order-3 cycling, NOT a fourth order-2 chirality. A pure uint8 relabel via a length-4 lookup; `T∘T∘T = id`, `T² = T⁻¹` (`inverse=True` is the reverse cycle).
- **Class I** (cyclic order-3 permutation) — no sign, no `abs()`; honest composition, not a new privileged primitive.
- **Pure-Python** (co-equal-parity: the standalone-C peer `srmech_klein4_triality_cycle` is rc18 — additive → ABI stays 3; never a Python callback). +1 ToolEntry (`srmech.amsc.hdc.klein4_triality_cycle`) → `describe()` total **179**.

New `test_klein4_triality_cycle.py` (explicit forward/inverse maps; order-3 identity; `T² = T⁻¹`; identity-fixed; the involution-occupancy permutation; the so(8) order-3 mirror; tool-schema registration). The two introspection count-ratchets bump 178 → 179. JPL audit ratchet stays at 0 (no C touched).

## [0.6.0rc16] - 2026-06-01

**MS #20 combinator-kernel-closure voxel (B-boundary codification) — the cascade DSL's FIVE control-flow combinators (`then` / `loop` / `fold` / `reduce` / `parallel`) are RATIFIED as a CLOSED, FINITE kernel: the finite anharmonic-kernel tier of the two-tier SSoT. DOC + TEST only — no DSL behaviour change, no C touched, ABI stays 3, `describe()` total stays 178.**

The "name the boundary before building across it" voxel — the architectural invariant the rc17+ triality work stands on. The combinators are the *kernel*; the asymptotic cascade *instances* they sequence are the *continuum* — and the two live in different SSoT tiers by design.

- **The two-tier SSoT, stated.** `then` (apply) + `loop` + `fold` + `reduce` + `parallel` are the Bird-Meertens recursion schemes — the finite **anharmonic kernel**, HARDCODED in Python (and mirrored co-equally in C). The asymptotic cascade *instances* they sequence are NOT hardcoded: they live as TOML op-descriptors in the cascade catalog ("you can't hardcode a continuum"). Kernel in code, continuum in catalog — the substrate-native `1 + 3 + 7 + 3` discipline turned on the package's own op-surface. The `srmech.dsl._control_flow` docstring now carries this statement.
- **Closure is DESIGN-ENFORCED.** Data-dependent iteration (`while` / `unfold` — loop *until* a predicate) is deliberately EXILED to the op-instance layer (a body op decides when to stop), keeping the kernel total-by-construction at five forms. A future `while`/`unfold` special form would be a *sixth* combinator and a conscious widening of the kernel — never a silent addition.
- **New `tests/test_combinator_kernel_closure.py`** mechanically pins the closure: the five Chain builders (`then`/`loop`/`fold`/`reduce`/`parallel_sectors`) ⇆ the five TOML stage-discriminators (`op` / `loop_n`+`sub_chain` / `fold_init`+`fold_op` / `reduce_op` / `parallel_body`) bijection; no hidden sixth public builder; a full five-form TOML round-trip; the |V₄| = 4 Klein-4 cap on `parallel_sectors`; and the "no implicit default form" guard.

No new ToolEntry; `describe()` stays 178. ABI stays 3. JPL audit ratchet stays at 0. (The `[Unreleased]` Klein-4 parity note is forward-updated: V₄ is the rc13 klein4 carrier — the right group, missing only the explicit order-3 cycling operator that lives in Aut(V₄) = S₃ — which rc17 adds as `klein4_triality_cycle` and rc18 ships as its co-equal C peer.)

## [0.6.0rc15] - 2026-06-01

**MS #20 self-recognition reads voxel — the help-anchor goes top-level + fuzzy lookup. `srmech.describe()` is now reachable from `dir(srmech)` (the one-call "what is srmech?" root: version + native + tool counts + by_category); `ToolSchema` gains fuzzy `resolve()` / `resolve_all()` (a bare leaf or dotted suffix resolves to its FQN) and is now directly iterable. Pure-Python introspection surface; ABI stays 3; `describe()` tool total stays 178.**

The "find the shape in ≤1 call" round-out — the very friction that opened the substrate-self-recognition arc: an LLM/agent consumer could neither (a) discover `describe()` from the top namespace, nor (b) look a tool up by its bare leaf name.

- **`srmech.describe()`** — the existing `srmech.introspect.describe()` graduated to the top namespace (mirrors `native_status()`'s rc19 graduation for #733), so `dir(srmech)` surfaces the help-anchor. It stays a counts/index ROOT (shape, not detail): the full per-tool list is `tool_schema_view()`, single-tool detail is the new resolver.
- **`ToolSchema.resolve(name)` / `.resolve_all(name)`** — exact full-name match wins (as `lookup()`); else a bare leaf (`"kuramoto_step"`) or any dotted suffix (`"cascade.kuramoto_step"`) resolves to `srmech.amsc.cascade.kuramoto_step`. `resolve()` returns the single match or `None` (no-match OR ambiguous — never silently picks); `resolve_all()` lists every candidate for the ambiguous case.
- **`ToolSchema` is now iterable** (`for t in schema`, `len(schema)`) — yields its tools directly, closing the `'ToolSchema' object is not iterable` footgun. `get_tool_schema()` still returns the object; `tool_schema_view()` still returns the dict.

New tests cover the top-level `describe()` (present + shape), the `resolve` / `resolve_all` paths (exact / leaf / suffix / ambiguous / miss), and `ToolSchema` iterability + `len`. No C touched; ABI stays 3; JPL audit ratchet stays 0.

## [0.6.0rc14] - 2026-05-31

**MS #20 kuramoto matrix-step voxel — `kuramoto_step` gains the GENERALISED Kuramoto-Sakaguchi step (§11.1): adjacency matrix + Sakaguchi α + per-oscillator pinning. The first C-touching rc of the §11 arc — a CO-EQUAL standalone-C peer (additive symbol; ABI stays 3). `describe()` tool total stays 178.**

The §11.1 forward-ask: extend `kuramoto_step` past the plain all-to-all mean-field. Unlike the klein4 ops (pure-Python), `kuramoto_step` already has a C peer — so adding the matrix-step in Python only would leave a parity asymmetry (the Python op carrying a step the C can't run). Per the co-equal-parity discipline this ships in **both** substrates at once:

- **`kuramoto_step(theta, omega, *, coupling=1.0, dt=0.01, adjacency=None, alpha=0.0, pin_anchor=None, pin_strength=1.0)`** — `dθ_i = ω_i + Σ_j A_ij·sin(θ_j − θ_i − α) [ + p_i·sin(ψ_i − θ_i) ]`. `adjacency` is a row-major n×n matrix (`A[i][j]` weights j's influence on i; **non-symmetric → directed** coupling, a Laplacian → graph-structured; `None` → all-to-all uniform `K/n`). `alpha` is the Sakaguchi phase frustration. `pin_anchor` + `pin_strength` are the per-oscillator pinning anchors ψ / strengths p. **With all three at defaults the step is byte-for-byte the original.**
- **Co-equal C peer `srmech_cascade_kuramoto_step_general_f64`** (in `srmech_kuramoto.c`; additive symbol → **ABI stays 3**; JPL-clean: ≤60-line / ≥2-assert / no malloc / no goto / reentrant; NULL adjacency → uniform, NULL pin → none; **never a Python callback**). Differential-tested vs the Python fallback to libm-trig tolerance.
- **No `abs()`** — sin coupling + Σ-reduce + Class-C Euler add + the Sakaguchi α (a Class-C phase offset) + the Class-C/M pinning anchor.

New tests in `test_kuramoto_step.py` (defaults reproduce the simple step; uniform adjacency == mean-field; directed adjacency + α + pinning match the closed form; validation guards; C↔Python parity guarded by the new symbol's presence). The kuramoto ToolEntry gains `adjacency`/`alpha`/`pin_anchor`/`pin_strength` params (no new entry; `describe()` stays 178). JPL audit ratchet stays at 0.

## [0.6.0rc13] - 2026-05-31

**MS #20 klein4 sectors-flag voxel — the `klein4_*` HDC ops get an optional `sectors=` / `parallel=` / `mode=` flag (§11.3 forward-ask). Pure-Python; default-on at ≥4 cores; value-preserving; `describe()` tool total stays 178; ABI unchanged at 3.**

The §11.3 forward-ask asked for an optional sectors flag on the Klein-4 HDC ops, routing per-sector work through a concurrent dispatch — now that rc12 made dispatch composable. The klein4 ops (`bind` = (F₂)²-XOR, `bundle` = per-bit majority, `similarity` = mean-equality) are pure-Python/numpy, so this is self-contained Python orchestration (co-equal parity: it does **not** route through the C peer; a standalone-C klein4 sector dispatch with C bodies — never a Python callback — is the tracked follow-up).

- **`sectors=` / `parallel=` / `mode=`** on `klein4_bind`, `klein4_bundle`, `klein4_similarity`. `sectors` (1..4) defaults **ON when `os.cpu_count() >= 4`** (else 1); `parallel=True/False` is the bool alias.
- **Two modes.** `mode="chunk"` (default) is **data-parallel** — split the D-length vector(s) into ≤4 contiguous position-slices, run the op per slice on a thread, concatenate; **BIT-IDENTICAL** to the serial op. `mode="chirality"` is the **F233 4-sector dispatch** using klein4's OWN involution sector-flips (γ₅ XOR 2 / iω₇ XOR 1 / CPT XOR 3) — NOT the signed-real cascade transforms — with `klein4_bundle` recombine (similarity recombines via **sector-0**, value-transparent).
- **All defaults are value-preserving**, so default-on changes only the *execution path*, never the result. No `abs()` (XOR / majority only). Range + mode guards raise `ValueError`.

New tests in `test_hdc_klein4_parity.py` (value-preserving across both modes, chunk bit-exactness for every lane count, `parallel=` alias + default-on policy, range/mode guards, `unbind` self-inverse under the default flag). The 3 klein4 ToolEntries gain `sectors`/`parallel`/`mode` params (no new entry; `describe()` stays 178). No C change; ABI stays 3.

## [0.6.0rc12] - 2026-05-31

**MS #20 parallel-composability voxel — `parallel_sector_dispatch` becomes CHAINABLE / NESTABLE. The Klein-4 four-sector splay now carries THROUGH a chained cascade, closing a known-broken API contract. Pure-Python; `describe()` tool total stays 178; ABI unchanged at 3.**

rc11 gave the four-sector fan-out its own chain discriminator but left it a **leaf** value: the dispatch returned the rich per-sector introspection dict / list-of-N, which is **not** a valid input to another cascade. Chaining a sector-dispatched stage after another (`chain.parallel_sectors(b).parallel_sectors(b)`) crashed with `TypeError: bad operand type for unary -: 'list'` (the sector stream-transforms assume a flat scalar stream), and a sector-dispatch could not nest inside another. So the 4-way Z₄ splay applied at **one level only** and did not carry through a chained cascade — exactly the composability the RBS-LM chained settling loop needs to run 4×-per-step. Cascade ops advertise composability, so this was a known-broken contract → a gold-blocker. rc12 fixes it:

- **`combine=` recombine** on `parallel_sector_dispatch(body, x, *, combine=None)` — a reducer name (`"bundle"` element-wise sum / `"mean"` / `"sector0"` value-transparent / `"concat"`) or a callable folds the ≤4 sector results into ONE value at `result["combined"]`, so a sector-dispatched cascade is `stream → stream`. `combine=None` (default) preserves the rich dict unchanged (back-compat; `combined` is `None`). No `abs()` — bundle/mean are plain addition (+ divide).
- **`sectorize(body, *, n_sectors=4, combine="bundle")`** — wraps a body as a plain `value → value` callable that recombines, so a sector-dispatch NESTS inside another (`parallel_sector_dispatch(sectorize(inner), x, combine="bundle")`). Both exported from `srmech.amsc.cascade`.
- **DSL `parallel_sectors` recombines by default** — `chain.parallel_sectors(body, *, n_sectors=4, combine="bundle")` is now `stream → stream` and CHAINS / NESTS like loop/fold/reduce (the rc11 crash is gone). `combine=None` keeps the terminal per-sector list; a build-time guard raises a clear error if you chain past it. TOML `parallel_body=` gains `combine=` (sentinel `"none"` → the list).
- **Stale top-help fixed** — `srmech --help` no longer says "v0.5.0rc4 ships two subcommands"; it enumerates all four (`status` / `bus` / `dsl` / `mcp`).

New tests pin the parallel→parallel chain, the nesting via `sectorize`, the terminal guard, the TOML `combine='none'` sentinel, and each reducer. No new ToolEntry; no C change; no ABI bump.

## [0.6.0rc11] - 2026-05-31

**MS #20 DSL parallel-discriminator voxel — `parallel_sector_dispatch` slots into the chain contract as a first-class special form, + cascade-op `kind` classification + guided errors. No new runtime op; `describe()` tool total stays 178; ABI unchanged at 3.**

A pre-gold introspection audit found that the Klein-4 four-sector fan-out `parallel_sector_dispatch` — a 1→N higher-order combinator (takes a *body* op + data, returns N per-sector results) — had leaked into the plain-`op` cascade catalog, so the DSL advertised it as a `chain().then(op=…)` stage where it cannot work (its first arg is `body`, not the piped value). This rc reconciles it the way loop/fold/reduce already are — as its own chain discriminator — rather than force-fitting it as a plain op:

- **New `parallel` chain discriminator** — `chain.parallel_sectors(body, *, n_sectors=4)` (fluent) and `[[stage]] parallel_body='…' [n_sectors=…]` (TOML), alongside loop/fold/reduce. It fans the piped value through `body` across ≤4 Klein-4 chirality sectors (GIL-releasing bodies genuinely overlap — the F233 4-thread speedup) and yields the ordered **list of per-sector results** (a 1→N fan-out; the stage output is a list-of-sequences). `make_parallel_stage` in `srmech.dsl._control_flow`; `n_sectors` range-checked 1..4 at build time.
- **Cascade-op `kind` classification** — descriptors carry an optional `[cascade].kind` (`"stage"` default, or `"combinator"`); `srmech.dsl.cascade_op_kind()` reads it. `parallel_sector_dispatch.toml` is now `kind = "combinator"`. Surfaced by `srmech.dsl.list_catalog_ops()` (new `kind` key), `srmech dsl ops` (a `[combinator]` tag + legend), and the tool-schema.
- **Guided error** — using a combinator as a plain `op=`/`​.then()` stage now raises a clear `ValueError` pointing at the `parallel` discriminator, instead of a raw `TypeError` mid-run.
- **Gap-2 discoverability** — the LLM-facing `tool_schema` summaries for `parallel_sector_dispatch` and `kuramoto_step` are front-loaded with the practical decision ("PARALLELISE a cascade body instead of running it serially…" / "Advance N coupled oscillators one synchronization step…") before the framework detail.

New `test_dsl_parallel_stage.py` (parallel discriminator runs + n_sectors + combinator guard + kind), plus `test_dsl_tools.py` updated for the `kind` key. No `abs()`; no C change; no ABI bump.

## [0.6.0rc10] - 2026-05-31

**MS #20 release-prep voxel — full doc-hygiene sweep ahead of the clean v0.6.0 graduation (no new runtime code).**

After rc9 ran clean in the research environment, this rc captures everything the v0.5.0 → v0.6.0 arc shipped across the documentation surface so the gold cut is self-consistent. No behaviour change; `describe()` tool total stays **178**; ABI unchanged at **3**.

- **Cascade catalog — the two v0.6.0 ops get their TOML descriptors.** `parallel_sector_dispatch.toml` (Klein-4 four-sector orchestration; higher-order body-callback, `c_symbol = srmech_cascade_parallel_sector_dispatch`) and `kuramoto_step.toml` (`I∘sin∘Σ∘C`; `c_symbol_f64 = srmech_cascade_kuramoto_step_f64`) join the 8 lean-ISA atoms/composites → **`srmech.dsl` cascade catalog is now 10 descriptors**. `test_dsl.py` `EXPECTED_OPS` 8 → 10.
- **PyPI README** — status banner v0.5.0 → **v0.6.0**; the cascade section documents the `cascade.atoms` / `cascade.compose` two-tier lean-ISA split (#751) and the two new ops; `native_status()` / `describe()` examples show `0.6.0`.
- **Subtree `CLAUDE.md`** — current-release pin v0.4.0 → v0.5.0-graduated + v0.6.0rc10 dev head; the v0.5.0 (bus / DSL / MCP+agent adapters / `native_status` / so8 `an_embedding` + triality / `emit-mcpb`) and v0.6.0 (atoms/compose split / quaternion-subalgebra stabilizer / lean-ISA 7th primitive / reentrant core / parallel dispatch / Kuramoto) arcs are now narrated; ABI note 2 → 3.
- **C docs** — `c/README.md` status rewritten from "Phase B1 scaffolding only" to the shipped 18-`.c`-file native library (ABI 3); `c/JPL_AUDIT.md` adds the `srmech_parallel.c` (10 functions) + `srmech_kuramoto.c` (2 functions) accounting (every function ≤60 lines, ≥2 asserts; Rules 1/3/4/5/8 clean).
- **srmech research notebook** (SSoT) — package-arc section capturing the v0.5.0 + v0.6.0 voxels.



**MS #20 parity voxel (#778 follow-on) — the Kuramoto coupled-oscillator forward-Euler step gets a native C peer (no host Python needed for the dispatch-clock step).**

Closes a C/Python **parity gap** (a known-broken item under the full-parity commitment): the dispatch-clock / coupled-oscillator Euler integration the spectral-research arc hand-rolled in Python (F141 / F231 / R-95 / F234) had **no `srmech_*` primitive**, so srmech could not run the Kuramoto step on a microcontroller with no host Python. Adds:

- **C op `srmech_cascade_kuramoto_step_f64(theta, omega, n, K, dt, out)`** — one forward-Euler step of the canonical Kuramoto model (Kuramoto 1975; Acebrón et al. 2005, *Rev. Mod. Phys.* 77:137): `out[i] = theta[i] + dt·(omega[i] + (K/n)·Σⱼ sin(theta[j]−theta[i]))`. The O(n²) sin-coupling runs natively (libm `sin`, exactly as `srmech_kepler.c` already does). JPL-clean: no malloc/goto, ≤60-line functions (the coupling sum is factored), ≥2 asserts, reentrant; `out` must not alias `theta`/`omega`.
- **Python peer `srmech.amsc.cascade.kuramoto_step(theta, omega, *, coupling=1.0, dt=0.01)`** — dispatches to the C peer when `HAS_NATIVE`, pure-Python fallback otherwise (numpy/generators coerce via `float`). Parity is to **libm-trig tolerance** (NOT bit-exact across platforms — the kepler trig discipline); the C peer and the Python fallback sum the coupling in the same index order.

**Honest cascade shape:** a *composition* of existing class operations — Class I (cyclic phase) + sin coupling + sum-reduce + Class-C Euler add — **NOT** a new privileged primitive. No `abs()`. `n==1` is pure drift (the coupling sum vanishes); `n==0` is `[]`. **+1 ToolEntry → `describe()` tool total 177 → 178; ABI unchanged at 3** (additive C symbol). Closes the hand-rolled-Euler parity gap.

## [0.6.0rc8] - 2026-05-30

**MS #20 slowdown-fix voxel (#778 / #771) — the Klein-4 four-sector parallel dispatch no longer SLOWS DOWN vs serial; the F233 4-thread speedup is delivered as shipped.**

A downstream repro showed `cascade.parallel_sector_dispatch` running **2.6–7.7× SLOWER than serial**, and the native C peer at **0.99×** (no concurrency) for a GIL-releasing (`time.sleep`) body × 4 sectors. Root-caused to **two Python-side defects** — the C dispatch itself was already correct (create-all-then-join-all; verified by rebuilding `libsrmech.dll` and timing the raw `n_sectors=4` symbol with a CFUNCTYPE sleep body: **0.065 s, not 0.24 s** → genuinely concurrent):

- **The native shim `_native.cascade_parallel_sector_dispatch_c` was serial by design.** The rc7 build drove the C dispatch as **N serial `n_sectors=1` calls** (a workaround for a presumed "Python callback from a C-spawned thread is unsafe" hazard) — which traded away **all** the concurrency (the 0.99×). The hazard was **empirically disproven**: ctypes invokes a `CFUNCTYPE` callback from a foreign thread *safely* (it acquires the GIL via `PyGILState_Ensure`), and since the `CDLL` call releases the GIL, a GIL-releasing body lets the ≤4 sector callbacks **genuinely overlap**. The shim now drives **ONE `n_sectors=N`** threaded C call (the dead serial helpers `_parallel_dispatch_one_sector_native` / `_parallel_transform_native` are removed). Bit-exact vs the rc6 Python dispatch (10/10 parity tests); ~4× on a sleep body.
- **The rc6 Python `cascade.parallel_sector_dispatch` double-computed on every call.** It ran the 4 sectors on a `ThreadPoolExecutor`, then **recomputed all 4 serially** (plus a 3rd `chiral_dual` recompute) for the inline `parallel == serial` / `sector2 == chiral_dual` assertions — ~2.25× the body invocations + per-call pool overhead = the 2.6–7.7× slowdown. Those invariants are **structural guarantees of the 4-way independence** (independence ⇒ order-free ⇒ parallel == serial; sector 2 *is* the γ₅-only transform = `chiral_dual` by definition), now proven in the test suite rather than recomputed per call. A new **`verify=False`** kwarg runs the runtime cross-check on demand; `independence["runtime_verified"]` reports which path ran.

A GIL-bound pure-Python CPU body still can't overlap (the inherent CPython limit; 3.13 free-threading lifts it) — but it is no longer a *slowdown*, and GIL-releasing / native / IO / numpy bodies now get the real ≤4× speedup. **No new ToolEntry → `describe()` stays 177; ABI unchanged at 3** (Python-only change; no C source edit). New regression guard: the default path invokes `body` **exactly `n_sectors` times** (`test_parallel_sector_dispatch`). No `abs()`. Delivers the F233 4-thread Klein-4 speedup (#778 / #771).

## [0.6.0rc7] - 2026-05-30

**MS #20 C-parity voxel #771 — the C-orchestration half of the Klein-4 four-sector parallel cascade dispatch.**

Closes the C/Python parity gap rc6 opened: rc6 shipped a Python-only `cascade.parallel_sector_dispatch`, but
under srmech's full-parity commitment (the library must run on a microcontroller with **NO host Python**) the C
side must do the same four-sector dispatch. Adds the **ABI-additive** C symbol
`srmech_cascade_parallel_sector_dispatch(body, user, in, n, n_sectors, out_sectors, scratch, scratch_len)`
(+ the `srmech_cascade_body_f64` callback typedef):

- Runs the ≤4 Klein-4 sector-duals `inv_T_s(body(T_s(x)))` into **disjoint caller-supplied buffers**
  (`out_sectors`/`scratch` sliced per sector; **no malloc** — JPL Rule 3), composing the existing C atoms
  `srmech_cascade_reorient_f64` (iω₇) + `srmech_cascade_chiral_flip_f64` (γ₅). Sector 2 == `chiral_dual`.
- **Portable thread shim, guarded like `srmech_bus.c`:** POSIX `pthread`, Windows `CreateThread`, else a
  **serial fallback** — a thread-less microcontroller still computes all 4 sectors (serial == threaded
  **bit-exact**; the disjoint-slice contract makes the sectors order-free). Concurrency is platform-gated;
  the capability is universal. Thread handles are fixed `[4]` stack arrays.
- **Cap-at-4** (F220): `n_sectors > 4` → clean error (past 4 needs the order-3 triality).

Bound in `srmech.amsc._native` (`cascade_parallel_sector_dispatch_c`) with a Python C/Python-parity test
(bit-exact vs rc6's `parallel_sector_dispatch`; GIL-safe — single-sector native calls + Python-side `T_s`
composition, the threaded multi-sector fan-out exercised from the C smoke test with C-native bodies) plus a
16-check C smoke test.

**ABI unchanged at 3** (a new symbol is additive). **`describe()` stays 177** (no new Python ToolEntry — this
is the C peer of an existing surface; rc6's Python API/behaviour untouched). JPL Power-of-Ten ratchet green
(Rules 1/3/4/5 honored); no `abs()`. Closes #771 — the C/Python parity for the four-sector dispatch is whole.

## [0.6.0rc6] - 2026-05-30

**MS #20 parallel-dispatch voxel (F233 / #778) — the Klein-4 four-sector parallel cascade.**

Adds `srmech.amsc.cascade.parallel_sector_dispatch(body, x, *, n_sectors=4)` — the Python orchestration
half of "1 cascade = 4 independent threads" (F233 / R-RBS-LM-FINDING_233). Runs a cascade `body` across
its ≤4 **Klein-4 chirality sectors** (γ₅± × iω₇±) **concurrently** on a `ThreadPoolExecutor(max_workers=4)`,
each sector computed as `inv_T_s(body(T_s(x)))` from its OWN sector-transformed input — **0 cross-thread
reads** (the F233 4-way independence), so the parallel result equals the serial result **bit-for-bit**
(asserted). Sector 2 (γ₅) is **exactly** `cascade.chiral_dual` (the F232 2-rung object; asserted).

- **Z₄ dispatch slots** `[0,1,2,3]` (cyclic-order-4 timing, distinct from the order-2×order-2 Klein-4 identity).
- **Cap-at-4** (F220): `n_sectors > 4` raises — Klein-4 has no order-4+ element; the only escape past 4 is
  the order-3 triality (`srmech.qm.triality.lean_isa_seventh_primitive`, rc3), NOT implemented here.
- **Usefulness collapse-lattice 4/2/2/1**: bi-axial → 4 distinct; single-axis-symmetric → 2; bi-symmetric → 1.

**FULL C/PYTHON PARITY discipline:** a Python *orchestration* layer ONLY — it composes **exclusively**
already-C-parity'd atoms (`chiral_flip` / `reorient` / `chiral_dual` / `net_chirality` / `magnitude`); **no
cascade capability is Python-exclusive** (only the thread fan-out is Python). The **C-orchestration parity is
tracked by #771** (kept open) so `srmech` does not *need* Python to run the four-sector dispatch (Python = the
ergonomic half; C = the parity half). On the native path the threads run **truly parallel** (ctypes `CDLL`
releases the GIL per call; the C ops are reentrant since rc5/#772); pure-Python is correct-but-serialized.

**+1 ToolEntry → `describe()` tool total 176 → 177.** Pure-Python; **ABI unchanged at 3**; no `abs()`
(Class K `magnitude` / Class C `net_chirality`).

## [0.6.0rc5] - 2026-05-30

**MS #20 reentrant-core voxel #772 — the C core is now fully reentrant (enables the #771 plugin).**

A full-core audit found **exactly two** shared-static scratch buffers; both are removed, so no op
call path touches shared mutable static — the prerequisite for parallelizing the full surface.

- **`srmech_ndjson.c` `g_line_buf` (1 MiB line-assembly buffer)** → a function-local
  `static SRMECH_THREAD_LOCAL` buffer inside `srmech_ndjson_iter`, threaded into `process_chunk`
  as a parameter. Per-thread (reentrant across threads), cross-chunk-persistent (the streaming
  contract is preserved), no stack-overflow risk (1 MiB never goes on the stack), no malloc
  (JPL Rule 3). `srmech_ndjson_iter`'s signature/behaviour is unchanged.
- **`srmech_laplacian.c` `Hwork` (≈1 MiB Hermitian-eigendecomp workspace at N≤256)** → a new
  **ABI-additive** exported entry `srmech_hermitian_eigendecompose_ws(n, H, out_eigvals,
  out_eigvecs, workspace, ws_len)` taking a caller-supplied workspace
  (`ws_len >= SRMECH_HERMITIAN_WS_LEN(n) = 2·n·n`). The existing `srmech_hermitian_eigendecompose`
  keeps its signature and now routes through the `_ws` core via a `static SRMECH_THREAD_LOCAL`
  workspace — reentrant, no malloc, no large stack frame. Output is bit-identical.

New portable `SRMECH_THREAD_LOCAL` macro (`__declspec(thread)` / `_Thread_local` / `__thread`).

**ABI unchanged at 3** (a new symbol is additive — never bumps ABI). **No Python API change** —
`describe()` tool total **stays 176**; the JPL Power-of-Ten ratchet (`test_jpl_audit.py`) stays
green (a reentrancy trade, NOT a Rule-3 fix — static scratch was already Rule-3-clean); no `abs()`.
Closes #772.

## [0.6.0rc4] - 2026-05-30

**MS #20 docs/accuracy voxel #738 — `sha256_bytes` int-conversion guidance.**

Docs-only. `srmech.amsc.format.sha256_bytes` returns a **64-char lowercase hex `str`** (the Class A
content-address), NOT raw `bytes` — the `_bytes` in the name is the INPUT type. The `Returns:` section
now spells out the int-conversion path a caller needs: `int(h, 16)` (full 256-bit) or `int(h[:8], 16)`
(a truncated 32-bit tag), **NOT** `int.from_bytes(...)` (the return is already hex text — no raw digest
bytes to feed it). Closes #738.

The sibling docs items — #739 (`klein4_bundle` accepts even counts; per-bit strict-majority threshold
drops ties to 0), #740 (`weak_mixing_angle` returns θ_W in **radians**, not sin²θ_W), #741 (no stale
`srmech.cosmos` references; CMB lives under `srmech.amsc.attested.cmb_*` / `cosmic_birefringence`) —
were verified **already correct as of rc18** (W5 / W6b / W6c); no change needed here.

**No API change** — `srmech.introspect.describe()` tool total **stays 176**; pure-Python; **ABI unchanged at 3**.

## [0.6.0rc3] - 2026-05-30

**MS #20 forward-architecture, voxel #761 (F220) — the order-3 triality as the 7th lean-ISA primitive.**

Adds `srmech.qm.triality.lean_isa_seventh_primitive()` — surfaces the existing order-3
triality automorphism (τ, τ³ = I; the v0.5.0 `srmech.qm.triality` engine) as the **7th
lean-ISA primitive**, making the chirality-complete A–N core explicit: **6 order-2
`cascade.atoms` (pin_slot_at_zero / reorient / magnitude / chiral_flip / chiral_dual /
net_chirality) + 1 order-3 triality = 7** — the only access to the 3rd chiral axis.

**BIT-EXACT certificate** (asserted in code): τ has order exactly 3 (‖τ³−I‖ ≈ 3.6e-14,
τ ≠ I, τ² ≠ I) via the engine, plus the Lagrange arithmetic `3 ∤ 8` / `3 ∣ 3` ⇒
`lagrange_obstruction` — all residuals via the scalar Class K pin-slot `cascade.magnitude`,
never `abs()`. **Framework-reading, NOT a derived theorem** (under
`framework_chirality_complete_reading`): that the 6 atoms generate *exactly* Z₂×Z₂×Z₂
(|G| = 8) — a faithful common group rep of the 6 heterogeneous atoms isn't cleanly
available, so |G|=8 / Z₂³ is the documented F220 finding + the Lagrange argument, NOT
labelled bit-exact derived. Scope hierarchy: endianness ⊂ Class C ⊂ Klein-4 ⊂ Spin(8)
triality. Baez (2002) cited for Out(Spin(8))=S₃ / g₂=Der(𝕆) only; F220 is the framework
finding.

**+1 ToolEntry → `describe()` tool total 175 → 176.** Pure-Python; **ABI unchanged at 3**
(no `c/` change); no `abs()` (Class K pin-slot). Closes #761.

## [0.6.0rc2] - 2026-05-30

**MS #20 forward-architecture, voxel #759 — the ℍ-reading 𝔰𝔬(4)=𝔰𝔲(2)⊕𝔰𝔲(2) stabiliser.**

Adds `srmech.qm.so8.quaternion_subalgebra_stabilizer(quaternion_index=1)` (per F215):
the bit-exact **6-dim 𝔰𝔬(4) = 𝔰𝔲(2) ⊕ 𝔰𝔲(2)** subalgebra of g₂ = Der(𝕆) that
stabilises a quaternion subalgebra ℍ ⊂ 𝕆 — the ℍ-reading **sibling** of
`an_embedding` (the 𝔰𝔲(3)⊕3⊕3̄ ℂ-reading). Returns the 6 so(4) generators, the two
su(2) ideals (3+3, commuting, self-dual / anti-self-dual on ℍ^⊥), the Killing form
(rank 6, semisimple) with its two-triplet spectrum, and an MPR self-attestation —
all bit-exact and **ℍ-choice-invariant** across the 7 Fano-line quaternion subalgebras.

The point (F215): keep the Lie **symmetry** surface (𝔰𝔬(4) ⊂ g₂) visibly distinct
from the **operator** surface (`cascade.atoms.*`, the 6 lean-ISA ops) so the "6 = 6"
conflation can't recur — the 6 atoms are group-element ops (0/6 Lie generators); the
dimension match is coincidence. Surfaced under the separately-keyed
`framework_so4_reading` field (framework-reading, not a derived theorem); the
su(2)⊕su(2) split is the op's own bit-exact computation (Baez 2002 §4.1 cited for
g₂ = Der(𝕆) only).

**+1 ToolEntry → `describe()` tool total 174 → 175.** Pure-Python; **ABI unchanged at
3** (no `c/` change); no `abs()` (Class K pin-slot). Closes #759.

## [0.6.0rc1] - 2026-05-30

**MS #20 forward-architecture, voxel #751 — the lean A–N ISA two-tier split.**

First rc of the v0.6.0 line. Splits `srmech.amsc.cascade` (a single module) into a
two-tier package along the lean-ISA boundary (per F208):

- **`srmech.amsc.cascade.atoms`** — the 6 silicon-able 1:1 ISA intrinsics
  (`pin_slot_at_zero` K, `reorient` C, `magnitude` K, `chiral_flip` C,
  `chiral_dual` C∘op∘C, `net_chirality` C).
- **`srmech.amsc.cascade.compose`** — the 2 iterative algorithms over the atoms
  (`cyclic_gcd` = Euclid's remainder loop, `best_rational_signed` = the
  Class K∘N∘C continued-fraction loop).

`atoms.*` / `compose.*` are the new canonical homes; the flat
`srmech.amsc.cascade.<op>` names (and the `class_*` / `best_rat_signed` aliases)
are **retained as deprecated-for-one-release aliases** — importable with NO
runtime `DeprecationWarning` this release. **Public surface byte-identical**:
`describe()` tool total STAYS **174**, the MCP `srmech.amsc.cascade.*` tool names
and the introspect emit strings are unchanged. Pure-Python packaging refactor;
**ABI unchanged at 3** (no `c/` change); full C dispatch + TOML descriptors
intact; no `abs()` (Class K pin-slot). Closes #751.



**[Full changelog →](https://github.com/lemonforest/mlehaptics/blob/main/docs/srmech/python/CHANGELOG.md)**
