Persist per-utterance attribution transparency so the system can auto-accept
high-confidence lines and surface only a small review queue:
- DB: utterances gains attribution_evidence_kind, attribution_tag_name,
attribution_uncertain, needs_review, review_reason; attribution_confidence
default -> NULL. Idempotent migrations in database._migrate, routes
_ensure_utterance_columns, and a new ensure in reattribute_all (raw-sqlite
driver must add columns itself).
- llm_attributor: accumulates scene-level mechanical evidence across batches and
stamps evidence kind + tag name on attributed utterances.
- New umbraaudio/analysis/clarity.py: deterministic evidence classification
(name/role/pronoun/address_only/none), in-scene pronoun resolvability
(gender-filtered candidate count), and composite needs_review rule:
QA issues (long_monologue excluded), non-unique pronoun referent, role tag
with no profile, none/address_only evidence < 0.75, unresolved uncertain
flag, narrator-left dialogue, confidence < 0.7. Addressee counts only for
known names/titles (guards 'But where...' false positives).
- Drivers persist all fields: reattribute_all (per-chapter clarity pass),
run_llm_attribution (via update_utterance_speaker), both web attribution
paths. _populate_exceptions keys off needs_review (was confidence<0.7 +
keyword sniff) and fixes a latent NameError (pid not in scope).
- tests/test_clarity.py: 25 tests. Eval on Last Dwarf: 3918 dialogue lines ->
173 needs_review (4.4%), within the 1-5% target.
Full suite 380 passed; 6 pre-existing voice_profile_manager accent failures.
ruff/mypy clean on new code; routes.py baseline unchanged (73 pre-existing).
Phase A: derive the active project id dynamically (active_project_id) instead of
hard-coded project_id=1 in cli/orchestrator/scripts/web; enable SQLite WAL on the
project + voice-registry DBs; parameterize tts_server/sync_voice_data.sh via
UMBRA_DATA_DIR/PROJECT_SLUG.
Phase B: reusable project_bootstrap.create_project_from_manuscript (+ async
variant for web) boots an isolated project dir + DB from a manuscript; new
POST /api/projects and /projects/new page + "New Project" button let a second
book be created and activated (active_project cookie) without touching the
current project. Fix async route (asyncio.run in running loop) and source-path
persistence ordering bugs in the new endpoint.
Phase C: list_projects() skips the legacy root .umbra so /projects is clean;
load_config() now resolves a bare project name under PROJECT_BASE even when the
dir doesn't exist yet (new-project creation no longer depends on CWD); add
tests/test_multi_project.py (5 tests).
Phase D: scripts/verify_projects.py + scripts/verify_nas.py idempotent
integrity/mirror checkers.
All 249 tests pass.