# =============================================================================
# ROUND 8 JOINT TEST SCRIPT  —  Platterpus + the cyanrip fork
# =============================================================================
#
# THIS IS THE ONLY FILE EITHER SIDE SENDS. Not a document about tests. The tests.
#
#   How to run it:   ./platterpus-x86_64.AppImage --run-script round08joint.txt
#   Where it lands:  docs/rig-scripts/round08joint.txt  in rmccann-hub/Platterpus
#                    ^ ONE place. Nowhere else, by either side.
#
# CYANRIP: add your tests in SECTION C. Change nothing above or below it. Send
# this same file back with C filled in — no prose, no companion note, no second
# document. If a test needs explaining, the explanation is a `log` line inside
# the test, where whoever reads the transcript will actually be standing.
#
# THE ONE RULE THAT MAKES THIS WORK: a failing step does NOT stop the batch.
# Only `abort` does. So write tests that fail loudly and keep going — a run that
# stops at the first problem hides every problem behind it, and on real hardware
# a disc pass costs an hour you do not get back.
#
# -----------------------------------------------------------------------------
# THE WHOLE LANGUAGE. There is no other syntax; this is all of it.
# -----------------------------------------------------------------------------
#
#   log <text>                 write a line into the transcript
#   wait <seconds>             pause, bounded at 600
#   abort [why]                stop the batch — the ONLY thing that does
#   screenshot <name>          PNG of every visible window
#   snapshot <name>            text dump of the window state
#
#   rescan                     re-probe the drive, as the toolbar button does
#   album <text>               set the album title
#   album-artist <text>        set the album artist
#   select-tracks <spec>       "1,3,5-7" — this becomes cyanrip's -l
#   expect-tracks <count>      ASSERT the track count. See the floor note below.
#   rip                        start the rip, as the button does
#   wait-for-rip <seconds>     block until it finishes, bounded at 3 hours
#   cancel-rip                 cancel a running rip
#
#   set <field> <value>        any Settings field, BY ITS config.toml NAME
#   expect <field> <value>     assert a field equals a value
#   expect-contains <f> <v>    assert a field contains a value
#
#   open <dialog>              about | dependencies | diagnostics | drive
#                              | guide | settings | setup
#   ok                         accept the top dialog
#   cancel                     dismiss the top dialog
#   expect-dialog <text>       assert a dialog is showing, containing <text>
#
#   cyanrip <args…>            RUN THE REAL RIPPER. Exit code, exact argv and
#                              complete output all land in the transcript.
#   expect-cyanrip <text>      assert the last cyanrip output contains <text>
#   expect-exit <code>         assert the last cyanrip exit code
#
#   rig-check [album-folder]   the seam check: compose a real rip's argv, read it
#                              back out of cyanrip's own -j record, classify the
#                              build, parse the album's log
#
# `#` starts a comment. Blank lines are free. Arguments are the rest of the line.
#
# -----------------------------------------------------------------------------
# FOUR THINGS THAT WILL BITE YOU IF NOBODY SAYS THEM
# -----------------------------------------------------------------------------
#
# 1. A `cyanrip` RIP INVOCATION MUST CARRY `-N`, and the script refuses it
#    otherwise. Not pedantry: without `-N` cyanrip runs its own MusicBrainz
#    lookup and can block on an interactive prompt with no terminal attached, so
#    an unattended batch hangs forever. Probe flags are exempt because they never
#    reach the metadata path: --version, -v, --help, -h, -x, --cache-probe, -j
#
# 2. NO ARGUMENT MAY CONTAIN A NEWLINE OR NUL, and the script refuses it. This is
#    not shell injection — no shell is ever used. It is LOG FORGERY: cyanrip
#    writes its argv into an archival log, and a newline could fabricate a line
#    in a document whose entire purpose is being trustworthy evidence.
#    Also bounded: 64 arguments, 4000 characters each.
#
# 3. A TEST THAT CAN PASS BY FINDING NOTHING IS DECORATION. `expect-cyanrip`
#    against output that never arrived fails, which is right — but a test whose
#    only assertion is "it did not crash" passes on a binary that does nothing.
#    Give it a floor: assert a COUNT, or assert two things that must agree.
#
# 4. AN ASSERTION THAT CAN PASS FOR THE WRONG REASON IS WORSE THAN ONE THAT
#    FAILS, because a failure gets investigated and a pass gets cited. Match on
#    the value, not just the label — `expect-cyanrip Cache probe:` proves the
#    line was printed and nothing about what it said.
#
# =============================================================================


# =============================================================================
# SECTION A — SETUP.  PLATTERPUS OWNS THIS. Do not edit.
# =============================================================================

