Files
transcription/docs/phase2-codex-prompt.md
T
2026-08-20 15:05:17 -05:00

5.2 KiB

You are working in the transcription repository (Python 3.12+, FastAPI, NiceGUI, SQLModel/SQLAlchemy, Pydantic V2, asyncio). Follow .github/instructions/ui.instructions.md and .github/instructions/services.instructions.md for any code you touch, and keep docs/ver4/* as canonical authority for intended behavior. Do not modify unrelated code.

Prerequisite: This prompt assumes Phase 1 (docs/phase1-codex-prompt.md) is already merged — the SQLite atomic job claim and the JobSource(job_id, source_id) uniqueness constraint should already exist. If they do not, stop and flag this before proceeding, since this phase's regression tests depend on that groundwork.

Goal

Implement Phase 2 (Enforcement hardening) from docs/architecture-code-review-2026-08-20.md:

1. [HIGH-02] General UI media resolution falls back to basename instead of failing closed

Location: src/transcription/ui/components/media_urls.py:41-74, src/transcription/ui/pages/sources_page.py:261-265

Problem: When a stored path is not provably under upload_dir and does not match an approved prefix, resolve_media_url still returns /uploads/{path_obj.name}. The source detail page passes that URL directly to the viewer. If an unmanaged or stale DB path shares a basename with another upload, the UI can render the wrong file instead of the recorded source — violating record fidelity and the "controlled resolver paths" rule (REQ-4-031). This is distinct from print/export media, which is already correctly record-validated in src/transcription/api/v4_print.py:31-54.

Required fix:

  • In resolve_media_url (src/transcription/ui/components/media_urls.py), remove the basename fallback. Return None (or an explicit "unavailable" sentinel/token consistent with how the rest of the UI layer signals missing/invalid media — check error_presenter.py and existing viewer components for the established pattern) unless the path is validated as either (a) safely resolvable under the configured upload_dir, or (b) an already-approved upload-relative form recognized elsewhere in the codebase (mirror the validation used by v4_print.py).
  • Update src/transcription/ui/pages/sources_page.py:261-265 (and any other call site relying on the old fallback behavior) to handle the None/unavailable case gracefully — e.g. show a clear "media unavailable" state in the viewer rather than crashing or rendering a blank/broken image.
  • Add a dedicated resolver test suite (new or extended, e.g. tests/ui/test_media_urls.py) covering: a validated path under upload_dir (should resolve), an unmanaged absolute path outside upload_dir (should fail closed), a stale/nonexistent relative path (should fail closed), and a basename-collision scenario where an unmanaged path shares a filename with a legitimate upload (must NOT resolve to the wrong file). This directly satisfies the meta-tooling recommendation in docs/architecture-code-review-2026-08-20.md section 8: "Add a deterministic resolver test suite for resolve_media_url, not just public_media_path_label."

2. Deterministic regression tests for Phase 1 fixes

Rationale: Phase 1 fixed the underlying atomicity/uniqueness issues; this phase locks them in with deterministic, always-run tests so no future change can silently regress them (per the review's Action Plan, item 2, and section 8 meta-tooling recommendations).

Required work:

  • Confirm (or add if missing) a concurrency regression test that races two concurrent claim_next_queued_job calls against the same queued job on SQLite and asserts exactly one caller wins. If Phase 1 already added this test, review it for robustness (e.g. does it actually force a race rather than relying on incidental ordering?) and strengthen it if needed — for example by using two independent sessions/connections and asserting via asyncio.gather that exactly one result is non-None.
  • Confirm (or add if missing) a deterministic test asserting that creating a second JobSource for an existing (job_id, source_id) pair is rejected/handled predictably (not a silent duplicate). Ensure this test exercises the actual insertion code path used by services/sources.py, not just the raw model constraint.
  • Both tests should live alongside the existing service test files for jobs.py/sources.py (check tests/services/ for the correct location and naming convention).

Validation

  • Run pytest (via the project's normal invocation, e.g. uv run pytest, potentially through tools/run_destructive_tests.py --auto-restore -- pytest if that's how destructive/DB tests are run in this repo — check pyproject.toml/README) and ensure all tests pass, including new/strengthened tests.
  • Run ruff check and ty check and ensure no new issues are introduced by your changes (do not attempt to fix the pre-existing baseline debt — that is Phase 5's responsibility).
  • Do not touch error taxonomy (Phase 3), UI/service consolidation (Phase 4), or provenance/env config and ruff/ty baseline cleanup (Phase 5) — those are out of scope for this task.

Report back with: files changed, the fail-closed validation logic chosen for resolve_media_url, the new/updated test coverage, and final pytest/ruff/ty results.