# =============================================================================
# FULL ACCEPTANCE RUN — end to end, every path the program has, one pass
# =============================================================================
#
#   How to run it:  Tools -> Run acceptance test...  (the app does the rest:
#                   session folder, sleep lock, the run, and one file to send)
#   Where it lives: INSIDE the app, since v0.6.32 — there is nothing to download.
#                   Source: src/platterpus/rig_scripts/fullacceptance.txt
#   What it costs:  4 to 6 hours. LEAVE IT RUNNING OVERNIGHT.
#                   It rips the whole disc TWICE (once fast, once with every
#                   track read at least twice) plus six short partial rips.
#
# NOTHING IN THIS FILE NEEDS EDITING. No album name, no track count, no path,
# and — as of this version — no cyanrip build tag either. Put any ordinary
# audio CD in the drive, start it, and go to bed.
#
# AND IT IS RE-RUNNABLE, which it was not before. Every `album` line carries
# `(run)`, expanding to this run's own timestamp, so a second run never lands on
# the first run's folders. Before that, a re-run raised "Album already ripped"
# on every rip; the file answers that prompt in exactly ONE place (§H, on
# purpose), so the other seven rips were refused behind an unanswered modal and
# the 2026-08-25 attempt lost sixteen of its seventeen failures — and all of T1 —
# to it. **You no longer need to move the previous run's output aside.**
#
# §F and §H still name the SAME album deliberately: §H exists to raise that
# prompt and answer it with `click=new`, and `(run)` is stable within one run so
# that collision still happens exactly where it is wanted.
#
# -----------------------------------------------------------------------------
# BEFORE YOU START — two things, and only two
# -----------------------------------------------------------------------------
# 1. Be on the newest Platterpus. Help -> Check for updates, or download the
#    AppImage from the releases page.
# 2. Be on the cyanrip build THIS Platterpus expects — which is **not** always
#    the newest one, and **not** always the one that installs without a warning.
#    Help -> Check for cyanrip updates..., and TAKE WHATEVER OFFER IT MAKES:
#    a plain one-click install if that is what appears, and the warned
#    "Install it anyway" if that is what appears instead. Either way, accept it.
#
#    Read that twice if you remember the old wording, because it said the
#    opposite. Until v0.6.36 this comment told you to take the offer ONLY if it
#    was a plain one-click install, and to refuse a warned one. That is right
#    BETWEEN handshake rounds and exactly wrong WHILE ONE IS OPEN — and a round
#    being open is the only time anybody re-reads this. Section A asserts the
#    build UNDER REVIEW, and a build under review is by definition one no closed
#    round has approved, so its offer is the warned one. Refusing it is what
#    ends the night at section A, four seconds in, having spent no drive time
#    and produced no evidence. It has now done that twice.
#
#    Yes, the warned build makes every rip report `unapproved`. That is the
#    record being honest about an open round, not a fault, and it is precisely
#    what the run exists to produce evidence for.
#
#    Do not reach for a channel toggle to decide this, and do not look for a
#    BUILD TAG in this comment: THIS FILE NAMES NO BUILD, on purpose. Which one
#    is wanted changes every time a handshake round opens or closes, and this
#    file ships inside a release — so anything written here freezes on the day
#    it was built and cannot learn that the answer moved. Both previous attempts
#    were wrong within days, and each one sent operators to a build section A
#    refuses. The app holds that fact in one place and checks it — which is why
#    step 2 above is the whole answer, and there is no second copy of it here.
#
#    The rule ABOVE was the same failure one level up: not a frozen build tag,
#    but a frozen answer to "which offer is the right one", which moves for the
#    same reason and just as often. "Take whichever one it offers" is the form
#    that cannot go stale, because it delegates to the surface that knows.
#
#    Section A asserts the exact expected build and STOPS THE RUN in the first
#    few seconds if you are not on it — before any drive time is spent.
#
#    That stop is real as of this version. It used to be a promise this file
#    could not keep: nothing but `abort` ends a batch and this file never used
#    it, so a wrong ripper produced a FAIL on line ~20 and then six hours of
#    evidence about the wrong binary. The cyanrip fork read the old sentence and
#    relayed it to the operator (round 14 lap 11 §J7). `abort-if-failed` below is
#    what makes it true.
#
# Everything else is in this file.
#
# -----------------------------------------------------------------------------
# WHY THE ORDER IS WHAT IT IS
# -----------------------------------------------------------------------------
# Maintainer directive: *"fresh start, rip, every test there is, all of them.
# this needs to be a good pass fail test"* — the gate on 0.7.100. And KDD-35: a
# version number is a claim about the field, not about CI. Every defect that
# mattered in August was found on hardware with the suite green throughout.
#
# LEAST-LIKELY-TO-FAIL FIRST, deliberately, and it has a cost. Sections A-E are
# near-certain passes that take about five minutes; the first rip is section F.
# Putting the cheap checks first means a broken build or a wrong ripper is
# caught before hours of drive time, and the transcript reads as a widening
# cone — identity, then settings, then validation, then UI, then disc, then
# audio, then the derived formats, then the long one.
#
# The cost: if section F fails, A-E having passed tells you almost nothing about
# why. Accepted. The alternative spends the night before learning the ripper was
# not installed.
#
# THE RULE THAT MAKES THIS SAFE TO LEAVE UNATTENDED: a failing step does NOT
# stop the batch. Only `abort` does, and this file never uses it. Every check
# below fails loudly and the run continues. A run that stops at the first
# problem hides every problem behind it, and a disc pass costs hours you do not
# get back.
#
# -----------------------------------------------------------------------------
# WHAT THIS RUN IS FOR: cyanrip handshake round 14, close condition CC-2
# -----------------------------------------------------------------------------
# CC-2 is the round's ONLY close condition: *one hardware acceptance pass on the
# RELEASED pair* — the cyanrip beta the round is reviewing, against the current
# Platterpus release — exercising the fork's round-14 lap 1 §T list.
#
# Round 13's version of CC-2 measured a mid-round TEST PIN while the release
# would necessarily be a later commit, so satisfying it would have closed a
# round on evidence about a build nobody installs. This one tests what ships.
#
#   §T1  a `-Z` rip that GENUINELY re-reads, and keep the log     -> section N
#   §T2  `-T unicode` end to end on a title carrying `<` and `:`  -> sections F, H
#   §T3  `-x -I`, the probe-only cache invocation                 -> section P
#   §T4  an interrupted rip, on hardware                          -> section I
#   §T5  an Enhanced CD, if one turns up                          -> not scripted
#
# T5 is deliberately absent: it needs a disc we may not own, the fork says it is
# not a blocker, and "no such disc available" is a different claim from "none".
#
# -----------------------------------------------------------------------------
# WHAT THIS RUN CANNOT ASSERT — read the transcript and the bundle for these
# -----------------------------------------------------------------------------
# Stated up front rather than buried, because a verdict implying more than it
# checked is worse than a shorter one.
#
#   * THAT THE AUDIO IS BIT-PERFECT. `wait-for-rip` waits for the worker to
#     disappear; it does not grade the rip. AccurateRip and CTDB verdicts are in
#     the report and the log — `rig-check` parses them and the bundle carries
#     both. Read them.
#   * WHETHER DIALOG TEXT IS CLIPPED. A rendering fact at your font size and DPI
#     that no assertion can see. Section D takes screenshots; a person must look.
#   * OVERREAD (`-O`). It has run on the BDR-209D and it HUNG THE DRIVE ~23
#     minutes. Never enabled here; section Q asserts it is still off.
#   * `-f` READ-OFFSET AUTODETECTION. Never run on this rig.
#   * C2 ERROR REPORTING. This drive reports it unsupported, so a green run is
#     not evidence about C2.
#   * DAMAGED MEDIA, and therefore paranoia's actual error correction.
#   * A NON-ZERO `Read stalls:` COUNT. A silent watchdog is not a working
#     watchdog; healthy media cannot produce the other branch.
#   * THE WELL-FORMED ENHANCED CD branch. Exercised by nothing, anywhere.
# -----------------------------------------------------------------------------