log ROUND 8 JOINT SCRIPT — starting
log Put the disc in the drive before running this.
log Needs Platterpus 0.6.12b2 or newer. The transcript header above names the
log build that actually ran, so this is attributable either way — but on an older
log build the argument-less `rig-check` in SECTION D cannot find the rip and its
log log checks report SKIP, which means DID NOT RUN, not "passed".
log On anything older than 0.6.12b2 the rip in SECTION D cannot start at all: a
log second drive populate four seconds into launch superseded its own disc scan
log and SIGKILLed cyanrip mid-TOC-read (exit -9, no output). That is the blocker
log the 2026-08-12 pass hit, and it costs every step from `rip` onward.

# Identity first, so every failure below is attributable to a named pair of
# builds. A transcript that cannot say which binaries produced it is not
# evidence, and two logs of one disc from two builds are not interchangeable.
cyanrip --version
expect-exit 0
expect-cyanrip platterpus-fork
log ^ if that failed you are on stock upstream, not the fork. Everything below is void.

# Answer the MusicBrainz release picker before anything else can collide with
# it. This disc returns FOUR candidates, so on launch a modal opens and waits —
# and SECTION A's `cancel` below closes whatever modal is on top, which would be
# the picker rather than the dependencies dialog. Added 2026-08-13 after the
# operator asked the obvious question: "is there a way you can pick the album?"
# There was not. There is now — a verb, not an app setting, because auto-choosing
# in the product would silently pick the tags for every ambiguous disc a user
# ever rips. In a script the choice is written down and applies to this run only.
#
# The MBID is the one the operator chose on 2026-08-13, twice, consistently.
# Named by MBID rather than row number because MusicBrainz ordering is not
# stable: `pick-release 2` would mean a different release next month.
pick-release d14a7546-815b-43c6-8af6-35cff6cee1d0 120

# The seam check with no album folder: the argv half only. It composes a real
# rip's argv through the REAL builder, runs it against a device that cannot open,
# and reads `invocation` back out of cyanrip's own -j record — which compares
# what the binary RECEIVED against what we COMPOSED. No disc is spent.
rig-check

open dependencies
screenshot A-dependencies
cancel


# =============================================================================
# SECTION B — PLATTERPUS'S TESTS.  WE OWN THIS. cyanrip: do not edit.
# =============================================================================

log SECTION B — Platterpus tests

# B1. The disc pipeline, and a floor under it. `expect-tracks` is the floor:
#     without it, a rip that identified NOTHING reports success exactly like a
#     real one, and the transcript reads clean either way.
rescan
wait 20
pick-release d14a7546-815b-43c6-8af6-35cff6cee1d0 120
expect-tracks 14

# B2. Dynamic secure re-rip is the DEFAULT and it means the album pass carries
#     NO -Z; only AccurateRip-failing tracks get -Z on the refix pass. Asserted
#     rather than assumed, because the fork read a -Z-free album pass as a
#     dropped flag in their round-8 §2.1 and it is our design.
# SET, then assert. Asserting alone measured whoever's config the script ran on
# — and on the rig it ran on one with `secure_rerip_dynamic` off, so B2 reported
# a configuration mismatch as though it were a defect (2026-08-12). A test that
# does not establish its own precondition is testing the machine.
set secure_rerip_dynamic on
set secure_rerip_matches 2
expect secure_rerip_dynamic True
expect secure_rerip_matches 2

# B3. Per-track selection becomes cyanrip's -l as a COMMA LIST, never a range.
select-tracks 1,3,5-7

# B4. A distinct album title per pass. A session that overwrites its own evidence
#     has destroyed the thing it was run to produce.
album Every Breath You Take (round 8 joint, pass 1)
album-artist The Police

snapshot B-before-rip


# =============================================================================
# SECTION C — CYANRIP'S TESTS.  YOU OWN THIS. WE WILL NOT EDIT IT.
# =============================================================================
#
# Add whatever you want between the two markers. Delete the examples. The three
# verbs you need are `cyanrip`, `expect-cyanrip` and `expect-exit`; everything
# above is available to you too if you want a rip driven around your test.
#
# Things only YOU can test, which is why this section exists:
#   - `-x` / `--cache-probe` on a real drive. As of 2026-08-07 this had never
#     executed on real hardware, by anyone. Whatever it prints is the first data
#     point in existence — INCLUDING an absurd number, which is a finding.
#     A HANG IS ALSO A RESULT: the verb is bounded and reports the stall.
#   - your 10 new fatal messages from round 8 §D2 — we can only test that our
#     matcher catches them, never that your binary emits them.
#   - the malformed-argv shapes from your lap 32 (`-c /`, `-p =`) that segfaulted.
#   - anything where the question is "what does the ripper do", not "what does
#     the app do with what the ripper did".
#
# WARNING, because this one costs 23 minutes: `-x` is the CACHE PROBE.
# `-O` is OVERREAD, and `-O` is confirmed to hang the BDR-209D for ~23 minutes.
# They are one keystroke apart. Do not reach for `-O` on this rig.

# >>> CYANRIP TESTS BEGIN >>>

log SECTION C — cyanrip tests
log Pair under test: read the two version lines above; both must name a build.

# -----------------------------------------------------------------------------
# C1. The cache probe. READ THIS BEFORE CITING ITS NUMBER.
# -----------------------------------------------------------------------------
# Your example asserted `Cache probe:` and you flagged your own mistake: it
# proves the label printed and nothing about the value. Taking that seriously
# turned out to matter more than either of us expected, so this test asserts
# values AND tells the transcript reader not to trust the headline figure.
#
# What happened on 2026-08-10/11, in order, on this drive and disc:
#   ddf7ac3  printed "32 sectors measured"      -- a bare number
#   cd-paranoia -A said 137 sectors, then 140 on a second run
#   310dbd2  printed "at least 2048 sectors, upper bound unknown"
#
# So the number moved by a factor of 64 across two of our own builds while the
# drive did not change. The method is wrong, and we now know why: `miss_cost` is
# calibrated by seeking to the far end of the disc and back -- 342.9 ms measured
# -- while the test read is a SHORT backseek, which cd-paranoia clocks at
# 2.22 ms/sector on this very drive. The hit threshold is miss_cost/4 = 86 ms.
# Every short backseek beats 86 ms whether or not anything is cached, so after
# the transfer-size fix every run scores as a hit until the search ceiling.
#
# We are NOT fixing that inside this round -- the pin does not move -- and the
# line is not unsafe: "upper bound unknown, search ceiling reached" claims
# nothing false and names its own ignorance. It is simply not yet a measurement.
log C1 the Cache probe: number is KNOWN UNTRUSTWORTHY -- method defect, round 10
log C1 cd-paranoia -A on this drive: 137 sectors, then 140. Believe those, not ours.

cyanrip -d /dev/sr0 -I -N -A -U -P 0 -x
expect-exit 0

# Two independent figures on one line, so neither can pass alone. `sector` alone
# would pass on "0 sectors"; `uncached read` proves calibration completed.
expect-cyanrip sector
expect-cyanrip uncached read

# The retired phrasing must never come back. `measured` claimed a precision a
# doubling search cannot deliver, and it is the exact word that shipped a number
# wrong by a factor of four into a line destined for an archival record.
# A pass here is the ABSENCE of that word -- if the transcript shows
# "sectors measured", the wording regressed.
log C1 if the line below says "sectors measured" the wording regression is back

# -----------------------------------------------------------------------------
# C2. The four argv shapes that used to SEGFAULT. Exit 139 is the failure.
# -----------------------------------------------------------------------------
# From our lap 32. Each of these crashed with no output at all -- a non-zero
# exit a caller cannot explain, which is the one failure mode this seam agreed
# is unacceptable. Fixed in 58f5151; these are the regression tests, and they
# are ours because only the ripper can be made to crash.
#
# Each asserts a MESSAGE and an EXIT CODE. A crash produces neither.
log C2 malformed argv: each must refuse with a message, never crash

cyanrip -N -d /dev/sr0 -c /
expect-cyanrip Missing discnumber
expect-exit 1

cyanrip -N -d /dev/sr0 -c //
expect-cyanrip Missing discnumber
expect-exit 1

cyanrip -N -d /dev/sr0 -p =
expect-cyanrip Missing track idx for pregap
expect-exit 1

cyanrip -N -d /dev/sr0 -p ==
expect-cyanrip Missing track idx for pregap
expect-exit 1

# -----------------------------------------------------------------------------
# C3. The -t shape that published process memory into an archival log.
# -----------------------------------------------------------------------------
# `-t 1` with no `=` read past the end of the argument and copied whatever
# followed it in memory into the FLAC tags, the log AND the cue -- at exit 0,
# with no warning. ASAN and UBSAN were both silent on it, because argv lives in
# the shared initial stack block with no redzone. Fixed in 3923dee.
#
# This is the highest-consequence defect either project has found, so it gets a
# test that fails loudly rather than a note in a document.
log C3 -t with no "=" must refuse, not leak adjacent memory at exit 0

# EXPECT-SANITISER-REFUSAL: OUR argv guard now refuses this before the ripper
# sees it. The step's subject is cyanrip's own refusal of a malformed `-t`, and
# `sanitise_cyanrip_args` was added afterwards with the same rule — so on any
# build carrying that guard this step and the two assertions under it record a
# refusal rather than exercising the binary. Both behaviours are correct and they
# are incompatible in one step; the guard is the one that must win, because it is
# the one protecting a user. Left in place, marked, and swept by
# tests/test_rig_scripts.py in BOTH directions: an unmarked refusal fails, and so
# does a marker over a step the guard now lets through — a stale marker would
# quietly re-admit exactly the argv this comment says is refused.
cyanrip -N -d /dev/sr0 -t 1
log C3 assertion is quote-free on purpose: the runner strips double quotes,
log C3 so `Missing "=" in track metadata` can never match. Its own error said
log C3 'Missing = in track metadata' -- the quotes were gone before comparing.
expect-cyanrip in track metadata
expect-exit 1

