arborist/docs/_source/pi-star.rst
russell@unturf.com 0a2e347f9c
qa: math/logic π* reach the user — arborist canon + query preflight
`make query Q="0.1 + 0.2"` used to return `no_sources` because
arithmetic-shaped input has no FTS5 hits in any text shard. Two
surfaces close that gap.

**`arborist canon <key> "<input>"`** — direct π* call, no shards,
no LLM, no audit chain. Pure projection:

    $ arborist canon arithmetic@v1 "0.1 + 0.2"      → 3/10
    $ arborist canon logic-kernel@v1 "A IMPL B"     → (NOT A OR B)
    $ arborist canon --list                         → registry contents
    $ arborist canon --json arithmetic@v1 "0.1+0.2" → SHA-256 envelope

**Math/logic preflight in `arborist query`** — pure-arithmetic and
pure-propositional questions short-circuit RAG and answer through
arithmetic@v1 / logic-kernel@v1 directly. Synthetic
`audit_mode=CANONICAL_PROJECTION`, renders as
`CANONICAL · via <pi_star_ref>`:

    $ arborist query "0.1 + 0.2"
    0.1 + 0.2
      CANONICAL · via arithmetic@v1   0.0s   (projected)
    3/10

    $ arborist query "(NOT B) IMPL (NOT A)"
    (NOT B) IMPL (NOT A)
      CANONICAL · via logic-kernel@v1   0.0s   (projected)
    (NOT A OR B)

Sniff is conservative: pure-arithmetic shape (digits + ops, no
letters) or pure-propositional shape (uppercase atoms + reserved
keywords only). Natural-language wrapping ("what is 0.1+0.2?")
falls through to RAG. PiStarError on a shape match also falls
through — preflight is best-effort, never blocking.

Disable per-call: `--no-canonical-preflight` flag,
`policy["canonical_projection_preflight"]=False`.

No schema changes: CANONICAL_PROJECTION is a render-layer audit_mode
token. No providence_cache writes, no audit_events, no
governance_policy_hash bump. The canonical bytes ARE the answer;
SHA-256 of the bytes is the equivalence-class identity (already
committed via the π* registry).

Side housekeeping: arborist/pi_star/__init__.py docstring caught up
with reality — six concrete π*'s ship today, only tabular-pinned@v1
remains as a stub.

31 new tests (preflight sniff + dispatch, query short-circuit,
contrapositive equivalence-class collapse, CLI subcommand exit codes
and JSON envelope, --no-canonical-preflight policy gate). Full
suite: 1300 passed, 36 skipped.
2026-05-08 12:18:28 -04:00

198 lines
7.3 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