log =============================================================
log FULL ACCEPTANCE RUN - end to end, one pass
log order: cheapest and least likely to fail first
log the first rip is section F; the long one is section N
log =============================================================

# Debug logging ON for the whole run. A defect found at 4am is only as
# diagnosable as the log, and this is the one setting that changes how much of
# the run is recoverable afterwards. Restored in section Q.
set debug_logging on
expect debug_logging on
snapshot atstart

# --- A. IDENTITY: which binary is about to be graded -----------------------
# FIRST, always. Every claim below is about a specific build, and a result that
# looks wrong must be attributable rather than guessed at. A build tag we do not
# recognise reads as "not determined", never as a pass.
#
# This also arms the `(ripper)` placeholder used by the album titles further
# down: it expands to the installed build tag, read from the banner captured
# here. If this step is removed, those `album` steps FAIL and say so rather than
# writing the literal text — an unexpanded placeholder would give two rips the
# same folder while looking like it worked.
#
# `expect-ripper-under-review` TAKES NO ARGUMENT, and that is the fix for a
# defect that recurred three times in two days. This file used to name an exact
# build tag; the fork then published two more betas on the channel our own
# installer resolves, so an operator who followed our instructions installed the
# build we sent them to and was told here that it was wrong. The verb now reads
# the constant the handshake record derives, so a pin move fails in CI instead
# of at 2am on your rig.

