Files
UmbraAudio/tests/test_generation/test_staleness.py
B.D. Murphy 829a03b89f Action the controller alignment proposal: source_unit_key + phase reports
The controller (oldbox) sent a data-model alignment proposal and asked for six
additive changes on this node, the validation protocol, and an honest report.
Six of the seven changes are done; the seventh is declared missing rather than faked.

Stage 0 - reachability verified exactly as documented: health 200, heartbeat 401
(the correct answer - auth is refusing), missing Host 400. Nothing else was sent.

source_unit_key (controller 5.1, the blocking item)
  umbraaudio/source_units.py. sha256("uk1" | chapter_source_text_sha256 |
  occurrence | text_sha256). Nothing positional in the key: "occurrence"
  disambiguates repeated text in document order.
  - 18,181/18,181 units keyed across all four projects, 0 collisions
  - chapter 1 renumbered +100 -> 0 keys changed (the load-bearing proof)
  - backfill 10,015 rows in 0.11s, second run stamps 0 (idempotent)
  - alignment phase 3: 200/200 probe units resolved with used_index: false
  - tolerates the older projects that have no chapters.chapter_index
  Recorded at generation time in chapter_builder, BEFORE the rows are read and
  BEFORE the marker is written, so every path that renders audio also produces
  identity and the clip can name its source unit.

5.7 recorded: utterance_index is KEPT as a display ordinal and made
  non-load-bearing. spec 21 §10.

Blob hashes and effect chain (5.2)
  Markers now carry the full sha256 of the delivered bytes and the effects that
  produced them. Verified the existing order is already correct - write_marker
  runs AFTER apply_effects - and closed the remaining hole: a changed trait list
  changed the hash with no recorded cause. sha256 verification is opt-in, since
  hashing every file on every validation pass would re-read gigabytes.

Vocabularies published (5.4) - umbraaudio/definitions.py, 19 sets, 126 values,
  each value carrying whether it is 'declared' (spec/controller) or 'extracted'
  (read out of code), so a diff shows a difference of meaning, not of wording.

Alignment report tool (6.6) - scripts/alignment_report.py --phase all. Emits the
  controller's envelope with the per-phase evidence shapes, and obeys the two
  rules they set: no evidence means blocked, never pass; a field we cannot produce
  is null PLUS a finding, never omitted. Phase 2 is deliberately 'fail' - the
  three-way store disagreement is real, and a pass there would be a check that
  passed by doing nothing.

Fingerprint discipline (5.5) - the drift detector reported its OWN fingerprint
  change when source_unit_key was added rather than hiding it. Node profile
  updated with the new fingerprints and an honest note that they moved.

Node report extended - per-project source-unit-key coverage, per-voice-asset
  sha256 + object_key, definition_set_version, controller stage reached.

Six gaps found in the proposal (spec 21 §11), including the one they invited plus
five more. The sharpest: "hash at generation" ignores that audio is
post-processed afterwards, and our key is anchored to chapters.source_text - a
second copy of the manuscript - so any change to chapter storage silently
invalidates every key in that chapter.

5.6 deliberately NOT faked: personality has no versioned artifact here, only
  unversioned LLM prose in speakers.json. Reported as phase 6b finding B-03
  instead, because wrapping unversioned inputs in a version field would produce
  artifacts claiming a lineage they do not have.

Answered the controller's two open questions (infra/controller-handoff-2026-10-03.md):
forward-only link for a revealed recurring character, and yes - a new edition may
contradict a frozen state, since this node's correction path IS a regeneration.

Full suite 484 passed / 8 failed - the same 8 pre-existing and unrelated failures.
ruff clean on every file touched. The parallel session's chapter_builder hunk
(helper_url) was filtered out of the commit and left intact in the working tree.
2026-10-02 20:16:06 -07:00

229 lines
8.9 KiB
Python

