# =============================================================================
# RIG TEST — the cancel path, the completed re-rip, and the PlainText fix
# =============================================================================
#
#   How to run it:  ./platterpus-x86_64.AppImage --run-script rigcancelandoverread.txt
#   Where it lives: rigcancelandoverread.txt
#
# NOTHING IN THIS FILE NEEDS EDITING. No album name, no track count, no path.
# Put any ordinary audio CD in the drive and run it. Everything disc-specific is
# either discovered or expressed as "the first few tracks", so the same file
# works on the next disc and on somebody else's.
#
# -----------------------------------------------------------------------------
# READ THIS BEFORE CHANGING ANYTHING — two flag corrections, 2026-08-18 and -19
# -----------------------------------------------------------------------------
# CORRECTION 1 (2026-08-18). This script USED to turn the Overread setting on and
# rip with it, under the heading "force overread has never run on a real drive".
# Every part of that was wrong, and running it would have cost a session:
#
#   * Overread is `-O`. It HAS run on the BDR-209D, on 2026-07-22, and it HUNG
#     THE DRIVE for ~23 minutes — 13 of 14 tracks ripped perfectly, then the last
#     track's lead-out froze the progress bar near 100%. Turning it back on here
#     would deliberately re-trigger a known hardware hang.
#   * The flag that had never run on any drive, by anyone, was the FORK'S CACHE
#     PROBE — also spelled `-x`, which is where the confusion came from. It is a
#     different flag doing a different thing.
#   * A third `-x` exists in the fossil record: whipper's `--force-overread`.
#     That is where the mix-up originated. whipper has been gone since KDD-18.
#
# `docs/dependency-contracts.md` carries the hazard table and calls this
# confusion "a hardware hazard rather than a documentation nit". It was right.
# Overread is never enabled by this script at all.
#
# CORRECTION 2 (2026-08-19) — AND IT CORRECTS CORRECTION 1. Correction 1 closed
# by saying the cache probe "costs SECONDS", and section C was written on that
# claim. IT IS MEASURED FALSE. `cyanrip -x -N -s 0` on the BDR-209D printed its
# measurement — `32 sectors, 73.5 KiB, uncached read 362.6 ms` — and then WENT ON
# TO RIP THE WHOLE DISC, ETA 1h 3m. The verb's five-minute ceiling killed it at
# 300s and the child could not be reaped (`exit: null`), so the drive stayed held
# for the rest of the run and the two rips that follow had no clean drive to use.
# That is the most likely reason round 1's evidence came back unusable.
#
# Note what happened there: a correction was applied with less scrutiny than the
# claim it replaced (`CLAUDE.md` names that exact failure). Correction 1 was right
# about `-O` vs `-x` and guessed about the cost, and the guess is the half that
# cost a session. So the number above is quoted with its source: it is off the
# 2026-08-19 rig transcript, not from anyone's reading of the fork's source.
#
# WHAT THIS FILE PROVES, then:
#
#   1. The drive-open fix — cancelling a rip must release the reader, not leave
#      the drive held by a process nobody can see.
#
# That is hardware-only. No amount of suite is evidence for it.
#
# The cache probe is NO LONGER RUN HERE. Its first-ever execution succeeded and
# the number is recorded in section C; re-running it costs five minutes, holds
# the drive afterwards, and writes a partial album into whatever directory the
# app was launched from. It comes back when the fork ships a build whose `-x`
# exits after measuring — which is now an ask on them, not a gap here.
#
# THE RULE THAT MAKES THIS SAFE TO LEAVE ALONE: a failing step does NOT stop the
# batch; only `abort` does. So every check below fails loudly and keeps going. A
# run that stops at the first problem hides every problem behind it, and a disc
# pass costs an hour you do not get back.
#
# WHAT IT COSTS: roughly 20-30 minutes. It rips a handful of tracks twice, never
# the whole disc.
# -----------------------------------------------------------------------------

log =============================================================
log RIG TEST: the cancel path, the completed re-rip, and the PlainText fix
log =============================================================
snapshot atstart

# --- A. Identity, before anything is measured -------------------------------
# Every claim below is about a specific binary. Establish which one FIRST, so a
# result that looks wrong can be attributed rather than guessed at. A build tag
# we do not recognise must read as "not determined", never as a pass.
#
# THE TWO-PASS SESSION NEEDS NO EDITING EITHER, AND THAT IS NEW IN 0.6.17.
# Both `album` lines below end in `(ripper)`, which expands to the installed
# build tag — so pass 1 writes "cancel me platterpus-fork-gddf7ac3" and pass 2
# writes "cancel me platterpus-fork-gc4d1a00". Two folders, no collision, no
# `mv` between passes. Before this, the fixed titles meant pass 2 landed on top
# of pass 1 and destroyed the evidence the session existed to produce, and the
# workaround was two commands handed to the operator.
#
# The placeholder reads the banner captured by the `cyanrip --version` step in
# section A, which is why that step comes first. If it is ever removed, the
# `album` steps FAIL and say so rather than writing the literal text — an
# unexpanded placeholder would give both passes the same folder again while
# looking like it worked.
#
# NOTE for a two-pass session (ddf7ac3 then c4d1a00): `expect-cyanrip
# platterpus-fork` matches BOTH builds deliberately, so this one file runs
# unedited against either. The build that actually ran is recorded by the
# `cyanrip --version` output captured just above it — that line, not this
# assertion, is what attributes the pass.

log --- A. which ripper is actually installed ---
cyanrip --version
expect-exit 0
expect-cyanrip platterpus-fork

# --- B. Dialog sizing — the clipped text from the brief's Appendix A ---------
# Screenshots, not assertions: clipping is a rendering fact at the operator's
# real font size and DPI, and no assertion here can see it. These are for a
# person to look at. They cost seconds and they are the only evidence that
# survives the session.
#
# TWO, not four (trimmed 2026-08-19 on the maintainer's "it is a lot"). Every
# dialog is still OPENED and closed — that is the part that can crash or hang,
# and it still runs. What was dropped is the *picture* of the two whose text is
# short and fixed (Set up Platterpus, Dependency check); the clipping report that
# started this was against the drive dialog, and Settings is the densest text in
# the app, so those two keep theirs. Fewer screenshots, same coverage of the
# thing that fails — and from 0.6.17 the ones that remain travel inside the run's
# single .tar.gz, so "a lot of screenshots" is no longer a lot of uploads.

log --- B. dialogs, for the clipping check (look at these afterwards) ---
open drive
screenshot dialogdrive
cancel
open setup
cancel
open settings
screenshot dialogsettings
cancel
open dependencies
cancel
expect-dialog none

# --- C. THE CACHE PROBE: MEASURED ONCE, AND DELIBERATELY NOT RE-RUN ---------
# `-x` / `--cache-probe` measures the drive's readback cache. The fork added it in
# round 7 lap 1 at our own round-5 request, and until 2026-08-19 it had never been
# executed against real hardware by anybody. It has been now, and this is the
# result — recorded here so it travels in every transcript rather than living in
# one session's scrollback:
#
#     Cache probe: 32 sectors, 73.5 KiB, uncached read 362.6 ms
#
# `-s 0` was required to get it: without an offset cyanrip refuses to open the
# drive and says so itself, exiting 1 in two seconds having measured nothing —
#
#     Opening drive...
#     Offset is unset! To continue with an offset of 0, run with -s 0!
#
# — which is what wasted the probe's first two attempts (2026-08-18, both runs).
# Zero and not the rig's real `+667` on purpose: the probe measures the drive's
# readback CACHE, which has nothing to do with where the audio starts.
#
# WHY THE STEP IS GONE. On the same run the probe printed the line above and then
# went on to rip the entire disc (ETA 1h 3m). The verb killed it at 300s; the child
# could not be reaped, so the drive stayed held for everything after it. Re-running
# it costs five minutes, wrecks the sections below, and writes a partial album into
# whatever directory the app was launched from. The measurement is obtained; the
# cost is all that is left.
#
# WHEN IT COMES BACK: when the fork ships a build whose `-x` exits after
# measuring. That is an ask on them (`docs/cyanrip-handshake.md`), and it is the
# useful outcome of having run this — "the flag does something nobody expected" is
# exactly the finding the first execution existed to produce.
#
# WHAT WE STILL DO NOT KNOW, said out loud so nobody reads the silence as a pass:
# whether 32 sectors is the drive's real cache, and which of the `Cache probe:`
# states a different drive would report. The states are all legitimate outcomes
# and NONE means "the cache was defeated": `N sectors measured`, `no readback
# cache measured`, `not run (disc image has no drive cache)`, and four
# `unknown (<reason>)` forms.

log --- C. cache probe: measured 2026-08-19, not re-run (see the note above) ---
log measured once: 32 sectors, 73.5 KiB, uncached read 362.6 ms
log the flag then rips the whole disc, so re-running it would hold the drive for
log everything below. it returns when the fork's -x exits after measuring.
snapshot beforethecancelpath

# --- D. THE CANCEL PATH, AND THE ONLY HONEST PROOF OF IT --------------------
# Cancelling is easy to *appear* to fix: the button greys out, the status says
# cancelled, and the drive is still held by a reader nobody can see. A snapshot
# taken right after the cancel cannot tell those apart.
#
# So the proof is not the cancel — it is the RIP AFTER IT, in section E.
#
# WHY `pick-release 1` AND NOT AN MBID, WHEN THE VERB ITSELF ADVISES AN MBID:
# the verb's warning is right — MusicBrainz ordering is not stable, so row 1 can
# mean a different release next month, and on 2026-08-18 it resolved to
# `65282302-…` while the hardware checklist names `d14a7546-…` as this disc's
# canonical release. Both are releases of the same album. That is an acceptable
# cost HERE and only here: this script tests the cancel path and the drive, not
# the tags, and pinning an MBID would break the promise at the top of this file
# that any ordinary CD works in any drive. If you ever repurpose these sections to
# test METADATA, swap in an MBID prefix (`pick-release d14a7546 90`) first — the
# verb accepts one, and for that question row 1 is not good enough.
#
# WHY THE WAIT BEFORE CANCELLING IS 90s AND NOT 60s: the cancel has to land
# mid-track. Three tracks on a fast drive in burst mode can finish inside a
# minute, and a "cancel" issued after the rip already ended tests nothing while
# looking exactly like a pass. 90s with secure mode on is comfortably mid-disc.
# If the rip HAS finished by then the transcript shows it, and that reading is
# yours to make — the script cannot assert it (see the limits note at the end).

