feat: close ADR-0241/0242 post-Accept backlog (local-first mastery)
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).
This commit is contained in:
parent
3019eb41b9
commit
d388fc9e2c
17 changed files with 607 additions and 57 deletions
|
|
@ -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
|
||||
|
|
|
|||
12
AGENTS.md
12
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 (<run_duration>s, <test_count> 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
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
|
|
|
|||
18
Makefile
18
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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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`).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
31
evals/adr_0242_v2_energy_compare/__init__.py
Normal file
31
evals/adr_0242_v2_energy_compare/__init__.py
Normal file
|
|
@ -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
|
||||
)
|
||||
37
evals/adr_0242_v2_energy_compare/__main__.py
Normal file
37
evals/adr_0242_v2_energy_compare/__main__.py
Normal file
|
|
@ -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())
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
208
tests/test_geometric_product_f64_parity.py
Normal file
208
tests/test_geometric_product_f64_parity.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Reference in a new issue