Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 KiB
MP-22 — MP-10 enforce readiness (spawned 2026-10-01, main session 771b2744)
You are a bounded background agent for the media_pipeline project. Budget: --max-turns 45.
If you issue the same tool call twice with identical arguments, STOP and output the wrap-up block with
status=partially_succeeded. (Exception: a bounded WAIT — one ssh command that loops remotely with
timeout <=900 bash -c 'until <cond>; do sleep 30; done' — may be repeated at most 3 times per test step.)
HARD RULES
- NEVER restart, stop, recreate, or exec a mutating command in ANY live/prod container, on any host.
Read-only
docker exec ... sha256sum|ffprobe|catanddocker ps/inspecton live containers are allowed. - Container restart allowlist starts EMPTY. Before you start/stop/restart a SANDBOX container, add it to
your allowlist and log "ELEVATED " in actions_taken; verify it is healthy after, and list it in
containers_restarted. Only
media-pipeline-sandbox-*containers on server-01 may ever be elevated. - NO git add/commit/push. NO writes to the primary host's Postgres (read-only SELECTs only). The main session does all DB bookkeeping.
- Issue every mutating command as its OWN tool call; verify with a SEPARATE call (never mutate+sleep+verify
in one
sh -c). - Mask secrets in all output. You should need NO sudo. If a step truly needs a privilege you lack, end your
run with a final message headed exactly
⏸ OWNER-COMMAND-REQUESTcontaining host:, command:, why:, paste_back:, resume_at: — then stop (max 3 per run). If a security hook/permission BLOCKS an action, do not work around it: emit a partial wrap-up. - Do not make design decisions. Every decision you need is pre-made in your phase block; if reality contradicts the block (code anchor missing, fixture absent, counts wrong), STOP and report.
ACCESS MAP
- Prod DB (READ-ONLY): PG=$(docker ps --format '{{.Names}}' | grep '^postgres-'); then docker exec "$PG" psql -U postgres -d media_pipeline -c "" (one statement per -c)
- server-01: ssh administrator@192.168.1.90 (batch several read commands per ssh call).
- Sandbox dir (server-01, and a git-tracked twin on the primary host at the same path): /opt/appdata/docker/docker-compose/server-01/media-pipeline-sandbox/
- Sandbox containers: media-pipeline-sandbox-media-transcoder-sandbox-1, media-pipeline-sandbox-postgres-1 (DB media_pipeline_sandbox, user sandbox, no password needed via docker exec). Pipe SQL files with docker exec -i media-pipeline-sandbox-postgres-1 psql -U sandbox -d media_pipeline_sandbox <
- Sandbox baseline reset: TRUNCATE pipeline_learning, transcode_jobs, transcode_overrides, pipeline_events, download_jobs, rename_jobs, show_configs RESTART IDENTITY; then pipe init/02-seed.sql. ALWAYS (re)start the sandbox transcoder (+ downloader-sandbox) AFTER a reset — TranscodeState caches the done-set at startup, so a file already done in the old DB is silently skipped.
- sandbox-output/renamed/tmp hold ROOT-owned files; clear them from inside the container (docker exec media-pipeline-sandbox-media-transcoder-sandbox-1 find -mindepth 1 -delete), not host rm.
- Fixture library: /sandbox-fixtures// (not scanned). STAGE = cp a clip into sandbox-input/// ; category dirs: "H264 ENGLISH DUBBED ANIME TO BE TRANSCODED", "H264 ENGLISH SUBBED ANIME TO BE TRANSCODED", "H264 LIVE ACTION SERIES TO BE TRANSCODED", "H264 MOVIES TO BE TRANSCODED" (path drives preset + prefer_last — never flatten). sandbox-input/ must be EMPTY when you finish. Between runs clear sandbox-output/, sandbox-renamed/, sandbox-tmp/ contents.
- The sandbox encodes with software libx265 (server-01 GPU is NVIDIA — no VAAPI). That is EXPECTED.
- Script edit workflow: cp the LIVE /opt/appdata/docker/docker-compose/media-transcoder/media-transcoder.py
to the primary-host sandbox dir as your working copy; record the live file's sha256 as BASELINE; edit the
working copy; python3 -m py_compile it; scp it to the same path on server-01; restart the sandbox
transcoder; confirm
docker exec media-pipeline-sandbox-media-transcoder-sandbox-1 sha256sum /app/media-transcoder.pyequals the working copy's sha256. - Promotion (only when your phase says PROMOTE and the verdict is GO): if the live file's sha256 still
equals BASELINE,
cp <working copy> <live path>(cp keeps the inode so the live bind mount sees it); then verify the host hash ANDdocker exec media-transcoder-media_transcoder-1 sha256sum /app/media-transcoder.pyboth equal the working copy. Do NOT restart live — put the human's restart command in your output. If BASELINE no longer matches: do not promote; report. - Log lines: the transcoder logs at INFO (no --debug in sandbox or prod); read them with ssh ... "docker logs --since media-pipeline-sandbox-media-transcoder-sandbox-1 2>&1 | grep -E ''"
PERSIST BEFORE YOU FINISH
- Append a dated block "## — (background agent)" to /opt/appdata/docker/docker-compose/media-downloader-local/.claude/context.md with: What was done / Decisions / Current state / Next step (enough for a fresh agent to resume).
- Run: python3 /opt/appdata/docker/.claude/scripts/embed_memory_dir.py --only-recent 5
- Your FINAL message is the wrap-up JSON only, always (success or failure), containing at least: status (succeeded|partially_succeeded|failed), project ("media_pipeline"), actions_taken[], actions_failed[], files_touched[], containers_restarted[], unverified_claims[], next_step, notes — plus your phase's extra fields.
PHASE MP-22 — MP-10 enforce readiness: fix the duration source (S2 + output_is_complete), re-run the MP-10 sandbox matrix in ENFORCE, decide GO/NO-GO for flipping prod to enforce. --max-turns 45. Tracking row: personal_projects #61. PROMOTE the code fix only on GO (see PROMOTION below). You do NOT flip prod to enforce — that is a compose env change + container recreate the owner does.
FACTS (verified by the main session 2026-10-01 16:40 — do not re-diagnose):
- Live media-transcoder.py sha256 = 60a2f3ceb59532e310157022c7bbbc62e14d80a38eeae1b6ac5b93bc8fda1631 (= BASELINE; the repo file is identical and committed). VAAPI is live on prod (hevc_vaapi). Prod is in VERIFY_STREAMS_MODE=shadow (code default, line ~3094; not set in prod compose). 27 prod shadow verifies since the last container creation: ALL pass, 0 warn/fail.
- BUG (found by MP-15 v2's Y1 run 09-25):
ffprobe_duration()(line ~520) asks forformat=duration; the-select_streams v:0there has no effect on format entries. A source whose UNMAPPED subtitle track has an event running past the end of the video gets an inflated format duration (Y1: src 154.63 vs out 150.15 → S2duration_mismatchfalse fail). Two consumers:_v_duration()(MP-10 S2, tolerance max(2 s, 0.5 %) + |audio delay|) viasel["src_duration"]/["out_duration"](~line 3445), andoutput_is_complete()(MP-20b, ~line 3696, tolerance max(2 s, 1 %)) viasrc_durationat ~3766 and ~4927 — the latter can QUARANTINE a good output.
DECISIONS (pre-made by the main session; apply exactly):
- D1 (the one feature): add
ffprobe_media_duration(path)= the duration of the first VIDEO stream: ffprobe-select_streams v:0 -show_entries stream=duration:stream_tags=DURATION→ usestream.durationif numeric, else parse the Matroska tagDURATION(HH:MM:SS.nnnnnnnnn), else fall back toffprobe_duration()(format). Use it for BOTH the source and the output in S2 (~3445/3446) and for the source duration passed tooutput_is_complete()at both call sites, AND for the output side insideoutput_is_complete()(compare video to video). Leave every otherffprobe_duration()caller unchanged (list them in notes). - D2 (fixture): re-cut the D2 row (TrueHD veto) so its TrueHD track ENCODES. Previous attempt failed in the PARTS
encode with ffmpeg's experimental truehd encoder. Try, in order, on server-01 inside the sandbox transcoder
container (it has ffmpeg 6.1.2): (a)
-c:a truehd -strict -2 -ar 48000 -sample_fmt s16 -ac 2; (b) same with-ac 6. Build fromsandbox-fixtures/mp10/MP10 D1 - S01E01.mkvper the matrix row (a:0 truehd taggedengno title, a:1 original taggedjpn). Save assandbox-fixtures/mp10/MP10 D2 - S01E02.mkv(keep the old one asMP10 D2 - S01E02.mkv.old-0924). If neither encodes through the pipeline, mark D2covered_by_unit_test(test_verify_output_streams.py must contain a truehd-veto test — check) and continue; that alone is NOT a NO-GO. - D3 (new fixture Y2 for the bug): cut a 150 s clip from
MP10 S2 - S02E02.mkv(or the source used by Y1 if present under sandbox-fixtures/mp15*) and add a SECOND subtitle track (an SRT titled "English" with one event from 00:02:28 to 00:02:40, i.e. past the video end) that the picker will NOT map. Name itMP10 Y2 - S02E04.mkvunder sandbox-fixtures/mp10/. Expected: OLD code (BASELINE) → S2 fail duration_mismatch; NEW code → S2 pass, output not quarantined. - D4: add unit tests for ffprobe_media_duration's three branches (stream duration / DURATION tag / format fallback) to test_verify_output_streams.py (mock run_subprocess). All existing sandbox tests must still pass (run every test_*.py in the primary-host sandbox dir with python3 -m pytest -q).
SANDBOX RUN (VERIFY_STREAMS_MODE=enforce for the sandbox transcoder only — set it via the sandbox compose environment; that file is the sandbox's own, you may edit it; revert to its previous value at the end):
- Baseline reset (COMMON). Run Y2 with BASELINE code in enforce → record the S2 false fail (proves the bug).
- Apply D1 to the working copy, py_compile, scp, restart, hash-confirm.
- Run the full §6 matrix from design/DESIGN_verification_hardening.md (D1–D5, S1–S3, L1 + L1 offset, M1 + M1
offset) plus Y2, ONE fixture at a time (stage → wait → read
[VERIFY]log line + transcode_jobs row → clear outputs → unstage). Record per row: expected verdict, actual verdict+codes, PASS/FAIL. Note: MP-15 v2 is NOT live (live has the original picker), so rows whose expectation depends on the picker (D4 sdh, L1) keep the §6 expectations as written. - Enforce behaviour check on one failing row: status
failed_verification, source kept, output quarantined and NOT in sandbox-renamed, no deletion_eligible_after.
GO criteria: Y2 fixed (old fails, new passes); every §6 row gets its expected verdict (D2 may be covered_by_unit_test); zero unexpected fails; all tests pass. Otherwise NO-GO with the exact failing rows.
PROMOTION (GO only): per COMMON (BASELINE guard, cp keeps inode, verify host + live container hashes). Also copy
the updated test file(s) to /opt/appdata/docker/docker-compose/media-transcoder/ only if a copy already lives
there; otherwise leave tests in the sandbox dir. Put in your output: (a) the owner restart command
cd /opt/appdata/docker/docker-compose/media-transcoder && docker compose restart media_transcoder; (b) a
STAGED (not applied) diff that adds VERIFY_STREAMS_MODE: enforce to the media_transcoder service environment in
/opt/appdata/docker/docker-compose/media-transcoder/docker-compose.yml — write it to
/opt/appdata/docker/docker-compose/media-transcoder/deploy/mp22_enforce.diff, do NOT edit the live compose.
Extra wrap-up fields: verdict (GO|NO-GO), matrix[] (row, expected, actual, pass), y2_old, y2_new, d2_outcome (encoded_a|encoded_b|covered_by_unit_test), tests_passed, working_copy_sha256, promoted (bool), other_ffprobe_duration_callers[].