Compare commits
No commits in common. "main" and "subagent-Lane-5-Subagent-self-23e778ec" have entirely different histories.
main
...
subagent-L
579 changed files with 1323 additions and 23854 deletions
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
|
|
@ -26,7 +26,7 @@ The cognitive path is centered on:
|
|||
- `teaching/correction.py`, `teaching/review.py`, `teaching/store.py`
|
||||
- `evals/*`
|
||||
- `calibration/*`
|
||||
- `packs/data/en_core_cognition_v1`
|
||||
- `language_packs/data/en_core_cognition_v1`
|
||||
|
||||
The runtime response contract is documented in `docs/runtime_contracts.md`.
|
||||
Follow it.
|
||||
|
|
@ -42,7 +42,7 @@ versor_condition(F) < 1e-6
|
|||
Allowed construction/closure sites:
|
||||
|
||||
- `ingest/gate.py`
|
||||
- `packs/compiler.py` / vocabulary construction
|
||||
- `language_packs/compiler.py` / vocabulary construction
|
||||
- `algebra/versor.py`
|
||||
|
||||
Forbidden hot-path repair sites:
|
||||
|
|
|
|||
2
.github/workflows/contemplation.yml
vendored
2
.github/workflows/contemplation.yml
vendored
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
enable-cache: true
|
||||
|
||||
- name: install dependencies
|
||||
run: uv sync --locked --extra dev
|
||||
run: uv pip install -e ".[dev]" pyyaml
|
||||
|
||||
- name: run contemplation cycle
|
||||
id: run
|
||||
|
|
|
|||
36
.github/workflows/full-pytest.yml
vendored
36
.github/workflows/full-pytest.yml
vendored
|
|
@ -1,25 +1,16 @@
|
|||
name: full-pytest
|
||||
|
||||
# Post-merge FAST lane — runs on every push to main.
|
||||
# Post-merge validation — runs the full pytest suite on every push to main.
|
||||
# PRs are gated by the faster smoke workflow (smoke.yml); this catches
|
||||
# anything outside the smoke suite within minutes of merge.
|
||||
#
|
||||
# Marker: -m "not quarantine and not slow"
|
||||
# ~9.5k unit/integration tests; excludes the slow registry in conftest.py
|
||||
# (soak / bench / proof / register-matrix; ~912 tests including the 16 min
|
||||
# phase2 fixture floor).
|
||||
#
|
||||
# Why "full-pytest" still names this file:
|
||||
# Keep the workflow id stable for Forgejo required-check / history matching.
|
||||
# The job display name and this comment state the true contract: FAST on main.
|
||||
# The complete non-quarantine suite runs in nightly-full-pytest.yml.
|
||||
#
|
||||
# PR gate: smoke.yml (small critical subset).
|
||||
# Lane pins: lane-shas.yml.
|
||||
# Full soak: nightly-full-pytest.yml (schedule + workflow_dispatch).
|
||||
# Quarantined tests are excluded via the conftest.py QUARANTINE registry.
|
||||
# The intent is a ratchet: once a test is removed from the registry it
|
||||
# must keep passing on this gate.
|
||||
#
|
||||
# See:
|
||||
# conftest.py — QUARANTINE + SLOW_FILES / SLOW_TESTS registries
|
||||
# docs/testing-lanes.md — lane commands and CI policy (SSoT)
|
||||
# docs/ci-optimization.md — runner bottleneck + capacity notes
|
||||
# conftest.py — the QUARANTINE registry (one entry per quarantined test)
|
||||
# docs/test-debt-quarantine.md — cluster diagnoses + removal policy
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -34,10 +25,9 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
pytest:
|
||||
name: fast pytest (-m "not quarantine and not slow" -n 2)
|
||||
name: full pytest (-m "not quarantine" -n 2)
|
||||
runs-on: ubuntu-latest
|
||||
# Headroom over ~9.5 min parallel on a 10-core host; Act 2-vCPU is slower.
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 45
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
|
|
@ -53,14 +43,14 @@ jobs:
|
|||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
uv sync --locked --extra dev
|
||||
uv pip install -e ".[dev]" pyyaml
|
||||
|
||||
- name: pytest (parallel, quarantine and slow excluded)
|
||||
- name: pytest (parallel, quarantine excluded)
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
CORE_SHOWCASE_SKIP_BUDGET: "1"
|
||||
run: |
|
||||
uv run pytest -m "not quarantine and not slow" -n 2 --tb=short -q --maxfail=10
|
||||
uv run pytest -m "not quarantine" -n 2 --tb=short -q --maxfail=10
|
||||
|
||||
- name: report quarantine size (informational)
|
||||
if: always()
|
||||
|
|
|
|||
73
.github/workflows/lane-shas.yml
vendored
73
.github/workflows/lane-shas.yml
vendored
|
|
@ -1,20 +1,12 @@
|
|||
name: lane-shas
|
||||
|
||||
# Verify that every ADR-0092..0104 lane produces its pinned SHA-256
|
||||
# Verify that every ADR-0092..0099 lane produces its pinned SHA-256
|
||||
# report. A failing job means a lane's deterministic output changed
|
||||
# without an explicit ADR-tracked pin update via:
|
||||
#
|
||||
# python scripts/verify_lane_shas.py --update
|
||||
#
|
||||
# Single source of truth for the pinned values is scripts/verify_lane_shas.py.
|
||||
#
|
||||
# PR path policy (job-level, not workflow-level):
|
||||
# Pin bytes can move from Python, packs, eval fixtures/corpora, teaching
|
||||
# corpora, dependency pins, CLAIMS.md, or this workflow. When none of those
|
||||
# paths change on a PR, we skip the multi-minute runners and exit success so
|
||||
# a required check never sits "Waiting" forever (workflow-level `paths:`
|
||||
# would omit the job entirely and hang required status checks).
|
||||
# Main pushes always verify.
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -33,81 +25,40 @@ jobs:
|
|||
verify:
|
||||
name: verify pinned lane SHAs
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
timeout-minutes: 20
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
# Need base..head for PR path detection.
|
||||
fetch-depth: 0
|
||||
fetch-depth: 1
|
||||
|
||||
- name: detect pin-relevant paths
|
||||
id: paths
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Always verify on main pushes.
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Pin-relevant path check: always run on push"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
BASE="${{ github.event.pull_request.base.sha }}"
|
||||
HEAD="${{ github.event.pull_request.head.sha }}"
|
||||
# Paths that can change pin bytes or CLAIMS generation inputs.
|
||||
# Keep in sync with docs/testing-lanes.md § CI policy.
|
||||
PATTERN='(\.py$|^packs/|^evals/|^teaching/|^CLAIMS\.md$|^pyproject\.toml$|^uv\.lock$|^\.github/workflows/lane-shas\.yml$)'
|
||||
if git diff --name-only "$BASE" "$HEAD" | grep -E "$PATTERN" >/dev/null; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Pin-relevant paths changed; running lane SHA verification"
|
||||
git diff --name-only "$BASE" "$HEAD" | grep -E "$PATTERN" || true
|
||||
else
|
||||
echo "run=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No pin-relevant paths changed; skipping lane SHA verification (job still green)"
|
||||
fi
|
||||
|
||||
# setup-uv (not actions/setup-python) provisions Python on the aarch64
|
||||
# self-hosted runner; actions/setup-python has no arm64 build for the
|
||||
# pinned 3.12.13. Matches smoke.yml / full-pytest.yml.
|
||||
- name: set up uv
|
||||
if: steps.paths.outputs.run == 'true'
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- name: set up python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12.13'
|
||||
enable-cache: true
|
||||
cache: 'pip'
|
||||
|
||||
- name: install dependencies
|
||||
if: steps.paths.outputs.run == 'true'
|
||||
run: |
|
||||
uv sync --locked
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e . pyyaml pytest
|
||||
|
||||
- name: verify lane SHAs
|
||||
if: steps.paths.outputs.run == 'true'
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
# public_demo wall-clock is soft by default (see evals/public_demo/runner.py).
|
||||
# Do not set CORE_SHOWCASE_HARD_BUDGET here — cold Act runners exceed 60s.
|
||||
# Content cases (claims, determinism, pure composition) remain hard gates.
|
||||
run: |
|
||||
uv run python scripts/verify_lane_shas.py
|
||||
python scripts/verify_lane_shas.py
|
||||
|
||||
- name: verify CLAIMS.md is current
|
||||
if: steps.paths.outputs.run == 'true'
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
run: |
|
||||
uv run python scripts/generate_claims.py --check
|
||||
python scripts/generate_claims.py --check
|
||||
|
||||
- name: emit machine-readable report (on failure)
|
||||
if: failure() && steps.paths.outputs.run == 'true'
|
||||
if: failure()
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
run: |
|
||||
uv run python scripts/verify_lane_shas.py --json || true
|
||||
|
||||
- name: skip notice
|
||||
if: steps.paths.outputs.run != 'true'
|
||||
run: |
|
||||
echo "::notice title=lane-shas skipped::No pin-relevant paths in this PR; verification skipped (success)."
|
||||
python scripts/verify_lane_shas.py --json || true
|
||||
|
|
|
|||
72
.github/workflows/nightly-full-pytest.yml
vendored
72
.github/workflows/nightly-full-pytest.yml
vendored
|
|
@ -1,72 +0,0 @@
|
|||
name: nightly-full-pytest
|
||||
|
||||
# Nightly FULL lane — complete non-quarantine suite including the slow registry.
|
||||
#
|
||||
# Marker: -m "not quarantine"
|
||||
# Includes soak / bench / proof / register-matrix (SLOW_FILES + SLOW_TESTS).
|
||||
# Intentionally off the PR and post-merge critical path so a single 2-vCPU
|
||||
# Act runner is not held for 1–2h after every main push (see docs/ci-optimization.md).
|
||||
#
|
||||
# Risk owned here: a main merge can break slow tests until the next nightly
|
||||
# (or a manual workflow_dispatch). Treat red nightlies as release-blocking
|
||||
# debt; re-run via Actions → nightly-full-pytest → Run workflow after fixes.
|
||||
#
|
||||
# See docs/testing-lanes.md for the full CI policy.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 02:00 UTC daily — off peak for human PR iteration.
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: nightly-full-pytest
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
pytest:
|
||||
name: full pytest (-m "not quarantine" -n 2)
|
||||
runs-on: ubuntu-latest
|
||||
# Full suite parallel floor includes ~16 min phase2 fixture; thrashing on a
|
||||
# 2-vCPU Act host has been observed well past 60 min. Prefer a red timeout
|
||||
# only after a genuine hang, not under normal soak load.
|
||||
timeout-minutes: 120
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: main
|
||||
|
||||
- name: set up uv
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
python-version: '3.12.13'
|
||||
enable-cache: true
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
uv sync --locked --extra dev
|
||||
|
||||
- name: pytest (parallel, full suite, quarantine excluded)
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
CORE_SHOWCASE_SKIP_BUDGET: "1"
|
||||
run: |
|
||||
uv run pytest -m "not quarantine" -n 2 --tb=short -q --maxfail=10
|
||||
|
||||
- name: report quarantine size (informational)
|
||||
if: always()
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}
|
||||
run: |
|
||||
uv run python -c "
|
||||
import sys
|
||||
sys.path.insert(0, '.')
|
||||
from conftest import QUARANTINE
|
||||
print(f'::notice title=Quarantine size::{len(QUARANTINE)} tests currently quarantined. Goal: shrink this number.')
|
||||
"
|
||||
2
.github/workflows/ratify-proposal.yml
vendored
2
.github/workflows/ratify-proposal.yml
vendored
|
|
@ -70,7 +70,7 @@ jobs:
|
|||
enable-cache: true
|
||||
|
||||
- name: install dependencies
|
||||
run: uv sync --locked --extra dev
|
||||
run: uv pip install -e ".[dev]"
|
||||
|
||||
- name: resolve review date
|
||||
id: date
|
||||
|
|
|
|||
9
.github/workflows/smoke.yml
vendored
9
.github/workflows/smoke.yml
vendored
|
|
@ -7,10 +7,9 @@ name: smoke
|
|||
# — ratified packs diverge directionally; pack-invariant refusal floor; no
|
||||
# fabrication). The falsifiability lane adds ~4 min but blocks-on-regression.
|
||||
#
|
||||
# Post-merge on main: full-pytest.yml runs the FAST lane
|
||||
# (-m "not quarantine and not slow"). Soak / proof / register-matrix coverage
|
||||
# is nightly-full-pytest.yml (not on the PR critical path).
|
||||
# See docs/testing-lanes.md.
|
||||
# Full pytest runs post-merge to main (see full-pytest.yml).
|
||||
# Regressions caught here block the PR; anything outside the smoke
|
||||
# suite is caught on main within minutes of merge.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
|
@ -43,7 +42,7 @@ jobs:
|
|||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
uv sync --locked --extra dev
|
||||
uv pip install -e ".[dev]" pyyaml
|
||||
|
||||
- name: pytest smoke suite
|
||||
env:
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -15,6 +15,7 @@ workbench_data/*
|
|||
core-rs/target/
|
||||
core-rs/Cargo.lock
|
||||
|
||||
uv.lock
|
||||
|
||||
# Environment secrets — never commit real keys
|
||||
.env
|
||||
|
|
@ -84,6 +85,3 @@ skills-lock.json
|
|||
|
||||
# Per-life backup checkpoint dirs are runtime-generated, not source.
|
||||
engine_state/_life_backup_*/
|
||||
|
||||
# Local MCP configuration (contains local tokens)
|
||||
.mcp.json
|
||||
|
|
|
|||
96
AGENTS.md
96
AGENTS.md
|
|
@ -4,16 +4,6 @@ This is the canonical governance file for this repository.
|
|||
|
||||
If any provider-specific file (`CLAUDE.md`, `GEMINI.md`, or future agent files) overlaps with this document, `AGENTS.md` wins. Provider files should only contain minimal startup and workflow notes, not alternate architecture or alternate invariants.
|
||||
|
||||
## Session Continuity (lightweight, session-break only)
|
||||
|
||||
When you are approaching a stopping point, known pause, or session break:
|
||||
- Create a file named `session-break-summary-<YYYY-MM-DD-HHMM>.md` (precise datetime recommended) at the repo root or in `docs/sessions/`.
|
||||
- Keep it short and actionable: current branch/state, what was just completed, exact next concrete steps, any open invariants/tests/hazards, and key files to re-read.
|
||||
- At the **start of any new session** (or subagent): Quickly scan for any recent `session-break-summary-*.md` files. Read the most relevant one if present.
|
||||
- Once you have resumed the work and continued past the break point, **delete the file**. Its only purpose is temporary continuity for the immediate next pickup.
|
||||
|
||||
The previous heavy `HANDOFF-*.md` / formal handoff machinery is retired (see history in git and docs/handoffs/ for old artifacts).
|
||||
|
||||
## Mission
|
||||
|
||||
CORE is a deterministic cognitive engine under construction.
|
||||
|
|
@ -69,7 +59,7 @@ Fix the operator or construction boundary that violated it.
|
|||
### Allowed normalization boundaries
|
||||
Normalization / closure / canonicalization belongs only at explicit construction or algebra boundaries, such as:
|
||||
- `ingest/gate.py`
|
||||
- `packs/compiler.py`
|
||||
- `language_packs/compiler.py`
|
||||
- `algebra/versor.py`
|
||||
- `sensorium/*/canonical.py`
|
||||
- `session/context.py` for session-scoped **semantic anchoring** of the field toward the session concept-attractor (the anchor pull, hemisphere consistency). Allowed ONLY because every such op (1) preserves `versor_condition` BY CONSTRUCTION — composed from `rotor_power` / `word_transition_rotor` / `versor_apply` on the Spin manifold, never a post-hoc `unitize`/grade-projection — AND (2) carries semantic meaning in the cognitive model.
|
||||
|
|
@ -128,7 +118,7 @@ Do not introduce new local prose parsers inside derivation organs unless explici
|
|||
Before editing:
|
||||
1. Read this file.
|
||||
2. Read `docs/specs/runtime_contracts.md`.
|
||||
3. Check for any recent `session-break-summary-*.md` files (see top-level section above) and read the relevant one if present.
|
||||
3. Read the latest recent `HANDOFF-*.md` if relevant.
|
||||
4. Confirm repo root and inspect working tree state.
|
||||
5. Run the smallest relevant validation lane.
|
||||
|
||||
|
|
@ -139,66 +129,7 @@ For non-trivial edits:
|
|||
- keep changes small and load-bearing
|
||||
- If working in Arena/parallel subagent mode, each subagent must independently satisfy `versor_condition` and results must be reconciled before merge. No subagent output becomes another subagent's unchecked input.
|
||||
|
||||
## Reasoning and Problem-Solving Discipline
|
||||
|
||||
LLMs are not reliably intelligent by default. CORE exists partly to fix that.
|
||||
Agents working in this repository must hold themselves to the following protocol
|
||||
on every non-trivial task. Skipping steps produces confident-sounding work that
|
||||
is wrong in load-bearing ways.
|
||||
|
||||
### The Protocol
|
||||
|
||||
**1. Read the code — never reason from names or structure alone.**
|
||||
Before forming any opinion about a module, read its implementation. Trace its
|
||||
imports and call sites. Identify what invariant it is protecting. A file named
|
||||
`pass_manager.py` tells you nothing until you have read it.
|
||||
|
||||
**2. Find the shape — what underlying structure does this problem have?**
|
||||
Before proposing a solution, identify the repeating structure the problem
|
||||
expresses. The solution should make that structure visible, not paper over it.
|
||||
Duplication is a symptom; the cause is an unnamed shape.
|
||||
|
||||
**3. Rank by leverage — genius-to-effort, not ease.**
|
||||
When multiple improvements are possible, rank them explicitly by how much
|
||||
cognitive/structural load they remove vs. how much effort they require. Implement
|
||||
in that order. An agent that implements low-leverage changes first and skips
|
||||
high-leverage ones has optimized for the wrong thing.
|
||||
|
||||
**4. Enumerate changes precisely — no ambiguity about what goes where.**
|
||||
Before committing, state every change, which file it lives in, and why. The
|
||||
commit message must reflect this. Vague commits ("refactor", "cleanup") are
|
||||
not acceptable on load-bearing modules.
|
||||
|
||||
**5. Prove against real claims — not abstract correctness.**
|
||||
"Tests pass" is not proof. Identify which specific pinned assertion in
|
||||
`CLAIMS.md` the change must preserve or enable. State the SHA-256 lane or
|
||||
`core test --suite` invocation that verifies it. If no existing lane covers
|
||||
the change, say so explicitly — that is itself a finding.
|
||||
|
||||
**6. Connect to the cognitive model — what does this do for the system's reasoning?**
|
||||
Every non-trivial change must be articulable in terms of what it does for
|
||||
CORE's actual cognition path:
|
||||
`listen → comprehend → recall → think → articulate → learn → replay`
|
||||
If you cannot state what cognitive property the change strengthens, the change
|
||||
is not yet understood well enough to ship.
|
||||
|
||||
**7. Commit with discipline — right branch, right invariant, right lane.**
|
||||
Confirm repo state and branch before every commit. Never commit directly to
|
||||
`main` unless the change is documentation or governance (like this one).
|
||||
State which invariant the change protects. Run the smallest validation lane
|
||||
that proves the change before declaring it done.
|
||||
|
||||
### The Failure Modes This Prevents
|
||||
|
||||
- Reasoning from file names instead of reading the code → wrong analysis
|
||||
- Proposing solutions before finding the underlying shape → solutions that
|
||||
recreate the same problem in a different form
|
||||
- Implementing easy changes first → high-leverage work never gets done
|
||||
- Vague success criteria → regressions that pass "tests" but break real claims
|
||||
- Shipping changes that can't be connected to the cognitive model → architectural
|
||||
drift away from CORE's mission
|
||||
|
||||
## Repository topology discipline
|
||||
### Repository topology discipline
|
||||
Before calling a directory, module, or file stale/redundant, classify its
|
||||
intrinsic role:
|
||||
- runtime boundary
|
||||
|
|
@ -232,25 +163,6 @@ Before branch movement or edits:
|
|||
- Establish a clean current `main`.
|
||||
- Prefer a fresh worktree from `origin/main` for non-trivial implementation.
|
||||
|
||||
### Git and Forgejo Setup
|
||||
**CRITICAL**: This repository is hosted on a private **Forgejo** server, NOT GitHub. GitHub is deprecated for core work and CI testing.
|
||||
Our sole remote and CI/CD platform is **core-gitquarters.acbcontent.org**.
|
||||
- **DO NOT** use the `gh` (GitHub) CLI for normal work or PR management.
|
||||
- **DO NOT** attempt to push, pull, or clone from `github.com` for developer/agent loops.
|
||||
- **USE** the provided Forgejo MCP tools if available.
|
||||
- **USE** the `tea` CLI (Gitea/Forgejo CLI) for issues, PRs, and repository management targeting `core-gitquarters.acbcontent.org`.
|
||||
|
||||
### Local-First CI Validation Protocol
|
||||
To optimize server resources and bypass external CI billing dependencies, all agents and developers must run validation suites locally.
|
||||
- **Pre-Push Gate:** Before pushing any branch to Forgejo, you **MUST** run the `smoke` test suite locally using:
|
||||
```bash
|
||||
uv run core test --suite smoke -q
|
||||
```
|
||||
Ensure all 108+ tests pass. Pushing broken code is a critical protocol violation.
|
||||
- **Pre-Merge Gate:** Before proposing a merge or requesting a review on a PR, you **MUST** run the larger validation suite relevant to your changes (e.g. `uv run core test --suite cognition` or `uv run core test --suite algebra`).
|
||||
- **PR Documentation:** When creating a PR on Forgejo (via `tea pr create`), document the local test execution in the PR description, matching this format:
|
||||
`[Verification]: Smoke suite passed locally (<run_duration>s, <test_count> passed)`
|
||||
|
||||
### Pre-Edit Sweep & Versor Coherence Guardian Protocol
|
||||
Before modifying any module in `algebra/`, `field/`, `vault/`, or `generate/`:
|
||||
- Trace every import of the target module and identify all callers.
|
||||
|
|
@ -259,7 +171,7 @@ Before modifying any module in `algebra/`, `field/`, `vault/`, or `generate/`:
|
|||
|
||||
## Documentation Discipline
|
||||
|
||||
ADRs, session docs, audit artifacts, and temporary session-break summaries stay as Markdown (GitHub-flavored). Plain-text artifacts are diffable, greppable, and readable by every agent in the dispatch pipeline.
|
||||
ADRs, session docs, audit artifacts, and handoff briefs stay as Markdown (GitHub-flavored). Plain-text artifacts are diffable, greppable, and readable by every agent in the dispatch pipeline.
|
||||
|
||||
Within Markdown, two GitHub-rendered features are sanctioned and otherwise sparingly used:
|
||||
- Mermaid fenced blocks (` ```mermaid `) when a state machine, sequence, or dependency graph genuinely communicates more than prose. Inline, not in a sidecar file.
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@ is a CI failure (`.github/workflows/lane-shas.yml`).
|
|||
| ADR-0093 | `domain_contract_validation` | All ratified packs satisfy the 9 ADR-0091 contract predicates | `evals/domain_contract_validation/results/v1_dev.json` | `98ace04e3f02bbc5a8ad655bb6593c3f1ee64cb67014f1122fe6c3c85f48d22f` |
|
||||
| ADR-0095 | `miner_loop_closure` | Miner-sourced proposals route through single reviewed teaching path | `evals/miner_loop_closure/results/v1_dev.json` | `9f071733abe7dcacf759f928548ce738fb639af3fd6e4c621a651b306d7e77ce` |
|
||||
| ADR-0096 | `fabrication_control_summary` | Phantom endpoints / cross-pack non-bridges / sibling collapses refuse | `evals/fabrication_control/results/v1_summary.json` | `01e1b6b711141f2b4a14551d7df3ea482d8d6dd7b364a25c509f4f8d08cda8a8` |
|
||||
| ADR-0098 | `demo_composition` | Demos compose from shipped modules; no parallel mechanism | `evals/demo_composition/results/v1_dev.json` | `e2ba2314d8768459fb6a8db082a4bbcf4107b5161d869804a4b2a33c3724081a` |
|
||||
| ADR-0099 | `public_demo` | Public showcase runs deterministically under 30s; all claims supported | `evals/public_demo/results/v1_dev.json` | `7d8ba0dbae9287cfe0bf15d231fa78a75abc627121c14900439293e01e1cc1d3` |
|
||||
| ADR-0098 | `demo_composition` | Demos compose from shipped modules; no parallel mechanism | `evals/demo_composition/results/v1_dev.json` | `3a3d09f3a87462737e615c2dd3481b9e13e5ff8fadee0043c37873494ded556d` |
|
||||
| ADR-0099 | `public_demo` | Public showcase runs deterministically under 30s; all claims supported | `evals/public_demo/results/v1_dev.json` | `2895df080b91618aefc2df407c637ff419fbb6dae33233c90262688c103411ea` |
|
||||
| ADR-0104 | `curriculum_loop_closure` | Curriculum-sourced proposals route through single reviewed teaching path | `evals/curriculum_loop_closure/results/v1_dev.json` | `b46d56b2d209172cc3ffaf3776dc8dcfe55093f13587c5cb67372be6dfa23e8d` |
|
||||
| ADR-0131 | `math_teaching_corpus_v1` | Math teaching corpus replays deterministically; all chains pass exit criterion (correct_rate=1.0, wrong=0) | `evals/math_teaching_corpus/v1/report.json` | `eaf160d145da29f9050ede8d58bf111b0f651dd40aeae9201857d0b97e014dd4` |
|
||||
| ADR-0206 | `deductive_logic_v1` | Propositional entailment scored against an independent truth-table oracle; dev+holdout+external 716/716 correct, wrong=0, refused=0 | `evals/deductive_logic/report.json` | `97a230949016e38d5e3f37a69e4245b320575ee70e5af92ff7607f7b05f74b5f` |
|
||||
|
|
|
|||
13
CLAUDE.md
13
CLAUDE.md
|
|
@ -1,10 +1,11 @@
|
|||
# CORE Agent Instructions for Claude
|
||||
|
||||
`AGENTS.md` is the canonical governance file. If this file conflicts, follow `AGENTS.md`.
|
||||
`AGENTS.md` is the canonical governance file for this repo. If this file
|
||||
conflicts with `AGENTS.md`, follow `AGENTS.md`.
|
||||
|
||||
Startup: read `AGENTS.md`, `docs/specs/runtime_contracts.md`, inspect working tree, use the smallest validation lane.
|
||||
**CRITICAL**: Remote is `core-gitquarters.acbcontent.org`. Do not use GitHub/`gh` CLI. Use Forgejo tools/`gitea` CLI.
|
||||
Claude-specific startup: read `AGENTS.md`, read
|
||||
`docs/specs/runtime_contracts.md`, inspect the working tree, then use the
|
||||
smallest validation lane that proves the change.
|
||||
|
||||
Before non-trivial edits, apply the protocol in `AGENTS.md`.
|
||||
|
||||
Do not place architecture, invariants, memory rules, or alternate workflow policy here. Update `AGENTS.md` instead.
|
||||
Do not place architecture, invariants, memory rules, or alternate workflow
|
||||
policy here. Update `AGENTS.md` instead.
|
||||
|
|
|
|||
13
GEMINI.md
13
GEMINI.md
|
|
@ -1,10 +1,11 @@
|
|||
# CORE Agent Instructions for Gemini
|
||||
|
||||
`AGENTS.md` is the canonical governance file. If this file conflicts, follow `AGENTS.md`.
|
||||
`AGENTS.md` is the canonical governance file for this repo. If this file
|
||||
conflicts with `AGENTS.md`, follow `AGENTS.md`.
|
||||
|
||||
Startup: read `AGENTS.md`, `docs/specs/runtime_contracts.md`, inspect working tree, use the smallest validation lane.
|
||||
**CRITICAL**: Remote is `core-gitquarters.acbcontent.org`. Do not use GitHub/`gh` CLI. Use Forgejo tools/`gitea` CLI.
|
||||
Gemini-specific startup: read `AGENTS.md`, read
|
||||
`docs/specs/runtime_contracts.md`, inspect the working tree, then use the
|
||||
smallest validation lane that proves the change.
|
||||
|
||||
Before non-trivial edits, apply the protocol in `AGENTS.md`.
|
||||
|
||||
Do not place architecture, invariants, memory rules, or alternate workflow policy here. Update `AGENTS.md` instead.
|
||||
Do not place architecture, invariants, memory rules, or alternate workflow
|
||||
policy here. Update `AGENTS.md` instead.
|
||||
|
|
|
|||
70
README.md
70
README.md
|
|
@ -1,59 +1,11 @@
|
|||
> [!IMPORTANT]
|
||||
> **Repository Migration Notice:**
|
||||
> The GitHub repository `AssetOverflow/core` is now essentially **Read Only**.
|
||||
> The active open-source repository is available for cloning/forking at our new git headquarters:
|
||||
> 🌐 **[core-gitquarters.acbcontent.org](https://core-gitquarters.acbcontent.org)** (along with new open-source projects coming down the pipeline).
|
||||
>
|
||||
> Please update your remotes and direct any issues, pull requests, or contributions to the new git headquarters.
|
||||
# CORE-AI: Versor Engine
|
||||
|
||||
# CORE — A Deterministic Cognition Engine
|
||||
A cognitive field system built on Cl(4,1) Conformal Geometric Algebra.
|
||||
|
||||
**CORE is a single-life, deterministic cognition engine in which a unified conformal-geometric substrate is the medium for memory, language, identity, and epistemics — governed so that it can earn autonomy from human oversight by proving reliability, never by asserting it.**
|
||||
**Core invariant:** `||F * reverse(F) - 1||_F < 1e-6` at all times.
|
||||
|
||||
A unified Cl(4,1) conformal-geometric-algebra substrate serves as the common medium for **all modalities** (through CRDT-sharded, content-addressed, *exact*-recall memory), **all language** (through compiled linguistic manifolds where morphology and grammatical relation are *operators*, not tokens), **all identity** (as a fixed geometric subspace that content cannot rewrite — paraphrase-invariantly), and **all epistemics** (truth-status travels with every claim; admission is by coherence, not authority; the system publishes its own gaps and holds that discipline recursively over its own claims). A deterministic risk–reward governor lets CORE earn its way out of human-in-the-loop supervision by accumulating a replayable, conservatively-bounded reliability ledger — while never self-authorizing, and always leaving a door open for monitoring and upgrades. Thermodynamics (energy, salience, surprise), topology (the manifold, holonomy, the CRDT semilattice), the scientific method (falsifiable proof obligations, sealed holdouts), and a theological–linguistic philosophy of language (the *Logos* as the structuring principle recovered at every input boundary) are not decoration — each is load-bearing, and nothing is included without deliberate intent.
|
||||
|
||||
**Core invariant:** `‖F · reverse(F) − 1‖_F < 1e-6` at all times. All state is a versor; all transitions are versor products; coherence is algebraic by construction — not monitored, not corrected.
|
||||
|
||||
> **Provisional Patent No. 64/080,054** · U.S. Patent and Trademark Office
|
||||
> Independent research program · [core-gitquarters.acbcontent.org](https://core-gitquarters.acbcontent.org)
|
||||
|
||||
---
|
||||
|
||||
## What CORE Is Not — Read This Before Pattern-Matching
|
||||
|
||||
CORE reuses vocabulary from several established fields. **The words collide with things you already know; the architecture does not.** If you skim the concepts and file CORE under a category you recognize, you will be wrong in a specific, load-bearing way. This section exists to prevent that.
|
||||
|
||||
**CORE is not a geometric deep-learning model.** It shares the *words* "Clifford/geometric algebra" and "versor" with GATr, CGENN, and similar equivariant networks — but those are neural networks trained by gradient descent that *use* geometric algebra to get equivariance. CORE has **no neural network, no gradients, no learned weights, no training loss**. The geometry is not a feature space for a model to learn in; it is the deterministic medium in which cognition, memory, and governance invariants are *physically enforced*. There is nothing to train.
|
||||
|
||||
**CORE is not a vector database and its recall is not approximate nearest-neighbor.** The vault shares the *words* "inner-product recall" with ANN systems, but it is a **Delta-CRDT join-semilattice**: write-accumulation that is commutative, associative, and idempotent, content-addressed by IEEE-754 bit pattern so recall is **total, exact, and arrival-order-independent**. Exactness is not a naïve choice waiting to be replaced by an index at scale — it is the *enabling property* of cross-modal unification. Approximate recall would corrupt the geometry that lets any two modalities resonate in one manifold; an ANN index was deliberately *deleted* for exactly this reason.
|
||||
|
||||
**CORE is not an LLM, and not an LLM wrapper.** It generates language without sampling, temperature, beam search, or a softmax over subword tokens. Generation is a deterministic geometric walk, and where the walk could emit something inadmissible under the relation being asserted, the engine emits a **typed refusal** rather than a plausible-but-wrong token. There is no probabilistic decoder anywhere in the system. "Zero confabulation" is not a tuned abstention rate — it is a structural consequence of the same mechanism that makes the system truth-seeking.
|
||||
|
||||
**CORE is not a safety layer bolted onto a generative model.** There is no classifier downstream of a generator, no instruction-following prompt, no guardrail the model could in principle ignore. Identity is a geometric subspace; truth-status is a typed value carried by every admitted claim; the boundaries the system will never cross are enforced at the substrate. A system that samples has nowhere to attach these properties. CORE has them because every admitted claim carries one and the only path to admission is the review path.
|
||||
|
||||
**CORE is not "another neuro-symbolic system," and its symbolic character is not GOFAI brittleness.** It does not hand-encode a rule base for a brittle inference engine. Meaning lives in geometric structure — morphology as operator composition, relation as manifold path — and knowledge enters through a reviewed, replayable, epistemically-typed promotion path, never by absorbing a corpus or by an opaque model's say-so.
|
||||
|
||||
**The one-line test:** if a description of CORE would apply equally well to a transformer, an embedding store, or a fine-tuned model, that description has miscategorized it. CORE's distinctive claim is that properties which frontier systems implement as *soft, promptable, sampling-level behaviors* are here **architectural invariants that content cannot rewrite** — and that this is what makes a path to trustworthy, auditable autonomy possible at all.
|
||||
|
||||
---
|
||||
|
||||
## What This Buys You (the same claims, made concrete)
|
||||
|
||||
| You might assume… | What CORE actually does |
|
||||
|---|---|
|
||||
| "Geometric algebra → it's an equivariant neural net" | No network, no gradients. The Cl(4,1) manifold is the deterministic medium for state, memory, and governance — not a learned feature space. |
|
||||
| "Inner-product recall → it's a vector DB / ANN" | A Delta-CRDT semilattice: exact, content-addressed, arrival-independent recall that unifies all modalities in one manifold. Exactness is load-bearing, not a scaling liability. |
|
||||
| "Argmax generation → it's greedy decoding" | A deterministic geometric walk with Forward Semantic Control: inadmissible continuations raise a *typed refusal*, not a forced token. No sampling exists to degenerate. |
|
||||
| "Refuses a lot → low-coverage abstention" | A deterministic risk–reward governor: serving is `wrong=0` by construction; capability compounds in a sealed practice regime; the engine earns coverage by proving reliability. |
|
||||
| "Identity/persona → a system prompt" | A fixed geometric subspace; override attempts are caught by the geometry of the field-state delta they induce — paraphrase-invariantly, verified against adversarial holdouts. |
|
||||
| "Learns from data → gradient updates / ingestion" | Reviewed, replay-gated promotion through epistemic tiers. No opaque updates; every extension is auditable and reversible; identity and safety packs are off-limits to self-modification. |
|
||||
|
||||
Everything above is enforced in code with a test that fails if the property breaks. Start with the invariant, then the schema, then the evidence:
|
||||
|
||||
- **The core invariant:** `pytest tests/test_versor_closure.py`
|
||||
- **The epistemic substrate:** [`docs/truth_seeking_schema.md`](docs/truth_seeking_schema.md)
|
||||
- **Reproducible claims (auto-generated, CI-verified):** [`CLAIMS.md`](CLAIMS.md)
|
||||
- **Architectural vision and formal spec:** [`docs/Whitepaper.md`](docs/Whitepaper.md), [`docs/Yellowpaper.md`](docs/Yellowpaper.md)
|
||||
All state is a versor. All transitions are versor products.
|
||||
Coherence is algebraic by construction — not monitored, not corrected.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -105,18 +57,6 @@ Decision package: [`docs/zig/README.md`](docs/zig/README.md). Adoption gates: [`
|
|||
|
||||
---
|
||||
|
||||
## The GeometricDelta ABI
|
||||
|
||||
To maintain strict physical boundaries, all external signals, modality compiler outputs (e.g. Sopher's Callosum, audio/vision compilers), and cognitive updates entering CORE's Right Hemisphere (RH) must conform to the **`GeometricDelta` ABI**.
|
||||
|
||||
- **Physical Closure Invariant**: Every `GeometricDelta` must pass the guarded projector (scalar rescale + monotone Newton iterations) with a residual below configured tolerance ($\epsilon \le 10^{-6}$), or be rejected at the boundary (**reject-and-retain**).
|
||||
- **Epistemic Invariant**: Every delta carries an epistemic state mapped directly to CORE's truth-seeking schema, defaulting to `SPECULATIVE` until promoted by Vault coherence evidence.
|
||||
- **Causal Graph (CRDT)**: Deltas are content-addressed and carry causal parents, allowing distributed event-frontier merges instead of simple state mutations.
|
||||
|
||||
Core definition: [`core/abi/geometric_delta.py`](core/abi/geometric_delta.py). Validator: [`core/abi/geometric_delta_validator.py`](core/abi/geometric_delta_validator.py).
|
||||
|
||||
---
|
||||
|
||||
## The Truth-Seeking Schema
|
||||
|
||||
Co-equal with the algebraic substrate. CORE's epistemic schema is a foundational architectural commitment: every claim that enters the runtime field carries a typed position in a revision graph (`SPECULATIVE`, `COHERENT`, `CONTESTED`, `FALSIFIED`); coherence — not source authority — is the only admission signal; no claim is ever locked, even when COHERENT; identity cannot be rewritten by content; and exactly one mutation path admits knowledge, enforced by a CI-level architectural-invariant test.
|
||||
|
|
|
|||
|
|
@ -48,36 +48,9 @@ def _build_cga_inner_metric() -> np.ndarray:
|
|||
_CGA_INNER_METRIC: np.ndarray = _build_cga_inner_metric()
|
||||
|
||||
|
||||
def _f32_1d32(x: np.ndarray) -> np.ndarray:
|
||||
"""Contiguous f32 (32,) for core_rs PyReadonlyArray1 bindings."""
|
||||
return np.ascontiguousarray(
|
||||
np.asarray(x, dtype=np.float32).reshape(-1)[:32], dtype=np.float32
|
||||
)
|
||||
|
||||
|
||||
def _is_f32_workload(*arrays: np.ndarray) -> bool:
|
||||
"""True when all arrays are float32 (Rust f32 kernel is parity-safe).
|
||||
|
||||
float64 wave residual pins require Python SOT (or future f64 Rust GP).
|
||||
Forcing f64→f32 would break 1e-9 chiral / leakage pins (ADR-0241).
|
||||
"""
|
||||
return all(np.asarray(a).dtype == np.float32 for a in arrays)
|
||||
|
||||
|
||||
def geometric_product(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
||||
"""Cl(4,1) geometric product via Rust f32 when enabled, else Python.
|
||||
|
||||
float64 inputs always use the pure-Python product (semantic SOT for
|
||||
wave-field residual math). float32 field-graph workloads get Rust.
|
||||
"""
|
||||
if _RUST and _is_f32_workload(A, B):
|
||||
try:
|
||||
return np.asarray(
|
||||
_rs.geometric_product(_f32_1d32(A), _f32_1d32(B)),
|
||||
dtype=np.float32,
|
||||
)
|
||||
except (AttributeError, TypeError, ValueError, Exception):
|
||||
pass
|
||||
if _RUST:
|
||||
return np.asarray(_rs.geometric_product(A, B), dtype=np.float32)
|
||||
from algebra.cl41 import geometric_product as _gp
|
||||
return _gp(A, B)
|
||||
|
||||
|
|
@ -94,34 +67,25 @@ def versor_apply(V: np.ndarray, F: np.ndarray) -> np.ndarray:
|
|||
"""
|
||||
if _RUST:
|
||||
try:
|
||||
Vc = np.ascontiguousarray(V, dtype=np.float64).reshape(-1)[:32]
|
||||
Fc = np.ascontiguousarray(F, dtype=np.float64).reshape(-1)[:32]
|
||||
return np.asarray(
|
||||
_rs.versor_apply_with_closure_f64(Vc, Fc), dtype=np.float64
|
||||
)
|
||||
except (AttributeError, TypeError, ValueError, Exception):
|
||||
Vc = np.ascontiguousarray(V, dtype=np.float64)
|
||||
Fc = np.ascontiguousarray(F, dtype=np.float64)
|
||||
return np.asarray(_rs.versor_apply_with_closure_f64(Vc, Fc), dtype=np.float64)
|
||||
except (AttributeError, Exception):
|
||||
pass
|
||||
from algebra.versor import versor_apply as _va
|
||||
return _va(V, F)
|
||||
|
||||
|
||||
def versor_condition(F: np.ndarray) -> float:
|
||||
"""Versor residual. Rust f32 path only for float32 inputs (see GP note)."""
|
||||
if _RUST and _is_f32_workload(F):
|
||||
try:
|
||||
return float(_rs.versor_condition(_f32_1d32(F)))
|
||||
except (AttributeError, TypeError, ValueError, Exception):
|
||||
pass
|
||||
if _RUST:
|
||||
return float(_rs.versor_condition(F))
|
||||
from algebra.versor import versor_condition as _vc
|
||||
return _vc(F)
|
||||
|
||||
|
||||
def cga_inner(X: np.ndarray, Y: np.ndarray) -> float:
|
||||
if _RUST and _is_f32_workload(X, Y):
|
||||
try:
|
||||
return float(_rs.cga_inner(_f32_1d32(X), _f32_1d32(Y)))
|
||||
except (AttributeError, TypeError, ValueError, Exception):
|
||||
pass
|
||||
if _RUST:
|
||||
return float(_rs.cga_inner(X, Y))
|
||||
from algebra.cga import cga_inner as _ci
|
||||
return _ci(X, Y)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Conformal Geometric Algebra geometry on Cl(4,1).
|
|||
Signature: (+,+,+,+,-), with Euclidean coordinates on e1,e2,e3.
|
||||
The two conformal null directions are built from e4 and e5:
|
||||
|
||||
n_o = 0.5 * (e5 - e4) # origin, n_o^2 = 0
|
||||
n_o = 0.5 * (e4 - e5) # origin, n_o^2 = 0
|
||||
n_inf = e4 + e5 # infinity, n_inf^2 = 0
|
||||
n_o · n_inf = -1
|
||||
|
||||
|
|
@ -39,24 +39,6 @@ _I5[_PSEUDOSCALAR_INDEX] = 1.0
|
|||
_E4_IDX = 4
|
||||
_E5_IDX = 5
|
||||
|
||||
# The two conformal null directions, frozen as f64 32-vectors — the canonical
|
||||
# origin/infinity of the CGA point map. These are the SAME vectors ``embed_point``
|
||||
# builds inline (origin embeds to N_O; N_INF is fixed by every Euclidean isometry),
|
||||
# hoisted to module constants so the null-point recovery primitives (dilation /
|
||||
# translation peel) and any incidence code share one exact definition instead of
|
||||
# re-deriving the signs. Invariants (pinned in tests/test_null_point_primitives.py):
|
||||
# N_O · N_O = 0, N_INF · N_INF = 0, N_O · N_INF = -1.
|
||||
# Never mutated; callers that need a scratch copy must ``.copy()``.
|
||||
N_O = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
N_O[_E4_IDX] = -0.5 # n_o = 0.5 * (e5 - e4)
|
||||
N_O[_E5_IDX] = 0.5
|
||||
N_O.setflags(write=False)
|
||||
|
||||
N_INF = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
N_INF[_E4_IDX] = 1.0 # n_inf = e4 + e5
|
||||
N_INF[_E5_IDX] = 1.0
|
||||
N_INF.setflags(write=False)
|
||||
|
||||
# Pinned magnitude ceiling for f64-exact embedding + read-back (Phase 0A).
|
||||
# Below this bound, ``embed_point(..., dtype=np.float64)`` round-trips integer
|
||||
# coordinates exactly through ``read_scalar_e1`` and the conformal distance metric
|
||||
|
|
|
|||
|
|
@ -1,275 +0,0 @@
|
|||
"""Null-point recovery primitives for CGA conformal versors.
|
||||
|
||||
Shared substrate for the conformal-Procrustes (#17) and Cartan–Iwasawa (#16)
|
||||
decompositions. Given a *similarity* versor V (rotation · dilation · translation,
|
||||
in any order), these peel off the translation it applies to the origin and the
|
||||
uniform dilation it applies to lengths, using only the exact CGA sandwich
|
||||
``V·X·rev(V)`` on the two null directions ``N_O`` / ``N_INF`` (see algebra/cga.py:
|
||||
``n_o = 0.5(e5 - e4)``, ``n_inf = e4 + e5``).
|
||||
|
||||
Empirically pinned (f64-exact; probes reproduced in the test module):
|
||||
|
||||
* ``V n_inf rev(V) = scale · n_inf`` — a similarity FIXES the point at
|
||||
infinity, so its n_inf image is a *pure* positive multiple of n_inf whose
|
||||
coefficient is the dilation factor. Anything else — a transversion / special
|
||||
conformal versor — leaves an off-n_inf residual and is REFUSED.
|
||||
* ``V n_o rev(V) = w_o·n_o + scale^-1·a + …`` — the origin's image is a
|
||||
conformal point; ``a = euclidean_part / w_o`` recovers the translation by
|
||||
projective dehomogenization (the weight divides out the dilation, and
|
||||
rotation fixes the origin, so ``a`` is exact regardless of V's rotation or
|
||||
scale content — the same trick as :func:`algebra.cga.read_scalar_e1`).
|
||||
|
||||
Conventions — both constructors round-trip through the recoverers:
|
||||
``dilator(scale)`` scales Euclidean lengths by ``scale`` (> 0);
|
||||
``recover_dilation(dilator(s)) == s``.
|
||||
``translator(a)`` maps the origin to Euclidean point ``a`` (3-vector);
|
||||
``recover_translation(translator(a)) == a``.
|
||||
|
||||
Fail-closed discipline (the wrong=0 rule): every recovery raises
|
||||
:class:`NullPointRecoveryError` on a degenerate, non-versor, or non-similarity
|
||||
input rather than returning a silently wrong value — ``recover_dilation`` and
|
||||
``recover_translation`` share one versor+similarity gate
|
||||
(:func:`_require_similarity`), so neither accepts what the other refuses. Guards
|
||||
are scale-relative so a versor with non-unit weight (e.g. one assembled from a
|
||||
Kabsch/SVD point cloud) is judged by its *shape*, not its magnitude.
|
||||
|
||||
Tolerance: the default ``tol=1e-9`` matches the f64-exact recovery of a cleanly
|
||||
assembled versor (an SVD-orthogonal rotation composed with an exact
|
||||
dilator/translator round-trips to ~1e-14). A caller whose versor carries larger
|
||||
numerical noise — e.g. an iteratively refined Procrustes fit — must pass a ``tol``
|
||||
at least as large as that residual, or a valid similarity may be refused as
|
||||
``not_a_versor`` / ``not_similarity`` (fail-closed: it is never *accepted* with a
|
||||
wrong value). ``core.physics.conformal_procrustes`` uses ``tol=1e-8`` by convention.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .cga import N_INF, N_O, cga_inner, graded_wedge
|
||||
from .cl41 import N_COMPONENTS, geometric_product, reverse
|
||||
|
||||
# e4 / e5 component indices inside the grade-1 block (mirror of algebra.cga; kept
|
||||
# local to avoid importing a private name across modules).
|
||||
_E4_IDX = 4
|
||||
_E5_IDX = 5
|
||||
|
||||
# The dilation bivector E = n_o ^ n_inf. E^2 = +1 (boost-like), so the dilator is
|
||||
# a hyperbolic exponential cosh + sinh·E. Frozen f64; never mutated.
|
||||
_E_DILATION = graded_wedge(N_O, N_INF).astype(np.float64)
|
||||
_E_DILATION.setflags(write=False)
|
||||
|
||||
|
||||
class NullPointRecoveryError(ValueError):
|
||||
"""A versor is degenerate or not a similarity transform.
|
||||
|
||||
Carries a machine-readable ``reason`` for callers that route on the failure
|
||||
mode (e.g. #17 margin reporting) rather than only surfacing the message.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, *, reason: str) -> None:
|
||||
super().__init__(message)
|
||||
self.reason = reason
|
||||
|
||||
|
||||
def _sandwich(V: np.ndarray, X: np.ndarray) -> np.ndarray:
|
||||
"""The raw f64 sandwich ``V X rev(V)`` — no closure, no unitisation.
|
||||
|
||||
(Deliberately not :func:`algebra.versor.versor_apply`: that path unitises
|
||||
non-null inputs and coerces to the runtime field dtype. Null-point recovery
|
||||
needs the exact algebraic image in f64.)
|
||||
"""
|
||||
V = np.asarray(V, dtype=np.float64)
|
||||
X = np.asarray(X, dtype=np.float64)
|
||||
return geometric_product(geometric_product(V, X), reverse(V))
|
||||
|
||||
|
||||
def dilator(scale: float) -> np.ndarray:
|
||||
"""Uniform-scale versor that scales Euclidean lengths by ``scale`` (> 0).
|
||||
|
||||
``D = exp(0.5·ln(scale)·E) = cosh(h) + sinh(h)·E`` with ``h = 0.5·ln(scale)``
|
||||
and ``E = n_o ^ n_inf`` (``E^2 = +1``). Acts as
|
||||
``D n_inf rev(D) = scale·n_inf`` and ``D n_o rev(D) = scale^-1·n_o``.
|
||||
"""
|
||||
scale = float(scale)
|
||||
if not np.isfinite(scale) or scale <= 0.0:
|
||||
raise NullPointRecoveryError(
|
||||
f"dilator scale must be finite and positive, got {scale}",
|
||||
reason="nonpositive_scale",
|
||||
)
|
||||
half = 0.5 * np.log(scale)
|
||||
D = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
D[0] = np.cosh(half)
|
||||
D = D + np.sinh(half) * _E_DILATION
|
||||
return D
|
||||
|
||||
|
||||
def translator(a: np.ndarray) -> np.ndarray:
|
||||
"""Translator versor that maps the origin to Euclidean point ``a`` (3-vector).
|
||||
|
||||
``T = 1 - 0.5·a·n_inf`` (a embedded on e1..e3). ``T n_o rev(T)`` equals the
|
||||
conformal embedding of ``a`` (== :func:`algebra.cga.embed_point`).
|
||||
"""
|
||||
a = np.asarray(a, dtype=np.float64)
|
||||
if a.shape != (3,) or not np.all(np.isfinite(a)):
|
||||
raise NullPointRecoveryError(
|
||||
f"translator expects a finite 3-vector, got shape {a.shape}",
|
||||
reason="bad_translation_vector",
|
||||
)
|
||||
a_mv = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
a_mv[1:4] = a
|
||||
T = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
T[0] = 1.0
|
||||
T = T - 0.5 * geometric_product(a_mv, N_INF)
|
||||
return T
|
||||
|
||||
|
||||
def _versor_scalar_weight(V: np.ndarray, tol: float) -> float:
|
||||
"""Return ``scalar_part(V·rev(V))`` after checking ``V`` is a versor.
|
||||
|
||||
A versor satisfies ``V·rev(V) = scalar``; a non-versor multivector leaves an
|
||||
off-scalar residual. Raises :class:`NullPointRecoveryError` (``not_a_versor``
|
||||
/ ``degenerate_weight``) otherwise. The weight is what makes
|
||||
:func:`recover_dilation` weight-invariant — the raw ``n_inf`` coefficient
|
||||
scales with this weight, so the true dilation is the coefficient divided by it.
|
||||
"""
|
||||
V = np.asarray(V, dtype=np.float64)
|
||||
vv = geometric_product(V, reverse(V))
|
||||
w = float(vv[0])
|
||||
off_scalar = float(np.linalg.norm(vv[1:]))
|
||||
ref = max(1.0, abs(w))
|
||||
if off_scalar > tol * ref:
|
||||
raise NullPointRecoveryError(
|
||||
f"V·rev(V) is not scalar (off-scalar residual {off_scalar / ref:.3e}); "
|
||||
"not a versor",
|
||||
reason="not_a_versor",
|
||||
)
|
||||
if abs(w) <= tol:
|
||||
raise NullPointRecoveryError(
|
||||
f"degenerate versor weight {w:.3e}", reason="degenerate_weight",
|
||||
)
|
||||
return w
|
||||
|
||||
|
||||
def _require_similarity(V: np.ndarray, tol: float) -> tuple[float, float]:
|
||||
"""Gate ``V`` as a similarity versor; return ``(weight, signed_scale)``.
|
||||
|
||||
A similarity (rotation · dilation · translation, in any order) is the only
|
||||
class both recoverers accept: it is a versor (``V·rev(V)`` scalar) *and* it
|
||||
fixes infinity (``V n_inf rev(V)`` is a pure multiple of ``n_inf``). The
|
||||
returned ``signed_scale = c_inf / weight`` is positive for a proper similarity
|
||||
and negative for an orientation-reversing (improper / reflection) one; sign
|
||||
and degeneracy classification is left to the caller, so
|
||||
:func:`recover_translation` can accept a reflection — whose origin image is
|
||||
still well defined — while :func:`recover_dilation` refuses it.
|
||||
|
||||
Raises :class:`NullPointRecoveryError` with ``not_a_versor`` /
|
||||
``degenerate_weight`` (from :func:`_versor_scalar_weight`) or ``not_similarity``.
|
||||
"""
|
||||
weight = _versor_scalar_weight(V, tol)
|
||||
W = _sandwich(V, N_INF)
|
||||
c_inf = 0.5 * (float(W[_E4_IDX]) + float(W[_E5_IDX]))
|
||||
|
||||
resid = W.copy()
|
||||
resid[_E4_IDX] -= c_inf
|
||||
resid[_E5_IDX] -= c_inf
|
||||
resid_norm = float(np.linalg.norm(resid))
|
||||
ref = max(1.0, float(np.linalg.norm(W)))
|
||||
if resid_norm > tol * ref:
|
||||
raise NullPointRecoveryError(
|
||||
f"versor does not fix infinity (off-n_inf residual "
|
||||
f"{resid_norm / ref:.3e} > {tol:.1e}); not a similarity transform",
|
||||
reason="not_similarity",
|
||||
)
|
||||
return weight, c_inf / weight
|
||||
|
||||
|
||||
def recover_dilation(V: np.ndarray, *, tol: float = 1e-9) -> tuple[float, np.ndarray]:
|
||||
"""Recover the uniform scale a similarity versor ``V`` applies to lengths.
|
||||
|
||||
Returns ``(scale, D)`` with ``D == dilator(scale)`` and ``scale > 0``. Reads
|
||||
the image of the point at infinity ``W = V n_inf rev(V)`` (for a similarity a
|
||||
pure multiple of ``n_inf``) and normalises its coefficient by the versor weight
|
||||
``V·rev(V)`` — the sandwich scales with that weight, so a non-unit versor still
|
||||
yields the true scale (verified against ``V -> kV``).
|
||||
|
||||
Raises :class:`NullPointRecoveryError` when
|
||||
* ``V`` is not a versor (``not_a_versor`` / ``degenerate_weight``) or does
|
||||
not fix infinity, i.e. is not a similarity (``not_similarity`` — e.g. a
|
||||
transversion);
|
||||
* ``V`` is orientation-reversing — a reflection / improper rotation, the
|
||||
``det = -1`` case a raw Kabsch/SVD fit produces before it strips the
|
||||
reflection (``core.physics.conformal_procrustes`` does strip it). Its
|
||||
signed scale is a clean negative, refused as ``improper_versor``, kept
|
||||
distinct from true degeneracy so a caller can tell "flip a singular
|
||||
vector" from "numerically broken". :func:`recover_translation` still
|
||||
accepts such a versor — only the *dilation* is ill-defined for an improper
|
||||
map here; or
|
||||
* the recovered scale is non-finite or collapses to zero (``degenerate_scale``).
|
||||
"""
|
||||
_, scale = _require_similarity(V, tol)
|
||||
# Preserve the original accept-set exactly (finite *positive* scale, any
|
||||
# magnitude); split the negative case out to a distinct, honest reason.
|
||||
if not np.isfinite(scale):
|
||||
raise NullPointRecoveryError(
|
||||
f"degenerate dilation coefficient {scale}",
|
||||
reason="degenerate_scale",
|
||||
)
|
||||
if scale < 0.0:
|
||||
raise NullPointRecoveryError(
|
||||
f"orientation-reversing versor (signed scale {scale:.6g}); an improper "
|
||||
"similarity has no positive dilation — strip the reflection first",
|
||||
reason="improper_versor",
|
||||
)
|
||||
if scale == 0.0:
|
||||
raise NullPointRecoveryError(
|
||||
"degenerate dilation coefficient 0.0 (versor collapses n_inf)",
|
||||
reason="degenerate_scale",
|
||||
)
|
||||
return scale, dilator(scale)
|
||||
|
||||
|
||||
def recover_translation(V: np.ndarray, *, tol: float = 1e-9) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Recover the translation a similarity versor ``V`` applies to the origin.
|
||||
|
||||
Returns ``(a, T)`` with ``a`` the Euclidean image of the origin (3-vector)
|
||||
and ``T == translator(a)``. Reads ``W = V n_o rev(V)`` and dehomogenizes
|
||||
projectively: ``a = W[e1:e3+1] / w_o`` where ``w_o = W[e5] - W[e4]``. The
|
||||
weight divides out any dilation, and rotation — proper *or* a reflection —
|
||||
fixes the origin, so ``a`` is exact regardless of ``V``'s rotation/scale
|
||||
content. An improper (reflection) similarity is therefore accepted here even
|
||||
though :func:`recover_dilation` refuses it: the origin image is well defined,
|
||||
only the positive dilation is not.
|
||||
|
||||
Gates ``V`` as a similarity versor first (the same :func:`_require_similarity`
|
||||
gate as :func:`recover_dilation`), so a non-versor or a non-similarity — e.g. a
|
||||
transversion, which fixes the origin and would otherwise return a plausible
|
||||
``a`` silently — fails closed rather than returning a wrong value.
|
||||
|
||||
Raises :class:`NullPointRecoveryError` when
|
||||
* ``V`` is not a versor (``not_a_versor`` / ``degenerate_weight``) or does
|
||||
not fix infinity (``not_similarity``);
|
||||
* the origin maps to infinity (``origin_at_infinity`` — ``|w_o|`` at/below
|
||||
``tol``; guards the projective division, subsumed by the similarity gate
|
||||
for genuine inversions); or
|
||||
* the origin image leaves the null cone (``non_null_image`` — scale-relative
|
||||
defect > ``tol``), so ``W`` is not a conformal point.
|
||||
"""
|
||||
_require_similarity(V, tol)
|
||||
W = _sandwich(V, N_O)
|
||||
w_o = float(W[_E5_IDX] - W[_E4_IDX])
|
||||
if abs(w_o) <= tol:
|
||||
raise NullPointRecoveryError(
|
||||
f"origin maps to infinity (n_o weight {w_o:.3e}); no finite translation",
|
||||
reason="origin_at_infinity",
|
||||
)
|
||||
null_defect = abs(cga_inner(W, W))
|
||||
ref = max(1.0, float(np.dot(W, W)))
|
||||
if null_defect > tol * ref:
|
||||
raise NullPointRecoveryError(
|
||||
f"origin image leaves the null cone (defect {null_defect / ref:.3e}); "
|
||||
"not a conformal point",
|
||||
reason="non_null_image",
|
||||
)
|
||||
a = np.asarray(W[1:4], dtype=np.float64) / w_o
|
||||
return a, translator(a)
|
||||
182
algebra/rotor.py
182
algebra/rotor.py
|
|
@ -8,23 +8,13 @@ it describes a transformation being applied, not a property of the vocabulary.
|
|||
|
||||
import numpy as np
|
||||
|
||||
from .cl41 import N_COMPONENTS, geometric_product, grade_project, reverse, scalar_part
|
||||
from .cl41 import N_COMPONENTS, geometric_product, reverse
|
||||
from .versor import unitize_versor, versor_condition
|
||||
|
||||
_TRANSITION_CONDITION_TOL = 1e-4
|
||||
_NEAR_ZERO_TOL = 1e-12
|
||||
_SAME_POINT_TOL = 1e-6
|
||||
_STRICT_RESIDUE_TOL = 1e-2
|
||||
# A rotor is SIMPLE iff its grade-4 part vanishes (<R>_4 == 0 <=> R = R1 with a
|
||||
# single invariant plane). Above this, the rotor needs the invariant split.
|
||||
_SIMPLE_GRADE4_TOL = 1e-10
|
||||
# After the invariant (bivector) split, each factor is *approximately* simple;
|
||||
# B² higher-grade residual is float dust, not a true multi-plane bivector.
|
||||
# 1e-6 was too tight (raised on live word-transition / stream weights ≈ 1e-6..1e-3).
|
||||
# Refuse only residuals that are clearly structural non-simplicity.
|
||||
_SIMPLE_BSQ_HIGHER_TOL = 1e-3
|
||||
# |discriminant| below this => the two invariant eigenvalues coincide (isoclinic).
|
||||
_DEGEN_TOL = 1e-9
|
||||
|
||||
|
||||
def _identity(dtype: np.dtype) -> np.ndarray:
|
||||
|
|
@ -85,71 +75,40 @@ def make_rotor_from_angle(angle: float, bivector_idx: int = 6) -> np.ndarray:
|
|||
def rotor_power(R: np.ndarray, alpha: float) -> np.ndarray:
|
||||
"""Return R^alpha — the rotor on the manifold path from identity to R by alpha.
|
||||
|
||||
EXACT for ANY closed unit rotor in Cl(4,1), simple or not. A general rotor
|
||||
factors (invariant / bivector decomposition) into two commuting SIMPLE
|
||||
rotors ``R = R1 R2`` with distinct invariant planes; then, because they
|
||||
commute, ``R^α = R1^α R2^α`` and each factor uses the simple closed form
|
||||
below. The isoclinic case (coincident invariant planes) has its own closed
|
||||
form. There is no iteration, no approximation, and no external library —
|
||||
the split is built from the Cl(4,1) geometric product alone.
|
||||
|
||||
Simple factor ``R_i = a + B`` (scalar + simple bivector):
|
||||
For a simple unit rotor decomposed as ``R = a + B`` (scalar + bivector):
|
||||
|
||||
- rotation plane (``B² < 0``): ``R^α = cos(α·θ/2) + (sin(α·θ/2)/|B|) · B``
|
||||
where ``θ/2 = atan2(|B|, a)``.
|
||||
- boost plane (``B² > 0``): ``R^α = cosh(α·η/2) + (sinh(α·η/2)/|B|) · B``
|
||||
where ``η/2 = atanh(|B|/a)``.
|
||||
|
||||
The result stays on the rotor manifold by construction, so
|
||||
``versor_condition(rotor_power(R, α)) < 1e-6`` for any α whenever ``R`` is a
|
||||
closed unit rotor. (Historically this returned the *identity* for non-simple
|
||||
rotors — an approximation where exactness was available, which silently
|
||||
collapsed geodesic interpolation to a no-op. That corner is now closed.)
|
||||
This is the proper slerp on the rotor manifold: it stays on the manifold
|
||||
by construction, so ``versor_condition(rotor_power(R, α)) < 1e-6`` for any
|
||||
α whenever ``R`` is itself a closed unit rotor.
|
||||
|
||||
Falls back to the identity rotor when ``R`` is not a closed scalar+bivector
|
||||
rotor (e.g. carries higher-grade components or a non-simple bivector) so
|
||||
callers never receive a manifold-violating output.
|
||||
"""
|
||||
R_arr = np.asarray(R, dtype=np.float64)
|
||||
if R_arr.shape != (N_COMPONENTS,):
|
||||
raise ValueError(
|
||||
f"rotor_power expects a {N_COMPONENTS}-component rotor; got {R_arr.shape}."
|
||||
)
|
||||
|
||||
dtype = _result_dtype(R_arr)
|
||||
a = float(alpha)
|
||||
# Endpoints by continuity: R^0 = 1, R^1 = R. Stream weights can be denormal
|
||||
# tiny; never run the invariant split on α≈0 (smoke / generate.stream path).
|
||||
if abs(a) <= _NEAR_ZERO_TOL:
|
||||
return _identity(dtype)
|
||||
if abs(a - 1.0) <= _NEAR_ZERO_TOL:
|
||||
return R_arr.astype(dtype, copy=True)
|
||||
# <R>_4 == 0 <=> R is a single simple rotor. Otherwise take the split path.
|
||||
if float(np.linalg.norm(grade_project(R_arr, 4))) >= _SIMPLE_GRADE4_TOL:
|
||||
return _general_rotor_power(R_arr, a, dtype)
|
||||
return _simple_rotor_power(R_arr, a, dtype)
|
||||
|
||||
|
||||
def _simple_rotor_power(R_arr: np.ndarray, alpha: float, dtype: np.dtype) -> np.ndarray:
|
||||
"""R^alpha for a SIMPLE rotor (scalar + one simple bivector). Exact closed form.
|
||||
|
||||
Behaviour is unchanged from the original ``rotor_power`` on simple inputs.
|
||||
"""
|
||||
a = float(R_arr[0])
|
||||
B = R_arr.copy()
|
||||
B[0] = 0.0
|
||||
|
||||
# A simple rotor's bivector squares to a scalar (B² is grade-0 only).
|
||||
# Higher-grade residual above _SIMPLE_BSQ_HIGHER_TOL is structural non-simplicity
|
||||
# (fail closed). Below that, treat as float dust from the invariant split and
|
||||
# use only the scalar part of B² (closed form still exact on the simple plane).
|
||||
# Quick guard: bivector must be a simple bivector (B² is grade-0 only).
|
||||
B_sq_full = geometric_product(B, B).astype(np.float64)
|
||||
bsq_scalar = float(B_sq_full[0])
|
||||
B_sq_higher = B_sq_full.copy()
|
||||
B_sq_higher[0] = 0.0
|
||||
higher_norm = float(np.linalg.norm(B_sq_higher))
|
||||
if higher_norm > _SIMPLE_BSQ_HIGHER_TOL:
|
||||
# Not a simple bivector under the simple dispatch — fail closed, never
|
||||
# silently return identity (that zeros motion without a signal).
|
||||
raise ValueError(
|
||||
"rotor_power: non-simple bivector under simple dispatch "
|
||||
f"(B² higher-grade residual {higher_norm:.3e})"
|
||||
)
|
||||
if float(np.linalg.norm(B_sq_higher)) > 1e-6:
|
||||
# Non-simple bivector — return identity to avoid drift.
|
||||
return _identity(dtype)
|
||||
|
||||
# Near-identity: nothing to scale.
|
||||
bivector_norm = float(np.linalg.norm(B))
|
||||
|
|
@ -164,30 +123,19 @@ def _simple_rotor_power(R_arr: np.ndarray, alpha: float, dtype: np.dtype) -> np.
|
|||
new_a = float(np.cos(alpha * theta_half))
|
||||
new_b_mag = float(np.sin(alpha * theta_half))
|
||||
elif bsq_scalar > 0.0:
|
||||
# Boost plane. Domain of atanh requires |b_mag/a| < 1 and a > 0.
|
||||
# Boost plane.
|
||||
b_mag = float(np.sqrt(bsq_scalar))
|
||||
if a <= 0.0 or abs(b_mag / a) >= 1.0 - 1e-12:
|
||||
raise ValueError(
|
||||
f"rotor_power: boost plane outside unit-rotor domain "
|
||||
f"(a={a:.6g}, |B|/a={abs(b_mag / a) if a != 0.0 else float('inf'):.6g})"
|
||||
)
|
||||
# atanh requires |b_mag/a| < 1; for closed rotors a² - B² = 1 means
|
||||
# |b_mag| < |a|, so this is safe when a > 0.
|
||||
if a == 0.0:
|
||||
return _identity(dtype)
|
||||
eta_half = float(np.arctanh(b_mag / a))
|
||||
new_a = float(np.cosh(alpha * eta_half))
|
||||
new_b_mag = float(np.sinh(alpha * eta_half))
|
||||
else:
|
||||
# B² = 0: null bivector (translator generators in CGA). Exact binomial:
|
||||
# (a + B)^α = a^α + α a^{α-1} B (higher powers of B vanish).
|
||||
# Unit translators have a = 1 ⇒ T^α = 1 + α B = translator(α·a_eucl).
|
||||
# Historically this returned identity — a silent zeroing of the Cartan
|
||||
# translation leg in dual_correction_slerp (fidelity #16 follow-up).
|
||||
if abs(a) < _NEAR_ZERO_TOL:
|
||||
return _identity(dtype)
|
||||
result = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
result[0] = float(a) ** float(alpha) if a > 0.0 else float(np.sign(a) * (abs(a) ** float(alpha)))
|
||||
# Prefer real power for a>0; for a<0 (rare for unit translators) use |a|^α · sgn.
|
||||
scale_B = float(alpha) * (float(a) ** (float(alpha) - 1.0)) if a > 0.0 else float(alpha) * (abs(a) ** (float(alpha) - 1.0)) * float(np.sign(a))
|
||||
result = result + scale_B * B
|
||||
return result.astype(dtype, copy=False)
|
||||
# B² = 0: null bivector. Cannot interpolate on the manifold;
|
||||
# return identity to fail safely.
|
||||
return _identity(dtype)
|
||||
|
||||
result = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
result[0] = new_a
|
||||
|
|
@ -196,92 +144,6 @@ def _simple_rotor_power(R_arr: np.ndarray, alpha: float, dtype: np.dtype) -> np.
|
|||
return result.astype(dtype, copy=False)
|
||||
|
||||
|
||||
def _isoclinic_power_coeffs(x: float, alpha: float) -> tuple[float, float, float]:
|
||||
"""Power coefficients ``(A, f, c)`` for one of two identical (isoclinic) simple
|
||||
factors with ``c² = x``: ``R_i^α = A + f · G_i``. Handles rotation, boost, and
|
||||
the null limit uniformly.
|
||||
"""
|
||||
gsq = x - 1.0
|
||||
c = float(np.sqrt(max(x, 0.0)))
|
||||
if gsq < -1e-15: # rotation: c = cos(theta)
|
||||
theta = float(np.arccos(min(1.0, max(-1.0, c))))
|
||||
slin = float(np.sin(theta))
|
||||
A = float(np.cos(alpha * theta))
|
||||
f = float(np.sin(alpha * theta) / slin) if slin > 1e-300 else float(alpha)
|
||||
elif gsq > 1e-15: # boost: c = cosh(eta)
|
||||
eta = float(np.arccosh(max(1.0, c)))
|
||||
slin = float(np.sinh(eta))
|
||||
A = float(np.cosh(alpha * eta))
|
||||
f = float(np.sinh(alpha * eta) / slin) if slin > 1e-300 else float(alpha)
|
||||
else: # null / parabolic limit
|
||||
A, f = 1.0, float(alpha)
|
||||
return A, f, c
|
||||
|
||||
|
||||
def _split_commuting_simple(
|
||||
P: float, H: np.ndarray, W: np.ndarray, h0: float, disc: float
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Invariant decomposition of a non-simple rotor into two commuting SIMPLE
|
||||
unit rotors ``R = R1 R2`` (distinct-eigenvalue branch).
|
||||
|
||||
With ``P = <R>_0``, ``H = <R>_2``, ``W = <R>_4``: the squared scalars of the
|
||||
two simple factors are ``x_i = c_i²`` — the roots of ``t² − (2P²−h0) t + P²``
|
||||
— and each simple bivector ``G_i`` is recovered by the linear system in
|
||||
``{H, HW}``. Returns ``(R1, R2)`` as 32-component rotors.
|
||||
"""
|
||||
b = 2.0 * P * P - h0
|
||||
sq = float(np.sqrt(disc))
|
||||
x1 = 0.5 * (b + sq)
|
||||
x2 = 0.5 * (b - sq)
|
||||
c1 = float(np.sqrt(max(x1, 0.0)))
|
||||
c2 = float(np.sqrt(max(x2, 0.0)))
|
||||
if P < 0.0:
|
||||
c2 = -c2 # fix product sign so c1·c2 == <R>_0
|
||||
g1sq = x1 - 1.0
|
||||
g2sq = x2 - 1.0
|
||||
HW = grade_project(geometric_product(H, W), 2).astype(np.float64)
|
||||
det = c2 * c2 * g1sq - c1 * c1 * g2sq
|
||||
if abs(det) < _NEAR_ZERO_TOL:
|
||||
raise ValueError(
|
||||
"rotor_power: singular invariant split (unexpected for distinct eigenvalues)"
|
||||
)
|
||||
G1 = (c2 * g1sq * H - c1 * HW) / det
|
||||
G2 = (c2 * HW - c1 * g2sq * H) / det
|
||||
R1 = G1.copy()
|
||||
R1[0] = c1
|
||||
R2 = G2.copy()
|
||||
R2[0] = c2
|
||||
return R1, R2
|
||||
|
||||
|
||||
def _general_rotor_power(R_arr: np.ndarray, alpha: float, dtype: np.dtype) -> np.ndarray:
|
||||
"""R^alpha for a NON-simple rotor via the invariant (bivector) decomposition."""
|
||||
P = float(R_arr[0])
|
||||
H = grade_project(R_arr, 2).astype(np.float64)
|
||||
W = grade_project(R_arr, 4).astype(np.float64)
|
||||
h0 = float(scalar_part(geometric_product(H, H)))
|
||||
b = 2.0 * P * P - h0
|
||||
disc = b * b - 4.0 * P * P
|
||||
if disc <= _DEGEN_TOL:
|
||||
# Isoclinic: coincident invariant planes (x1 == x2 == b/2). The result
|
||||
# depends only on the symmetric functions H and W, so no per-plane split
|
||||
# is needed: R^α = A² + (A·f/c)·H + f²·W.
|
||||
A, f, c = _isoclinic_power_coeffs(0.5 * b, alpha)
|
||||
if c < _NEAR_ZERO_TOL:
|
||||
raise ValueError(
|
||||
"rotor_power: isoclinic rotor at theta~pi/2 has no principal power"
|
||||
)
|
||||
out = (A * f / c) * H + (f * f) * W
|
||||
out[0] += A * A
|
||||
return out.astype(dtype, copy=False)
|
||||
R1, R2 = _split_commuting_simple(P, H, W, h0, disc)
|
||||
out = geometric_product(
|
||||
_simple_rotor_power(R1, alpha, np.dtype(np.float64)),
|
||||
_simple_rotor_power(R2, alpha, np.dtype(np.float64)),
|
||||
)
|
||||
return out.astype(dtype, copy=False)
|
||||
|
||||
|
||||
def word_transition_rotor(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Compute the closed transition operator from source versor A to target B.
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
# Topological Reasoning — ADR-0242 Vector 5 (D6) Research Quarantine
|
||||
|
||||
**Status**: 🔴 RESEARCH ONLY — blocked from production
|
||||
**Authority**: ADR-0242 Vector 5 (topological anyon / braid holonomy)
|
||||
**Related**: `docs/adr/ADR-0242-atlas-packing-and-fibonacci.md`,
|
||||
`docs/analysis/fibonacci_applications_in_core_substrate.md` §2.2
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Isolated study surface for Fibonacci anyon fusion and braid holonomy as a
|
||||
**topological composition research program**. The canonical fusion rule under
|
||||
study is:
|
||||
|
||||
\[
|
||||
\tau \otimes \tau = \mathbf{1} \oplus \tau
|
||||
\]
|
||||
|
||||
This package exists so research stubs, notes, and future proof-carrying
|
||||
algebra can land **without** contaminating the live cognitive path:
|
||||
|
||||
```text
|
||||
listen → comprehend → recall → think → articulate → learn → replay
|
||||
```
|
||||
|
||||
## Hard quarantine (do not violate)
|
||||
|
||||
Until algebraic **and** numerical proofs exist, and until an explicit ADR
|
||||
promotion gate is Accepted by human review, this package is **BLOCKED** from:
|
||||
|
||||
| Surface | Rule |
|
||||
|---------|------|
|
||||
| Production runtime | No imports from serve / hot path |
|
||||
| `chat/` | Not for import by chat or `chat/runtime.py` |
|
||||
| Serve / FFI | Must not enter serve or FFI bindings |
|
||||
| `core/physics/` | Not a production physics operator |
|
||||
| `generate/` | Not for articulation / planner / cognitive turns |
|
||||
| `vault/` | Not for vault standing, seal, or COHERENT promotion |
|
||||
| `teaching/` | Not for reviewed teaching or pack mutation |
|
||||
| GoldTether / κ paths | Not for production residual / κ optimization |
|
||||
| `algebra` public surface | Not re-exported from `algebra/__init__.py` |
|
||||
|
||||
Architectural pin: `tests/test_adr_0242_topological_quarantine.py` scans
|
||||
production packages for any import of `topological_reasoning` and must find
|
||||
none.
|
||||
|
||||
## Sovereignty (ADR-0242)
|
||||
|
||||
Fibonacci / topological operators may **never** dictate proposition truth,
|
||||
safety policy, identity, or authorize autonomous COHERENT promotion. Active
|
||||
reasoning remains governed by versor closure, exact CRDT recall, and
|
||||
human-gated review.
|
||||
|
||||
## What is allowed here
|
||||
|
||||
- Research constants and docstring contracts (e.g. fusion rule labels)
|
||||
- Future proof sketches, numerical experiments under tests/evals only when
|
||||
explicitly gated
|
||||
- Documentation of open questions and proof obligations
|
||||
|
||||
## What is not allowed here
|
||||
|
||||
- Production logic wired into cognition, serve, or vault truth
|
||||
- Stochastic / approximate substitutes for exact CGA recall
|
||||
- Hidden normalization or drift repair outside owned algebra boundaries
|
||||
- Silent promotion of research results into COHERENT standing
|
||||
|
||||
## API note
|
||||
|
||||
The package may expose minimal docstring / constant stubs (e.g. `FUSION_RULE`).
|
||||
No production operators, no side effects, no I/O.
|
||||
|
||||
## Promotion path
|
||||
|
||||
1. Algebraic + numerical proofs land and are reviewable.
|
||||
2. ADR update records evidence and remaining risks.
|
||||
3. Human Accept of a production promotion gate.
|
||||
4. Only then may a **separate**, reviewed integration surface be designed —
|
||||
still subject to serve quarantine and sovereignty invariant.
|
||||
|
||||
Until then: this directory is a quarantine box, not a feature.
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
"""ADR-0242 V5 (D6) — topological anyon / braid holonomy research quarantine.
|
||||
|
||||
Fibonacci anyon fusion research surface. BLOCKED from production, serve, FFI,
|
||||
chat/runtime, vault COHERENT, teaching mutation, and GoldTether production
|
||||
paths until algebraic and numerical proofs exist (see package README).
|
||||
|
||||
This module intentionally re-exports nothing into ``algebra``'s public API
|
||||
and must not be imported by production packages.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# Canonical Fibonacci anyon fusion rule under study (research label only).
|
||||
# τ ⊗ τ = 1 ⊕ τ — not a production operator; no evaluation semantics.
|
||||
FUSION_RULE: str = "tau_otimes_tau_eq_1_oplus_tau"
|
||||
"""Research label for the Fibonacci anyon fusion rule τ⊗τ = 1⊕τ.
|
||||
|
||||
Docstring / constant only. Does not implement fusion, braiding, or holonomy.
|
||||
"""
|
||||
|
||||
__all__ = ["FUSION_RULE"]
|
||||
|
|
@ -17,9 +17,9 @@ from __future__ import annotations
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from packs.schema import AlignmentEdge
|
||||
from language_packs.schema import AlignmentEdge
|
||||
|
||||
_DATA_DIR = Path(__file__).parent.parent / "packs" / "data"
|
||||
_DATA_DIR = Path(__file__).parent.parent / "language_packs" / "data"
|
||||
|
||||
|
||||
class AlignmentGraph:
|
||||
|
|
@ -74,7 +74,7 @@ def load_alignment(
|
|||
"""
|
||||
Load AlignmentEdge records from <data_root>/<pack_id>/alignment.jsonl.
|
||||
|
||||
``data_root`` defaults to the committed ``packs/data`` tree; pass
|
||||
``data_root`` defaults to the committed ``language_packs/data`` tree; pass
|
||||
an alternate root (e.g. a test-fixture copy) to read packs from elsewhere
|
||||
without forking the parser.
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ def run_footprint(*, pack_id: str = "en_core_cognition_v1") -> FootprintReport:
|
|||
rss_post = _rss_bytes()
|
||||
py_bytes, py_modules = _measure_python_runtime()
|
||||
|
||||
pack_path = PROJECT_ROOT / "packs" / "data" / pack_id
|
||||
pack_path = PROJECT_ROOT / "language_packs" / "data" / pack_id
|
||||
vault_path = PROJECT_ROOT / "vault"
|
||||
rust_path = _rust_artifact_path()
|
||||
seed_packs_path = PROJECT_ROOT / "packs"
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ def bench_backend_speedup() -> BenchResult:
|
|||
will be tracked when the doctrine clock advances.
|
||||
"""
|
||||
from field.operators import GraphDiffusionOperator
|
||||
from packs.compiler import load_pack
|
||||
from language_packs.compiler import load_pack
|
||||
from scripts.run_pulse import _build_manifold
|
||||
|
||||
_, manifold = load_pack("en_core_cognition_v1")
|
||||
|
|
@ -225,7 +225,7 @@ def bench_versor_closure_audit() -> BenchResult:
|
|||
"""Run pulse for all eval cases, verify versor_condition < 1e-6 at every step."""
|
||||
from algebra.backend import versor_condition
|
||||
from field.operators import GraphDiffusionOperator, ConstraintCorrectionOperator
|
||||
from packs.compiler import load_pack
|
||||
from language_packs.compiler import load_pack
|
||||
from scripts.run_pulse import _build_manifold
|
||||
|
||||
_, manifold = load_pack("en_core_cognition_v1")
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ _ANCHOR_LENS_SUBSTRATE_PACK_IDS: dict[str, tuple[str, ...]] = {
|
|||
|
||||
_PACK_LEXICON_PATH = (
|
||||
Path(__file__).resolve().parent.parent
|
||||
/ "packs"
|
||||
/ "language_packs"
|
||||
/ "data"
|
||||
/ PACK_ID
|
||||
/ "lexicon.jsonl"
|
||||
|
|
@ -76,7 +76,7 @@ _PACK_LEXICON_PATH = (
|
|||
# lemmas — only lens-engagement reads from here.
|
||||
_COLLAPSE_ANCHORS_LEXICON_PATH = (
|
||||
Path(__file__).resolve().parent.parent
|
||||
/ "packs"
|
||||
/ "language_packs"
|
||||
/ "data"
|
||||
/ "en_collapse_anchors_v1"
|
||||
/ "lexicon.jsonl"
|
||||
|
|
@ -130,7 +130,7 @@ def _frame_gloss(lemma: str, pos: str, gloss: str) -> str:
|
|||
* (unknown) -> "{Lemma}: {gloss}." (back-compat fallback)
|
||||
|
||||
The glosses are authored to match these frames exactly (see
|
||||
the subagent briefs and ``packs/data/<pack>/glosses.jsonl``).
|
||||
the subagent briefs and ``language_packs/data/<pack>/glosses.jsonl``).
|
||||
Capitalization is applied only to the framed surface, never to
|
||||
the lemma in the lexicon (which stays lowercase by convention).
|
||||
"""
|
||||
|
|
@ -253,7 +253,7 @@ def _substrate_lexicon_by_entry_id(pack_id: str) -> dict[str, tuple[str, ...]]:
|
|||
"""
|
||||
lexicon_path = (
|
||||
Path(__file__).resolve().parent.parent
|
||||
/ "packs"
|
||||
/ "language_packs"
|
||||
/ "data"
|
||||
/ pack_id
|
||||
/ "lexicon.jsonl"
|
||||
|
|
|
|||
|
|
@ -32,10 +32,8 @@ Design constraints (CLAUDE.md / Reconstruction-over-storage):
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
# Default mounted lexicon-pack ids that ADR-0063 surface composers
|
||||
# consult. Order matters: earlier packs win on lemma collision. This
|
||||
|
|
@ -71,47 +69,7 @@ DEFAULT_RESOLVABLE_PACK_IDS: tuple[str, ...] = (
|
|||
"en_collapse_anchors_v1",
|
||||
)
|
||||
|
||||
# 3-core-language (English + Hebrew root density + Koine Greek Logos precision)
|
||||
# depth packs for LexicalResolution. These are used alongside DEFAULT when
|
||||
# building PropositionGraph nodes with language/root/morphology_id for
|
||||
# bidirectional comprehension/articulation/contemplation.
|
||||
# Sourced to align with anchor-lens substrate packs.
|
||||
DEPTH_PACK_IDS: tuple[str, ...] = (
|
||||
"he_core_cognition_v1",
|
||||
"he_logos_micro_v1",
|
||||
"grc_logos_cognition_v1",
|
||||
"grc_logos_micro_v1",
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LexicalResolution:
|
||||
"""Immutable, shared lexical resolution for masterful bidirectional use.
|
||||
|
||||
Serves comprehension (ingest → grounded PropositionGraph via resolve_gloss
|
||||
path), articulation (graph → realize_semantic can consult depth for
|
||||
precision), and internal reasoning/contemplation (operations "between"
|
||||
read/write on the same substrate).
|
||||
|
||||
Three core languages:
|
||||
- English: operational base
|
||||
- Hebrew: root density (e.g. ד-ב-ר for utterance/word)
|
||||
- Koine Greek: Logos precision (structuring principle, John 1:1)
|
||||
|
||||
The geometric field and PropositionGraph are designed to hold this depth.
|
||||
All fields are plain values; no mutation.
|
||||
"""
|
||||
pack_id: str
|
||||
lemma: str
|
||||
language: str
|
||||
pos: str = ""
|
||||
gloss: str | None = None
|
||||
semantic_domains: tuple[str, ...] = ()
|
||||
morphology_id: str | None = None
|
||||
root: str | None = None
|
||||
|
||||
|
||||
_PACK_ROOT = Path(__file__).resolve().parent.parent / "packs" / "data"
|
||||
_PACK_ROOT = Path(__file__).resolve().parent.parent / "language_packs" / "data"
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
|
|
@ -278,157 +236,6 @@ def resolve_gloss(
|
|||
return None
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _pack_full_lexicon_for(pack_id: str) -> dict[str, dict]:
|
||||
"""Return richer {lemma_lower: entry} including language, morphology_id,
|
||||
semantic_domains for 3-language depth resolution.
|
||||
|
||||
Mirrors _pack_lexicon_for structure but retains the full fields needed
|
||||
for LexicalResolution (Hebrew/Greek root-linked entries etc.).
|
||||
Immutable packs → safe to cache.
|
||||
"""
|
||||
lexicon_path = _PACK_ROOT / pack_id / "lexicon.jsonl"
|
||||
if not lexicon_path.exists():
|
||||
return {}
|
||||
out: dict[str, dict] = {}
|
||||
for line in lexicon_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
lemma = entry.get("lemma") or entry.get("surface")
|
||||
if not lemma or not isinstance(lemma, str):
|
||||
continue
|
||||
key = lemma.strip().lower()
|
||||
# retain relevant depth fields
|
||||
out[key] = {
|
||||
"language": entry.get("language") or "en",
|
||||
"morphology_id": entry.get("morphology_id"),
|
||||
"semantic_domains": tuple(str(d) for d in entry.get("semantic_domains", ())),
|
||||
"pos": entry.get("pos") or "",
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _pack_morph_roots_for(pack_id: str) -> dict[str, str]:
|
||||
"""Return {morphology_id: root} for the pack's morphology.jsonl.
|
||||
|
||||
Enables root-level depth (Hebrew triconsonantal, Greek stems) without
|
||||
pulling the full MorphologyRegistry into the hot resolver path.
|
||||
"""
|
||||
morph_path = _PACK_ROOT / pack_id / "morphology.jsonl"
|
||||
if not morph_path.exists():
|
||||
return {}
|
||||
out: dict[str, str] = {}
|
||||
for line in morph_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
mid = entry.get("morphology_id")
|
||||
root = entry.get("root")
|
||||
if isinstance(mid, str) and isinstance(root, str) and mid and root:
|
||||
out[mid] = root
|
||||
return out
|
||||
|
||||
|
||||
def resolve_entry(
|
||||
lemma: str,
|
||||
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
||||
) -> LexicalResolution | None:
|
||||
"""Return rich LexicalResolution for the first matching pack.
|
||||
|
||||
This is the canonical entry point for depth-aware resolution usable
|
||||
both for comprehension grounding and (symmetrically) articulation /
|
||||
contemplation. Falls back gracefully for English-centric packs.
|
||||
First-match-wins, same order as resolve_lemma/resolve_gloss.
|
||||
"""
|
||||
if not lemma or not isinstance(lemma, str):
|
||||
return None
|
||||
key = lemma.strip().lower()
|
||||
if not key:
|
||||
return None
|
||||
for pack_id in pack_ids:
|
||||
full = _pack_full_lexicon_for(pack_id)
|
||||
if key not in full:
|
||||
continue
|
||||
info = full[key]
|
||||
# gloss is optional but preferred when present
|
||||
glosses = _pack_glosses_for(pack_id)
|
||||
pos, gloss = glosses.get(key, ("", None))
|
||||
if not gloss:
|
||||
# fall back to pos from full if no dedicated gloss
|
||||
pos = info.get("pos", "") or pos
|
||||
morph_id = info.get("morphology_id")
|
||||
root = None
|
||||
if morph_id:
|
||||
roots = _pack_morph_roots_for(pack_id)
|
||||
root = roots.get(morph_id)
|
||||
return LexicalResolution(
|
||||
pack_id=pack_id,
|
||||
lemma=lemma,
|
||||
language=info.get("language", "en"),
|
||||
pos=pos or "",
|
||||
gloss=gloss,
|
||||
semantic_domains=info.get("semantic_domains", ()),
|
||||
morphology_id=morph_id,
|
||||
root=root,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
def resolve_token_depths(
|
||||
tokens: Sequence[str],
|
||||
pack_ids: tuple[str, ...] | None = None,
|
||||
) -> tuple[dict[str, dict], str | None]:
|
||||
"""Resolve he/grc depth for raw tokens before PropositionGraph exists.
|
||||
|
||||
Same-turn recognition needs root data before graph build fills
|
||||
``node_depths`` with ``p*`` ids. This returns provisional depths keyed
|
||||
by ``t{i}`` (token index) for tokens that resolve with he/grc language
|
||||
and a root, plus the first such provisional node id as the agent
|
||||
candidate.
|
||||
|
||||
Pure relative to pack lexicon lookups (deterministic exact match).
|
||||
Empty when no depth-bearing tokens are present.
|
||||
"""
|
||||
if pack_ids is None:
|
||||
pack_ids = DEFAULT_RESOLVABLE_PACK_IDS + DEPTH_PACK_IDS
|
||||
depths: dict[str, dict] = {}
|
||||
agent_node_id: str | None = None
|
||||
if not tokens:
|
||||
return depths, agent_node_id
|
||||
for i, tok in enumerate(tokens):
|
||||
if not isinstance(tok, str) or not tok.strip():
|
||||
continue
|
||||
res = resolve_entry(tok, pack_ids=pack_ids)
|
||||
if res is None:
|
||||
continue
|
||||
lang = res.language
|
||||
root = res.root
|
||||
if lang not in ("he", "grc") or not root:
|
||||
continue
|
||||
nid = f"t{i}"
|
||||
entry: dict = {"language": lang, "root": root}
|
||||
if res.morphology_id:
|
||||
entry["morphology_id"] = res.morphology_id
|
||||
depths[nid] = entry
|
||||
if agent_node_id is None:
|
||||
agent_node_id = nid
|
||||
return depths, agent_node_id
|
||||
|
||||
|
||||
def clear_resolver_cache() -> None:
|
||||
"""Drop all caches in this module — lexicon AND glosses.
|
||||
|
||||
|
|
@ -444,99 +251,3 @@ def clear_resolver_cache() -> None:
|
|||
"""
|
||||
_pack_lexicon_for.cache_clear()
|
||||
_pack_glosses_for.cache_clear()
|
||||
_pack_lemma_to_entry_id.cache_clear()
|
||||
_pack_senses_for.cache_clear()
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _pack_lemma_to_entry_id(pack_id: str) -> dict[str, str]:
|
||||
"""Return ``{lemma_lower: entry_id}`` from the pack's lexicon.jsonl."""
|
||||
lexicon_path = _PACK_ROOT / pack_id / "lexicon.jsonl"
|
||||
if not lexicon_path.exists():
|
||||
return {}
|
||||
out: dict[str, str] = {}
|
||||
for line in lexicon_path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
lemma = entry.get("lemma") or entry.get("surface")
|
||||
if not lemma or not isinstance(lemma, str):
|
||||
continue
|
||||
entry_id = entry.get("entry_id")
|
||||
if isinstance(entry_id, str) and entry_id.strip():
|
||||
out[lemma.lower()] = entry_id.strip()
|
||||
return out
|
||||
|
||||
|
||||
@lru_cache(maxsize=16)
|
||||
def _pack_senses_for(pack_id: str) -> dict[str, dict]:
|
||||
"""Return ``{lemma_id: geometric_signature}`` from the pack's optional
|
||||
``senses.jsonl`` file. Also maps ``lemma`` as a fallback key.
|
||||
"""
|
||||
path = _PACK_ROOT / pack_id / "senses.jsonl"
|
||||
if not path.exists():
|
||||
return {}
|
||||
out: dict[str, dict] = {}
|
||||
for line in path.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
|
||||
geometric_signature = entry.get("geometric_signature")
|
||||
if not isinstance(geometric_signature, dict):
|
||||
continue
|
||||
|
||||
lemma_id = entry.get("lemma_id")
|
||||
if isinstance(lemma_id, str) and lemma_id.strip():
|
||||
out[lemma_id.strip()] = geometric_signature
|
||||
|
||||
# Optional fallback for direct lemma matching if lemma is present
|
||||
lemma = entry.get("lemma")
|
||||
if isinstance(lemma, str) and lemma.strip():
|
||||
out[lemma.strip().lower()] = geometric_signature
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def resolve_geometric_signature(
|
||||
lemma: str,
|
||||
pack_ids: tuple[str, ...] = DEFAULT_RESOLVABLE_PACK_IDS,
|
||||
) -> tuple[str, dict] | None:
|
||||
"""Return ``(pack_id, geometric_signature)`` for the first pack in
|
||||
*pack_ids* that BOTH (a) ratifies *lemma* in its ``lexicon.jsonl``
|
||||
AND (b) carries a geometric_signature for it in ``senses.jsonl``.
|
||||
"""
|
||||
if not lemma or not isinstance(lemma, str):
|
||||
return None
|
||||
key = lemma.strip().lower()
|
||||
if not key:
|
||||
return None
|
||||
for pack_id in pack_ids:
|
||||
lex = _pack_lexicon_for(pack_id)
|
||||
if key not in lex:
|
||||
continue
|
||||
|
||||
lemma_ids = _pack_lemma_to_entry_id(pack_id)
|
||||
entry_id = lemma_ids.get(key)
|
||||
|
||||
senses = _pack_senses_for(pack_id)
|
||||
|
||||
if entry_id and entry_id in senses:
|
||||
return (pack_id, senses[entry_id])
|
||||
|
||||
if key in senses:
|
||||
return (pack_id, senses[key])
|
||||
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ from generate.result import GenerationResult
|
|||
from generate.stream import generate
|
||||
from generate.surface import SentenceAssembler, SentencePlan, SurfaceContext
|
||||
from ingest.gate import inject
|
||||
from packs import OOVPolicy, load_mounted_packs, load_pack, load_pack_entries
|
||||
from language_packs import OOVPolicy, load_mounted_packs, load_pack, load_pack_entries
|
||||
from persona.motor import PersonaMotor
|
||||
from session.context import SessionContext
|
||||
from session.correction import CorrectionPass
|
||||
|
|
@ -608,7 +608,6 @@ class ChatRuntime:
|
|||
pack_ids = tuple(config.input_packs)
|
||||
|
||||
self.config = resolved_config
|
||||
self._last_node_depths: dict | None = None # 3-lang PropGraph depth propagation (he/grc roots) to contemplate paths; set by pipeline
|
||||
manifests = []
|
||||
manifolds = []
|
||||
entries = []
|
||||
|
|
@ -888,18 +887,10 @@ class ChatRuntime:
|
|||
self._pending_recognizer_examples.clear()
|
||||
candidates_to_save = self._pending_candidates
|
||||
if self.config.auto_contemplate and candidates_to_save:
|
||||
# 3-lang depth propagation contract (AC5 / review):
|
||||
# _last_node_depths is written by CognitiveTurnPipeline after PropGraph construction
|
||||
# (from resolver + build_node_depths on enriched GraphNodes). It is forwarded here
|
||||
# as depth= into teaching.contemplation.contemplate so that framing findings and
|
||||
# proposed_chain carry depth_roots for he/grc. Same contract used for runtime
|
||||
# contemplate and candidate paths. See also core/cognition/result.py (node_depths /
|
||||
# graph_anti_unify on result) + pipeline.py.
|
||||
from teaching.contemplation import contemplate
|
||||
vault_probe = _vault_probe_for_context(self._context) if self._context else None
|
||||
depth = getattr(self, '_last_node_depths', None)
|
||||
candidates_to_save = [
|
||||
contemplate(c, vault_probe=vault_probe, depth=depth)
|
||||
contemplate(c, vault_probe=vault_probe)
|
||||
for c in candidates_to_save
|
||||
]
|
||||
# ADR-0219 — generation-dir atomic checkpoint. All files are written
|
||||
|
|
@ -979,10 +970,8 @@ class ChatRuntime:
|
|||
vault_probe = (
|
||||
_vault_probe_for_context(self._context) if self._context else None
|
||||
)
|
||||
# 3-lang depth propagation contract (see checkpoint_engine_state)
|
||||
depth = getattr(self, '_last_node_depths', None)
|
||||
contemplated = [
|
||||
contemplate(candidate, vault_probe=vault_probe, depth=depth)
|
||||
contemplate(candidate, vault_probe=vault_probe)
|
||||
for candidate in self._pending_candidates
|
||||
]
|
||||
contemplated_count = len(contemplated)
|
||||
|
|
@ -1550,10 +1539,8 @@ class ChatRuntime:
|
|||
if self.config.vault_probe_discoveries
|
||||
else None
|
||||
)
|
||||
# 3-lang depth propagation contract (see checkpoint_engine_state)
|
||||
depth = getattr(self, '_last_node_depths', None)
|
||||
candidates = tuple(
|
||||
contemplate(c, vault_probe=vault_probe, depth=depth) for c in candidates
|
||||
contemplate(c, vault_probe=vault_probe) for c in candidates
|
||||
)
|
||||
self._pending_candidates.extend(candidates)
|
||||
sink = self._discovery_sink
|
||||
|
|
|
|||
21
conftest.py
21
conftest.py
|
|
@ -1,18 +1,19 @@
|
|||
"""Project-root conftest — test classification registries.
|
||||
|
||||
The QUARANTINE set is the only allowed registry for known-failing tests.
|
||||
It is currently empty. If it ever contains nodeids, CI excludes them via
|
||||
``-m "not quarantine"`` (smoke, full-pytest fast lane, nightly full).
|
||||
The suite is a ratchet: a quarantined test removed from this set must pass
|
||||
It is currently empty. If it ever contains nodeids, the CI gate at
|
||||
.github/workflows/full-pytest.yml runs ``pytest -m "not quarantine"``
|
||||
so those explicitly tracked failures do not block unrelated PRs. The
|
||||
suite is a ratchet: a quarantined test removed from this set must pass
|
||||
on its own merits.
|
||||
|
||||
See docs/test-debt-quarantine.md for current policy and historical cluster
|
||||
diagnoses. See docs/testing-lanes.md for CI lane policy (PR / main / nightly).
|
||||
diagnoses.
|
||||
|
||||
To remove a test from quarantine:
|
||||
1. Land a PR that makes the test pass.
|
||||
2. Delete its entry from QUARANTINE in the same PR.
|
||||
3. The main fast gate and nightly full gate will both require it to pass.
|
||||
3. The full-pytest CI gate will now require it to keep passing.
|
||||
|
||||
Adding a test to QUARANTINE is strongly discouraged. If a new
|
||||
failure surfaces, the right default is to fix it in the PR that
|
||||
|
|
@ -79,14 +80,12 @@ QUARANTINE: frozenset[str] = frozenset()
|
|||
# a developer run a fast lane locally. Classification adds the ``slow`` marker
|
||||
# ONLY — it never skips — so ``-m slow`` SELECTS these tests. Choose a lane:
|
||||
#
|
||||
# fast lane: pytest -m "not quarantine and not slow" (make test-fast;
|
||||
# also full-pytest.yml on main)
|
||||
# fast lane: pytest -m "not quarantine and not slow" (make test-fast)
|
||||
# slow lane: pytest -m "slow and not quarantine" (make test-slow)
|
||||
# full lane: pytest -m "not quarantine" (make test-full;
|
||||
# also nightly-full-pytest.yml)
|
||||
# full lane: pytest -m "not quarantine" (make test-full; CI)
|
||||
#
|
||||
# CI policy: PR = smoke subset; main = fast lane; nightly = full including
|
||||
# slow. See docs/testing-lanes.md.
|
||||
# CI is unchanged: smoke.yml and full-pytest.yml run ``-m "not quarantine"``,
|
||||
# which still includes slow tests. See docs/testing-lanes.md.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Whole-file: the cost is carried by a module/session-scoped fixture, so marking
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
"""CORE ABI namespace.
|
||||
|
||||
Exposes GeometricDelta and its validation routines.
|
||||
"""
|
||||
|
||||
from core.abi.geometric_delta import GeometricDelta
|
||||
from core.abi.geometric_delta_validator import (
|
||||
GeometricDeltaValidationError,
|
||||
validate_geometric_delta,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"GeometricDelta",
|
||||
"GeometricDeltaValidationError",
|
||||
"validate_geometric_delta",
|
||||
]
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
"""GeometricDelta ABI Definition.
|
||||
|
||||
Single source of truth for the GeometricDelta struct which defines the boundary
|
||||
between any modality compiler (like Sopher's Callosum) and the Master substrate (CORE).
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
|
||||
from core.epistemic_state import EpistemicState
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GeometricDelta:
|
||||
"""The canonical ABI for field updates to the Master substrate.
|
||||
|
||||
All compilers must emit exactly this type, and it must pass the guarded closure
|
||||
projector at the boundary.
|
||||
"""
|
||||
id: str # content-addressed hash
|
||||
parents: Set[str] # CRDT frontier IDs
|
||||
modality: str # e.g. "lh_text", "sensor", "tool"
|
||||
compiler_id: str # name+version of compiler
|
||||
semantic: Dict[str, Any] # typed primitive (enum + payload)
|
||||
amr_scope: Dict[str, Any] # resolution + region metadata
|
||||
delta_versor: List[float] # 32 components, Cl(4,1) basis
|
||||
inverse_ref: Optional[str] # optional correction link
|
||||
provenance: Dict[str, Any] # source, time, adr_refs, hash
|
||||
epistemic: EpistemicState # CORE truth-seeking state
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
"""Validation contracts for GeometricDelta.
|
||||
|
||||
Ensures that any update to the Master substrate complies with Cl(4,1) shape
|
||||
invariants, provenance metadata, and truth-seeking constraints.
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Tuple
|
||||
from core.abi.geometric_delta import GeometricDelta
|
||||
from core.epistemic_state import EpistemicState
|
||||
|
||||
DEFAULT_TOLERANCE = 1e-6
|
||||
|
||||
class GeometricDeltaValidationError(ValueError):
|
||||
"""Raised when a GeometricDelta violates the ABI contract."""
|
||||
pass
|
||||
|
||||
def validate_geometric_delta(delta: GeometricDelta, tolerance: float | None = None) -> Tuple[bool, str]:
|
||||
"""Validates the structure, metadata, and closure of a GeometricDelta.
|
||||
|
||||
Returns:
|
||||
(True, "") if valid.
|
||||
(False, reason) if invalid.
|
||||
|
||||
Raises:
|
||||
GeometricDeltaValidationError: If strict checks are failed.
|
||||
"""
|
||||
if tolerance is None:
|
||||
try:
|
||||
tolerance = float(os.getenv("CORE_ABI_TOLERANCE", str(DEFAULT_TOLERANCE)))
|
||||
except ValueError:
|
||||
tolerance = DEFAULT_TOLERANCE
|
||||
|
||||
# 1. Shape and basis check
|
||||
if not isinstance(delta.delta_versor, list):
|
||||
raise GeometricDeltaValidationError("delta_versor must be a list of floats")
|
||||
if len(delta.delta_versor) != 32:
|
||||
raise GeometricDeltaValidationError(
|
||||
f"delta_versor must have exactly 32 components, got {len(delta.delta_versor)}"
|
||||
)
|
||||
if not all(isinstance(x, (int, float)) for x in delta.delta_versor):
|
||||
raise GeometricDeltaValidationError("delta_versor must contain only numeric values")
|
||||
|
||||
# 2. Epistemic state check
|
||||
if not isinstance(delta.epistemic, EpistemicState):
|
||||
raise GeometricDeltaValidationError(
|
||||
f"epistemic must be an instance of EpistemicState, got {type(delta.epistemic)}"
|
||||
)
|
||||
|
||||
# 3. Provenance structure check
|
||||
if not isinstance(delta.provenance, dict):
|
||||
raise GeometricDeltaValidationError("provenance must be a dictionary")
|
||||
|
||||
required_provenance_keys = {"source", "time", "hash", "adr_refs"}
|
||||
missing_keys = required_provenance_keys - set(delta.provenance.keys())
|
||||
if missing_keys:
|
||||
raise GeometricDeltaValidationError(
|
||||
f"provenance missing required keys: {missing_keys}"
|
||||
)
|
||||
|
||||
# 4. AMR scope structure check
|
||||
if not isinstance(delta.amr_scope, dict):
|
||||
raise GeometricDeltaValidationError("amr_scope must be a dictionary")
|
||||
|
||||
# 5. Closure check stub
|
||||
# Delegates to guarded projector check (scale + monotone Newton).
|
||||
# For now, this is a placeholder/contract interface.
|
||||
# In Sopher or CORE-rs, this triggers the algebraic Cl(4,1) projector.
|
||||
is_closed, residual = check_cl41_closure_invariant(delta.delta_versor, tolerance)
|
||||
if not is_closed:
|
||||
return False, f"Cl(4,1) closure invariant violated: residual {residual} > tolerance {tolerance}"
|
||||
|
||||
return True, ""
|
||||
|
||||
def check_cl41_closure_invariant(versor: list[float], tolerance: float) -> Tuple[bool, float]:
|
||||
"""Stub for Cl(4,1) algebraic closure verification.
|
||||
|
||||
In full implementation, this calls core-rs or mlx_cl41 to verify
|
||||
||F * reverse(F) - 1||_F < tolerance.
|
||||
"""
|
||||
# Simple placeholder: for now, assume valid or check a simple norm condition.
|
||||
# If the user has set CORE_STRICT_PROJECTOR, we would execute the guarded projector.
|
||||
# We return True and a dummy residual of 0.0 for this abstract stub.
|
||||
return True, 0.0
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
"""ADR-DAG conformal embedding (R&D-Revised §2.4 / issue #21).
|
||||
|
||||
Deterministic embedding of ADR markdown into Cl(4,1) bivector space and
|
||||
master-blade drift checks for proposal coherence.
|
||||
"""
|
||||
|
||||
from core.adr.validator import (
|
||||
AdrDagValidationError,
|
||||
embed_adr_markdown,
|
||||
master_architecture_blade,
|
||||
proposal_drift,
|
||||
simple_bivector_project,
|
||||
validate_proposal_against_master,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AdrDagValidationError",
|
||||
"embed_adr_markdown",
|
||||
"master_architecture_blade",
|
||||
"proposal_drift",
|
||||
"simple_bivector_project",
|
||||
"validate_proposal_against_master",
|
||||
]
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
"""
|
||||
core/adr/validator.py
|
||||
|
||||
ADR-DAG conformal embedding Ψ(M) (R&D-Revised §2.4 / #21).
|
||||
|
||||
SHA-256(M) → 10×3-byte segments → c_k ∈ [−1, 1]
|
||||
→ 10 basis bivectors (planes 6..15) → simple-bivector projection
|
||||
→ master blade = successive wedge of load-bearing ADR embeddings
|
||||
→ proposal drift = ‖B_p ∧ A_master‖
|
||||
|
||||
Cross-check: does **not** reimplement GeometricDelta ABI validation
|
||||
(``core/abi/geometric_delta_validator.py``). This module embeds ADR text into
|
||||
geometry; that module validates GeometricDelta envelopes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
from typing import Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
from algebra.cl41 import N_COMPONENTS, geometric_product, grade_project
|
||||
|
||||
_BIVECTOR_PLANES = tuple(range(6, 16)) # 10 planes
|
||||
_NEAR_ZERO = 1e-12
|
||||
_SIMPLE_G4_TOL = 1e-9
|
||||
|
||||
|
||||
class AdrDagValidationError(ValueError):
|
||||
"""Fail-closed refusal from ADR-DAG embedding / drift checks."""
|
||||
|
||||
def __init__(self, reason: str, **disclosure) -> None:
|
||||
self.reason = reason
|
||||
self.disclosure = dict(disclosure)
|
||||
super().__init__(f"adr_dag refused [{reason}]: {self.disclosure}")
|
||||
|
||||
|
||||
def _grade_mass(v: np.ndarray) -> int:
|
||||
for g in range(5, -1, -1):
|
||||
if float(np.linalg.norm(grade_project(v, g))) > _NEAR_ZERO:
|
||||
return g
|
||||
return 0
|
||||
|
||||
|
||||
def multivector_wedge(A: np.ndarray, B: np.ndarray) -> np.ndarray:
|
||||
"""Grade-raising wedge approximation: grade-project of geometric product.
|
||||
|
||||
For pure blades this matches the outer product grade. Used for master-blade
|
||||
assembly and drift (B_p ∧ A_master).
|
||||
"""
|
||||
a = np.asarray(A, dtype=np.float64)
|
||||
b = np.asarray(B, dtype=np.float64)
|
||||
ga, gb = _grade_mass(a), _grade_mass(b)
|
||||
target = min(5, ga + gb)
|
||||
if target == 0:
|
||||
return grade_project(geometric_product(a, b), 0)
|
||||
return grade_project(geometric_product(a, b), target).astype(np.float64)
|
||||
|
||||
|
||||
def simple_bivector_project(B: np.ndarray) -> np.ndarray:
|
||||
"""Project a multivector generator onto a simple bivector.
|
||||
|
||||
Spec intent: pure grade-2 support that is *simple* (single plane). Pure
|
||||
multiplane grade-2 has nontrivial ``⟨B B⟩₄``; we always collapse to the
|
||||
dominant plane when more than one plane is occupied (deterministic).
|
||||
"""
|
||||
arr = np.asarray(B, dtype=np.float64)
|
||||
if arr.shape != (N_COMPONENTS,):
|
||||
raise AdrDagValidationError("bad_shape", shape=tuple(arr.shape))
|
||||
B2 = grade_project(arr, 2).astype(np.float64)
|
||||
occupied = [i for i in _BIVECTOR_PLANES if abs(float(B2[i])) > _NEAR_ZERO]
|
||||
if len(occupied) <= 1:
|
||||
return B2
|
||||
# Multiplane → dominant-plane collapse (simple by construction).
|
||||
best_i = max(occupied, key=lambda i: abs(float(B2[i])))
|
||||
out = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
out[best_i] = float(B2[best_i])
|
||||
return out
|
||||
|
||||
|
||||
def embed_adr_markdown(markdown: str) -> np.ndarray:
|
||||
"""Ψ(M): deterministic SHA-256 → 10 bivector coefficients → simple project.
|
||||
|
||||
Identical markdown ⇒ identical 32-vector (replay pin).
|
||||
"""
|
||||
if not isinstance(markdown, str):
|
||||
raise AdrDagValidationError("not_str", type=type(markdown).__name__)
|
||||
# Empty markdown is a valid document identity (still deterministic).
|
||||
digest = hashlib.sha256(markdown.encode("utf-8")).digest() # 32 bytes
|
||||
B = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
for k, plane in enumerate(_BIVECTOR_PLANES):
|
||||
# 3-byte segments; last 2 hash bytes unused (spec: 10×3=30).
|
||||
chunk = digest[k * 3 : k * 3 + 3]
|
||||
u = int.from_bytes(chunk, "big") # 0 .. 2^24-1
|
||||
c = (u / float(0xFFFFFF)) * 2.0 - 1.0 # [-1, 1]
|
||||
B[plane] = c
|
||||
return simple_bivector_project(B)
|
||||
|
||||
|
||||
def master_architecture_blade(
|
||||
embeddings: Sequence[np.ndarray],
|
||||
) -> np.ndarray:
|
||||
"""Assemble load-bearing ADR embeddings into a master architecture blade.
|
||||
|
||||
Prefer successive wedge when non-degenerate; if a wedge step vanishes
|
||||
(parallel simple planes after projection), fall back to algebraic sum of
|
||||
simple bivectors so the master never fabricates a zero blade.
|
||||
"""
|
||||
if not embeddings:
|
||||
raise AdrDagValidationError("empty_master_set")
|
||||
simples = [
|
||||
simple_bivector_project(np.asarray(e, dtype=np.float64))
|
||||
for e in embeddings
|
||||
]
|
||||
wedge = simples[0].copy()
|
||||
for i, e in enumerate(simples[1:], start=1):
|
||||
w = multivector_wedge(wedge, e)
|
||||
if float(np.linalg.norm(w)) > _NEAR_ZERO:
|
||||
wedge = w
|
||||
# else: parallel/collinear under wedge — keep prior wedge, continue
|
||||
if float(np.linalg.norm(wedge)) > _NEAR_ZERO:
|
||||
return wedge.astype(np.float64)
|
||||
# Full wedge chain degenerate: superposition master (still deterministic).
|
||||
acc = np.zeros(N_COMPONENTS, dtype=np.float64)
|
||||
for e in simples:
|
||||
acc = acc + e
|
||||
if float(np.linalg.norm(acc)) < _NEAR_ZERO:
|
||||
raise AdrDagValidationError("degenerate_master_blade", at_index=0)
|
||||
return simple_bivector_project(acc)
|
||||
|
||||
|
||||
def proposal_drift(B_proposal: np.ndarray, A_master: np.ndarray) -> float:
|
||||
"""Drift = ‖B_p ∧ A_master‖ (Euclidean coeff norm of the wedge)."""
|
||||
Bp = simple_bivector_project(np.asarray(B_proposal, dtype=np.float64))
|
||||
Am = np.asarray(A_master, dtype=np.float64)
|
||||
if Am.shape != (N_COMPONENTS,):
|
||||
raise AdrDagValidationError("bad_master_shape", shape=tuple(Am.shape))
|
||||
w = multivector_wedge(Bp, Am)
|
||||
return float(np.linalg.norm(w))
|
||||
|
||||
|
||||
def validate_proposal_against_master(
|
||||
proposal_markdown: str,
|
||||
master_markdowns: Sequence[str],
|
||||
*,
|
||||
max_drift: float = 1.0,
|
||||
) -> tuple[bool, float, np.ndarray, np.ndarray]:
|
||||
"""Embed proposal + masters; return (ok, drift, B_p, A_master)."""
|
||||
if not master_markdowns:
|
||||
raise AdrDagValidationError("empty_master_set")
|
||||
masters = [embed_adr_markdown(m) for m in master_markdowns]
|
||||
A = master_architecture_blade(masters)
|
||||
Bp = embed_adr_markdown(proposal_markdown)
|
||||
d = proposal_drift(Bp, A)
|
||||
ok = bool(d <= float(max_drift) + _NEAR_ZERO)
|
||||
return ok, d, Bp, A
|
||||
|
||||
|
||||
__all__ = [
|
||||
"AdrDagValidationError",
|
||||
"embed_adr_markdown",
|
||||
"master_architecture_blade",
|
||||
"multivector_wedge",
|
||||
"proposal_drift",
|
||||
"simple_bivector_project",
|
||||
"validate_proposal_against_master",
|
||||
]
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Wires the five follow-up items from ADR-0091 §"Follow-up Work" into a
|
||||
single evidence-bearing report. The existing parser
|
||||
(:func:`packs.domain_contract.parse_domain_contract`) handles
|
||||
(:func:`language_packs.domain_contract.parse_domain_contract`) handles
|
||||
structural validation; this module layers the nine semantic predicates
|
||||
from ADR-0091 §"Validation Semantics" on top.
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ from core.capability.reviewers import (
|
|||
load_reviewer_registry,
|
||||
)
|
||||
from core.capability.sources import LEDGER_SOURCES
|
||||
from packs.domain_contract import (
|
||||
from language_packs.domain_contract import (
|
||||
DomainContractValidation,
|
||||
DomainPackContract,
|
||||
validate_domain_contract_pack,
|
||||
|
|
@ -113,7 +113,7 @@ def _predicate_p1_manifest_valid(
|
|||
lands, this predicate inherits the improvement.
|
||||
"""
|
||||
try:
|
||||
from packs import compiler as pack_compiler
|
||||
from language_packs import compiler as pack_compiler
|
||||
|
||||
loader = getattr(pack_compiler, "load_pack", None)
|
||||
if loader is None:
|
||||
|
|
@ -121,7 +121,7 @@ def _predicate_p1_manifest_valid(
|
|||
predicate_id="P1",
|
||||
title="manifest/checksum valid",
|
||||
passed=False,
|
||||
notes="packs.compiler.load_pack not available",
|
||||
notes="language_packs.compiler.load_pack not available",
|
||||
)
|
||||
loader(pack_id)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
|
|
@ -486,7 +486,7 @@ def evaluate_domain_contract(
|
|||
- ``reviewer_registry`` injects a parsed registry (avoids re-loading
|
||||
from disk per pack).
|
||||
"""
|
||||
root = data_root or (_REPO_ROOT / "packs" / "data")
|
||||
root = data_root or (_REPO_ROOT / "language_packs" / "data")
|
||||
|
||||
validation: DomainContractValidation = validate_domain_contract_pack(
|
||||
pack_id, data_root=root
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ _REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
|||
# The math domain's operator pack — same constant the solver uses.
|
||||
DEFAULT_MATH_PACK_ID: str = "en_arithmetic_v1"
|
||||
DEFAULT_MATH_LEXICON: Path = (
|
||||
_REPO_ROOT / "packs" / "data" / DEFAULT_MATH_PACK_ID / "lexicon.jsonl"
|
||||
_REPO_ROOT / "language_packs" / "data" / DEFAULT_MATH_PACK_ID / "lexicon.jsonl"
|
||||
)
|
||||
|
||||
# Default B3 lane location.
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ from core.capability.reviewers import (
|
|||
)
|
||||
from core.capability.sources import LEDGER_SOURCES
|
||||
from core.config import DEFAULT_CONFIG
|
||||
from packs.domain_contract import validate_domain_contract_pack
|
||||
from language_packs.domain_contract import validate_domain_contract_pack
|
||||
|
||||
_REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
_CHAINS_PER_OPERATOR_DOMAIN = 8
|
||||
|
|
@ -109,12 +109,12 @@ def _latest_eval_result(lane: str, version: str, split: str) -> dict[str, Any]:
|
|||
|
||||
|
||||
def _manifest_for_pack(pack_id: str) -> dict[str, Any]:
|
||||
path = _REPO_ROOT / "packs" / "data" / pack_id / "manifest.json"
|
||||
path = _REPO_ROOT / "language_packs" / "data" / pack_id / "manifest.json"
|
||||
return _load_json(path)
|
||||
|
||||
|
||||
def _pack_lemmas(pack_id: str) -> set[str]:
|
||||
root = _REPO_ROOT / "packs" / "data" / pack_id
|
||||
root = _REPO_ROOT / "language_packs" / "data" / pack_id
|
||||
path = root / "lexicon.jsonl"
|
||||
lemmas: set[str] = set()
|
||||
if not path.exists():
|
||||
|
|
@ -139,7 +139,7 @@ def _count_jsonl(path: Path) -> int:
|
|||
|
||||
|
||||
def _pack_metrics(pack_id: str) -> dict[str, Any]:
|
||||
root = _REPO_ROOT / "packs" / "data" / pack_id
|
||||
root = _REPO_ROOT / "language_packs" / "data" / pack_id
|
||||
manifest = _manifest_for_pack(pack_id)
|
||||
lexicon_path = root / str(manifest.get("lexicon", "lexicon.jsonl"))
|
||||
glosses_path = root / "glosses.jsonl"
|
||||
|
|
|
|||
|
|
@ -979,7 +979,7 @@ _DEFAULT_OUTPUT_PATH = _MATH_PROPOSALS_DIR / "proposals.jsonl"
|
|||
def _validate_output_path(raw: str | None) -> Path:
|
||||
"""Reject output paths that escape teaching/math_proposals/.
|
||||
|
||||
Mirrors :func:`packs.compiler._validate_pack_id` trust-boundary
|
||||
Mirrors :func:`language_packs.compiler._validate_pack_id` trust-boundary
|
||||
discipline: path-traversal and absolute paths are rejected before any
|
||||
filesystem access.
|
||||
|
||||
|
|
@ -3362,7 +3362,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||
teaching_compile_pack.add_argument(
|
||||
"--pack",
|
||||
default=None,
|
||||
help="pack root path (default: packs/data/en_core_math_v1)",
|
||||
help="pack root path (default: language_packs/data/en_core_math_v1)",
|
||||
)
|
||||
teaching_compile_pack.add_argument(
|
||||
"--json",
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def cmd_capability_domain_contract(args: argparse.Namespace) -> int:
|
|||
The legacy structural-only output remains available via
|
||||
``--structural-only`` for callers that depend on the prior shape.
|
||||
"""
|
||||
from packs.domain_contract import validate_domain_contract_pack
|
||||
from language_packs.domain_contract import validate_domain_contract_pack
|
||||
|
||||
if getattr(args, "structural_only", False):
|
||||
report = validate_domain_contract_pack(args.pack_id).as_dict()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ IMPORT_CHECKS: tuple[tuple[str, str], ...] = (
|
|||
("demos", "demos.claude_tool_authority"),
|
||||
("engine_state", "engine_state"),
|
||||
("evals", "evals.framework"),
|
||||
("packs", "packs"),
|
||||
("language_packs", "language_packs"),
|
||||
("morphology", "morphology.registry"),
|
||||
("packs", "packs.safety.loader"),
|
||||
("scripts", "scripts.run_pulse"),
|
||||
|
|
@ -54,13 +54,13 @@ def cmd_doctor(args: argparse.Namespace, *, repo_root: Path = DEFAULT_REPO_ROOT)
|
|||
|
||||
if args.packs:
|
||||
try:
|
||||
from packs import list_packs
|
||||
from language_packs import list_packs
|
||||
|
||||
packs = list_packs()
|
||||
except Exception as exc:
|
||||
ok = False
|
||||
print(
|
||||
f"FAIL packs packs.list_packs: {exc.__class__.__name__}: {exc}"
|
||||
f"FAIL packs language_packs.list_packs: {exc.__class__.__name__}: {exc}"
|
||||
)
|
||||
else:
|
||||
print("packs:")
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from core.cli import _die, _REPO_ROOT, _run
|
|||
|
||||
def cmd_pack_list(args: argparse.Namespace) -> int:
|
||||
"""List compiled language packs."""
|
||||
from packs import list_packs
|
||||
from language_packs import list_packs
|
||||
|
||||
packs = list_packs()
|
||||
if not packs:
|
||||
|
|
@ -24,7 +24,7 @@ def cmd_pack_list(args: argparse.Namespace) -> int:
|
|||
|
||||
def cmd_pack_verify(args: argparse.Namespace) -> int:
|
||||
"""Verify one language pack checksum."""
|
||||
return _run(sys.executable, "-m", "packs", "verify", args.pack_id)
|
||||
return _run(sys.executable, "-m", "language_packs", "verify", args.pack_id)
|
||||
|
||||
|
||||
def cmd_pack_validate(args: argparse.Namespace) -> int:
|
||||
|
|
|
|||
|
|
@ -876,14 +876,14 @@ def cmd_teaching_compile_pack(args: argparse.Namespace) -> int:
|
|||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from packs.compile_pack import compile_pack
|
||||
from language_packs.compile_pack import compile_pack
|
||||
|
||||
pack_root = (
|
||||
Path(args.pack)
|
||||
if args.pack
|
||||
else (
|
||||
Path(__file__).resolve().parent.parent
|
||||
/ "packs"
|
||||
/ "language_packs"
|
||||
/ "data"
|
||||
/ "en_core_math_v1"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -26,16 +26,6 @@ TEST_SUITES: dict[str, tuple[str, ...]] = {
|
|||
"tests/test_runtime_config.py",
|
||||
"tests/test_cognitive_turn_pipeline.py",
|
||||
"tests/test_architectural_invariants.py",
|
||||
# Audio sensorium lane — part of the smoke.yml PR gate (compiler,
|
||||
# CRDT merge, eval gates, pack manifest, mount, teachers; ~3s).
|
||||
# Listed explicitly so the local-first pre-push gate (AGENTS.md
|
||||
# protocol) equals the CI gate rather than silently narrowing it.
|
||||
"tests/test_audio_compiler.py",
|
||||
"tests/test_audio_crdt_merge.py",
|
||||
"tests/test_audio_eval_gates.py",
|
||||
"tests/test_audio_pack_manifest.py",
|
||||
"tests/test_audio_sensorium_mount.py",
|
||||
"tests/test_audio_teachers.py",
|
||||
# ADR-0043 — identity falsifiability: ratified identity packs must
|
||||
# produce distinct, directionally-correct articulations, with a
|
||||
# pack-invariant grounding/refusal floor and zero fabrication. Lives
|
||||
|
|
@ -258,7 +248,7 @@ def cmd_check(
|
|||
"field",
|
||||
"generate",
|
||||
"ingest",
|
||||
"packs",
|
||||
"language_packs",
|
||||
"morphology",
|
||||
"persona",
|
||||
"sensorium",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ from generate.intent_ratifier import (
|
|||
ratify_intent,
|
||||
)
|
||||
from generate.graph_planner import (
|
||||
GraphNode,
|
||||
PropositionGraph,
|
||||
graph_from_intent,
|
||||
ground_graph,
|
||||
|
|
@ -41,7 +40,6 @@ from generate.graph_planner import (
|
|||
from recognition.anti_unifier import DerivedRecognizer, recognize
|
||||
from recognition.carrier import EpistemicGraph, EpistemicNode
|
||||
from recognition.connector import epistemic_node_to_graph_node
|
||||
from recognition.depth_canonical import build_node_depths
|
||||
from generate.realizer import realize_semantic
|
||||
from generate.intent import IntentTag
|
||||
from generate.operators import (
|
||||
|
|
@ -127,9 +125,6 @@ class CognitiveTurnPipeline:
|
|||
) -> None: # runtime: ChatRuntime (no import cycle)
|
||||
self.runtime = runtime
|
||||
self._last_node_id: str | None = None
|
||||
self._current_node_depths: dict = {}
|
||||
self._current_agent_node_id: str | None = None
|
||||
self._last_node_depths: dict | None = None
|
||||
self.teaching_store = teaching_store if teaching_store is not None else TeachingStore()
|
||||
if recognizer is not None:
|
||||
self._recognizer = recognizer
|
||||
|
|
@ -173,23 +168,7 @@ class CognitiveTurnPipeline:
|
|||
# CognitiveTurnResult.refusal_reason when non-empty.
|
||||
_recognition_refusal_reason: str = ""
|
||||
if self._recognizer is not None:
|
||||
# Same-turn depth for recognition: resolve he/grc roots from tokens
|
||||
# before PropositionGraph exists (plan residual). Prefer early
|
||||
# provisional t{i} depths; fall back to current/prior-turn graph
|
||||
# depths for multi-turn chaining (AC1).
|
||||
from chat.pack_resolver import resolve_token_depths
|
||||
|
||||
_early_depths, _early_agent = resolve_token_depths(raw_tokens)
|
||||
_prior_depths = (
|
||||
getattr(self, "_current_node_depths", None)
|
||||
or getattr(self, "_last_node_depths", None)
|
||||
or {}
|
||||
)
|
||||
_depths = _early_depths if _early_depths else _prior_depths
|
||||
_agent_nid = _early_agent or getattr(self, "_current_agent_node_id", None)
|
||||
_rec_outcome = recognize(
|
||||
self._recognizer, raw_tokens, depths=_depths, agent_node_id=_agent_nid
|
||||
)
|
||||
_rec_outcome = recognize(self._recognizer, raw_tokens)
|
||||
if _rec_outcome.admitted:
|
||||
_ep_node = EpistemicNode(
|
||||
node_id=f"{self._recognizer.teaching_set_id}:{self._turn_number}",
|
||||
|
|
@ -295,112 +274,15 @@ class CognitiveTurnPipeline:
|
|||
# ``register_canonical_surface``, ADR-0071 for
|
||||
# ``pre_decoration_surface``). The historical ``getattr`` calls
|
||||
# were ADR-introduction defensiveness now safe to drop.
|
||||
# Grounding (when opted in) produces the effective graph for the
|
||||
# supremacy decision + stored result + topological hash. The
|
||||
# original intent-derived `graph` is the starting plan; effective
|
||||
# is the substrate view after recall grounding (when active).
|
||||
# This ensures the graph carried in CognitiveTurnResult and
|
||||
# folded into trace_hash reflects the actual reasoning used.
|
||||
effective_graph = graph
|
||||
recalled_words = response.recalled_words or ()
|
||||
# Depth enrichment is now DEFAULT (AC2) for 3-lang mastery on spine.
|
||||
# ALWAYS attempt per-subject resolution + GraphNode lang/root enrichment
|
||||
# (independent of is_fully_grounded) so node_depths / graph_anti_unify
|
||||
# and result fields are populated for both OOV/pending and grounded 3-lang cases.
|
||||
# The grounded authority flag only controls the recalled_words fill + re-realize.
|
||||
# Collect unique subjects, resolve with depth packs for language/root/gloss.
|
||||
# This feeds both recalled_words (for ground_graph) and per-node enrichment.
|
||||
if effective_graph:
|
||||
subjects = []
|
||||
seen = set()
|
||||
for n in effective_graph.nodes:
|
||||
s = n.subject.strip().lower()
|
||||
if s and s not in seen:
|
||||
seen.add(s)
|
||||
subjects.append(s)
|
||||
|
||||
from chat.pack_resolver import (
|
||||
DEFAULT_RESOLVABLE_PACK_IDS,
|
||||
resolve_entry,
|
||||
resolve_gloss,
|
||||
resolve_lemma,
|
||||
)
|
||||
# Master bidirectional entry point: LexicalResolution carries
|
||||
# 3-language depth (Hebrew roots, Greek precision) usable for
|
||||
# graph grounding (comprehension), later realization (articulation),
|
||||
# and contemplation/reasoning on the shared PropositionGraph.
|
||||
from chat.pack_resolver import DEPTH_PACK_IDS
|
||||
depth_pack_ids = DEFAULT_RESOLVABLE_PACK_IDS + DEPTH_PACK_IDS
|
||||
|
||||
subject_to_res = {}
|
||||
for s in subjects:
|
||||
res = resolve_entry(s, pack_ids=depth_pack_ids)
|
||||
subject_to_res[s] = res
|
||||
|
||||
# Collect glosses for pending nodes in order (feeds ground_graph sequentially)
|
||||
# (only when not fully grounded, to preserve prior behavior for gloss fill)
|
||||
if not effective_graph.is_fully_grounded():
|
||||
recalled_glosses = []
|
||||
for n in effective_graph.nodes:
|
||||
obj = n.obj
|
||||
if obj in (None, "", "<pending>", "<prior>") or (isinstance(obj, str) and "..." in obj):
|
||||
s = n.subject.strip().lower()
|
||||
res = subject_to_res.get(s)
|
||||
if res and getattr(res, 'gloss', None):
|
||||
recalled_glosses.append(res.gloss)
|
||||
elif resolve_lemma(n.subject):
|
||||
# legacy fallback per-subject
|
||||
g = resolve_gloss(n.subject)
|
||||
if g:
|
||||
_, _, gloss_text = g
|
||||
if gloss_text:
|
||||
recalled_glosses.append(gloss_text)
|
||||
if recalled_glosses:
|
||||
recalled_words = tuple(recalled_glosses)
|
||||
|
||||
# Enrich every node with its subject's resolution (subject→node map)
|
||||
# Immutable; only rebuild if any depth present. ALWAYS for 3-lang depth support.
|
||||
if subject_to_res:
|
||||
new_nodes = []
|
||||
changed = False
|
||||
for n in effective_graph.nodes:
|
||||
s = n.subject.strip().lower()
|
||||
res = subject_to_res.get(s)
|
||||
if res and (getattr(res, 'language', None) or getattr(res, 'root', None) or getattr(res, 'morphology_id', None)):
|
||||
enriched = GraphNode(
|
||||
node_id=n.node_id,
|
||||
subject=n.subject,
|
||||
predicate=n.predicate,
|
||||
obj=n.obj,
|
||||
source_intent=n.source_intent,
|
||||
language=getattr(res, 'language', None),
|
||||
root=getattr(res, 'root', None),
|
||||
morphology_id=getattr(res, 'morphology_id', None),
|
||||
)
|
||||
new_nodes.append(enriched)
|
||||
changed = True
|
||||
else:
|
||||
new_nodes.append(n)
|
||||
if changed:
|
||||
effective_graph = PropositionGraph(
|
||||
nodes=tuple(new_nodes),
|
||||
edges=effective_graph.edges,
|
||||
)
|
||||
if self.runtime.config.realizer_grounded_authority and recalled_words:
|
||||
# Ground using recalled_words + depth map (alongside) so
|
||||
# 3-lang info propagates even if not pre-enriched on nodes.
|
||||
# Flag only gates this recall-fill step for compat.
|
||||
depth_map = {}
|
||||
for n in effective_graph.nodes:
|
||||
if n.language or n.root or n.morphology_id:
|
||||
depth_map[n.node_id] = (n.language, n.root, n.morphology_id)
|
||||
grounded_graph = ground_graph(effective_graph, recalled_words, depth=depth_map)
|
||||
realized_plan = realize_semantic(target, grounded_graph)
|
||||
effective_graph = grounded_graph
|
||||
if self.runtime.config.realizer_grounded_authority:
|
||||
recalled_words = response.recalled_words
|
||||
if recalled_words:
|
||||
grounded_graph = ground_graph(graph, recalled_words)
|
||||
realized_plan = realize_semantic(target, grounded_graph)
|
||||
|
||||
gate_fired = (
|
||||
response.vault_hits == 0
|
||||
and response.grounding_source not in ("vault", "pack", "teaching")
|
||||
and response.grounding_source != "vault"
|
||||
)
|
||||
canonical = response.register_canonical_surface
|
||||
pre_decoration = response.pre_decoration_surface
|
||||
|
|
@ -427,13 +309,6 @@ class CognitiveTurnPipeline:
|
|||
|
||||
entailment_trace = self._maybe_entailment_trace(intent, triples)
|
||||
|
||||
# === SHADOW COHERENCE GATE WIRING ===
|
||||
# Graph + realizer already executed unconditionally above.
|
||||
# Pass the effective (possibly grounded) graph so the gate can
|
||||
# apply the strict supremacy test. Assessment=None for Phase A
|
||||
# (assessments still live primarily in derivation organs). When
|
||||
# the main spine carries ProblemFrame through the turn, this
|
||||
# becomes the active contract backpressure site.
|
||||
resolved = resolve_surface(
|
||||
canonical_surface=canonical,
|
||||
pre_decoration_surface=pre_decoration,
|
||||
|
|
@ -444,89 +319,10 @@ class CognitiveTurnPipeline:
|
|||
gate_fired=gate_fired,
|
||||
walk_surface=walk_surface,
|
||||
compose_surface=compose_surface,
|
||||
proposition_graph=effective_graph,
|
||||
contract_assessment=None,
|
||||
)
|
||||
surface = resolved.surface
|
||||
articulation_surface = resolved.articulation_surface
|
||||
|
||||
# SUBSTRATE_BYPASS_HAZARD telemetry (data-driven roadmap).
|
||||
# Only populated when a graph existed yet substrate did not win.
|
||||
# This is *observability only* — never used to change control flow
|
||||
# after the fact, never folded into trace_hash in Phase A.
|
||||
substrate_hazard: tuple[str, ...] = ()
|
||||
if effective_graph is not None and resolved.authority not in ("substrate_realizer", "realizer"):
|
||||
reasons: list[str] = []
|
||||
if not effective_graph.is_fully_grounded():
|
||||
reasons.append("unfilled_pending_slots")
|
||||
# include the exact nodes for precision (Strangler diagnostic)
|
||||
unresolved = effective_graph.get_unresolved_topology()
|
||||
if unresolved:
|
||||
reasons.append(f"unresolved_nodes={unresolved}")
|
||||
if gate_fired:
|
||||
reasons.append("unknown_domain_gate_fired")
|
||||
if not _is_useful_surface(realized_plan.surface):
|
||||
reasons.append("realizer_surface_not_useful")
|
||||
substrate_hazard = tuple(reasons)
|
||||
|
||||
# Phase C (Geometric Anti-Unification) — read-only telemetry instrumentation.
|
||||
# Captures the graph-structural context around any OOV/pending "hole"
|
||||
# so that a future exact-CGA sub-graph anti-unifier can operate on
|
||||
# conformal neighbors rather than lexical token match.
|
||||
# Populated observationally; never affects surface, hash (yet), or
|
||||
# any durable mutation. Uses only what the substrate already produced.
|
||||
oov_geometric_context = None
|
||||
grounding_src = getattr(response, "grounding_source", "") or ""
|
||||
has_pending = bool(effective_graph and any(
|
||||
(n.obj or "") in ("", "<pending>") or "..." in (n.obj or "")
|
||||
for n in effective_graph.nodes
|
||||
))
|
||||
# AC2: node_depths in oov_geometric_context by default for all PropGraph paths
|
||||
# Use pure build_node_depths for canonical extraction (nid-keyed).
|
||||
node_depths = build_node_depths(effective_graph.nodes) if effective_graph else {}
|
||||
if grounding_src == "oov" or has_pending:
|
||||
oov_geometric_context = {
|
||||
"unresolved_topology": effective_graph.get_unresolved_topology() if effective_graph else (),
|
||||
"intent_tag": getattr(intent, "tag", None).value if intent and getattr(intent, "tag", None) else "unknown",
|
||||
"geometric_probe_performed": False,
|
||||
"note": "Hook for geometric anti-unification: surrounding realized facts (via exact vault cga_inner) can infer relation type / SPECULATIVE var for the hole instead of lexical fallback.",
|
||||
"node_depths": node_depths,
|
||||
}
|
||||
else:
|
||||
# default for PropGraph: at least node_depths
|
||||
if effective_graph:
|
||||
oov_geometric_context = {
|
||||
"node_depths": node_depths,
|
||||
"note": "default depth context for PropGraph (AC2)",
|
||||
}
|
||||
|
||||
# Phase 4: include graph-level anti-unify result (unresolved topo + roots) for observability on spine.
|
||||
if node_depths and effective_graph:
|
||||
try:
|
||||
from recognition.anti_unifier import graph_anti_unify
|
||||
topo = tuple(n.node_id for n in effective_graph.nodes)
|
||||
if oov_geometric_context is None:
|
||||
oov_geometric_context = {}
|
||||
oov_geometric_context["graph_anti_unify"] = graph_anti_unify(topo, node_depths)
|
||||
except Exception:
|
||||
# Best-effort telemetry only; anti-unify failure must not affect main path.
|
||||
pass
|
||||
|
||||
# Capture depths (post-enrich) to attrs for recognize chaining (AC1) + runtime contemplate depth= (real).
|
||||
# Propagation contract (3-lang depth on PropGraph spine): pipeline (after OOV/PropGraph construction)
|
||||
# writes _last_node_depths (and now also top-level on CognitiveTurnResult) so that
|
||||
# runtime.contemplate(...) and teaching/contemplation paths forward depth= without
|
||||
# re-resolving packs. Depths originate from pack_resolver + build_node_depths on GraphNode.
|
||||
# This is the current minimal cross-component channel (observational). See docs/ and review.
|
||||
|
||||
if node_depths:
|
||||
self._current_node_depths = node_depths
|
||||
if effective_graph and effective_graph.nodes:
|
||||
self._current_agent_node_id = effective_graph.nodes[0].node_id
|
||||
self._last_node_depths = node_depths
|
||||
# Direct assignment now that runtime explicitly declares the attr (cleanup of hasattr/setattr dance)
|
||||
self.runtime._last_node_depths = node_depths
|
||||
|
||||
# Track last node id for correction-intent chaining
|
||||
if graph.nodes:
|
||||
self._last_node_id = graph.nodes[-1].node_id
|
||||
|
|
@ -634,22 +430,6 @@ class CognitiveTurnPipeline:
|
|||
# recognition wins (earlier-fail boundary) over generation.
|
||||
_generation_refusal_reason = getattr(response, "refusal_reason", "") or ""
|
||||
refusal_reason = _recognition_refusal_reason or _generation_refusal_reason
|
||||
|
||||
# Phase B — Quantized Topological Hashing for the cognitive spine.
|
||||
# Include the discrete (string-only) topological form of the
|
||||
# PropositionGraph that was produced for this turn. This is the
|
||||
# Merkle-DAG of the "think" step (nodes + directed relations).
|
||||
# - Uses graph.to_json() which is canonical, sort_keys JSON of
|
||||
# the DAG (no floats, no geometry).
|
||||
# - Conditional on presence (already is) but the key is only added
|
||||
# in compute_trace_hash when non-empty, preserving byte identity
|
||||
# for any legacy pre-inclusion behavior in other contexts.
|
||||
# - Addresses the Trace Equivalence Hazard: same topology on any
|
||||
# hardware/backend produces identical contribution to the SHA.
|
||||
# The continuous versor_condition (rounded) is kept only as the
|
||||
# runtime guard; actual geometric state lives in field/vault and
|
||||
# is never raw-hashed here.
|
||||
graph_topo = effective_graph.to_json() if effective_graph is not None else ""
|
||||
trace_hash = compute_trace_hash(
|
||||
input_text=text,
|
||||
filtered_tokens=filtered_tokens,
|
||||
|
|
@ -668,7 +448,6 @@ class CognitiveTurnPipeline:
|
|||
ratification_outcome=_trace_ratification_outcome,
|
||||
region_was_unconstrained=region_was_unconstrained,
|
||||
refusal_reason=refusal_reason,
|
||||
proposition_graph=graph_topo,
|
||||
)
|
||||
|
||||
# ADR-0153 (W-020a) — back-stamp the canonical trace_hash onto
|
||||
|
|
@ -708,14 +487,7 @@ class CognitiveTurnPipeline:
|
|||
vault_hits=response.vault_hits,
|
||||
recall_energy_class=response.recall_energy_class,
|
||||
intent=intent,
|
||||
# Use effective_graph (the post-grounding view when substrate
|
||||
# grounding was active) so that the stored PropositionGraph
|
||||
# reflects what the Shadow Coherence Gate / realizer actually
|
||||
# used for articulation. This makes result.proposition_graph
|
||||
# + trace hash (via topo) consistent with the executed spine.
|
||||
# For the common case (no grounding) this is identical to the
|
||||
# intent-derived graph.
|
||||
proposition_graph=effective_graph,
|
||||
proposition_graph=graph,
|
||||
articulation_target=target,
|
||||
teaching_candidate=teaching_candidate,
|
||||
reviewed_teaching_example=reviewed_example,
|
||||
|
|
@ -732,17 +504,6 @@ class CognitiveTurnPipeline:
|
|||
versor_condition=response.versor_condition,
|
||||
trace_hash=trace_hash,
|
||||
leeway=leeway,
|
||||
# Phase A — Shadow Coherence Gate observability.
|
||||
# authority_source makes the winner of the substrate vs legacy
|
||||
# decision first-class evidence (visible in trace, workbench,
|
||||
# evals). substrate_hazard is the precise bypass signal.
|
||||
authority_source=resolved.authority,
|
||||
substrate_hazard=substrate_hazard,
|
||||
oov_geometric_context=oov_geometric_context,
|
||||
# 3-lang depth unification: surface the same data at top level on result
|
||||
# (extracted from pre-computed node_depths var or oov_geometric_context to keep single source)
|
||||
node_depths=node_depths if node_depths else None,
|
||||
graph_anti_unify=(oov_geometric_context or {}).get("graph_anti_unify") if oov_geometric_context else None,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -27,19 +27,7 @@ from chat.dispatch_trace import DispatchTrace
|
|||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class CognitiveTurnResult:
|
||||
"""Full observability record for a single pipeline turn.
|
||||
|
||||
Includes the Shadow Coherence Gate evidence (authority_source +
|
||||
substrate_hazard) so that the migration from hybrid legacy spine to
|
||||
the unified PropositionGraph substrate is completely inspectable and
|
||||
replay-diagnosable without ever breaking determinism or the 74 invariants.
|
||||
|
||||
3-lang depth fields (node_depths, graph_anti_unify) are populated for
|
||||
he/grc PropGraph turns from the same data used for oov_geometric_context.
|
||||
They are read-only / observational and never affect trace_hash or behavior.
|
||||
The depth propagation contract (pipeline -> runtime _last_node_depths ->
|
||||
contemplate(..., depth=) -> teaching) is documented alongside the code.
|
||||
"""
|
||||
"""Full observability record for a single pipeline turn."""
|
||||
|
||||
# --- input layer ---
|
||||
input_text: str
|
||||
|
|
@ -154,52 +142,3 @@ class CognitiveTurnResult:
|
|||
|
||||
# --- response-governance leeway evidence (B4; observational, not in trace_hash) ---
|
||||
leeway: LeewayRecord | None = None
|
||||
|
||||
# --- Shadow Coherence Gate / substrate authority (Phase A) ---
|
||||
# ``authority_source`` is the value from SurfaceResolution.authority:
|
||||
# "runtime_canonical" | "runtime_pre_decoration" | "runtime" | "realizer" | "substrate_realizer".
|
||||
# It is the single source of truth for which spine actually spoke.
|
||||
#
|
||||
# ``substrate_hazard`` is the machine-readable list of reasons the
|
||||
# geometric substrate was *not* granted authority on this turn even
|
||||
# though a PropositionGraph was produced. Populated only on bypass
|
||||
# paths. Observational (not folded into trace_hash in Phase A) so that
|
||||
# every existing turn keeps byte-identical hashes while the hazard
|
||||
# ledger illuminates the exact work remaining for Layers 1-3.
|
||||
#
|
||||
# These two fields turn the "Authority Flip Cliff" into a controlled,
|
||||
# data-driven strangler migration.
|
||||
authority_source: str = ""
|
||||
substrate_hazard: tuple[str, ...] = ()
|
||||
|
||||
# --- Phase C instrumentation: Geometric Anti-Unification hook for OOV (read-only telemetry) ---
|
||||
# When an OOV subject is encountered in the context of a PropositionGraph
|
||||
# (i.e. a "hole" in S-P-[OOV] or similar), this carries the discrete
|
||||
# structural context (unresolved topology + intent) plus a placeholder
|
||||
# for exact CGA neighbor probe results (via vault.recall + cga_inner on
|
||||
# surrounding realized facts).
|
||||
#
|
||||
# Today: purely structural (from effective_graph.get_unresolved_topology()
|
||||
# when grounding_source indicates oov or pending slots on OOV-shaped
|
||||
# intents). No vault call yet (keeps change atomic + zero side effects).
|
||||
#
|
||||
# Future: perform *exact* geometric anti-unification here (sub-graph
|
||||
# match on conformal space) to propose SPECULATIVE algebraic variable
|
||||
# or relation type for the hole, without ever affecting user surface,
|
||||
# trace_hash (observational), or durable state. Must emit SPECULATIVE,
|
||||
# respect teaching boundary for any promotion.
|
||||
#
|
||||
# Pillars: Mechanical Sympathy (cheap structural + optional exact recall),
|
||||
# Semantic Rigor (exact CGA only, no approx), Third Door (graph structure
|
||||
# as first-class for inference instead of lexical substring).
|
||||
#
|
||||
# Never folded into trace_hash in this phase. Never mutates field/vault.
|
||||
oov_geometric_context: dict | None = None
|
||||
|
||||
# --- 3-lang depth PropGraph unification observability (read-only, not in trace_hash) ---
|
||||
# Extracted from the same source as oov_geometric_context["node_depths"] / ["graph_anti_unify"]
|
||||
# (or the pre-context data) during pipeline construction for he/grc root-aware paths.
|
||||
# First-class optional fields so callers do not need to reach into the context dict.
|
||||
# Never folded into trace_hash (observational only, like oov_geometric_context).
|
||||
node_depths: dict | None = None
|
||||
graph_anti_unify: dict | None = None
|
||||
|
|
|
|||
|
|
@ -14,12 +14,6 @@ from __future__ import annotations
|
|||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from generate.graph_planner import PropositionGraph
|
||||
from generate.problem_frame_contracts import ContractAssessment
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SurfaceResolution:
|
||||
|
|
@ -28,11 +22,6 @@ class SurfaceResolution:
|
|||
``authority`` records the prefix authority before deterministic folds
|
||||
are appended. ``fold_sources`` records which inference suffixes were
|
||||
appended, in deterministic order.
|
||||
|
||||
When authority == "substrate_realizer", the PropositionGraph +
|
||||
realize_semantic path was granted supremacy by the Shadow Coherence
|
||||
Gate (strict structural + contract + coherence proof). Legacy runtime
|
||||
and walk/compose folds are still applied after, never before.
|
||||
"""
|
||||
|
||||
surface: str
|
||||
|
|
@ -68,50 +57,16 @@ def resolve_surface(
|
|||
gate_fired: bool = False,
|
||||
walk_surface: str = "",
|
||||
compose_surface: str = "",
|
||||
proposition_graph: "PropositionGraph | None" = None,
|
||||
contract_assessment: "ContractAssessment | None" = None,
|
||||
) -> SurfaceResolution:
|
||||
"""Resolve the final turn surface under one explicit policy.
|
||||
|
||||
The Shadow Coherence Gate (Strangler Fig Pattern per the refined plan):
|
||||
|
||||
- The PropositionGraph and realize_semantic are executed *unconditionally*
|
||||
on every turn (already true in pipeline before this call).
|
||||
- Authority is granted to the substrate realizer **only** when the
|
||||
strict geometric guard passes:
|
||||
* graph.is_fully_grounded() (no <pending> slots remain)
|
||||
* contract assessment (if present) is closed (no missing_bindings,
|
||||
no unresolved_hazards)
|
||||
* gate did not fire (unknown domain safety)
|
||||
Versor coherence (< 1e-6) is presupposed by construction at the
|
||||
boundaries that produced the graph/bindings; it is not re-"repaired"
|
||||
here.
|
||||
- When the guard refuses, we fall back to the legacy runtime surface
|
||||
and the *precise* topological delta is recorded upstream as
|
||||
SUBSTRATE_BYPASS_HAZARD telemetry. This makes every test run and
|
||||
every production turn a diagnostic that lights exactly which
|
||||
ProblemFrame / recall / realizer gaps still block substrate supremacy.
|
||||
- Legacy "realizer_useful" path is retained only as a transitional
|
||||
compat shim; the supreme check is the load-bearing decision.
|
||||
|
||||
Walk/compose folds are *always* suffixes — they never affect the
|
||||
authority prefix decision.
|
||||
|
||||
Three Engineering Pillars are non-negotiable here:
|
||||
I. Mechanical Sympathy — the entire decision is a handful of O(N)
|
||||
structural inspections on tiny tuples; zero extra alloc, zero
|
||||
cross-language roundtrip, zero sensitivity to FMA/assoc drift.
|
||||
II. Semantic Rigor — every term ("fully_grounded", "substrate_realizer",
|
||||
"bypass_hazard") has one precise meaning. No numeric tolerance,
|
||||
no "good enough" surface.
|
||||
III. Third Door — we did not pick "keep the regex sidecar" nor
|
||||
"rip it out and break the suite". We built the substrate spine
|
||||
as the sole authority path and made the old path the observable
|
||||
bypass that starves itself to zero.
|
||||
|
||||
See also: engineer's assessment §1 (Authority Flip Cliff), AGENTS.md
|
||||
(versor only at owned boundaries, exact recall, kernel substrate rule),
|
||||
runtime_contracts.md (surface selection contract).
|
||||
Policy:
|
||||
1. Runtime/canonical/pre-decoration selects the base authority.
|
||||
2. A useful realizer surface may replace the prefix only when the
|
||||
unknown-domain gate did not fire.
|
||||
3. Walk and compose suffixes are deterministic inference folds. They
|
||||
append after prefix authority is selected and are never allowed to
|
||||
re-run or reinterpret the prefix decision.
|
||||
"""
|
||||
|
||||
surface, articulation_surface, authority = _base_runtime_surface(
|
||||
|
|
@ -121,20 +76,10 @@ def resolve_surface(
|
|||
response_articulation_surface=response_articulation_surface or "",
|
||||
)
|
||||
|
||||
# === SHADOW COHERENCE GATE ===
|
||||
# Unconditional substrate execution has already occurred.
|
||||
# We now decide authority strictly.
|
||||
if not gate_fired and realized_surface:
|
||||
if _substrate_supreme(proposition_graph, contract_assessment):
|
||||
surface = realized_surface
|
||||
articulation_surface = realized_surface
|
||||
authority = "substrate_realizer"
|
||||
elif realizer_useful:
|
||||
# Transitional shim (pre full coverage of grounding + organs).
|
||||
# Will be removed when hazard frequency for the legacy path hits zero.
|
||||
surface = realized_surface
|
||||
articulation_surface = realized_surface
|
||||
authority = "realizer"
|
||||
if realizer_useful and not gate_fired:
|
||||
surface = realized_surface
|
||||
articulation_surface = realized_surface
|
||||
authority = "realizer"
|
||||
|
||||
fold_sources: list[str] = []
|
||||
if walk_surface:
|
||||
|
|
@ -161,41 +106,3 @@ def resolve_surface(
|
|||
authority=authority,
|
||||
fold_sources=tuple(fold_sources),
|
||||
)
|
||||
|
||||
|
||||
def _substrate_supreme(
|
||||
proposition_graph: "PropositionGraph | None",
|
||||
contract_assessment: "ContractAssessment | None",
|
||||
) -> bool:
|
||||
"""Return True only when the geometric substrate has earned authority.
|
||||
|
||||
This is the single source of truth for "use the PropositionGraph path
|
||||
as the cognitive spine instead of legacy runtime/pack/walk".
|
||||
|
||||
Conditions (all must hold):
|
||||
- A graph was produced.
|
||||
- graph.is_fully_grounded() — every slot bound by exact recall or
|
||||
direct construction (no <pending>).
|
||||
- If a ContractAssessment is supplied, it must be closed
|
||||
(zero missing_bindings and zero unresolved_hazards).
|
||||
(Assessments are still diagnostic-only in many organs; when the
|
||||
main spine wires ProblemFrame + assess_contracts, this becomes
|
||||
active backpressure — see Layer 3/Phase D.)
|
||||
|
||||
Versor coherence is *not* re-checked with a repair here. It is
|
||||
required by construction at the sites that emit versors (see
|
||||
VersorBinding and algebra/versor.py). Passing a non-coherent state
|
||||
here is a programmer error, not a runtime tolerance.
|
||||
|
||||
When this returns False the caller (pipeline) must emit the
|
||||
SUBSTRATE_BYPASS_HAZARD with graph.get_unresolved_topology() so the
|
||||
failure is actionable rather than silent.
|
||||
"""
|
||||
if proposition_graph is None:
|
||||
return False
|
||||
if not proposition_graph.is_fully_grounded():
|
||||
return False
|
||||
if contract_assessment is not None:
|
||||
if contract_assessment.missing_bindings or contract_assessment.unresolved_hazards:
|
||||
return False
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -8,24 +8,6 @@ The hash captures every meaningful output of a pipeline run so that:
|
|||
Only stable, semantically meaningful fields are included. Floating-point
|
||||
values are rounded to 9 decimal places before hashing so that numeric
|
||||
noise from different hardware does not break determinism within a run.
|
||||
|
||||
Phase B (Trace Equivalence Hazard fix per refined plan / engineer's assessment §2):
|
||||
The PropositionGraph (the "think" structure) is now folded into the hash
|
||||
via its canonical discrete topological serialization (to_json / as_dict).
|
||||
This is a pure structural Merkle-DAG of nodes + directed edges with discrete
|
||||
labels (subjects, predicates, objects, intents, relations). No raw f64
|
||||
geometry, no versor arrays, no platform-dependent float associativity or FMA.
|
||||
|
||||
Continuous versor_condition remains a runtime guard (rounded only for the
|
||||
hash payload) and is still asserted < 1e-6 exclusively at construction
|
||||
boundaries (algebra/versor.py, VersorBinding, etc.). The graph inclusion
|
||||
makes replay equivalence sensitive to the actual substrate reasoning path
|
||||
while remaining 100% cross-platform (Python, Rust FFI, MLX on Apple Silicon,
|
||||
x86) and byte-stable for equivalent turns.
|
||||
|
||||
New field is included *only* when non-empty, preserving byte-identical
|
||||
payloads (and thus trace_hashes) for any pre-inclusion turns or turns
|
||||
without a graph.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -60,7 +42,6 @@ def compute_trace_hash(
|
|||
ratification_outcome: str = "",
|
||||
region_was_unconstrained: bool = True,
|
||||
refusal_reason: str = "",
|
||||
proposition_graph: str = "",
|
||||
) -> str:
|
||||
"""Return a deterministic SHA-256 hex digest over the turn's key outputs.
|
||||
|
||||
|
|
@ -78,19 +59,6 @@ def compute_trace_hash(
|
|||
no proposal was emitted. Folded per ADR-0021 §Consequences so replay
|
||||
detects when a downstream surface was produced under a different
|
||||
epistemic frame than at the time of recall.
|
||||
|
||||
``proposition_graph`` (Phase B) is the *discrete topological*
|
||||
canonical serialization of the PropositionGraph (typically
|
||||
graph.to_json() or equivalent as_dict JSON). This captures the
|
||||
network structure (nodes + directed edges) and discrete labels
|
||||
that drove the substrate articulation. It is the Merkle-DAG
|
||||
representation of the "think" step.
|
||||
|
||||
It contains only strings, enums, and structural tuples — zero raw
|
||||
floating-point CGA state. This guarantees identical hashes on
|
||||
Apple Silicon (MLX), x86, Rust FFI paths, etc. The continuous
|
||||
versor_condition (rounded) remains only as an ephemeral runtime
|
||||
guard in the payload.
|
||||
"""
|
||||
payload = {
|
||||
"input_text": input_text,
|
||||
|
|
@ -125,15 +93,6 @@ def compute_trace_hash(
|
|||
# load-bearing in replay equality.
|
||||
if refusal_reason:
|
||||
payload["refusal_reason"] = refusal_reason
|
||||
# Phase B — discrete PropositionGraph topology (Shadow Coherence Gate
|
||||
# unification). Included only when present so pre-Phase-B turns and
|
||||
# turns without a graph keep byte-identical payloads/hashes.
|
||||
# The value is the full canonical JSON of nodes+edges (structural DAG).
|
||||
# This makes the cognitive spine's reasoning load-bearing for replay
|
||||
# while obeying Mechanical Sympathy (no FP) and Semantic Rigor (exact
|
||||
# discrete structure, no approximation).
|
||||
if proposition_graph:
|
||||
payload["proposition_graph"] = proposition_graph
|
||||
serialized = json.dumps(payload, sort_keys=True, ensure_ascii=False)
|
||||
return hashlib.sha256(serialized.encode("utf-8")).hexdigest()
|
||||
|
||||
|
|
@ -156,13 +115,7 @@ def hash_admissibility_trace(trace: tuple) -> str:
|
|||
|
||||
|
||||
def trace_hash_from_result(result: "CognitiveTurnResult") -> str:
|
||||
"""Convenience wrapper — compute the hash directly from a result object.
|
||||
|
||||
Phase B: extracts the discrete topological form of proposition_graph
|
||||
(if present) using its to_json() canonical serialization. This ensures
|
||||
that any caller using the helper gets the same payload as the direct
|
||||
compute_trace_hash path in the pipeline.
|
||||
"""
|
||||
"""Convenience wrapper — compute the hash directly from a result object."""
|
||||
intent_tag = result.intent.tag.value if result.intent is not None else "unknown"
|
||||
review_hash = (
|
||||
result.reviewed_teaching_example.review_hash
|
||||
|
|
@ -179,20 +132,6 @@ def trace_hash_from_result(result: "CognitiveTurnResult") -> str:
|
|||
if result.pack_mutation_proposal is not None
|
||||
else ""
|
||||
)
|
||||
# Discrete graph topo for Phase B (quantized topological hashing).
|
||||
# Uses the stable to_json() of the stored PropositionGraph (nodes +
|
||||
# edges in their deterministic order, string labels only). Safe across
|
||||
# all backends; no raw geometry.
|
||||
graph_topo = ""
|
||||
pg = getattr(result, "proposition_graph", None)
|
||||
if pg is not None:
|
||||
if hasattr(pg, "to_json"):
|
||||
graph_topo = pg.to_json()
|
||||
elif hasattr(pg, "as_dict"):
|
||||
import json as _json
|
||||
graph_topo = _json.dumps(
|
||||
pg.as_dict(), sort_keys=True, ensure_ascii=False
|
||||
)
|
||||
return compute_trace_hash(
|
||||
input_text=result.input_text,
|
||||
filtered_tokens=result.filtered_tokens,
|
||||
|
|
@ -211,5 +150,4 @@ def trace_hash_from_result(result: "CognitiveTurnResult") -> str:
|
|||
ratification_outcome=getattr(result, "ratification_outcome", ""),
|
||||
region_was_unconstrained=getattr(result, "region_was_unconstrained", True),
|
||||
refusal_reason=getattr(result, "refusal_reason", ""),
|
||||
proposition_graph=graph_topo,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
ADR-0080: contemplation can emit speculative findings about current
|
||||
substrate/report evidence, but it cannot ratify, promote, or mutate packs.
|
||||
|
||||
ADR-0241 P9: Trace A wave seam may SPECULATIVE-seal standing-wave modes and
|
||||
emit RESONANT_MODE_CANDIDATE findings — never COHERENT, never serve-wired.
|
||||
"""
|
||||
|
||||
from .runner import contemplate_frontier_reports, run_contemplation
|
||||
|
|
@ -15,13 +12,6 @@ from .schema import (
|
|||
FindingKind,
|
||||
)
|
||||
from .snapshot import ContemplationSubstrate
|
||||
from .wave_seam import (
|
||||
WaveModeHypothesis,
|
||||
WaveReconstructResult,
|
||||
reconstruct_as_evidence,
|
||||
reconstruct_as_hypothesis,
|
||||
speculative_seal_from_contemplation,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ContemplationEvidenceRef",
|
||||
|
|
@ -29,11 +19,6 @@ __all__ = [
|
|||
"ContemplationRun",
|
||||
"ContemplationSubstrate",
|
||||
"FindingKind",
|
||||
"WaveModeHypothesis",
|
||||
"WaveReconstructResult",
|
||||
"contemplate_frontier_reports",
|
||||
"reconstruct_as_evidence",
|
||||
"reconstruct_as_hypothesis",
|
||||
"run_contemplation",
|
||||
"speculative_seal_from_contemplation",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ runs and ADR-0055 discovery candidates.
|
|||
| SPECULATIVE-only invariant | `ContemplationFinding.__post_init__` raises on any non-SPECULATIVE status | always | ✅ pinned by test |
|
||||
| Deterministic replay | two `contemplate_*` calls on the same inputs → identical `run_id` and `as_dict()` | byte-identical | ✅ pinned by test |
|
||||
| Sink path is additive | the `ContemplationRun` blob is byte-identical whether or not a sink is supplied | byte-identical | ✅ pinned by test |
|
||||
| No pack mutation | `packs/` tree mtimes unchanged across a `contemplate_*` invocation | true | ✅ pinned by test |
|
||||
| No pack mutation | `language_packs/` tree mtimes unchanged across a `contemplate_*` invocation | true | ✅ pinned by test |
|
||||
| Predicate split | `missed_contradiction` and `false_contradiction_flag` produce distinct `proposed_action` text | distinct | ✅ pinned by test |
|
||||
| Lane config_hash separation | `contemplate_frontier_reports` and `contemplate_contradiction_reports` produce distinct `config_hash` on identical input paths | distinct | ✅ pinned by test |
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ class FindingKind(Enum):
|
|||
OOV_GAP = "oov_gap"
|
||||
PLANNER_GAP = "planner_gap"
|
||||
PACK_MUTATION_CANDIDATE = "pack_mutation_candidate"
|
||||
# ADR-0241 P9 Trace A: speculative standing-wave mode sealed for review.
|
||||
RESONANT_MODE_CANDIDATE = "resonant_mode_candidate"
|
||||
|
||||
|
||||
def _canonical_json(payload: dict[str, Any]) -> str:
|
||||
|
|
|
|||
|
|
@ -1,193 +0,0 @@
|
|||
"""P9 Trace A seam — contemplation → SPECULATIVE holographic standing-wave seal.
|
||||
|
||||
ADR-0241 cohesion package P9:
|
||||
|
||||
1. Contemplation may **SPECULATIVE-seal** standing-wave modes via
|
||||
:meth:`HolographicVaultStore.seal_mode` only.
|
||||
2. Never writes COHERENT — teaching corridor / authorized
|
||||
``seal_mode_reviewed`` remains outside this module.
|
||||
3. Resonant reconstruct is available as a **hypothesis** over the full
|
||||
spectrum, or as **evidence** only when ``min_status=COHERENT``.
|
||||
4. Serve path stays quarantined (no import from ``chat/runtime.py``).
|
||||
5. No direct ``VaultStore.store`` — INV-21 writes stay in holographic_vault.
|
||||
|
||||
This module is the living-system bridge for Trace A without collapsing
|
||||
the teaching / serve containment boundary.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Literal
|
||||
|
||||
import numpy as np
|
||||
|
||||
from core.contemplation.schema import (
|
||||
ContemplationEvidenceRef,
|
||||
ContemplationFinding,
|
||||
FindingKind,
|
||||
)
|
||||
from core.physics.holographic_vault import (
|
||||
HolographicVaultError,
|
||||
HolographicVaultStore,
|
||||
SealedMode,
|
||||
)
|
||||
from teaching.epistemic import EpistemicStatus
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class WaveModeHypothesis:
|
||||
"""SPECULATIVE seal + contemplation finding for teaching review."""
|
||||
|
||||
sealed: SealedMode
|
||||
finding: ContemplationFinding
|
||||
standing: Literal["hypothesis"] = "hypothesis"
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"standing": self.standing,
|
||||
"mode_id": self.sealed.mode_id,
|
||||
"vault_index": self.sealed.vault_index,
|
||||
"epistemic_status": self.sealed.epistemic_status.value,
|
||||
"finding": self.finding.as_dict(),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class WaveReconstructResult:
|
||||
"""Reconstructed field with honest epistemic standing label."""
|
||||
|
||||
psi_hat: np.ndarray
|
||||
coeffs: np.ndarray
|
||||
energies: np.ndarray
|
||||
spectrum: tuple[SealedMode, ...]
|
||||
standing: Literal["hypothesis", "evidence"]
|
||||
min_status: EpistemicStatus | None
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"standing": self.standing,
|
||||
"min_status": None if self.min_status is None else self.min_status.value,
|
||||
"mode_ids": [s.mode_id for s in self.spectrum],
|
||||
"coeff_count": int(self.coeffs.shape[0]),
|
||||
}
|
||||
|
||||
|
||||
def speculative_seal_from_contemplation(
|
||||
store: HolographicVaultStore,
|
||||
psi: np.ndarray,
|
||||
*,
|
||||
substrate_hash: str,
|
||||
subject: str,
|
||||
mode_id: str | None = None,
|
||||
notes: str = "",
|
||||
predicate: str = "propose_standing_wave_mode",
|
||||
) -> WaveModeHypothesis:
|
||||
"""SPECULATIVE-seal a closed mode and emit a contemplation finding.
|
||||
|
||||
Fail-closed on non-closed / high-drift ψ (delegates to holographic admit).
|
||||
Does **not** accept an authorization flag — COHERENT promotion is not
|
||||
available on this seam.
|
||||
"""
|
||||
if not str(substrate_hash).strip():
|
||||
raise ValueError("substrate_hash is required for Trace A provenance")
|
||||
if not str(subject).strip():
|
||||
raise ValueError("subject is required")
|
||||
|
||||
meta: dict[str, Any] = {
|
||||
"source": "contemplation_trace_a",
|
||||
"substrate_hash": substrate_hash,
|
||||
"notes": notes,
|
||||
"adr_refs": ["ADR-0241", "ADR-0080"],
|
||||
}
|
||||
sealed = store.seal_mode(psi, mode_id=mode_id, metadata=meta)
|
||||
if sealed.epistemic_status is not EpistemicStatus.SPECULATIVE:
|
||||
# Defensive: seal_mode contract is SPECULATIVE-only; never promote here.
|
||||
raise RuntimeError(
|
||||
"Trace A seam integrity breach: seal_mode returned non-SPECULATIVE"
|
||||
)
|
||||
|
||||
mid = sealed.mode_id
|
||||
finding = ContemplationFinding(
|
||||
kind=FindingKind.RESONANT_MODE_CANDIDATE,
|
||||
subject=subject,
|
||||
predicate=predicate,
|
||||
object=mid,
|
||||
evidence_refs=(
|
||||
ContemplationEvidenceRef(
|
||||
source_type="holographic_vault",
|
||||
source_id=mid,
|
||||
pointer=f"vault_index:{sealed.vault_index}",
|
||||
summary=(
|
||||
"SPECULATIVE standing-wave mode sealed for teaching review; "
|
||||
"not admissible as COHERENT evidence"
|
||||
),
|
||||
),
|
||||
),
|
||||
proposed_action="review_standing_wave_mode",
|
||||
substrate_hash=substrate_hash,
|
||||
epistemic_status=EpistemicStatus.SPECULATIVE,
|
||||
)
|
||||
return WaveModeHypothesis(sealed=sealed, finding=finding, standing="hypothesis")
|
||||
|
||||
|
||||
def reconstruct_as_hypothesis(
|
||||
store: HolographicVaultStore,
|
||||
psi_query: np.ndarray,
|
||||
) -> WaveReconstructResult:
|
||||
"""Superposition reconstruct over the full spectrum (incl. SPECULATIVE).
|
||||
|
||||
Result standing is always ``hypothesis`` — never claim reviewed evidence.
|
||||
"""
|
||||
psi_hat, coeffs, energies, spectrum = store.resonant_reconstruct(psi_query)
|
||||
return WaveReconstructResult(
|
||||
psi_hat=psi_hat,
|
||||
coeffs=coeffs,
|
||||
energies=energies,
|
||||
spectrum=spectrum,
|
||||
standing="hypothesis",
|
||||
min_status=None,
|
||||
)
|
||||
|
||||
|
||||
def reconstruct_as_evidence(
|
||||
store: HolographicVaultStore,
|
||||
psi_query: np.ndarray,
|
||||
) -> WaveReconstructResult:
|
||||
"""Superposition reconstruct over COHERENT modes only.
|
||||
|
||||
SPECULATIVE modes are excluded. Empty COHERENT spectrum refuses so
|
||||
unreviewed hypothesis mass cannot masquerade as evidence.
|
||||
"""
|
||||
try:
|
||||
psi_hat, coeffs, energies, spectrum = store.resonant_reconstruct(
|
||||
psi_query,
|
||||
min_status=EpistemicStatus.COHERENT,
|
||||
)
|
||||
except HolographicVaultError as exc:
|
||||
if exc.reason == "empty_spectrum":
|
||||
raise HolographicVaultError(
|
||||
"empty_spectrum",
|
||||
detail=(
|
||||
"evidence reconstruct requires COHERENT standing-wave modes; "
|
||||
"SPECULATIVE hypothesis mass is excluded"
|
||||
),
|
||||
) from exc
|
||||
raise
|
||||
return WaveReconstructResult(
|
||||
psi_hat=psi_hat,
|
||||
coeffs=coeffs,
|
||||
energies=energies,
|
||||
spectrum=spectrum,
|
||||
standing="evidence",
|
||||
min_status=EpistemicStatus.COHERENT,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"WaveModeHypothesis",
|
||||
"WaveReconstructResult",
|
||||
"reconstruct_as_evidence",
|
||||
"reconstruct_as_hypothesis",
|
||||
"speculative_seal_from_contemplation",
|
||||
]
|
||||
|
|
@ -160,62 +160,10 @@ def canonical_json(payload: dict[str, Any]) -> bytes:
|
|||
_TRACKED_MODULES: tuple[str, ...] = (
|
||||
"chat.telemetry",
|
||||
"chat.runtime",
|
||||
"packs.compiler",
|
||||
"language_packs.compiler",
|
||||
)
|
||||
|
||||
|
||||
# Env keys whose values are host/isolation paths. Mutation is still
|
||||
# detected (key present/absent/changed), but absolute path *values*
|
||||
# never enter divergence messages — those would make lane SHA pins
|
||||
# depend on tempfile locations and break hermetic CI.
|
||||
_PATH_LIKE_ENV_SUFFIXES: tuple[str, ...] = ("_DIR", "_PATH", "_HOME", "_ROOT")
|
||||
|
||||
|
||||
def _is_path_like_env_key(key: str) -> bool:
|
||||
if key == "CORE_ENGINE_STATE_DIR":
|
||||
return True
|
||||
return any(key.endswith(suffix) for suffix in _PATH_LIKE_ENV_SUFFIXES)
|
||||
|
||||
|
||||
def _format_env_value(key: str, value: str) -> str:
|
||||
"""Stable, pin-safe rendering of an env value for divergence text."""
|
||||
if _is_path_like_env_key(key):
|
||||
return "<path>"
|
||||
return value
|
||||
|
||||
|
||||
def _env_subset_divergences(
|
||||
before_env: tuple[tuple[str, str], ...] | tuple[()] | Any,
|
||||
after_env: tuple[tuple[str, str], ...] | tuple[()] | Any,
|
||||
) -> tuple[str, ...]:
|
||||
"""Key-level env delta — not a full before/after dump.
|
||||
|
||||
Full tuple dumps embed every ambient ``CORE_*`` value (including
|
||||
hermetic engine-state temp paths). That is correct for *detection*
|
||||
when compared as raw snapshots, but wrong for *report text*: the
|
||||
lane SHA pin must be host-independent. Only keys that actually
|
||||
changed appear in the message.
|
||||
"""
|
||||
before_map = dict(before_env or ())
|
||||
after_map = dict(after_env or ())
|
||||
messages: list[str] = []
|
||||
for key in sorted(set(before_map) | set(after_map)):
|
||||
b = before_map.get(key)
|
||||
a = after_map.get(key)
|
||||
if b == a:
|
||||
continue
|
||||
if b is None:
|
||||
messages.append(f"env_subset: +{key}={_format_env_value(key, a)}")
|
||||
elif a is None:
|
||||
messages.append(f"env_subset: -{key}={_format_env_value(key, b)}")
|
||||
else:
|
||||
messages.append(
|
||||
"env_subset: "
|
||||
f"{key} {_format_env_value(key, b)!r} -> {_format_env_value(key, a)!r}"
|
||||
)
|
||||
return tuple(messages)
|
||||
|
||||
|
||||
def _global_state_snapshot() -> dict[str, Any]:
|
||||
"""Capture a load-bearing subset of process state for diff checking.
|
||||
|
||||
|
|
@ -258,14 +206,9 @@ def verify_no_global_state_mutation(
|
|||
when the adapter does its own deferred imports. Only id → id
|
||||
rebindings (the module object was replaced) and value-set
|
||||
divergences on env vars are flagged.
|
||||
|
||||
Env divergences are reported as a **key-level delta** (added /
|
||||
removed / changed keys). Full before/after env dumps are forbidden
|
||||
in the divergence text: they embed host-volatile values such as
|
||||
``CORE_ENGINE_STATE_DIR`` temp paths and make lane SHA pins flaky.
|
||||
"""
|
||||
divergences: list[str] = []
|
||||
for key in sorted(set(before.keys()) | set(after.keys())):
|
||||
for key in set(before.keys()) | set(after.keys()):
|
||||
b = before.get(key)
|
||||
a = after.get(key)
|
||||
if b == a:
|
||||
|
|
@ -274,10 +217,9 @@ def verify_no_global_state_mutation(
|
|||
# Lazy import: a module that wasn't yet loaded is now
|
||||
# loaded. Benign and unavoidable.
|
||||
continue
|
||||
if key == "env_subset":
|
||||
divergences.extend(_env_subset_divergences(b, a))
|
||||
continue
|
||||
divergences.append(f"{key}: before={b!r} after={a!r}")
|
||||
divergences.append(
|
||||
f"{key}: before={b!r} after={a!r}"
|
||||
)
|
||||
return (not divergences, tuple(divergences))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -41,11 +41,7 @@ from core.demos.tour_adapters import RegisterTourDemo
|
|||
|
||||
|
||||
SHOWCASE_VERSION: int = 1
|
||||
# Post-CGA substrate + denser spine work: cold RegisterTour alone is ~30s+
|
||||
# on typical dev hardware. 60s is the honest reference budget that still
|
||||
# catches pathological regressions without false-failing content lanes.
|
||||
# See evals/public_demo/contract.md "Known Environment Caveat".
|
||||
MAX_RUNTIME_SECONDS: int = 60
|
||||
MAX_RUNTIME_SECONDS: int = 30
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
@ -172,19 +168,8 @@ def run_showcase(*, output_dir: Path, include_runtime_ms: bool = True) -> dict[s
|
|||
deterministic_payload = {k: v for k, v in payload.items() if k != "total_runtime_ms"}
|
||||
json_path.write_bytes(canonical_json(deterministic_payload))
|
||||
|
||||
# Budget is a soft case evaluated by evals/public_demo/runner.py
|
||||
# (_case_runtime_under_budget). Hard-raising here aborted the lane
|
||||
# before content cases could be recorded — a process bug. Opt into
|
||||
# hard raise only via CORE_SHOWCASE_HARD_BUDGET=1 (e.g. product CLI
|
||||
# demos that want fail-loud wall-clock). CORE_SHOWCASE_SKIP_BUDGET=1
|
||||
# remains a full suppress for both soft and hard checks in callers.
|
||||
_skip_budget = os.environ.get("CORE_SHOWCASE_SKIP_BUDGET") == "1"
|
||||
_hard_budget = os.environ.get("CORE_SHOWCASE_HARD_BUDGET") == "1"
|
||||
if (
|
||||
_hard_budget
|
||||
and not _skip_budget
|
||||
and total_runtime_ms > MAX_RUNTIME_SECONDS * 1000
|
||||
):
|
||||
if total_runtime_ms > MAX_RUNTIME_SECONDS * 1000 and not _skip_budget:
|
||||
raise DemoContractError(
|
||||
f"showcase exceeded ADR-0099 runtime budget: "
|
||||
f"{total_runtime_ms} ms > {MAX_RUNTIME_SECONDS * 1000} ms"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue