From d388fc9e2ccb07787674e1848728caa1eeeb8f9c Mon Sep 17 00:00:00 2001 From: Shay Date: Thu, 16 Jul 2026 15:25:43 -0700 Subject: [PATCH] feat: close ADR-0241/0242 post-Accept backlog (local-first mastery) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete remaining W5/post-pivot backlog without Docker CI: INV-21 allowlist dedup, -n auto fast/full defaults, frozen f64 GP Python SOT suite, real sensorium compiler → ψ → algebraic ρ feed, V2 fib/dyadic/log fixed-replay evidence without production energy promotion, and project doctrine restored to local-first + ubuntu-latest:host (no Docker CI for merge). [Verification]: Smoke suite passed locally (145s, 176 passed); make test-fast (-n auto) passed twice (11709 passed, 108 skipped each ~11–12 min). --- .goosehints | 3 +- AGENTS.md | 12 +- CLAUDE.md | 8 +- GEMINI.md | 7 +- Makefile | 18 +- core/physics/multi_scale_energy.py | 85 ++++++- core/physics/sensorium_wave_feed.py | 36 +++ .../adr-0241-0242-mastery-close-status.md | 22 +- docs/testing-lanes.md | 15 +- evals/adr_0242_v2_energy_compare/__init__.py | 31 +++ evals/adr_0242_v2_energy_compare/__main__.py | 37 ++++ tests/test_adr_0241_sensorium_wave_feed.py | 59 +++++ tests/test_adr_0242_multi_scale_energy.py | 71 ++++++ tests/test_architectural_invariants.py | 18 +- ...est_deductive_entailment_authority_demo.py | 17 +- tests/test_geometric_product_f64_parity.py | 208 ++++++++++++++++++ tests/test_proof_carrying_promotion_demo.py | 17 +- 17 files changed, 607 insertions(+), 57 deletions(-) create mode 100644 evals/adr_0242_v2_energy_compare/__init__.py create mode 100644 evals/adr_0242_v2_energy_compare/__main__.py create mode 100644 tests/test_geometric_product_f64_parity.py diff --git a/.goosehints b/.goosehints index 99073ca4..b593c37d 100644 --- a/.goosehints +++ b/.goosehints @@ -1,6 +1,7 @@ # CORE + builder-II local agent hints - temperature 0 everywhere -- Read AGENTS.md, GROK.md, docs/runtime_contracts.md before edits +- Read AGENTS.md, docs/specs/runtime_contracts.md before edits (AGENTS.md wins) +- CI: local-first in-worktree then merge; no Docker CI; optional ubuntu-latest:host only - Proposals are SPECULATIVE until `builder verify` passes - Use skills: core-governed-coding, core-verify-loop, core-pre-edit-sweep, core-handoff - Slash: /explore /implement /review /verify /handoff /plan diff --git a/AGENTS.md b/AGENTS.md index 9e5382c9..eb3bdf88 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -241,21 +241,23 @@ Our sole remote and CI/CD platform is **core-gitquarters.acbcontent.org**. - **USE** the Gitea/Forgejo MCP tools 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. +To optimize server resources and bypass external CI billing dependencies, all agents and developers must run validation suites **locally in the worktree**. That local run is the merge bar. Remote Actions (including any Docker-based job) are **not** required to merge and must not block merge when local gates are green. - **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 175 tests pass (parity with the CI smoke gate is pinned by `test_cli_smoke_suite_covers_ci_smoke_gate`). Pushing broken code is a critical protocol violation. + Ensure all smoke tests pass (parity with the smoke gate is pinned by `test_cli_smoke_suite_covers_ci_smoke_gate`). 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`). +- **No Docker CI for merge:** Do not run, wait on, or re-provision Docker-container CI to green a merge. Fix and prove in-worktree; merge on local evidence. - **PR Documentation:** When creating a PR on Forgejo (via the Forgejo MCP tools), document the local test execution in the PR description, matching this format: `[Verification]: Smoke suite passed locally (s, passed)` ### CI/CD Runner Architecture (2026-07-16 doctrine) +**CRITICAL**: The real CI is **local-first**. Run validation in the worktree (`uv run core test --suite smoke -q` before push; larger suites before merge), document `[Verification]:` on the PR, then merge. Do **not** treat remote Actions / Docker job containers as the merge gate. **CRITICAL**: Do NOT attempt to run CI jobs on the central Forgejo server (`core-gitquarters`) — the `e2-micro` instance (1GB RAM) cannot handle test workflows without OOM thrashing. The server-side Act runner was deliberately removed on 2026-07-16. -- The `.github/workflows/*.yml` files are canonical and actively used; they install the **locked dependency universe** (`uv sync --locked` against the committed `uv.lock`) so lane pins can only move when code moves. -- CI jobs are dispatched to a **local Act runner on the primary developer's Mac** (registered label `ubuntu-latest:host`): jobs requesting `ubuntu-latest` run natively on the macOS host — the `ubuntu-latest` environment name is a fiction here; do not add Linux-only steps. -- **Availability tradeoff is intentional**: when the Mac is asleep or away, the CI queue simply waits. The real discipline is the pre-push local gates above. Do NOT "fix" a waiting queue by re-provisioning a runner on the GCP server, and do not modify server-side runner configuration. +- The `.github/workflows/*.yml` files install the **locked dependency universe** (`uv sync --locked` against the committed `uv.lock`) so lane pins can only move when code moves. They are secondary observability only — never a substitute for local gates, and never an excuse to wait on Docker. +- Optional secondary runner: a **local Act runner on the primary developer's Mac**, registered label **`ubuntu-latest:host`** = **native macOS host execution** (no Docker job containers). The `ubuntu-latest` environment name is a fiction; do not add Linux-only steps. If a runner config maps `ubuntu-latest` to `docker://…`, that config is **wrong** and must be fixed to `:host` — do **not** rewrite doctrine to bless Docker CI. +- **Availability tradeoff is intentional**: when the Mac is asleep or away, the Actions queue waits. That is fine: local-first already cleared the merge bar. Do NOT "fix" a waiting queue by re-provisioning a runner on the GCP server, and do not modify server-side runner configuration. - GitHub (`AssetOverflow/core`) is a **mirror only**; its Actions are billing-locked and produce dead signals — never chase them. ### Pre-Edit Sweep & Versor Coherence Guardian Protocol diff --git a/CLAUDE.md b/CLAUDE.md index fd5947fd..44a89304 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,10 +1,10 @@ -# CORE Agent Instructions for Claude +# CORE — Claude `AGENTS.md` is the canonical governance file. If this file conflicts, 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. Do not use `tea` CLI. Use Gitea/Forgejo MCP tools. -**CI**: local-first — pre-push gates are mandatory; CI jobs run on the developer's local Mac Act runner, never on the Forgejo server (see `AGENTS.md` §CI/CD Runner Architecture). +Startup: read `AGENTS.md`, `docs/specs/runtime_contracts.md`, inspect tree, smallest lane. +Remote: `core-gitquarters.acbcontent.org`. No GitHub/`gh`/`tea`. Forgejo MCP. +CI: local-first (smoke in-worktree, then merge). No Docker CI. Host runner only: `ubuntu-latest:host`. See `AGENTS.md`. Before non-trivial edits, apply the protocol in `AGENTS.md`. diff --git a/GEMINI.md b/GEMINI.md index 6b46bcd2..6a256851 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,9 +1,10 @@ -# CORE Agent Instructions for Gemini +# CORE — Gemini `AGENTS.md` is the canonical governance file. If this file conflicts, 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. +Startup: read `AGENTS.md`, `docs/specs/runtime_contracts.md`, inspect tree, smallest lane. +Remote: `core-gitquarters.acbcontent.org`. No GitHub/`gh`/`tea`. Forgejo MCP. +CI: local-first (smoke in-worktree, then merge). No Docker CI. Host runner only: `ubuntu-latest:host`. See `AGENTS.md`. Before non-trivial edits, apply the protocol in `AGENTS.md`. diff --git a/Makefile b/Makefile index 7c05059f..d4784aea 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,9 @@ # in-file decorators. Classification adds the `slow` marker only; it never # skips, so the lanes below are selected explicitly by marker expression. # -# These run serially. Parallel (`-n auto`) is intentionally NOT the default -# until the suite is xdist-hermetic (shared repo engine_state/ + report.json + -# teaching/proposals writers race under parallel workers). See -# docs/testing-lanes.md "Follow-up: xdist". +# Parallel (`-n auto`) is the default for fast/full after #46 xdist polluter +# isolation. Serial escape hatch: `make test-fast-serial` / `test-full-serial` +# for debugging. See docs/testing-lanes.md. # # CLOSE flywheel Claim-B determinism (post-#791 hardening): after any # CLOSE/idle_tick/realize_derived/consolidate/vault/determine change, also run @@ -15,18 +14,25 @@ # uv run python -m pytest tests/test_derived_close_proposals.py tests/test_architectural_invariants.py -q # See docs/testing-lanes.md "Recommended determinism / teaching regression invocation". -.PHONY: test-fast test-slow test-full test-close-flywheel +.PHONY: test-fast test-fast-serial test-slow test-full test-full-serial test-close-flywheel # Fast dev lane — excludes the slow soak/bench/proof/eval-matrix registry. test-fast: + uv run pytest -n auto -m "not quarantine and not slow" -q + +# Serial escape hatch when debugging xdist-specific failures. +test-fast-serial: uv run pytest -m "not quarantine and not slow" -q -# Slow lane — only the heavyweight registry tests. +# Slow lane — only the heavyweight registry tests (serial: long soaks). test-slow: uv run pytest -m "slow and not quarantine" -q # Full lane — everything except known-failing quarantine (what CI runs). test-full: + uv run pytest -n auto -m "not quarantine" -q + +test-full-serial: uv run pytest -m "not quarantine" -q # Dedicated CLOSE Flywheel Regression Surface (Claim-B Level) diff --git a/core/physics/multi_scale_energy.py b/core/physics/multi_scale_energy.py index fc63b884..e68f8a62 100644 --- a/core/physics/multi_scale_energy.py +++ b/core/physics/multi_scale_energy.py @@ -17,7 +17,7 @@ Reuses ``fibonacci_number`` / ``fibonacci_tau_schedule`` — no parallel Fibonac from __future__ import annotations from dataclasses import dataclass -from math import exp, isfinite +from math import exp, isfinite, log from typing import Sequence from core.physics.fibonacci_search import fibonacci_number @@ -68,6 +68,22 @@ def dyadic_tau_schedule( return tuple(float(t0 * (2 ** (i - 1))) for i in range(1, n + 1)) +def log_tau_schedule( + tau0: float = _DEFAULT_TAU0, + *, + levels: int = 8, +) -> tuple[float, ...]: + """Logarithmic comparison baseline τ_n = τ_0 · ln(n + 1) for n = 1..levels. + + ADR-0242 V2 third comparative baseline (with Fibonacci and dyadic). + Research-only — not a production default. ln(n+1) is strictly positive + and monotone for n ≥ 1. + """ + t0 = _validate_tau0(tau0) + n = _validate_levels(levels) + return tuple(float(t0 * log(i + 1)) for i in range(1, n + 1)) + + def multi_scale_energy_for_schedule( e0: float, age: float, @@ -151,6 +167,70 @@ def comparative_residual_separation( } +def comparative_three_way( + e0: float, + age: float, + *, + tau0: float = _DEFAULT_TAU0, + levels: int = 8, +) -> dict[str, object]: + """Fixed-replay Fibonacci vs dyadic vs log multi-scale comparison. + + Pure research helper — no I/O, no production promotion. Extends the + two-way residual separation with the logarithmic baseline. Joshua gate + required before any of these schedules becomes a FieldEnergyOperator default. + """ + t0 = _validate_tau0(tau0) + n = _validate_levels(levels) + two = comparative_residual_separation(e0, age, tau0=t0, levels=n) + log_taus = log_tau_schedule(t0, levels=n) + log_e = multi_scale_energy_for_schedule(e0, age, log_taus) + fib_e = two["fibonacci_energies"] + assert isinstance(fib_e, tuple) + return { + **two, + "log_taus": log_taus, + "log_energies": log_e, + "energy_gap_fib_minus_log": tuple( + float(f - lg) for f, lg in zip(fib_e, log_e, strict=True) + ), + "schedules": ("fibonacci", "dyadic", "log"), + "promotion_status": "research_only", + "joshua_gate_required": True, + } + + +def fixed_replay_compare_artifact( + *, + e0: float = 1.0, + ages: Sequence[float] = (0.0, 1.0, 3.0, 8.0), + tau0: float = _DEFAULT_TAU0, + levels: int = 8, +) -> dict[str, object]: + """Deterministic multi-age comparative artifact for V2 evidence records. + + Pure: returns a JSON-serializable dict. Callers may write it to disk for + audit; this function itself performs no I/O and does not touch production + energy operators. + """ + rows = [ + comparative_three_way(e0, float(age), tau0=tau0, levels=levels) + for age in ages + ] + return { + "artifact": "adr_0242_v2_energy_compare", + "schema_version": 1, + "e0": float(e0), + "tau0": float(tau0), + "levels": int(levels), + "ages": tuple(float(a) for a in ages), + "rows": rows, + "promotion_status": "research_only", + "joshua_gate_required": True, + "production_default_unchanged": True, + } + + def schedule_mid_span_fraction(taus: Sequence[float], *, index: int | None = None) -> float: """Fraction of max(τ) occupied by τ at mid (or given) index. @@ -267,8 +347,11 @@ def cross_band_discovery_gate( __all__ = [ "CrossBandVerdict", "comparative_residual_separation", + "comparative_three_way", "cross_band_discovery_gate", "dyadic_tau_schedule", + "fixed_replay_compare_artifact", + "log_tau_schedule", "multi_scale_energy_for_schedule", "multi_scale_energy_vector", "schedule_mid_span_fraction", diff --git a/core/physics/sensorium_wave_feed.py b/core/physics/sensorium_wave_feed.py index f4ae5f26..58eaa8a0 100644 --- a/core/physics/sensorium_wave_feed.py +++ b/core/physics/sensorium_wave_feed.py @@ -131,6 +131,40 @@ def fake_deterministic_packet( return ModalityPacket(modality_id=modality_id, coefficients=coeffs) +def packet_from_compiler_versor( + modality_id: str, + versor: np.ndarray, +) -> ModalityPacket: + """Lift a real modality-compiler projection (32-float versor) into a packet. + + Accepts the ``.versor`` field of ``AudioCompilationUnit`` / + ``VisionCompilationUnit`` (or any Cl(4,1) projection of shape ``(32,)``). + This is the construction-boundary seam for I-04 with live compilers — + compilers stay in ``sensorium/*``; this module only standardizes the feed. + """ + return ModalityPacket( + modality_id=modality_id, + coefficients=np.asarray(versor, dtype=np.float64), + ) + + +def packet_from_compilation_unit( + modality_id: str, + unit: Any, +) -> ModalityPacket: + """Lift a compilation unit with a ``.versor`` attribute into a packet. + + Thin duck-typed adapter for audio/vision/sensorimotor units. Refuses if + ``versor`` is missing so callers cannot pass unrelated objects silently. + """ + if not hasattr(unit, "versor"): + raise TypeError( + "compilation unit must expose .versor (audio/vision unit); " + f"got {type(unit).__name__}" + ) + return packet_from_compiler_versor(modality_id, unit.versor) + + __all__ = [ "ModalityPacket", "PacketLike", @@ -138,4 +172,6 @@ __all__ = [ "superpose_packets", "phase_correlate", "fake_deterministic_packet", + "packet_from_compiler_versor", + "packet_from_compilation_unit", ] diff --git a/docs/plans/adr-0241-0242-mastery-close-status.md b/docs/plans/adr-0241-0242-mastery-close-status.md index d7b73ebd..736bbf3e 100644 --- a/docs/plans/adr-0241-0242-mastery-close-status.md +++ b/docs/plans/adr-0241-0242-mastery-close-status.md @@ -49,7 +49,7 @@ Complete the wave-field substrate program (ADR-0241/0242 + core_ha deprecation + ### W5 — Post-Accept backlog — **PARTIALLY SUPERSEDED, see below** **Plan (as originally written):** D9 Rust f64 parity; runner capacity (second Act runner / larger VM, later superseded by a GitHub-Actions-mirror idea); sensorium real compilers; V2 energy promotion (Fibonacci vs dyadic vs log, benchmark-gated); progressive continuous field. -**Outcome:** none of the W5 items above have been started. Instead, the CI-infra item under-forecast how bad the CI situation would get — see "Unplanned: CI collapse and local-first pivot" below, which consumed the entire post-ratification session. The five W5 items are **still the real backlog**, now joined by items surfaced during the CI pivot (below). +**Outcome (as of mid-arc):** none of the W5 items had been started then; CI-infra under-forecast how bad the situation would get — see "Unplanned: CI collapse and local-first pivot" below. **Post-Accept follow-through (2026-07-16, this arc):** D9 f64 GP parity (Python SOT), sensorium real-compiler → ψ feed, V2 fib/dyadic/log fixed-replay evidence (no production promotion), `-n auto` fast-lane default, INV-21 allowlist dedup, and runner doctrine (local-first + `ubuntu-latest:host`, no Docker CI for merge) are closed. Progressive continuous field remains deferred research. See **Remaining backlog** below. --- @@ -71,7 +71,7 @@ GitHub Actions became billing-locked (~$6, account-level) mid-arc, forcing a piv - #51 (`207d9c6a`) — this CI doctrine committed to the repo (`AGENTS.md`, `CLAUDE.md`) — also the new Mac runner's first live dispatch test. - #52 (`18d3c70d`) — assessment note on a rejected parallel-session re-implementation submission (its "three critical bugs" claim did not survive verification; one had a literal Python syntax error). -**Open technical note, not yet actioned:** job logs pulled during #50/#51's CI triage show the Act runner still spinning up Docker containers (`docker pull node:20-bookworm`) rather than the native "no Docker" host execution the `ubuntu-latest:host` label's doctrine describes. Worth checking whenever CI infra is revisited; not currently blocking anything since local-first is the actual gate. +**Open technical note (infra, not doctrine):** some Act runner configs under `~/infra/runner-m1` still map `ubuntu-latest` → `docker://node:20-bookworm` and pull containers. That is a **runner misconfiguration**, not the merge gate. Doctrine: local-first in the worktree; optional secondary runner is `ubuntu-latest:host` (native, no Docker). Fix the label/platform map on the Mac runner when infra is next touched — do not re-author doctrine around Docker. --- @@ -81,12 +81,14 @@ GitHub Actions became billing-locked (~$6, account-level) mid-arc, forcing a piv - `main` @ `18d3c70d`. Zero open PRs. Arc branches and worktrees deleted. - CI: local-first is the real gate; `uv.lock` committed and locked installs enforced in all workflows; local Mac Act runner is live and has completed its first dispatch tests (#51, #52). -## Remaining backlog (none are completion blockers) +## Remaining backlog (post-Accept follow-through; 2026-07-16 mastery close) -1. **D9** — Rust f64 GP parity, only if the UMA path must match f64 wave residuals; freeze the Python parity suite first. -2. **Sensorium real compilers** — replace staged packets, closes I-04 feed fully. -3. **V2 energy promotion** — Fibonacci vs. dyadic vs. log under fixed replay, benchmark-gated, explicit Joshua gate required before promotion. -4. **Progressive continuous field** — research track, non-blocking. -5. **Runner Docker-vs-host discrepancy** — confirm whether `ubuntu-latest:host` jobs should truly run without Docker, and if so why the observed runs used containers. -6. **`-n auto` fast-lane default flip** — pending, xdist polluters now isolated (#46) so this is unblocked whenever it's prioritized. -7. **INV-21 allowlist dedup** — minor housekeeping surfaced during the xdist isolation work. +Status of items that were open after ratification / CI pivot: + +1. **D9** — ✅ **DONE (this arc):** frozen Python f64 Cl(4,1) GP parity suite (`tests/test_geometric_product_f64_parity.py`) is authority; backend proves f64 never silent-f32-truncates under `CORE_BACKEND=rust` (Rust f32 GP only; f64 stays Python SOT until a future `geometric_product_f64` PyO3 export is parity-gated). No scipy-as-truth. +2. **Sensorium real compilers → ψ (I-04)** — ✅ **DONE (this arc):** `packet_from_compiler_versor` / `packet_from_compilation_unit` lift live audio/vision compiler units into `ModalityPacket`; tests exercise compile→ψ→superpose→algebraic `phase_correlation` (not only `fake_deterministic_packet`). Cosine/ANN still banned; A-04 quarantine holds. +3. **V2 energy comparative evidence** — ✅ **DONE without production promotion (this arc):** Fibonacci vs dyadic vs log under fixed replay via `comparative_three_way` / `fixed_replay_compare_artifact` + `python -m evals.adr_0242_v2_energy_compare`. Artifact flags `joshua_gate_required=True`; `FieldEnergyOperator` / `energy.py` unchanged. **Promotion remains blocked** until Joshua’s explicit gate. +4. **Progressive continuous field** — ⏸ **deferred** — research track, non-blocking (out of scope for this close). +5. **Runner Docker-vs-host discrepancy** — ✅ **DONE (this arc, doctrine restored):** Global + repo doctrine is **local-first** (run locally, then merge). Optional Mac Act runner must be **`ubuntu-latest:host`** = native host execution — **not Docker job containers**. A misconfigured `~/infra/runner-m1` that mapped `ubuntu-latest` → `docker://node:20-bookworm` was the bug; doctrine was never “bless Docker.” `AGENTS.md` re-states host-native + local-first as the gate. +6. **`-n auto` fast-lane default flip** — ✅ **DONE (this arc):** `make test-fast` / `test-full` default to `-n auto`; serial escape hatches `test-fast-serial` / `test-full-serial`; `docs/testing-lanes.md` updated. #46 polluter isolation is the prerequisite. +7. **INV-21 allowlist dedup** — ✅ **DONE (this arc):** tuple-first pin with import-time uniqueness check; demos no longer re-list the full frozenset (single ownership in `TestINV21OneMutationPath`). diff --git a/docs/testing-lanes.md b/docs/testing-lanes.md index 6574861c..62b5a2ee 100644 --- a/docs/testing-lanes.md +++ b/docs/testing-lanes.md @@ -64,7 +64,9 @@ Combined (split + parallel) = **73 → 9.5 min (7.7×)**. The parallel fast lan scales ~5.7× because it excludes the 975s parallel-floor monster (see below); the full suite only reaches 2.9× because that one test pins a worker for 16 min. -`-n auto` is **not** wired into the `make` targets yet — see *Follow-up: xdist*. +`-n auto` **is** the default for `make test-fast` / `make test-full` (post-#46 +polluter isolation). Serial escape hatches: `make test-fast-serial` / +`make test-full-serial`. ## The slow registry (`conftest.py`) @@ -103,14 +105,9 @@ it is deferred, not done here. ## Follow-ups (separate PRs) -1. **xdist by default.** The fast/full lanes are *not* xdist-hermetic yet: - fresh-env-dict subprocess tests (`tests/formation/*`, `test_identity_packs`) - write to the repo `engine_state/` dir, and other tests write - `evals/.../report.json` and `teaching/proposals/` — these **race** under - parallel workers (e.g. `test_workbench_replay::test_replay_leaves_no_trace` - fails under `-n auto`, passes serially). Isolate those writers, then wire - `-n auto` into `make test-fast` / `test-full`. This is the same hermeticity - theme as `docs/issues/default-engine-state-test-hygiene.md`. +1. **xdist by default — DONE (#46 + this arc).** Polluter writers + (`engine_state/`, `report.json`, proposal sinks) were isolated; `make test-fast` + / `test-full` now default to `-n auto`. Serial targets remain for debugging. 2. **Warm-runtime fixture.** The fast lane's remaining ~9.5 min (parallel) is a long tail of 1–15s `ChatRuntime` constructions, not outliers. A shared/session-scoped warm-runtime fixture for read-only tests would cut this diff --git a/evals/adr_0242_v2_energy_compare/__init__.py b/evals/adr_0242_v2_energy_compare/__init__.py new file mode 100644 index 00000000..af3edda4 --- /dev/null +++ b/evals/adr_0242_v2_energy_compare/__init__.py @@ -0,0 +1,31 @@ +"""ADR-0242 V2 fixed-replay multi-scale energy comparison (research evidence). + +Does **not** promote Fibonacci (or log/dyadic) into production energy defaults. +Joshua gate required before any production promotion. +""" + +from __future__ import annotations + +from core.physics.multi_scale_energy import ( + comparative_three_way, + fixed_replay_compare_artifact, +) + +__all__ = [ + "comparative_three_way", + "fixed_replay_compare_artifact", + "run_fixed_replay", +] + + +def run_fixed_replay( + *, + e0: float = 1.0, + ages: tuple[float, ...] = (0.0, 1.0, 3.0, 8.0), + tau0: float = 1.0, + levels: int = 8, +) -> dict: + """Entry point for the fixed-replay comparative eval (deterministic).""" + return fixed_replay_compare_artifact( + e0=e0, ages=ages, tau0=tau0, levels=levels + ) diff --git a/evals/adr_0242_v2_energy_compare/__main__.py b/evals/adr_0242_v2_energy_compare/__main__.py new file mode 100644 index 00000000..eaf8d42b --- /dev/null +++ b/evals/adr_0242_v2_energy_compare/__main__.py @@ -0,0 +1,37 @@ +"""CLI: python -m evals.adr_0242_v2_energy_compare [--out PATH] + +Writes the fixed-replay Fibonacci/dyadic/log comparative artifact as JSON. +Research-only; never mutates FieldEnergyOperator defaults. +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +from evals.adr_0242_v2_energy_compare import run_fixed_replay + + +def main(argv: list[str] | None = None) -> int: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument( + "--out", + type=Path, + default=None, + help="Optional output path (default: stdout)", + ) + args = p.parse_args(argv) + artifact = run_fixed_replay() + text = json.dumps(artifact, indent=2, sort_keys=True) + "\n" + if args.out is not None: + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text(text, encoding="utf-8") + else: + sys.stdout.write(text) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_adr_0241_sensorium_wave_feed.py b/tests/test_adr_0241_sensorium_wave_feed.py index 59ae7a85..b4e48145 100644 --- a/tests/test_adr_0241_sensorium_wave_feed.py +++ b/tests/test_adr_0241_sensorium_wave_feed.py @@ -22,6 +22,8 @@ from core.physics.sensorium_wave_feed import ( ModalityPacket, compile_packet_to_psi, fake_deterministic_packet, + packet_from_compilation_unit, + packet_from_compiler_versor, phase_correlate, superpose_packets, ) @@ -220,3 +222,60 @@ def test_phase_correlate_source_only_calls_phase_correlation(): } for attr in call_attrs: assert attr not in forbidden, f"phase_correlate must not call .{attr}" + + +# --- Real modality compilers → ψ → ρ (I-04 close) ---------------------------- + + +def test_real_audio_and_vision_compilers_feed_phase_correlate(): + """I-04: live audio + vision compilers → ModalityPacket → ψ → algebraic ρ. + + Not fake_deterministic_packet: exercises real sensorium/* compilers. + """ + from sensorium.audio.compiler import AudioCompiler + from sensorium.vision import VisionCompiler, canonicalize_image + from sensorium.vision.grid import iter_tile_signals + + sr = 24_000 + n = int(sr * 0.35) + t = np.arange(n, dtype=np.float64) / sr + tone = (0.5 * np.sin(2 * np.pi * 160.0 * t)).astype(np.float32) + audio_unit = AudioCompiler().compile(tone, sr) + assert audio_unit.versor.shape == (N_COMPONENTS,) + assert audio_unit.versor_condition < _CLOSURE + + # Vision tile from a deterministic synthetic image (same pattern as vision tests). + x = np.linspace(0.0, 1.0, 32, dtype=np.float32) + y = np.linspace(0.0, 1.0, 32, dtype=np.float32) + xx, yy = np.meshgrid(x, y) + image = np.stack([xx, yy, 1.0 - xx], axis=2).astype(np.float32) + tile = iter_tile_signals(canonicalize_image(image))[0] + vision_unit = VisionCompiler().compile_tile(tile) + assert vision_unit.versor.shape == (N_COMPONENTS,) + assert vision_unit.versor_condition < _CLOSURE + + audio_pkt = packet_from_compilation_unit("audio", audio_unit) + vision_pkt = packet_from_compiler_versor("vision", vision_unit.versor) + psi_a = compile_packet_to_psi(audio_pkt) + psi_v = compile_packet_to_psi(vision_pkt) + assert psi_a.dtype == np.float64 + assert psi_v.dtype == np.float64 + assert versor_condition(psi_a) < _CLOSURE + assert versor_condition(psi_v) < _CLOSURE + + total = superpose_packets([audio_pkt, vision_pkt]) + assert total.shape == (N_COMPONENTS,) + assert float(np.linalg.norm(total - (psi_a + psi_v))) < 1e-12 + + rho = phase_correlate(psi_a, psi_v) + assert isinstance(rho, float) + # Algebraic ρ = ⟨ψ_A ~ψ_B + ψ_B ~ψ_A⟩_0 (not cosine): unit rotors self-correlate ≈ 2. + assert phase_correlate(psi_a, psi_a) > 1.5 + assert phase_correlate(psi_v, psi_v) > 1.5 + # Cross-modal path must run and return a finite float (no cosine/ANN). + assert np.isfinite(rho) + + +def test_packet_from_compilation_unit_rejects_non_units(): + with pytest.raises(TypeError, match="versor"): + packet_from_compilation_unit("audio", object()) diff --git a/tests/test_adr_0242_multi_scale_energy.py b/tests/test_adr_0242_multi_scale_energy.py index 229b1cc8..fca35a36 100644 --- a/tests/test_adr_0242_multi_scale_energy.py +++ b/tests/test_adr_0242_multi_scale_energy.py @@ -15,7 +15,10 @@ import pytest from core.physics.fibonacci_search import fibonacci_number from core.physics.multi_scale_energy import ( comparative_residual_separation, + comparative_three_way, dyadic_tau_schedule, + fixed_replay_compare_artifact, + log_tau_schedule, multi_scale_energy_for_schedule, multi_scale_energy_vector, schedule_mid_span_fraction, @@ -165,3 +168,71 @@ def test_field_energy_operator_untouched_by_multi_scale_module(): energy_src = (_ROOT / "core/physics/energy.py").read_text(encoding="utf-8") assert "multi_scale_energy" not in energy_src assert "fibonacci_tau_schedule" not in energy_src + + +# --- V2 three-way fixed-replay (fib / dyadic / log) — no production promotion - + + +def test_log_tau_schedule_positive_monotone(): + from math import log + + taus = log_tau_schedule(tau0=1.0, levels=5) + assert len(taus) == 5 + assert all(t > 0.0 for t in taus) + assert taus == tuple(float(log(i + 1)) for i in range(1, 6)) + assert taus[0] < taus[-1] + + +def test_comparative_three_way_shape_and_gate_flags(): + report = comparative_three_way(1.0, age=3.0, tau0=1.0, levels=5) + assert report["levels"] == 5 + assert report["schedules"] == ("fibonacci", "dyadic", "log") + assert report["promotion_status"] == "research_only" + assert report["joshua_gate_required"] is True + assert len(report["log_taus"]) == 5 + assert len(report["log_energies"]) == 5 + assert len(report["energy_gap_fib_minus_log"]) == 5 + # age=0 → unit energies on all three schedules + zero = comparative_three_way(1.0, age=0.0, tau0=1.0, levels=4) + assert zero["fibonacci_energies"] == (1.0, 1.0, 1.0, 1.0) + assert zero["dyadic_energies"] == (1.0, 1.0, 1.0, 1.0) + assert zero["log_energies"] == (1.0, 1.0, 1.0, 1.0) + + +def test_fixed_replay_compare_artifact_deterministic(): + a = fixed_replay_compare_artifact(e0=1.0, ages=(0.0, 1.0, 3.0), levels=6) + b = fixed_replay_compare_artifact(e0=1.0, ages=(0.0, 1.0, 3.0), levels=6) + assert a == b + assert a["artifact"] == "adr_0242_v2_energy_compare" + assert a["joshua_gate_required"] is True + assert a["production_default_unchanged"] is True + assert len(a["rows"]) == 3 + for row in a["rows"]: + assert set(row["schedules"]) == {"fibonacci", "dyadic", "log"} + + +def test_eval_entry_matches_physics_helper(): + from evals.adr_0242_v2_energy_compare import run_fixed_replay + + via_eval = run_fixed_replay(e0=1.0, ages=(0.0, 2.0), tau0=1.0, levels=5) + via_phys = fixed_replay_compare_artifact( + e0=1.0, ages=(0.0, 2.0), tau0=1.0, levels=5 + ) + assert via_eval == via_phys + + +def test_no_joshua_gate_artifact_means_no_production_promotion(): + """Without an explicit Joshua gate record, production energy stays default. + + Structural: energy.py does not reference multi_scale schedules; research + artifact itself declares promotion blocked. + """ + art = fixed_replay_compare_artifact() + assert art["joshua_gate_required"] is True + gate_docs = list((_ROOT / "docs").rglob("*joshua*gate*v2*energy*")) + # No silent gate file invented by this arc + assert not any("promote" in p.name.lower() and "v2" in p.name.lower() for p in gate_docs) + energy_src = (_ROOT / "core/physics/energy.py").read_text(encoding="utf-8") + assert "multi_scale_energy" not in energy_src + assert "log_tau_schedule" not in energy_src + assert "fibonacci_tau_schedule" not in energy_src diff --git a/tests/test_architectural_invariants.py b/tests/test_architectural_invariants.py index 24516142..fc95a364 100644 --- a/tests/test_architectural_invariants.py +++ b/tests/test_architectural_invariants.py @@ -639,7 +639,10 @@ class TestINV11ConvergentEvidence: import ast from pathlib import Path -ALLOWED_VAULT_WRITERS: frozenset[str] = frozenset({ +# Single ordered pin for INV-21 writers. Tuple-first so accidental duplicates +# fail at import (frozenset would silently collapse them). Demos import +# ALLOWED_VAULT_WRITERS; they must not re-list this set (dedup housekeeping). +_ALLOWED_VAULT_WRITER_ENTRIES: tuple[str, ...] = ( "session/context.py", "vault/store.py", "generate/proposition.py", @@ -652,7 +655,12 @@ ALLOWED_VAULT_WRITERS: frozenset[str] = frozenset({ # same VaultStore.store path (no parallel memory). Defaults SPECULATIVE; # COHERENT only through explicit authorized seal_mode_reviewed. "core/physics/holographic_vault.py", -}) +) +if len(_ALLOWED_VAULT_WRITER_ENTRIES) != len(set(_ALLOWED_VAULT_WRITER_ENTRIES)): + raise RuntimeError( + "ALLOWED_VAULT_WRITERS pin has duplicate paths — dedup before freezing" + ) +ALLOWED_VAULT_WRITERS: frozenset[str] = frozenset(_ALLOWED_VAULT_WRITER_ENTRIES) PROJECT_ROOT_FOR_INV21 = Path(__file__).resolve().parent.parent @@ -766,6 +774,12 @@ class TestINV21OneMutationPath: "Remove from ALLOWED_VAULT_WRITERS to keep the trust surface tight." ) + def test_allowlist_entries_are_unique_and_nonempty(self): + """INV-21 dedup: pin has no duplicate paths; trust surface is non-vacuous.""" + assert len(_ALLOWED_VAULT_WRITER_ENTRIES) == len(ALLOWED_VAULT_WRITERS) + assert len(ALLOWED_VAULT_WRITERS) >= 4 + assert "vault/store.py" in ALLOWED_VAULT_WRITERS + # =========================================================================== # INV-22 Pack lexicon default is SPECULATIVE — Leak A regression guard diff --git a/tests/test_deductive_entailment_authority_demo.py b/tests/test_deductive_entailment_authority_demo.py index a9d9c7c8..2dd43752 100644 --- a/tests/test_deductive_entailment_authority_demo.py +++ b/tests/test_deductive_entailment_authority_demo.py @@ -518,20 +518,21 @@ def test_demo_adds_no_vault_writer_no_status_transition_no_recall(): def test_inv21_and_inv29_allowlists_are_unchanged(): + """INV-21 exact writer set is owned by TestINV21OneMutationPath (single pin). + + Demos do not re-list ALLOWED_VAULT_WRITERS (dedup housekeeping): re-listing + drifts independently of the architectural pin. Here we only assert INV-29 + single-site and the load-bearing members demos care about. + """ from tests.test_architectural_invariants import ( ALLOWED_STATUS_TRANSITION_SITES, ALLOWED_VAULT_WRITERS, ) - assert ALLOWED_VAULT_WRITERS == frozenset({ - "session/context.py", - "vault/store.py", - "generate/proposition.py", - "generate/realize/realize.py", - # ADR-0241 holographic standing-wave spectrum (INV-21 allowlist expansion). - "core/physics/holographic_vault.py", - }) assert ALLOWED_STATUS_TRANSITION_SITES == frozenset({"vault/store.py"}) + assert "vault/store.py" in ALLOWED_VAULT_WRITERS + assert "generate/realize/realize.py" in ALLOWED_VAULT_WRITERS + assert "core/physics/holographic_vault.py" in ALLOWED_VAULT_WRITERS def test_no_private_strategy_or_named_company_terms(): diff --git a/tests/test_geometric_product_f64_parity.py b/tests/test_geometric_product_f64_parity.py new file mode 100644 index 00000000..9b3f5f5f --- /dev/null +++ b/tests/test_geometric_product_f64_parity.py @@ -0,0 +1,208 @@ +"""D9 — frozen Python f64 Cl(4,1) geometric-product parity suite (authority). + +Python ``algebra.cl41.geometric_product`` is the semantic source of truth for +float64 wave-field residuals (ADR-0241 chiral / trajectory pins at 1e-9). + +This suite: + 1. Freezes algebraic f64 GP properties against the pure-Python kernel. + 2. Proves ``algebra.backend.geometric_product`` never silently f32-truncates + float64 workloads (even when CORE_BACKEND=rust and core_rs is present). + 3. When Rust f64 GP is later exposed, extend with tol-matched parity — until + then, honest Python-SOT for f64 is the shipped contract. + +No scipy-as-truth. Fix the kernel (or the dispatch), not this suite, on red. +""" + +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + +import numpy as np +import pytest + +from algebra.cl41 import N_COMPONENTS, geometric_product as gp_py, reverse +from algebra.backend import geometric_product as gp_backend + +REPO = Path(__file__).resolve().parent.parent + +try: + import core_rs # noqa: F401 + + _RUST_AVAILABLE = True +except ImportError: + _RUST_AVAILABLE = False + + +def _rng(seed: int) -> np.random.Generator: + return np.random.default_rng(seed) + + +def _f64_mv(seed: int) -> np.ndarray: + return _rng(seed).standard_normal(N_COMPONENTS).astype(np.float64) + + +def _basis(i: int) -> np.ndarray: + e = np.zeros(N_COMPONENTS, dtype=np.float64) + e[i] = 1.0 + return e + + +# --- Frozen Python SOT properties ------------------------------------------- + + +@pytest.mark.parametrize("seed", [0xF64A, 0xF64B, 0xF64C, 0xC07E, 0xBEEF]) +def test_python_f64_gp_is_deterministic(seed: int) -> None: + a, b = _f64_mv(seed), _f64_mv(seed + 1) + r1 = gp_py(a, b) + r2 = gp_py(a.copy(), b.copy()) + assert r1.dtype == np.float64 + assert r1.shape == (N_COMPONENTS,) + assert np.array_equal(r1, r2) + + +def test_python_f64_scalar_identity() -> None: + one = _basis(0) + b = _f64_mv(0x11) + out = gp_py(one, b) + assert float(np.max(np.abs(out - b))) == 0.0 + + +def test_python_f64_basis_anticommutation_e1_e2() -> None: + e1, e2 = _basis(1), _basis(2) + ab = gp_py(e1, e2) + ba = gp_py(e2, e1) + # e1 e2 = - e2 e1 for distinct orthogonal spacelike basis vectors + assert float(np.max(np.abs(ab + ba))) < 1e-15 + + +@pytest.mark.parametrize("i", [0, 1, 5, 15, 31]) +def test_python_f64_basis_self_product_scalar_or_metric(i: int) -> None: + ei = _basis(i) + out = gp_py(ei, ei) + # Blade square is scalar (±1 or 0 for null patterns); residual grades small. + non_scalar = float(np.linalg.norm(out[1:])) + assert non_scalar < 1e-12 or abs(float(out[0])) > 0.0 + + +def test_python_f64_reverse_anti_automorphism_on_product() -> None: + """(AB)~ = B~ A~ within f64 accumulation tolerance.""" + a, b = _f64_mv(0xA11), _f64_mv(0xB22) + left = reverse(gp_py(a, b)) + right = gp_py(reverse(b), reverse(a)) + assert float(np.max(np.abs(left - right))) < 1e-12 + + +# --- Backend: no silent f32 truncation of f64 ------------------------------- + + +def test_backend_f64_matches_python_sot_exactly() -> None: + """Default backend must match pure Python bit-for-bit on f64 inputs.""" + a, b = _f64_mv(0xD9F1), _f64_mv(0xD9F2) + py = gp_py(a, b) + be = gp_backend(a, b) + assert be.dtype == np.float64 or np.asarray(be).dtype == np.float64 + assert float(np.max(np.abs(np.asarray(be, dtype=np.float64) - py))) == 0.0 + + +def test_backend_f64_does_not_match_forced_f32_truncation() -> None: + """Silent f32 cast of f64 residuals would destroy 1e-9 wave pins. + + Prove the shipped backend path differs from (or at least is not equal to + a forced) f32-truncated product when the true f64 product has sub-f32 + structure — or, if equal by chance on this fixture, still returns f64 + dtype and matches Python SOT (already covered). Here we pin that backend + never routes f64 through float32 geometric_product. + """ + # High-dynamic-range components that f32 cannot represent exactly. + a = np.zeros(N_COMPONENTS, dtype=np.float64) + b = np.zeros(N_COMPONENTS, dtype=np.float64) + a[0] = 1.0 + 1e-10 + a[6] = 1e-9 + b[0] = 1.0 + b[7] = 1e-9 + py = gp_py(a, b) + be = np.asarray(gp_backend(a, b), dtype=np.float64) + assert float(np.max(np.abs(be - py))) == 0.0 + # Truncating inputs to f32 changes the product for this fixture. + a32 = a.astype(np.float32) + b32 = b.astype(np.float32) + truncated = gp_py(a32.astype(np.float64), b32.astype(np.float64)) + # If f32 truncation mattered, SOT f64 and truncated differ; backend must + # follow SOT, not truncation. (If they coincide, equality still holds.) + assert float(np.max(np.abs(be - py))) == 0.0 + if float(np.max(np.abs(py - truncated))) > 0.0: + assert float(np.max(np.abs(be - truncated))) > 0.0 + + +SCRIPT_F64_BACKEND = r""" +import json, os, sys +import numpy as np +sys.path.insert(0, "__REPO__") +from algebra.backend import geometric_product, using_rust +from algebra.cl41 import geometric_product as gp_py + +seed = int(os.environ["FIXTURE_SEED"]) +rng = np.random.default_rng(seed) +a = rng.standard_normal(32).astype(np.float64) +b = rng.standard_normal(32).astype(np.float64) +out_be = np.asarray(geometric_product(a, b), dtype=np.float64) +out_py = np.asarray(gp_py(a, b), dtype=np.float64) +print(json.dumps({ + "using_rust": using_rust(), + "backend_hex": [np.float64(v).tobytes().hex() for v in out_be], + "python_hex": [np.float64(v).tobytes().hex() for v in out_py], + "dtype": str(out_be.dtype), +})) +""" + + +def _run_f64_backend(backend: str, seed: int) -> dict: + env = os.environ.copy() + if backend == "rust": + env["CORE_BACKEND"] = "rust" + else: + env.pop("CORE_BACKEND", None) + env["FIXTURE_SEED"] = str(seed) + script = SCRIPT_F64_BACKEND.replace("__REPO__", str(REPO)) + out = subprocess.check_output( + [sys.executable, "-c", script], + env=env, + cwd=str(REPO), + text=True, + ) + return json.loads(out.strip().splitlines()[-1]) + + +@pytest.mark.parametrize("seed", [0xD901, 0xD902, 0xD903]) +def test_f64_backend_matches_python_sot_in_subprocess(seed: int) -> None: + py = _run_f64_backend("python", seed) + assert py["using_rust"] is False + assert py["backend_hex"] == py["python_hex"] + assert py["dtype"] == "float64" + + +@pytest.mark.skipif(not _RUST_AVAILABLE, reason="core_rs extension not built") +@pytest.mark.parametrize("seed", [0xD911, 0xD912]) +def test_f64_with_rust_opt_in_still_python_sot(seed: int) -> None: + """CORE_BACKEND=rust must not f32-truncate f64 GP (D9 honesty pin). + + Rust exposes f32 geometric_product only; f64 remains Python SOT until a + future geometric_product_f64 PyO3 export is wired and parity-gated. + """ + rs = _run_f64_backend("rust", seed) + assert rs["using_rust"] is True + assert rs["backend_hex"] == rs["python_hex"] + assert rs["dtype"] == "float64" + + +def test_backend_source_documents_f64_python_sot() -> None: + """Structural pin: dispatch comments + _is_f32_workload gate remain.""" + src = (REPO / "algebra" / "backend.py").read_text(encoding="utf-8") + assert "_is_f32_workload" in src + assert "float64 wave residual" in src or "float64" in src + # geometric_product only calls Rust under f32 workload gate + assert "if _RUST and _is_f32_workload(A, B):" in src diff --git a/tests/test_proof_carrying_promotion_demo.py b/tests/test_proof_carrying_promotion_demo.py index 6910b588..47a93ae6 100644 --- a/tests/test_proof_carrying_promotion_demo.py +++ b/tests/test_proof_carrying_promotion_demo.py @@ -368,20 +368,21 @@ def test_demo_adds_no_vault_writer_no_status_transition_no_recall(): def test_inv21_and_inv29_allowlists_are_unchanged(): + """INV-21 exact writer set is owned by TestINV21OneMutationPath (single pin). + + Demos do not re-list ALLOWED_VAULT_WRITERS (dedup housekeeping): re-listing + drifts independently of the architectural pin. Here we only assert INV-29 + single-site and the load-bearing members demos care about. + """ from tests.test_architectural_invariants import ( ALLOWED_STATUS_TRANSITION_SITES, ALLOWED_VAULT_WRITERS, ) - assert ALLOWED_VAULT_WRITERS == frozenset({ - "session/context.py", - "vault/store.py", - "generate/proposition.py", - "generate/realize/realize.py", - # ADR-0241 holographic standing-wave spectrum (INV-21 allowlist expansion). - "core/physics/holographic_vault.py", - }) assert ALLOWED_STATUS_TRANSITION_SITES == frozenset({"vault/store.py"}) + assert "vault/store.py" in ALLOWED_VAULT_WRITERS + assert "generate/realize/realize.py" in ALLOWED_VAULT_WRITERS + assert "core/physics/holographic_vault.py" in ALLOWED_VAULT_WRITERS def test_no_private_strategy_or_named_company_terms():