log --- D. start a rip, then cancel it mid-flight ---
rescan
pick-release 1 90
expect-tracks 3+
select-tracks 1-3
# THE '<' IS DELIBERATE — IT IS WHAT EXERCISES THE 0.6.21 PLAINTEXT FIX.
#
# Three QMessageBox surfaces rendered external text as HTML until 0.6.21, so a
# '<' in an album-derived string was parsed as an unknown tag and EVERYTHING
# AFTER IT WAS SILENTLY DROPPED. One of the three is the "Album already ripped"
# prompt section E answers, and it names this folder. On 0.6.20 and earlier that
# prompt would have read "<...>" truncated with no indication anything was
# missing; on 0.6.21 it must show the whole name.
#
# WHAT TO LOOK FOR: if you are watching the screen when section E starts, the
# overwrite prompt must name the folder in full — the 'bracket' AFTER the
# '<' is the part that used to vanish. In the bundle
# afterwards, the written tag must contain a real '<'.
#
# THE TITLE HERE AND IN SECTION E ARE DELIBERATELY IDENTICAL, CHARACTER FOR
# CHARACTER. Same string in, same folder out, so section E's rip collides with
# this one and the overwrite prompt actually fires — which is the only way the
# PlainText fix above gets exercised on hardware, and the only way section E's
# `answer-dialog` has anything to answer. Identical is stronger than
# "equivalent" here on purpose: our folder preview only *mirrors* cyanrip's path
# sanitisation and is documented as best-effort, so two titles that merely ought
# to land in one folder might not. Two identical titles land together whatever
# either side does to them.
#
# (Measured, and it is why this says character for character: a colon in only one
# of the two titles does NOT collide. `render_preview` maps ':' to U+2236 in the
# folder, so 'cancel me: X' becomes 'cancel me∶ X' and 'cancel me X' stays put.
# The colon therefore has to be in BOTH — which is fine, and section E explains
# why it needs to be there at all.)
#
# It fired on 2026-08-20 for the WRONG REASON, and that is worth writing down
# because it is why this now says "deliberately". The two sections used to carry
# *different* titles, so they could never have collided by design. What made them
# collide was a defect: the `(ripper)` placeholder was being refused (the build
# tag it reads had been overwritten by an unrelated cache probe), both `album`
# steps therefore failed, and both rips fell back to the disc's real MusicBrainz
# title — which is of course the same for both. Fixing the placeholder in 0.6.21
# made the titles distinct, so on 2026-08-21 the prompt correctly did not appear
# and section E's `answer-dialog` failed with "no dialog opened at all". The verb
# was right both times; the script was relying on a bug to set up its own test.
album cancel me: angle<bracket (ripper)
rip
log letting it read for 90s so the cancel lands mid-track, not at a boundary
wait 90
snapshot beforecancel
cancel-rip
log cancel issued; giving the escalation its full SIGTERM to SIGKILL window
wait 30
snapshot aftercancel
screenshot aftercancel

# --- E. The drive-open proof: rip again ------------------------------------
# If this succeeds, the drive was released. If it hangs or fails to identify the
# disc, it was not — and THAT is the finding, recorded here rather than in
# somebody's memory of a session.
#
# THE TRAY SHOULD STILL BE CLOSED WHEN YOU GET HERE. 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 a few seconds after every successful cancel —
# which made this section unanswerable in both directions: an empty tray reads
# as a failure that is not real, and a reader freed by the rescue reads as a
# success the cancel did not earn. If the tray is open when you look, you are on
# an older build and this section proves nothing.