log --- A. identity: which ripper is installed ---
cyanrip --version
expect-exit 0
expect-cyanrip platterpus-fork
expect-ripper-under-review
snapshot identity

# WHICH LINK IN THE RIPPER CHAIN FAILS TO EXIT — gathered, never asserted.
#
# Two consecutive rig mornings (2026-08-26, 2026-08-27) produced ZERO rips and
# neither failed at ripping: both ended mid-probe with `cyanrip --version`
# printing its banner in full and then not returning, until a 60s timeout killed
# it. The cyanrip fork showed three independent ways the hang is not cyanrip and
# asked for three shell commands to be run by hand. This is those commands.
#
# It records `info` and moves on. A wrapper that hangs from an interactive shell
# does not affect this run — the app pipes its I/O — so failing here would abort
# a six-hour pass over something that changes no rip.
probe-ripper-wrapper

# THE ONE PLACE THIS FILE IS ALLOWED TO STOP, and the distinction is the point.
#
# The rule above — "a failing step does NOT stop the batch" — is about FINDINGS,
# and it is right: a run that halts on the first problem hides every problem
# behind it. It is the wrong rule for a PRECONDITION. A wrong ripper does not
# make the next six hours partially useful; it makes them evidence about a
# different subject, which is exactly what the handshake exists to prevent
# (two artifacts from the same ripper under different app versions are not
# interchangeable evidence).
#
# So: preconditions abort, findings do not. Nothing below this line uses it.
abort-if-failed the installed ripper is not the build the handshake record names — fix that first, the failing step above prints the one command

# --- B. SETTINGS VALIDATION: the cheapest real check in the program --------
# Pure round-trips through the REAL validator, which is the source of truth — a
# spin box's own range is a convenience, not the validation (CLAUDE.md: validate
# every input, visibly and to the log).
#
# Every `set` here is a value we then read back. A silent coercion would show up
# as an `expect` failure rather than as a wrong rip hours later.
#
# 667 IS THIS DRIVE'S TRUE READ OFFSET, not an arbitrary test value — so this is
# a guard and section Q is right not to restore it. The Pioneer BDR-209D is +667
# from three independent places: the bundled AccurateRip drive table (whose
# regeneration script REFUSES to write unless the BDR-209D=+667 sentinel still
# passes), `docs/hardware-test-checklist.md` (*"confirmed, two independent
# sources agree"*), and a rip verified byte-identical against the EAC baseline on
# 12 of 14 tracks. Said out loud because the cyanrip fork asked (round 14 lap 3
# §C6) and could not tell a guard from a mistake by reading it.
#
# ON ANY OTHER DRIVE, change this to that drive's offset before running.

log --- B. settings: validated round-trips ---
set read_offset 667
expect read_offset 667
set max_retries 3
expect max_retries 3
set output_format flac
expect output_format flac
set force_overread off
expect force_overread off
set auto_eject_after_rip off
expect auto_eject_after_rip off
set ripper_channel beta
expect ripper_channel beta
snapshot settingsafter

# --- C. VALIDATION REFUSALS: proving the guards actually fire ---------------
# The half of validation nothing has ever tested on hardware. `expect-refused`
# asserts the pure validator REJECTS a value **and leaves the setting
# unchanged** — both halves, because a guard that reports a refusal and writes
# the value anyway is worse than no guard: the log says the input was rejected
# while the setting still reaches cyanrip's argv.
#
# These are the numbers that become command-line arguments. A read offset out of
# range rips every subsequent disc wrong with a clean-looking log, which is
# exactly why the range exists and why it is worth one second to prove it holds.

log --- C. validation: every guard must refuse and not write ---
expect-refused read_offset 99999
expect-refused read_offset -99999
expect-refused max_retries 101
expect-refused secure_rerip_matches 11
expect-refused mp3_vbr_quality 10
# And the floor: the guards must not refuse everything. If these were also
# refused, every assertion above would pass for the wrong reason.
expect read_offset 667
expect max_retries 3
snapshot validationdone

# --- D. DIALOGS: everything that can be opened, opened and closed ----------
# Not assertions about text — assertions that opening and closing a dialog does
# not crash, hang, or leave one on screen. `expect-dialog none` at the end is the
# one that catches a dialog that failed to close, which is how a modal comes to
# swallow every later step.
#
# Screenshots for the dense ones (maintainer: "it is a lot"). Every dialog is
# still OPENED — that is the part that can crash.