from __future__ import annotations
from typing import TYPE_CHECKING
from umbraaudio.generation.staleness import is_current, marker_path, write_marker
if TYPE_CHECKING:
from pathlib import Path
def test_marker_path(tmp_path: Path) -> None:
wav = tmp_path / "utt_0003.wav"
assert marker_path(wav) == tmp_path / "utt_0003.wav.txt"
def test_missing_wav_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0000.wav"
assert is_current("Hello", wav) is False
def test_existing_wav_without_marker_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0000.wav"
wav.write_bytes(b"RIFF....")
assert is_current("Hello", wav) is False
def test_matching_text_is_current(tmp_path: Path) -> None:
wav = tmp_path / "utt_0001.wav"
wav.write_bytes(b"RIFF....")
write_marker("Google uses Android.", wav, speaker="Narrator")
assert is_current("Google uses Android.", wav, speaker="Narrator") is True
def test_changed_speaker_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0005.wav"
wav.write_bytes(b"RIFF....")
write_marker("Same words.", wav, speaker="Kabir")
assert is_current("Same words.", wav, speaker="Nanny") is False
def test_legacy_plain_text_marker_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0006.wav"
wav.write_bytes(b"RIFF....")
marker_path(wav).write_text("Same words.", encoding="utf-8")
assert is_current("Same words.", wav, speaker="Kabir") is False
def test_marker_keeps_text_without_speaker_given(tmp_path: Path) -> None:
wav = tmp_path / "utt_0007.wav"
wav.write_bytes(b"RIFF....")
write_marker("Only text recorded.", wav, speaker="Narrator")
assert is_current("Only text recorded.", wav) is True
def test_changed_text_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0002.wav"
wav.write_bytes(b"RIFF....")
write_marker("Old sentence from a previous segmentation.", wav, speaker="Narrator")
assert is_current("New sentence from re-segmentation.", wav, speaker="Narrator") is False
def test_write_marker_creates_sidecar(tmp_path: Path) -> None:
wav = tmp_path / "utt_0004.wav"
write_marker("Some text", wav, speaker="Narrator")
marker = marker_path(wav).read_text(encoding="utf-8")
assert marker == '{"speaker": "Narrator", "text": "Some text"}'
def test_voice_fingerprint_in_marker(tmp_path: Path) -> None:
wav = tmp_path / "utt_0010.wav"
wav.write_bytes(b"RIFF....")
ref = tmp_path / "female_ref.wav"
ref.write_bytes(b"RIFF-female-voice-data")
write_marker("Hello there.", wav, speaker="Nanny", voice_ref=ref)
marker = marker_path(wav).read_text(encoding="utf-8")
assert '"voice_fp":' in marker
def test_voice_fingerprint_mismatch_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0011.wav"
wav.write_bytes(b"RIFF....")
ref_old = tmp_path / "old_ref.wav"
ref_old.write_bytes(b"RIFF-old-male-voice")
ref_new = tmp_path / "new_ref.wav"
ref_new.write_bytes(b"RIFF-new-female-voice")
write_marker("Same line.", wav, speaker="Nanny", voice_ref=ref_old)
assert is_current("Same line.", wav, speaker="Nanny", voice_ref=ref_new) is False
def test_voice_fingerprint_match_is_current(tmp_path: Path) -> None:
wav = tmp_path / "utt_0012.wav"
wav.write_bytes(b"RIFF....")
ref = tmp_path / "same_ref.wav"
ref.write_bytes(b"RIFF-same-voice")
write_marker("Same line.", wav, speaker="Nanny", voice_ref=ref)
assert is_current("Same line.", wav, speaker="Nanny", voice_ref=ref) is True
def test_voice_fingerprint_skipped_when_no_ref(tmp_path: Path) -> None:
wav = tmp_path / "utt_0013.wav"
wav.write_bytes(b"RIFF....")
ref = tmp_path / "the_ref.wav"
ref.write_bytes(b"RIFF-voice")
write_marker("With fp.", wav, speaker="Nanny", voice_ref=ref)
assert is_current("With fp.", wav, speaker="Nanny") is True
def test_voice_fingerprint_stale_when_ref_deleted(tmp_path: Path) -> None:
wav = tmp_path / "utt_0014.wav"
wav.write_bytes(b"RIFF....")
ref = tmp_path / "deleted_ref.wav"
ref.write_bytes(b"RIFF-data")
write_marker("Gone ref.", wav, speaker="Nanny", voice_ref=ref)
ref.unlink()
assert is_current("Gone ref.", wav, speaker="Nanny", voice_ref=ref) is False
def test_changed_instruct_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0015.wav"
wav.write_bytes(b"RIFF....")
write_marker("Same words.", wav, speaker="Narrator", instruct="speak softly")
assert is_current("Same words.", wav, speaker="Narrator", instruct="speak loudly") is False
def test_matching_instruct_is_current(tmp_path: Path) -> None:
wav = tmp_path / "utt_0016.wav"
wav.write_bytes(b"RIFF....")
write_marker("Same words.", wav, speaker="Narrator", instruct="speak softly")
assert is_current("Same words.", wav, speaker="Narrator", instruct="speak softly") is True
def test_marker_without_instruct_matches_no_instruct_call(tmp_path: Path) -> None:
wav = tmp_path / "utt_0017.wav"
wav.write_bytes(b"RIFF....")
write_marker("Plain line.", wav, speaker="Narrator")
assert is_current("Plain line.", wav, speaker="Narrator") is True
def test_marker_without_instruct_stale_when_instruct_expected(tmp_path: Path) -> None:
wav = tmp_path / "utt_0018.wav"
wav.write_bytes(b"RIFF....")
write_marker("Plain line.", wav, speaker="Narrator")
assert is_current("Plain line.", wav, speaker="Narrator", instruct="speak softly") is False
def test_instruct_recorded_in_marker(tmp_path: Path) -> None:
wav = tmp_path / "utt_0019.wav"
write_marker("Some text", wav, speaker="Narrator", instruct="speak softly")
marker = marker_path(wav).read_text(encoding="utf-8")
assert marker == '{"speaker": "Narrator", "text": "Some text", "instruct": "speak softly"}'
def test_matching_fx_version_is_current(tmp_path: Path) -> None:
wav = tmp_path / "utt_0020.wav"
wav.write_bytes(b"RIFF....")
write_marker("Same words.", wav, speaker="Narrator", fx_version=1)
assert is_current("Same words.", wav, speaker="Narrator", fx_version=1) is True
def test_fx_version_mismatch_is_stale(tmp_path: Path) -> None:
wav = tmp_path / "utt_0021.wav"
wav.write_bytes(b"RIFF....")
write_marker("Same words.", wav, speaker="Narrator", fx_version=1)
assert is_current("Same words.", wav, speaker="Narrator", fx_version=2) is False
def test_marker_without_fx_stale_when_fx_expected(tmp_path: Path) -> None:
wav = tmp_path / "utt_0022.wav"
wav.write_bytes(b"RIFF....")
write_marker("Same words.", wav, speaker="Narrator")
assert is_current("Same words.", wav, speaker="Narrator", fx_version=1) is False
def test_fx_marked_audio_stale_for_plain_calls(tmp_path: Path) -> None:
wav = tmp_path / "utt_0023.wav"
wav.write_bytes(b"RIFF....")
write_marker("Same words.", wav, speaker="Narrator", fx_version=1)
assert is_current("Same words.", wav, speaker="Narrator") is False
def test_sha256_is_recorded_at_generation(tmp_path: Path) -> None:
"""Controller §5.2: every blob carries its own content hash from birth."""
import hashlib
import json
wav = tmp_path / "utt_0030.wav"
wav.write_bytes(b"RIFF-final-bytes")
write_marker("Same words.", wav, speaker="Hallr")
payload = json.loads(marker_path(wav).read_text())
assert payload["sha256"] == hashlib.sha256(b"RIFF-final-bytes").hexdigest()
def test_sha256_is_the_post_processed_bytes_not_the_raw_output(tmp_path: Path) -> None:
"""The hash must describe the delivered artifact, after effects are applied."""
import hashlib
import json
wav = tmp_path / "utt_0031.wav"
raw = b"RIFF-raw-tts"
wav.write_bytes(raw)
# Post-processing rewrites the file, exactly as apply_effects_to_file does.
wav.write_bytes(raw + b"-EFFECTED")
write_marker("Same words.", wav, speaker="Xylia", effects=["raspy"])
payload = json.loads(marker_path(wav).read_text())
assert payload["sha256"] == hashlib.sha256(raw + b"-EFFECTED").hexdigest()
assert payload["effects"] == ["raspy"]
def test_source_unit_key_is_recorded_so_audio_names_its_source_unit(tmp_path: Path) -> None:
import json
wav = tmp_path / "utt_0032.wav"
wav.write_bytes(b"RIFF")
write_marker("Same words.", wav, speaker="Hallr", source_unit_key="abc123")
payload = json.loads(marker_path(wav).read_text())
assert payload["source_unit_key"] == "abc123"
def test_sha256_verification_is_opt_in_and_detects_tampering(tmp_path: Path) -> None:
wav = tmp_path / "utt_0033.wav"
wav.write_bytes(b"RIFF-original")
write_marker("Same words.", wav, speaker="Narrator")
# Not verified by default: hashing every file on every validation pass is too slow.
assert is_current("Same words.", wav, speaker="Narrator") is True
assert is_current("Same words.", wav, speaker="Narrator", verify_sha256=True) is True
wav.write_bytes(b"RIFF-tampered")
assert is_current("Same words.", wav, speaker="Narrator") is True
assert is_current("Same words.", wav, speaker="Narrator", verify_sha256=True) is False