log --- E. can we rip again? this is the real drive-open test ---
rescan
pick-release 1 90
expect-tracks 1+
select-tracks 1
# THE COLON IS DELIBERATE — IT IS THE ONLY THING THAT EXERCISES THE TAG ESCAPE.
#
# `_escape_meta_value` sends a literal ':' to cyanrip as '\:' (round 7 lap 31,
# replacing the old U+2236 substitution). A safety net still reverses any
# leftover '∶' in the written tags, and it is armed ONLY when the metadata
# actually contains a colon. Every scripted rig rip until now set a colon-free
# album title, so that gate was False on all of them and the escape was never
# once exercised on hardware — a check waiting on evidence its own harness could
# not produce (found 2026-08-20).
#
# WHAT TO LOOK FOR in the bundle afterwards: this album's tag must read
# 'cancel me: <build>' with a REAL colon. A '∶' (U+2236 RATIO) there means the
# escape did not survive and the substitution has to come back. The FOLDER name
# is expected to differ from the tag — cyanrip sanitises ':' out of paths itself,
# which is a separate concern and not a failure.
#
# THE TITLE IS BYTE-FOR-BYTE SECTION D's, ON PURPOSE. That is what makes the
# target folder collide and the "Album already ripped" prompt fire — see the long
# note in section D for why this is now stated rather than left to chance, and for
# the measurement showing that a colon in only one of the two does NOT collide.
#
# So the colon is in both titles, and it is *this* rip that proves the escape,
# because this is the one that completes: section D's is cancelled and may never
# reach its tag write.
album cancel me: angle<bracket (ripper)
rip
# ANSWER THE CONFIRMATION, AND WAIT FOR IT RATHER THAN ASSUMING IT IS THERE.
#
# This disc was just ripped by section D, so starting a second rip raises
# "Album already ripped". On 2026-08-20 that dialog went unanswered and cost the
# run BOTH of its failures: `wait-for-rip` found no worker (the app was blocked
# on the modal), and `rig-check` then graded the *cancelled* rip because section
# E never produced one. Two failures, one cause — again.
#
# It is `answer-dialog` and NOT a bare `ok` on purpose. `rip` only *requests* the
# start, so the dialog appears a beat after it returns; `ok` answers whatever is
# on top at the instant it runs and fails when nothing is, which makes `rip` then
# `ok` a coin flip. Same defect as v0.6.17's `pick-release`, different door. The
# app's own failure message used to advise the racy form; 0.6.20 fixed that too.
#
# Naming the title is not decoration: an unattended accept will happily dismiss a
# dialog nobody predicted, and this verb refuses to answer anything else.
#
# AND IT IS `click=new`, NOT `ok`. This was `ok` until 2026-08-21, and `ok` would
# have CANCELLED the rip while the transcript said "accepted" — silently, with
# nothing downstream able to tell the difference except `wait-for-rip` timing out
# 30 minutes later for no stated reason.
#
# Why: the prompt is a QMessageBox with three ADDED buttons — Replace / Rip to a
# new folder / Cancel. `ok` calls accept(), and accept() on an addButton-built box
# leaves clickedButton() as None, so `_confirm_known_overwrite` falls through its
# two `is` comparisons to `return None  # Cancel`. The verb would have done
# exactly what it was told and the result would have been the opposite of what was
# meant. `click=new` names the button instead, and `new` matches only "Rip to a
# new folder" of the three.
#
# "Rip to a new folder" and not "Replace" deliberately: Replace would delete
# section D's partial cancelled rip, which is the evidence this whole script
# exists to produce. The new-folder path also exercises `free_album_folder_
# templates`, which nothing else here reaches.
answer-dialog click=new 60 Album already ripped
wait-for-rip 1800
snapshot afterrecovery
screenshot afterrecovery
rig-check

# --- F. Leave the rig as we found it ---------------------------------------
# This script never enables overread, so there is nothing to put back. The
# assertion stays as a GUARD: if `force_overread` is on here, something else
# turned it on, and that is worth knowing before the next disc — the setting
# reaches cyanrip's argv, and on this drive it hangs the read.

log --- F. confirming overread is off (this script never turns it on) ---
expect force_overread off
snapshot atend

# -----------------------------------------------------------------------------
# WHAT THIS SCRIPT CANNOT ASSERT — read the transcript for these, do not assume
# -----------------------------------------------------------------------------
# Named here rather than left as silence, because a check that does not exist
# looks identical to one that passed.
#
#   * THAT A RIP SUCCEEDED. `wait-for-rip` waits for the worker to disappear,
#     nothing more, so a rip that starts and dies immediately still records
#     PASS. Read the verdict banner in `afterrecovery` screenshot, and the
#     album's own report.
#   * WHAT ARGV THE RIP ACTUALLY SENT. No verb can read it. `expect` reads the
#     config field only, and `rig-check` composes a SYNTHETIC argv rather than
#     capturing the real one. The witness is the `Invoked as:` line in the
#     album's own cyanrip log.
#   * WHICH ALBUM `rig-check` EXAMINED. Bare `rig-check` auto-discovers the
#     newest report under the output/library roots and exits 0 when its log
#     checks SKIP — so PASS can mean "examined nothing" or "examined a different
#     rip". Confirm the album name in its output.
#
# All three need new script verbs, filed in TASKS.md. They are limits of the
# harness, not of this session.
# -----------------------------------------------------------------------------

log =============================================================
log DONE. Everything from this run is now in ONE .tar.gz under
log ~/.local/share/platterpus/bundles/ - transcript, report, screenshots,
log app log. Its path is the "SEND THIS ONE FILE" line in the app log.
log Then run:  ./platterpus-x86_64.AppImage --rig-session
log =============================================================