log --- D. dialogs: open, close, and prove none is left up ---
open drive
screenshot dialogdrive
cancel
open settings
screenshot dialogsettings
cancel
open dependencies
screenshot dialogdependencies
cancel
open about
cancel
open diagnostics
screenshot dialogdiagnostics
cancel
open guide
cancel
open setup
cancel
snapshot dialogsdone

# NO `expect-dialog none` HERE, and the omission is the fix rather than a gap.
#
# **Measured, 2026-08-25 (run 3).** With a disc in the drive, the app identifies
# it at launch and OPENS THE RELEASE PICKER BY ITSELF — before this script
# reaches section D at all. So the picker sits underneath the whole dialog
# section, and an `expect-dialog none` here reported it, correctly, as a
# failure: *"a dialog is open: 'Pick a MusicBrainz release'"*. The assertion was
# true; the expectation was wrong.
#
# The script cannot assert an empty screen before it has answered the picker,
# because having a disc in the drive is a PRECONDITION of this whole run. So the
# assertion moves to just after `pick-release`, where it says something real:
# the picker we answered is gone and nothing else was left behind it.

# --- E. DISC IDENTIFICATION -------------------------------------------------
# The last cheap section. If this fails, nothing after it can mean anything, and
# you have spent five minutes rather than a night finding out.

log --- E. disc: scan and identify ---
rescan
pick-release 1 120
expect-dialog none
expect-tracks 2+
snapshot discidentified

# STOP HERE IF THE DISC WAS NOT IDENTIFIED. The section header above has said
# "if this fails, nothing after it can mean anything" since the file was written,
# and until 2026-08-26 it said so and then carried on for six hours — a comment
# where a check belongs, which is the failure `CLAUDE.md` names by that phrase.
#
# What it costs when the check is absent, measured on the rig the same night: the
# release picker was still open at section F's `rip`, the guard correctly refused
# to press Start behind a modal, and the operator had to answer the picker BY HAND
# to unblock a run whose entire purpose is being unattended. Every rip after that
# point is evidence about a release nobody scripted.
#
# `pick-release` already refuses to pass on a picker that never appeared unless
# tracks are loaded, so a FAIL here is a real one: either the picker did not
# resolve inside its 120 s, or the disc loaded no tracks. Both mean the night is
# already lost — five minutes in, which is the whole point of putting the cheap
# sections first.
abort-if-failed the disc was never identified — the picker did not resolve or no tracks loaded, so every rip after this would be about an unknown release

# --- F. THE MAIN EVENT: a full-disc rip ------------------------------------
# ALL tracks, once, FLAC, fast-verified. This is the archival rip and the one
# whose artifacts matter most.
#
# THE TITLE CARRIES A COLON AND A '<', BOTH DELIBERATE — this is §T2.
#
#   * The COLON is the only thing that exercises the tag escape.
#     `_escape_meta_value` sends a literal ':' to cyanrip as '\:'. A safety net
#     reverses any leftover '∶' (U+2236 RATIO) in the written tags, armed ONLY
#     when the metadata actually contains a colon — so before 2026-08-20 that
#     gate was False on every scripted rig rip and the escape had never once run
#     on hardware.
#     WHAT TO LOOK FOR: this album's tag must read with a REAL colon. A '∶'
#     means the escape did not survive. The FOLDER name is expected to differ —
#     we now pass `-T unicode` and the fork's measured table says the folder
#     becomes `full acceptance∶ angle‹bracket`.
#
#   * The '<' exercises the PlainText fix. Three QMessageBox surfaces rendered
#     external text as HTML, so a '<' in an album-derived string was parsed as an
#     unknown tag and EVERYTHING AFTER IT WAS SILENTLY DROPPED. The surface that
#     names this folder is the overwrite prompt in section H.
#
# Cover art, CTDB verify, FLAC verify and the EAC-compatible log are all turned
# ON for this rip. They are the post-rip subsystem and nothing else in this file
# reaches all four at once.
#
# THE TIMEOUT COVERS A SECURE RE-READ, because this rip does one.
#
# It said three hours, reasoning that "a full disc on this hardware is 50-70
# minutes and one real session measured 2h45m". Both figures are real and the
# conclusion was wrong: they describe a rip WITHOUT the secure re-read, and this
# rip has one. `secure_rerip_matches` defaults to 2 (config.py), so cyanrip is
# invoked `-Z 2 -r 3` here exactly as in section N — the same work, budgeted at
# a third of the time.
#
# Measured 2026-09-03: it timed out at 10800.1s with the status reading
# "Re-ripping track 5 to secure it - 43% - about 1m 50s left in re-read 2".
# Roughly three hours and two minutes for work budgeted at three hours, and the
# four failures that followed were all this one cap: the status was not 'Done'
# because the rip was still running, the next `rip` collided with it, and the
# overwrite dialog never came.
#
# So it matches section N's budget, because it is section N's workload.

log --- F. the main event: full-disc rip, all tracks, every post-rip check on ---
set cover_art embed
expect cover_art embed
set save_additional_art on
set ctdb_verify_after_rip on
set verify_flac_after_rip on
set write_eac_log_after_rip on
expect write_eac_log_after_rip on
select-tracks all
album full acceptance: angle<bracket (run) (ripper)
album-artist Platterpus Acceptance
rip
wait-for-rip 21600
snapshot afterfullrip
screenshot afterfullrip
# 'Done' is measured, not guessed: the status line after a clean rip reads
# 'Done - all N tracks ripped cleanly, no read errors. AccurateRip: ...'.
# Matching one disc-agnostic word keeps this working on any CD.
expect-status Done

# --- G. POST-RIP VERIFICATION ----------------------------------------------
# `rig-check` is the seam check the cyanrip fork asked for, reachable both as a
# script verb and as `--rig-check` so both projects append to one manifest. With
# no argument it DISCOVERS the album folder, which is why it can run here without
# this file knowing a path.
#
# It composes a real rip's argv, runs it against a device that cannot open, reads
# `invocation` back out of cyanrip's own `-j` record, parses the rip's log, reads
# the handshake note, and reports the paranoia counters and any `Interrupted at:`
# line. SKIP means "did not run" and is not a pass — that distinction is the
# whole point of its status vocabulary.
#
# It also exercises a non-zero exit with a column-0 diagnostic and a complete
# `-j` record, which the fork's lap 3 §C4 pointed out is a real path this
# already covers.

log --- G. post-rip: the seam check, and the rip's own log ---
rig-check
snapshot afterrigcheck

# --- H. RE-RIP ONTO THE SAME FOLDER: the overwrite prompt ------------------
# The title is BYTE-FOR-BYTE section F's, on purpose. Same string in, same folder
# out, so this rip collides and the "Album already ripped" prompt actually fires
# — which is the only way the PlainText fix gets exercised on hardware, and the
# only test of the guard that resolves the predicted folder against what is on
# disk. That guard exists because a two-track rip once silently overwrote a
# finished 14-track archival rip.
#
# WHAT TO LOOK FOR if you are watching: the prompt must name the folder IN FULL.
# The word after the '<' is the part that used to vanish.
#
# `click=new`, NOT `ok`. `ok` calls accept(), and accept() on a QMessageBox built
# with addButton leaves clickedButton() as None — so the caller falls through to
# its Cancel branch and the rip is CANCELLED while the transcript says
# "accepted". "Rip to a new folder" rather than "Replace" so section F's audio
# survives; it also exercises free_album_folder_templates, which nothing else
# here reaches.

log --- H. re-rip the same title: the overwrite prompt must fire ---
select-tracks 1-2
album full acceptance: angle<bracket (run) (ripper)
rip
answer-dialog click=new 120 Album already ripped
wait-for-rip 3600
snapshot afteroverwrite
screenshot afteroverwrite

# --- I. THE CANCEL PATH, AND THE ONLY HONEST PROOF OF IT — §T4 -------------
# Cancel mid-track, not at a boundary — 90 seconds of reading gets us inside one.
# Then give the escalation its full SIGTERM-to-SIGKILL window before asking
# anything.
#
# A different album title here on purpose: this section is about the drive, and a
# collision would add a dialog that has nothing to do with what is being tested.
#
# The `rig-check` after it is taken HERE and not later, because `rig-check` reads
# the NEWEST rip and section J is about to make a newer one. `parser/interrupted`
# reports cyanrip's own `Interrupted at:` line — the field the fork added at our
# round-12 ask, which we parsed for a round and never put in an artifact anyone
# sends. It is an INFO row, not a pass/fail: a cancel that lands between tracks
# legitimately produces "between tracks, no read in progress", and grading that
# would turn drive timing into a verdict.

log --- I. cancel a rip in flight ---
select-tracks 1-3
album cancel me (run) (ripper)
rip
log reading for 90s so the cancel lands mid-track
wait 90
snapshot beforecancel
cancel-rip
log cancel issued; giving the escalation its full window
wait 30
# The cancelled line reads 'Rip cancelled by user. Partial files may remain.'
expect-status cancelled
snapshot aftercancel
screenshot aftercancel
rig-check
snapshot aftercancelrigcheck

