PR-3 of ADR-0181. Externalises the PR-2 in-code operator registry to a
versioned, checksum-locked pack and wires the sensorium adapter. Mounts
gate-closed until the PR-4 eval gates pass. No core mutation (ADR-0013).
Pack (packs/audio/audio_core_v1/):
- manifest.json canonical params, resampling config, gating defaults, ordering
- operators.jsonl the 8 elliptic operators (byte-equivalent to DEFAULT_OPERATOR_REGISTRY)
- basis_map.json alias→index over the six elliptic planes {6,7,8,10,11,13}
- resample_fir_v1.npy pinned odd-length symmetric windowed-sinc low-pass (unit DC)
- checksums.json sha256 of every artifact
Loader (packs/audio/loader.py) — trust boundary (ADR-0051 / CLAUDE.md §Security):
- _validate_pack_id rejects traversal/separators/dotfiles before any path join
- fail-closed checksum verification: every file re-hashed vs checksums.json
- resolved path must stay under the packs root (defense in depth)
- JSON format (matches every other CORE pack loader; spec §6.1 TOML was
illustrative; no tomllib dependency added)
Adapter (sensorium/adapters/audio.py):
- AudioProjectionHead wraps AudioCompiler; project(AudioSignal)->(32,) f32
- make_audio_pack loads+verifies the pack, builds a gate-closed ModalityPack
Tests (19): PR-2↔PR-3 registry byte-parity via manifest_sha256, elliptic-only
operators, FIR integrity, fail-closed checksum mismatch, path-traversal guard,
gate-closed refusal, mount-time unitarity (+ bad-unitarity blocks mount),
gate-engage requires checksum_verified, engaged pack projects (32,) f32.
All audio 32 + arch-invariants 40 + smoke 67 green.
71 lines
2.7 KiB
Python
71 lines
2.7 KiB
Python
"""
|
|
ADR-0181 PR-3 — audio pack manifest + loader trust boundary.
|
|
|
|
Covers:
|
|
- the loaded operator registry is byte-equivalent to the in-code
|
|
DEFAULT_OPERATOR_REGISTRY (PR-2 ↔ PR-3 parity, via manifest_sha256);
|
|
- fail-closed checksum verification (tampered artifact blocks load);
|
|
- the ADR-0051 path-traversal guard;
|
|
- the pinned FIR artifact integrity.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
|
|
import pytest
|
|
|
|
from packs.audio.loader import AudioPackError, load_audio_pack
|
|
from sensorium.audio.operators import DEFAULT_OPERATOR_REGISTRY, ELLIPTIC_PLANES
|
|
|
|
|
|
def test_pack_loads_and_verifies():
|
|
pack = load_audio_pack("audio_core_v1")
|
|
assert pack.pack_id == "audio_core_v1"
|
|
assert pack.manifest["cl41_dim"] == 32
|
|
assert pack.manifest["gating"]["gate_engaged"] is False # ships gate-closed
|
|
|
|
|
|
def test_loaded_registry_is_byte_equivalent_to_in_code_default():
|
|
"""The pack artifact and the in-code DEFAULT_OPERATOR_REGISTRY must produce
|
|
the identical content hash — PR-3 externalises PR-2 without drift."""
|
|
pack = load_audio_pack("audio_core_v1")
|
|
assert pack.registry.manifest_sha256() == DEFAULT_OPERATOR_REGISTRY.manifest_sha256()
|
|
assert set(pack.registry.specs) == set(DEFAULT_OPERATOR_REGISTRY.specs)
|
|
|
|
|
|
def test_all_loaded_operators_are_elliptic():
|
|
pack = load_audio_pack("audio_core_v1")
|
|
for spec in pack.registry.specs.values():
|
|
assert spec.blade_index in ELLIPTIC_PLANES
|
|
|
|
|
|
def test_fir_artifact_is_odd_length_and_matches_manifest():
|
|
pack = load_audio_pack("audio_core_v1")
|
|
assert pack.fir is not None
|
|
assert pack.fir.ndim == 1 and pack.fir.size % 2 == 1 # zero-phase symmetric
|
|
assert pack.fir_sha256.endswith(pack.manifest["resampling"]["fir_sha256"])
|
|
|
|
|
|
# --- trust boundary -------------------------------------------------------
|
|
|
|
def test_checksum_mismatch_fails_closed(tmp_path):
|
|
"""A tampered artifact must block the load (eval-plan §2 mount validation)."""
|
|
from packs.audio.loader import _PACKS_ROOT
|
|
dst = tmp_path / "audio_core_v1"
|
|
shutil.copytree(_PACKS_ROOT / "audio_core_v1", dst)
|
|
# Corrupt operators.jsonl without updating checksums.json.
|
|
(dst / "operators.jsonl").write_text((dst / "operators.jsonl").read_text() + "\n")
|
|
with pytest.raises(AudioPackError, match="checksum mismatch"):
|
|
load_audio_pack("audio_core_v1", packs_root=tmp_path)
|
|
|
|
|
|
@pytest.mark.parametrize("bad_id", ["..", "../secrets", "a/b", "a\\b", ".hidden", ""])
|
|
def test_path_traversal_rejected(bad_id):
|
|
with pytest.raises(AudioPackError):
|
|
load_audio_pack(bad_id)
|
|
|
|
|
|
def test_missing_pack_fails_closed(tmp_path):
|
|
with pytest.raises(AudioPackError, match="no audio pack"):
|
|
load_audio_pack("audio_core_v1", packs_root=tmp_path)
|