Files
claude-projects/claude-config/config/prompts/media-pipeline/MP-22_mp10_enforce_readiness.md
T
2026-10-02 00:19:54 -05:00

11 KiB
Raw Blame History

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|cat and docker ps/inspect on 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-REQUEST containing 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.py equals 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 AND docker exec media-transcoder-media_transcoder-1 sha256sum /app/media-transcoder.py both 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 for format=duration; the -select_streams v:0 there 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 → S2 duration_mismatch false fail). Two consumers: _v_duration() (MP-10 S2, tolerance max(2 s, 0.5 %) + |audio delay|) via sel["src_duration"]/["out_duration"] (~line 3445), and output_is_complete() (MP-20b, ~line 3696, tolerance max(2 s, 1 %)) via src_duration at ~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 → use stream.duration if numeric, else parse the Matroska tag DURATION (HH:MM:SS.nnnnnnnnn), else fall back to ffprobe_duration() (format). Use it for BOTH the source and the output in S2 (~3445/3446) and for the source duration passed to output_is_complete() at both call sites, AND for the output side inside output_is_complete() (compare video to video). Leave every other ffprobe_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 from sandbox-fixtures/mp10/MP10 D1 - S01E01.mkv per the matrix row (a:0 truehd tagged eng no title, a:1 original tagged jpn). Save as sandbox-fixtures/mp10/MP10 D2 - S01E02.mkv (keep the old one as MP10 D2 - S01E02.mkv.old-0924). If neither encodes through the pipeline, mark D2 covered_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 it MP10 Y2 - S02E04.mkv under 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):

  1. Baseline reset (COMMON). Run Y2 with BASELINE code in enforce → record the S2 false fail (proves the bug).
  2. Apply D1 to the working copy, py_compile, scp, restart, hash-confirm.
  3. 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.
  4. 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[].