# --- J. THE DRIVE-OPEN PROOF: can we rip again? ----------------------------
# If this succeeds, the cancel released the reader. If it hangs or cannot
# identify the disc, it did not — and THAT is the finding, recorded here rather
# than in somebody's memory of a session.
#
# THE TRAY SHOULD STILL BE CLOSED. Before v0.6.16 a cancel left a 5-second rescue
# timer armed even when the reader had already stopped, so the drive was
# force-ejected seconds after every successful cancel — which made this section
# unanswerable in both directions. If the tray is open when you look, you are on
# an older build and this section proves nothing.

log --- J. drive-open proof: identify and rip again after the cancel ---
rescan
pick-release 1 120
expect-tracks 2+
select-tracks 1-2
album after cancel (run) (ripper)
rip
wait-for-rip 3600
snapshot afterrecovery
screenshot afterrecovery
rig-check

# --- K. THE DERIVED FORMATS — the whole of Critical rule #4 ----------------
# **Nothing has ever tested this on hardware.** FLAC is the archival master and
# MP3, WavPack and WAV are DERIVED from it by the single post-rip transcode
# adapter. Every rip still produces FLAC first; selecting another format keeps
# that FLAC and derives the chosen one from it.
#
# So each of these three sections proves a different thing:
#
#   * MP3      — lossy by design, best-practice VBR, and the ONLY one with a
#                quality knob. `mp3_vbr_quality 2` is a real VBR setting, not the
#                default, so a knob that reaches nothing would show up.
#   * WavPack  — the second lossless format. Tags and art survive.
#   * WAV      — raw PCM, NO tags and NO art. The UI warns about this and the
#                warning is the point: a format that silently dropped metadata
#                without saying so is the defect.
#
# Two tracks each. The transcode path is per-file and a third track adds a row,
# not a discriminator.
#
# A DIFFERENT ALBUM TITLE PER FORMAT, so the three land in three folders and
# none of them collides with section F's archival master.

log --- K1. MP3: the lossy derived output, with a real VBR quality ---
set output_format mp3
expect output_format mp3
set mp3_vbr_quality 2
expect mp3_vbr_quality 2
select-tracks 1-2
album derived mp3 (run) (ripper)
rip
wait-for-rip 3600
snapshot aftermp3
screenshot aftermp3
rig-check

log --- K2. WavPack: the second lossless format ---
set output_format wavpack
expect output_format wavpack
select-tracks 1-2
album derived wavpack (run) (ripper)
rip
wait-for-rip 3600
snapshot afterwavpack
rig-check

log --- K3. WAV: raw PCM, no tags, no art - the UI must say so ---
set output_format wav
expect output_format wav
select-tracks 1-2
album derived wav (run) (ripper)
rip
wait-for-rip 3600
snapshot afterwav
screenshot afterwav
rig-check

log --- K4. back to FLAC, the archival master ---
set output_format flac
expect output_format flac

# --- L. THE GOAL PRESETS: a label must mean what it says -------------------
# A goal is not a setting; it is a NAME for a set of them. Selecting one applies
# the whole preset, and this section proves the label and the settings agree —
# because a goal that wrote only its own name would leave the app ripping with
# exactly the settings the user was trying not to use, under a label promising
# otherwise. "Archival Exact" was byte-identical to "Fast Verified" until
# v0.6.24 for a related reason.
#
# No drive time: pure settings round-trips through the real preset code.

log --- L. goal presets: selecting a goal applies all of it ---
set rip_goal archival
expect rip_goal archival
expect secure_rerip_dynamic off
expect rerip_offset_variant on
set rip_goal portable
expect rip_goal portable
expect output_format mp3
set rip_goal fast_verified
expect rip_goal fast_verified
expect secure_rerip_dynamic on
expect output_format flac
snapshot goalsdone

# --- M. NAMING TEMPLATES ----------------------------------------------------
# The templates decide where every file lands, so a template that silently fails
# to round-trip is a library-wide defect. Pure validation, no drive time — the
# rip in section N runs on the restored default.

log --- M. naming templates round-trip through the validator ---
set track_template %A/%d/%t - %n
expect track_template %A/%d/%t - %n
set disc_template %A/%d/%d
expect disc_template %A/%d/%d
snapshot templatesdone

