# Hardcoded Paths Audit — SSOT Violations to Fix

**Created:** 2026-06-01 (during the EP001 PASS_016 usability test)
**Why:** The post-migration layout is code=`~/CLAUDE_PROJECTS`, data=`~/Dropbox/CLAUDE_DATA/recoil/projects`,
resolved by the SSOT `recoil/core/paths.py` (`projects_root()`) + `recoil/execution/step_types.py::ProjectPaths`.
Many call sites hardcode paths instead, which is what keeps breaking after every migration.
**Status:** catalog only — NOT fixed (substrate freeze). Initial scan; may not be exhaustive.

Two failure modes:
- **(B) v3-layout drift** — uses `projects_root()` (good) but then hardcodes `"state"/"visual"/...`
  the PRE-v3 location, instead of the `_pipeline/state/visual/...` accessors (`paths.plans_dir`,
  `paths.shots_dir`, `paths.coverage_passes_dir`). This is what broke the live run tonight.
- **(A) stale root literal** — hardcodes `~/Dropbox/CLAUDE_PROJECTS` as code/data root (now wrong).

---

## TIER 1 — will break live runs (v3 plan/shots/bible path drift)

- `recoil/pipeline/cli/generate.py:354` — canonical plan path. **FIXED 2026-06-01** → `paths.plans_dir`. (See freeze_exceptions.md.)
- `recoil/pipeline/tools/build_upload_bundle.py:279` — `projects_root()/project/"state"/NS/"plans"/ep_NNN_plan.json` → should be `paths.plans_dir`. **Same bug as generate.py.**
- `recoil/pipeline/tools/generate_previs.py:68` — `plans_dir = projects_root()/project/"state"/NS/"plans"` → `paths.plans_dir`.
- `recoil/pipeline/tools/generate_previs.py:83`, `prep_location_refs.py:532`, `prep_prop_refs.py:223`, `generate_composite_sheet.py:85`, `migrate_assets.py:285/294/414/438`, `populate_assets.py:38`, `migrate_heroes.py:28` — `.../"state"/NS/"global_bible.json"` or `casting_state.json` → use `ProjectPaths` state accessors.
- `recoil/pipeline/tools/feedback_report.py:37`, `backfill_parent_take_id.py:149`, `backfill_episode_id.py:42` — `.../"state"/"visual"/"shots"` → `paths.shots_dir`.

## TIER 2 — stale root literal (`~/Dropbox/CLAUDE_PROJECTS`)

- `recoil/pipeline/tools/ingest_cli.py:55` — `RECOIL_ROOT` default `~/Dropbox/CLAUDE_PROJECTS`.
- `recoil/pipeline/tools/consult.py:63` — `sys.path.insert(... "~/Dropbox/CLAUDE_PROJECTS/cost-ledger")`.
- `recoil/pipeline/gemini_consult.py:170,199` — doc/prompt strings referencing old `RECOIL_ROOT`.
- One-off scripts (lower priority — consider deleting): `rename_torch_to_jade.py:49,53`, `_oneoff_soften_sh17_18.py:16`, `single_take_sh03.py:34`, `run_seq11_call911.py:23,100`.

## NOT bugs (leave alone)
- Comments/docstrings about Dropbox sync races (`io_utils.py`, `atomic_write.py`, `execution_store.py`, `pass_store.py`).
- `~/Dropbox/Claude_Config/...` (learning_engine.py) — correct, unchanged location.
- `~/.recoil/...db`, `~/.cache/starsend/` — correctly OUTSIDE Dropbox by design (ADR-0004).
- `paths.py` sentinel/Law-4 messages.

## Fix approach (when freeze lifts)
1. Add/confirm `ProjectPaths` accessors for every state subdir (plans, shots, global_bible, casting_state).
2. Replace all TIER-1 `projects_root()/.../"state"/...` with the accessor.
3. Replace TIER-2 root literals with `projects_root()` / env-resolved roots; delete dead one-offs.
4. Add a lint/test (extend `tests/test_no_direct_project_paths.py`) that fails CI on new hardcoded `"state"/"visual"` joins outside paths.py/step_types.py.

---

# Cruft / junk to remove (same "fix eventually" bucket)

- **`build_coverage_passes.py`** — `COST_PER_SHOT = 0.25` + fake `print("Estimated cost: $...")` lines. **REMOVED 2026-06-01** (junk: hardcoded estimate in a planning tool).
- **`seedance_vs_kling_v2v_ab.py:58-59`** — hardcoded `KLING_COST_PER_S=0.112`, `SEEDANCE_COST_PER_S=0.3034`. Duplicates `model_profiles.json` (the pricing SSOT). Should read from there.
- **`generate_location_refs.py:315`** — `print("Estimated cost: $...")`. Verify it's computed from real cost, else drop.
- **Batch-size cap inconsistency** — `pipeline_config.json` `max_batch_size: 8` vs `scene_clusterer` `DEFAULT_MAX_BATCH_SIZE` (4, docstring) vs fal r2v_multi hard cap (6) vs `min_batch_size: 3`. Three sources, unclear precedence. Per JT (2026-06-01): batching should be **creative-dictated, not hard-limited** — Flora bills per *run*, so a 15s run is a 15s run whether it's 1 long shot or 6 rapid cuts. The min-batch fallback-to-i2v and the `scene_index>1` auto-break should NOT force a creative pass apart. (Programmatic redesign TBD; near-term: honor the coverage pass as the batch unit.)