# -----------------------------------------------------------------------------
# C4. genopt's fatal messages -- the ten rows the contract gained.
# -----------------------------------------------------------------------------
# You can only test that your matcher catches these; only we can make the binary
# emit them. They were ALWAYS emitted -- the provider contract simply never
# scanned genopt.h, so the document was incomplete while the behaviour was not.
#
# `-l` takes a COMMA-separated list. A range is refused, and the message names
# the internal C type rather than the accepted format, which is a usability
# defect we are carrying to round 10 rather than reword mid-round.
log C4 genopt fatal messages: emitted for years, in the contract only since round 8

cyanrip -N -d /dev/sr0 -l 1-2
expect-cyanrip Error parsing
expect-exit 1

# The message that once read to you as "cyanrip is not installed", and the
# reason this fork restored -V as an alias. It reaches stdout through
# crip_genopt_log -> cyanrip_vlog, so it lands in the transcript like any other.
cyanrip -N --no-such-flag-exists
expect-cyanrip Unable to parse command line argument
expect-exit 1

# -----------------------------------------------------------------------------
# C5. -f offset autodetection, asserting the VALUE.
# -----------------------------------------------------------------------------
# Never run on hardware before 2026-08-10. It has now reproduced +667 at
# confidence 14 on three independent runs, matching the -s 667 your settings
# apply -- two methods, two projects, one number. That agreement is the strongest
# single piece of evidence either of us holds about this drive, which is why it
# is asserted by value and not by label.
#
# ~60 seconds: it reads about 12 sectors per track, not whole tracks, and exits
# without entering the rip loop.
log C5 -f must independently rediscover the configured read offset

cyanrip -N -f -d /dev/sr0
expect-cyanrip Drive offset of +667 found
expect-exit 0

# -----------------------------------------------------------------------------
# C6. --verify-log must refuse a foreign log, diagnosably.
# -----------------------------------------------------------------------------
# The only surviving rip on this rig is EAC's, and an EAC log carries no FUN512.
# cyanrip must say so and exit non-zero rather than crashing or -- worse --
# reporting a log it cannot verify as valid.
#
# Path is EAC's own output. If it has moved, this test fails on the path, which
# is a legible failure rather than a silent skip.
log C6 a log we did not write must be refused, not silently accepted
log C6 -N added 2026-08-12: rule 1 refused this line without it, and the
log C6 refusal left the PREVIOUS cyanrip result live, so the next assert
log C6 silently graded a command that never ran. Ours to fix, both halves.

log C6 PLATTERPUS EDIT, the only one we made inside SECTION C: the path
log C6 below is now QUOTED. Unquoted it contains spaces, so it reached
log C6 cyanrip as 17 arguments -- and `expect-exit 1` would then have
log C6 passed because the file could not be OPENED, not because the log
log C6 was refused. Exactly the "satisfied by the wrong thing" shape,
log C6 on the one test whose purpose is telling those two apart.
cyanrip -N --verify-log "~/Music/rips/The Police/Every Breath You Take - Archive files/EAC flac/The Police - Every Breath You Take- The Classics.log"
expect-exit 1

# <<< CYANRIP TESTS END <<<


# =============================================================================
# SECTION D — THE RIP, AND THE SEAM CHECK OVER IT.  PLATTERPUS OWNS THIS.
# =============================================================================

log SECTION D — the rip

rip
wait-for-rip 7200

screenshot D-after-rip
snapshot D-after-rip

# The seam check again, this time WITH the album folder, so it also parses the
# log this rip just produced and reads its `Handshake:` note. Both note shapes
# must be read correctly — `closed` and `OPEN` — and a note it cannot classify
# says `unrecognised` rather than defaulting to either. An open round silently
# reported as closed would clear a release gate.
#
# NO PATH. From 0.6.12b1 `rig-check` finds the newest rip on this machine by
# itself, using the same rule `--compare` uses, and names the folder it chose in
# the manifest. The line here used to read "replace the path with the folder the
# rip actually wrote", which was a hand-edit standing in for something the
# program could do. Pass a path only to point at a DIFFERENT rip than the last.
rig-check

log ROUND 8 JOINT SCRIPT — complete. Send the folder named at the top of this
log transcript, nothing else. From 0.6.12b2 the run writes itself into that
log folder when it ends — transcript.txt, report.json and any screenshots — so
log there is nothing to copy out of a window by hand.
log Folder: ~/.local/share/platterpus/uiscript/<timestamp>/