# --- N. §T1: A SECURE RE-READ THAT GENUINELY RE-READS — THE LONG ONE -------
# The fork's most-wanted test, and the one the whole round-13 paranoia argument
# turned on. **This is the section to leave running overnight.**
#
# WHY IT NEEDS ITS OWN SECTION. Under `-Z`, a track's own paranoia counter is the
# LAST pass while the disc block sums EVERY pass, so the two are equal exactly
# when each track was read once — and a clean disc in DYNAMIC mode converges on
# the first read, which is what every other rip in this file does. Every artifact
# either project has ever checked that claim against had that property, which is
# why a false invariant survived five handshake rounds: it is arithmetically
# forced in the only case anyone ever measured.
#
# `secure_rerip_dynamic off` is UNIFORM mode — EAC-style Test & Copy, every track
# read until two reads agree, not only the tracks AccurateRip could not confirm.
# So `total_repeats > 1` on every track regardless of how clean the disc is, and
# the fork's `Scope:` line is printed.
#
# WHAT THE INVARIANT IS, AND WHAT IT IS NOT. The property is an INEQUALITY:
#
#     sum(per-track counters)  <=  disc-level total
#
# with equality exactly when every track was read once. The tempting form —
# `disc == passes x sum` — holds on the fork's synthetic fixture BY CONSTRUCTION,
# because every pass there does identical work, and it will NOT hold on media,
# where re-reads exist precisely when passes differ. `rig-check` grades the `<=`
# and reports the multiple as an observation. Nothing here asserts the ratio.
#
# THE WHOLE DISC, not two tracks. The fork said two tracks is sufficient for the
# inequality and they are right — but they also said the interesting case is *a
# track that needed three or more reads*, and that is a property of the disc, not
# of the selection. Ripping every track is the only way to give the disc a
# chance to produce one. It costs about two hours and this run has all night.
#
# Six hours of timeout because uniform mode roughly doubles the read and this
# rig has measured 2h45m for a single dynamic pass.

log --- N. T1: uniform secure re-read, WHOLE DISC, so the counters actually move ---
set rip_goal archival
expect secure_rerip_dynamic off
expect rerip_offset_variant on
set secure_rerip_matches 2
expect secure_rerip_matches 2
rescan
pick-release 1 120
expect-tracks 2+
select-tracks all
album secure reread (run) (ripper)
rip
wait-for-rip 21600
snapshot aftersecurereread
screenshot aftersecurereread
expect-status Done
rig-check

# --- P. §T3: THE CACHE PROBE, PROBE-ONLY ------------------------------------
# LAST OF THE DRIVE WORK, DELIBERATELY. `-x` alone has form: measured once on
# this rig (32 sectors, 73.5 KiB, uncached read 362.6 ms, 2026-08-19) and then it
# ripped the whole disc, ETA 1h 3m, leaving the drive held. `-x` is a MODIFIER;
# `-x -I` is the probe-only invocation and writes no audio — the fork states that
# in their round-13 lap 5 and round-14 lap 1 §T3, and their lap 3 §C3 confirms
# they CANNOT promise it returns the drive, because nothing in their suite has
# ever executed a single timed read (on an image the probe refuses before doing
# anything).
#
# So it is real, it is theirs to be right about, they asked for it, and it goes
# after every rip in this file: if it does hold the drive, it costs the tail of
# the run and not the rip evidence. A hang here is the finding.
#
# `-N` is present because the script sanitiser requires it of any non-probe
# invocation and it is right to — without it cyanrip runs its own MusicBrainz
# lookup and can block on a prompt with no terminal attached, which is the
# unattended hang this whole feature exists to prevent.
#
# ASSERT THE FIELD NAME, NOT THE VALUE, AND THAT IS DELIBERATE. The build under
# review REMOVED the old `%i sectors measured (…)` wording — it claimed a
# precision the method does not have. The value is now one of five forms, and
# exactly ONE is ever emitted (they are arms of a switch, each writing the whole
# buffer):
#
#     Cache probe:    %i to %i sectors (…)                    a range
#     Cache probe:    at least %i sectors, upper bound unknown (…)
#     Cache probe:    no readback cache measured (…)          measured, found none
#     Cache probe:    unknown (read failed at %i sectors, …)  could not measure
#     Cache probe:    unknown (read could not be timed …)     could not measure
#
# `no readback cache measured` and `unknown (…)` are NOT the same claim — the
# first is a measurement that found nothing, the second a measurement that could
# not be taken. On an image the line reads `not run (disc image has no drive
# cache)`, so its ABSENCE here is the first sign the probe really ran on metal.
# A script asserting any one value would fail on a correct probe.
#
# WHERE THIS EVIDENCE LANDS: the script report and the transcript, NOT the
# rig-check manifest. `-x` is not in the rip argv builder at all, so no
# Platterpus rip ever probes and no rip log can carry the line. The `cyanrip`
# verb records the exact argv, the exit code and the complete output for this
# step, which is a stronger record than a manifest row would be.

log --- P. T3: the fork's cache probe, probe-only ---
cyanrip -N -x -I
expect-exit 0
expect-cyanrip Cache probe
snapshot aftercacheprobe