π* domain library
=================
Arborist's canonical-projection registry — the substrate that
implements SQD whitepaper §3's invariant projection
``π*: Σ* → 𝓘 {⊥}``. Every modality the bench surface or audit
chain touches has (or reserves) a registered π* that maps surface
bytes to canonical bytes; the SHA-256 of the canonical bytes is
the equivalence-class identity.
Registry overview
-----------------
Lookup is by ``name@version`` key. Six concrete π*'s + one stub
ship today:
========================== =========== ===========================================================
Key Domain Status
========================== =========== ===========================================================
``wikitext-base@v1`` text Wikitext → plain prose (Phase 1a)
``claim-lattice@v1`` text Claim lines → JSON parsed-claim list (Phase 1a)
``code-py-ast@v1`` code Python source → canonical AST S-expression
``arithmetic@v1`` arithmetic Expression → exact rational ``num/den`` (SQD §14.1)
``logic-kernel@v1`` logic Boolean expression → canonical CNF (SQD §14.3)
``time-series-quantized@v1`` time-series JSON sample array → quantized integer vector
``tabular-pinned@v1`` tabular reserved (stub)
========================== =========== ===========================================================
Cross-modality discipline
-------------------------
Every bench fixture and audit-bound canonicalization names its
:class:`PiStar` via:
- ``carrier`` — a Phase-1 whitelist enforced by
:data:`bench.batteries.base.PHASE_1_CARRIERS`
- ``pi_star_ref`` — the registry key
Unsupported carriers fail or skip explicitly with
``reason="unsupported_carrier"`` — never silently accepted.
Hidden-channel work is defensive only (detection / flagging,
never generation or concealment).
The discipline gives every benchmark, every audit row, and every
selection score a stable answer to "what canonicalizer was used"
that survives schema migrations and is reproducible from the
canonical bytes alone.
Math π*'s — SQD §14
-------------------
Two of the most recently graduated π*'s implement the SQD
whitepaper's math substrate:
**arithmetic@v1** — closed-form rational arithmetic. Solves the
canonical SQD test exactly:
.. code-block:: python
from arborist.pi_star import get
ps = get("arithmetic@v1")
ps.canonicalize(b"0.1+0.2") # → b"3/10"
ps.canonicalize(b"0.3") # → b"3/10"
ps.canonicalize(b"1+2") # → b"3/1"
ps.canonicalize(b"6/4") # → b"3/2" (lowest terms)
No floating-point drift — ``Decimal(str(0.1))`` gives exact
``1/10``, then ``fractions.Fraction`` arithmetic stays in .
Identifiers, function calls, division by zero, and non-integer
exponents raise :class:`PiStarError`.
**logic-kernel@v1** — propositional Boolean expression →
Conjunctive Normal Form (CNF):
.. code-block:: python
ps = get("logic-kernel@v1")
ps.canonicalize(b"A AND B") # → b"A AND B"
ps.canonicalize(b"B AND A") # → b"A AND B" (commutativity)
ps.canonicalize(b"A IMPL B") # → b"(NOT A OR B)"
ps.canonicalize(b"(NOT B) IMPL (NOT A)") # → b"(NOT A OR B)" (contrapositive)
ps.canonicalize(b"NOT NOT A") # → b"A" (double negation)
ps.canonicalize(b"A OR NOT A") # → b"TRUE" (tautology)
Atom cap: 8 (CNF expansion is exponential; cap keeps
canonicalization deterministic in bounded time).
Equivalences preserved:
commutativity, associativity, IMPL/IFF/XOR rewrites, De Morgan,
double negation, distribution, idempotence, tautology collapse,
contrapositive.
CLI surfaces
------------
Two ways to reach a registered π* from the shell — direct or
auto-routed.
**Direct call**``arborist canon <key> "<input>"`` runs the named
π* against the given input and prints the canonical bytes. No
shards, no LLM, no audit chain. Pure projection.
.. code-block:: console
$ arborist canon arithmetic@v1 "0.1 + 0.2"
3/10
$ arborist canon logic-kernel@v1 "(NOT B) IMPL (NOT A)"
(NOT A OR B)
$ arborist canon time-series-quantized@v1 \
'{"dt":1,"dv":0.1,"samples":[[0,1.0],[1,2.0]]}'
dt=1;dv=0.1;n=2;t0=0:10|20
# registry contents:
$ arborist canon --list
arithmetic@v1 arithmetic
claim-lattice@v1 text
code-py-ast@v1 code
logic-kernel@v1 logic
tabular-pinned@v1 tabular
time-series-quantized@v1 time-series
wikitext-base@v1 text-or-wikitext
# JSON envelope w/ canonical-bytes SHA-256:
$ arborist canon --json arithmetic@v1 "0.1 + 0.2"
{"pi_star_ref":"arithmetic@v1","input":"0.1 + 0.2",
"canonical":"3/10","canonical_sha256":"088ae5cc..."}
**Auto-routed via** ``arborist query`` — pure-arithmetic and
pure-propositional questions short-circuit the RAG path and answer
directly through ``arithmetic@v1`` / ``logic-kernel@v1``. The result
gets a synthetic ``audit_mode = CANONICAL_PROJECTION`` and renders as
``CANONICAL · via <pi_star_ref>``:
.. code-block:: console
$ arborist query "0.1 + 0.2"
0.1 + 0.2
CANONICAL · via arithmetic@v1 0.0s (projected)
3/10
$ arborist query "A IMPL B"
A IMPL B
CANONICAL · via logic-kernel@v1 0.0s (projected)
(NOT A OR B)
The sniff is conservative — pure-arithmetic shape (digits + ops, no
letters) or pure-propositional shape (uppercase atoms + reserved
keywords only). Anything natural-language ("what is 0.1 + 0.2?")
falls through to the existing RAG path. Pass
``--no-canonical-preflight`` to force the RAG path even on
matching input — useful when bench-comparing the canonical answer
against the LLM's reply. Disable per-call via
``policy["canonical_projection_preflight"] = False``.
Composition algebra
-------------------
Two π*'s can be composed into a third via
:func:`arborist.pi_star.compose`:
.. code-block:: python
from arborist.pi_star import compose
chain = compose("wikitext-base@v1", "claim-lattice@v1")
# Auto-registers as "wikitext-base-then-claim-lattice@v1"
chain.canonicalize(b"- The release date was July 3, 1985. [E1]")
Each composition is itself a registered π* with its own key. See
:file:`docs/pi-star-composition.md` for the algebra (type
compatibility, determinism preservation, equivalence-class
preservation, projective vs invertible compositions).
Authoring a new π*
------------------
1. Add ``arborist/pi_star/<name>.py`` with a frozen dataclass
declaring ``name``, ``version``, ``domain``, and a
``canonicalize(self, raw: bytes) -> bytes`` method.
2. Call :func:`arborist.pi_star.register` at module import time.
3. Import the new module in ``arborist/pi_star/__init__.py`` so
the registration fires at package load.
4. Write tests covering: idempotency on the canonical form (or
document projective behavior), equivalence classes preserved,
equivalence classes kept distinct, error paths (bad input
rejected explicitly).
5. Add bench fixtures under ``bench/fixtures/5s/{syntax,semantics}-<name>-v1.jsonl``
exercising the new carrier through the existing 5S Syntax and
Semantics runners.
6. Add the carrier name to
:data:`bench.batteries.base.PHASE_1_CARRIERS`.
7. Add ``make bench-5s-<name>`` Makefile target.
See :file:`docs/spec-methodology.md` for the full discipline.