## New findings — 2026-06-01 (generate_composite_sheet.py)

- **`generate_composite_sheet.py:85` `_bible()` path** — `"state"/"visual"/"global_bible.json"` doesn't exist; file lives at `"_pipeline"/"state"/"visual"/"global_bible.json"`. Patched with two-candidate fallback; proper fix: expose `ProjectPaths.visual_bible_path` accessor and use it here and in `prep_location_refs.py`, `prep_prop_refs.py`, `batch_gate2_test.py`.

- **`generate_composite_sheet.py` Section 4 boilerplate** — `"same rebreather, same jacket, same boots, same hook"` is hardcoded Jade-flavor text in `_character_prompt`. Replace with generic `"same equipment"` or drive from `sheet_props` list.

- **`generate_composite_sheet.py` proportions boilerplate** — `"1:7.5 head-to-body ratio, long legs, slender silhouette"` is hardcoded. Correct for Jade; wrong for a massive armored cyborg like Wren. Should be driven by `character.sheet_proportion_note` (bible field) with a sane default.

---

# 2026-06-01 — Fix pass + full audit (state-tree drift)

## CONFIRMED: dual state tree on disk (root cause)
Tartarus has TWO state trees:
- `_pipeline/state/visual/` — **CANONICAL** (6 plans, 81 shots, global_bible.json, casting_state). All `ProjectPaths` accessors resolve here.
- `state/visual/` — **near-empty migration remnant** (0 plans, 1 shot, no bible).

Every hardcoded `projects_root()/project/"state"/...` join reads/writes the WRONG (remnant) tree. The accessors (`plans_dir`, `shots_dir`, `global_bible_path`, `casting_state_path`) are correct. **Recommendation:** after all call sites migrate to accessors, DELETE the root-level `state/` remnant (carry any unique data in `state/visual/shots` + `state/visual/passes` into `_pipeline/state/visual/` first). This is an SSOT consolidation — needs JT sign-off.

## FIXED this pass (TIER-1 catalog — verified import + --help OK)
- `generate_composite_sheet.py::_bible()` → `ProjectPaths.for_project(project).global_bible_path` (live path re-verified via dry-run).
- `generate_previs.py:68/83` → `.plans_dir` / `.global_bible_path`.
- `build_upload_bundle.py:279` → `.plans_dir`.
- `prep_location_refs.py:532` + `prep_prop_refs.py:223` → `.global_bible_path` (project branch). Engine-default `PIPELINE_ROOT/state` fallbacks LEFT with TODO — likely dead post-split; verify+delete later.
- `batch_gate2_test.py:126` → `CoreProjectPaths.for_project(project).global_bible_path` (file already imports a DIFFERENT episode-scoped `ProjectPaths` from `execution.step_types`; aliased to avoid clash).
- `feedback_report.py:37`, `backfill_episode_id.py:42`, `backfill_parent_take_id.py:149` → `.shots_dir`.

## AUDIT RESULT — much larger than catalog: 70 files / ~195 lines
New guard `tests/test_no_direct_project_paths.py::test_no_direct_state_paths` (currently `@skip` — audit, not yet enforced) greps `/ "state"` drift. Also fixed: that test's `REPO_ROOT` was hardcoded to the OLD Dropbox tree (`~/Dropbox/CLAUDE_PROJECTS`) → now `Path(__file__).parent.parent`.

Biggest remaining offenders (NOT yet fixed — need triage + the dual-tree decision):
- `review_server.py` (24) — DEPRECATED 8430 console; low priority, churn risk.
- `script_doctor.py` (19) — narrative tool.
- Live pipeline core: `keyframe_context.py` (5), `run_overnight.py` (4), `run_shot.py`/`run_episode.py`/`bible_resolver.py`/`feedback_logger.py` (2 each), `assembler.py` (2), `generate.py` (1) — **these matter for live runs; fix next.**
- Non-`visual` namespaces (NO accessor exists yet — need new ProjectPaths properties): `persistence.py` (orchestration), `learning_engine.py` (learning), `starsend_to_engine.py` (starsend).
- One-time migration scripts (likely dead): `migrate_assets/migrate_refs/migrate_heroes/migrate_state_dirs/migrate_takes/populate_assets`.
- Pre-v3 hooks (already grandfathered in the OLD_DIRS test).

## To enforce the guard (burn-down plan)
1. Add `ProjectPaths` accessors for non-visual namespaces (orchestration/, learning/) so those sites have a target.
2. Fix live-core (`_lib/*`, `cli/*`, `orchestrator/*`, `execution/*`) → accessors.
3. Triage deprecated console + migration scripts: fix or grandfather-with-comment.
4. Consolidate + delete the root `state/` remnant.
5. Flip `test_no_direct_state_paths` from `@skip` to enforced.
