personal memory agent

Prove filtered raw audio deletions master

Every intentional raw-audio deletion now leaves terminal proof it was processed. Previously, the empty-statements filter path in process_audio and the VAD no-speech path in _process_one could unlink raw audio without writing any .jsonl, leaving neither media nor evidence. Both empty paths now hoist _write_empty_processing_jsonl(...) above the preserve/filter branch. The terminal solstone.processing.v1 record is written unconditionally on the empty path with state=empty, reason_code=no_decodable_audio, handler=transcribe, and input_size from the raw byte size; the branch then only decides outcome, log text, and whether to unlink. Write-before-unlink is structural, not a convention. is_salient no longer gates anything in main.py. Once the non-salient branch also writes the marker with available sound_tags, salient and non-salient branches produce the same record and differ only in log text, so the collapsed branches drop the main.py import. The helper remains in sound_tags.py and stays independently tested. This keeps computed sound_tags instead of discarding data that has no UI consumer. Failure semantics are unchanged: the marker write is not wrapped in try/except. If it fails, raw audio stays on disk and the run reports honestly: process_audio sets outcome=failed and exits, while _process_one propagates so --all counts the file failed. No unproved deletion is created. Unchanged: preserve_all behavior, salient-sound record contents, observe.transcribed telemetry outcome names/fields/timing stages, the deferral path, and the observer protocol. Known residual: segments filtered before this change with raw already gone and no .jsonl remain unproved and need a product decision. Update tests that locked in the old no-JSONL behavior and rename misleading test names. Add a parametrized invariant test that every successful raw-deletion branch leaves raw absent plus a one-line same-stem .jsonl pinned via processing_record constants and recognized by read_processing_record and derive_modality_state as DataState.EMPTY. test_transcribe_cli.py now uses a real VadResult because the empty path always serializes it.