# --- P2. C1: THE THIRTY-MINUTE HANG, DETECTED (NOT DIAGNOSED) ---------------
# The fork asked for this in round 14 lap 11 §K2, and the split of labour is the
# point: THIS STEP DETECTS, their `rig-c1-probe.sh` EXPLAINS. Only run their
# probe if this step hangs; if it does not hang, that is itself worth knowing —
# it says the hang is not unconditional, which no disc-image fixture could ever
# establish.
#
# WHAT IT PROVOKES. With no `-s`, cyanrip reads the TOC and refuses for want of
# a read offset. On the 2026-08-25 rig session that refusal wrote its `-j`
# diagnostics record fourteen seconds in — so the process decided to fail and ran
# its exit path all the way to completion — and then stayed alive for roughly
# THIRTY MORE MINUTES holding the drive, needing SIGKILL. Cause not determined,
# by either project. The branch is gated on a DRIVE CAPABILITY that image drivers
# do not report, so it is unreachable from every fixture either side has: it
# needs a real drive, and this is the cheapest place to put one in that state.
#
# WHY IT IS SAFE TO HAVE IN AN UNATTENDED RUN. The `cyanrip` verb is bounded —
# 300 s, then the child is killed, then a further 20 s before the runner stops
# waiting and reports an UNREAPABLE CHILD with a null exit code rather than 0.
# A hang here therefore costs five minutes and is recorded as a finding; it
# cannot eat the night. That bound is the answer to the fork's §J6, which asked
# before agreeing this step should exist at all.
#
# LAST OF THE DRIVE WORK, on §P's reasoning exactly: if it holds the drive it
# costs the tail of the run and not the rip evidence.
#
# `-l 1` is insurance, not intent. If the refusal does NOT fire on this drive,
# cyanrip would otherwise start ripping; limiting it to one track bounds the
# damage to something the verb timeout can end. In that case `expect-exit 1`
# fails and says so, which is the correct result — "it did not refuse" is a real
# finding about the rig, not a broken step.
#
# NO `-j` HERE, and the reason is worth recording rather than leaving as an
# omission. The fork's §K2 suggested one, to get cyanrip's own record as a
# channel independent of the capture. Our script language has no path
# placeholder, so a `-j` would need a hard-coded absolute path in a file whose
# whole promise is that nothing in it needs editing. It is also less necessary
# here than in their probe: this verb captures through `run_capture` — a pipe we
# drain — and not through the shell redirect whose contents went missing in
# `rig_session.sh` step 5b. Different channel, so an empty capture here would be
# a NEW finding rather than a repeat of that one.

log --- P2. C1: does a no-offset refusal hang the drive? ---
cyanrip -N -l 1
expect-exit 1
expect-cyanrip Offset is unset
snapshot afterc1

# --- Q. LEAVE THE RIG AS WE FOUND IT ---------------------------------------
# Restoring is not tidiness. Uniform secure re-read doubles every future rip on
# this machine, MP3 would make the next rip lossy, and a setting a test left
# behind is a setting nobody chose.
#
# The overread assertion is a GUARD, not a restore: this script never enables it,
# so if `force_overread` is on here something else turned it on, and that is
# worth knowing before the next disc — it reaches cyanrip's argv and on this
# drive it hangs the read.
#
# The read offset is NOT restored, because 667 is this drive's true offset (see
# section B) and putting it back to 0 would be the mis-configuration.

log --- Q. restoring what this run changed ---
set rip_goal fast_verified
expect rip_goal fast_verified
expect secure_rerip_dynamic on
set output_format flac
expect output_format flac
set mp3_vbr_quality 0
set write_eac_log_after_rip off
set debug_logging off
expect debug_logging off
expect force_overread off
expect read_offset 667
expect-dialog none
snapshot atend

log =============================================================
log DONE. There is nothing left for you to run.
log Platterpus packs this run into ONE .tar.gz and names it in a
log dialog when the window comes back - with a button that opens
log the folder. Send that file. It carries the transcript, the app
log log, the settings, the screenshots and every rip folder's text
log artifacts (logs, cue sheets, reports, checksums). No audio.
log If the dialog is gone, the same path is on the SEND THIS ONE FILE
log line in the app log.
log =============================================================
# WHY THIS DOES NOT NAME A FOLDER. It used to say the bundle was under
# `~/.local/share/platterpus/bundles/` and to then run `--rig-session`. Both were
# wrong after v0.6.32 moved the session into the app: the deliverable lands in
# `~/Downloads` (or `$HOME` when there is no Downloads folder), and there is no
# second command. Two surfaces answering "where is my file?" with different
# answers is `docs/testing.md` §5.al, and the one a tired operator reads at 7am
# was the stale one. So this text now points at the surface that COMPUTES the
# answer instead of restating it - the dialog cannot drift from the path it was
# handed.
