| Metadata | Value |
|---|---|
| Status | Completed |
| Version | 0.4.0 |
| Last Updated | 2026-09-20 |
| Author | Sangeetha Grantha Team |
| Priority | P2 — architectural hygiene; removes a recurring wrong-vehicle pattern |
| Decision | ADR-012 (Python extracts / Kotlin ingests / Curator reviews) · ADR-013 (Flyway is schema + reference data, not corpus data) · ADR-014 (versioned canon / provenance) |
| Depends on | TRACK-133 (the data-fixes to retire) · TRACK-093 (source URLs stored for re-import) |
| Interacts with | TRACK-138 (surfaced this while recovering a v56 backup that could not roll forward past V58) |
ID: TRACK-139 Status: Completed Owner: Sangeetha Grantha Team Created: 2026-09-06 Updated: 2026-09-20
Move one-off corpus-data corrections out of Flyway V__ migrations and into the place ADR-012 says they belong — the deterministic structure_parser / import / curation path — then retire the redundant migration scripts and add a guardrail so corpus fixes cannot sneak back into schema migrations.
Restoring a post_v56 backup during TRACK-138 exposed the problem sharply: the backup could not roll forward, because V58 is a data-remediation migration whose guard asserts a specific krithi’s section structure. That structure existed only in a later corpus state, so V58 aborted against the older-but-valid data. A schema migration that depends on the exact shape of corpus rows is a category error under ADR-013 (Flyway carries schema V__ and reference data R__, not composition content) and ADR-012 (composition correctness is produced by extraction/parsing, ingested by Kotlin, and reviewed by curators).
The five TRACK-133 migrations (V58–V62) are the immediate case, but the pattern is older — several earlier V__ scripts also mutate corpus rows.
Classification of every V__ migration by whether it mutates corpus rows (krithis, krithi_sections, krithi_lyric_*, krithi_ragas, krithi_revisions) with no schema change:
| Migration | Track | Kind | Corpus mutation |
|---|---|---|---|
V38__fix_inconsistent_lyric_sections |
— | data-fix | lyric sections |
V45__remove_stale_anupallavi_brhannayaki |
— | data-fix | delete section |
V46__delete_incomplete_devanagari_amba_nilayatakshi |
— | data-fix | delete variant/sections |
V47__demerge_ragamalika_visvanatham_from_natabharanam |
— | data-fix | ragamalika de-merge |
V58__track133_delete_phantom_empty_charanam_sections |
TRACK-133 | data-fix | delete sections |
V59__track133_merge_missplit_canon_sections |
TRACK-133 | data-fix | merge sections |
V60__track133_fix_alakalallaladaga_pallavi_missplit |
TRACK-133 | data-fix | re-split pallavi |
V61__track133_madhavo_ragamalika_metadata |
TRACK-133 | data-fix | seed ragamalika ragas |
V62__track133_ramaramanarara_indic_charanam_resplit |
TRACK-133 | data-fix | re-split charanams |
Raga identity/reference migrations (V39/V40 seed, V48/V49 mela/scale fixes, V50 merge, V53/V55 identity schema, V57 orphan-twin cleanup) are out of scope here — they concern raga reference entities (ADR-016/017), not composition content, and several are mixed with real schema. They are listed only so the boundary is explicit.
The fixed structure_parser was run (enrichment OFF, no API key) via the real HtmlExtractionStrategy against the five stored source URLs on 2026-09-06:
| Krithi | Section count in restored DB | Parser output (live source) | Migration | Superseded by re-import? |
|---|---|---|---|---|
rAma sItA rAma |
10 (6 real + 4 phantom-empty) | 6 (Pallavi + 5 Charanams) | V58 | ✅ yes |
Rama Rama Rama Sita |
14 (all real — over-split Kannada charanams) | 6 (Pallavi + 5 Charanams) | V58 | ✅ yes — parser correctly collapses the over-split; V58 itself could not (it aborted) |
ramA ramaNa rArā |
7 | 7 (Pallavi + 6 Charanams, all 6 scripts) | V59 + V62 | ✅ yes |
Alakalallalaadaga |
4 | 4 (canonical/en=4, indic=3) — not the adjudicated 3/3/3 | V60 | ❌ no — parser still mis-splits the two-line pallavi |
mAdhavō mām pātu |
ragamalika metadata | 10 sections typed Other, ragas=['Unknown'] |
V61 | ❌ no — dasāvatāra ragamalika raga sequence not parsed |
Conclusion: V58, V59, V62 are genuinely superseded — re-importing produces the correct structure natively (and fixes the Rama Rama Rama Sita case the migration could not). V60 and V61 are not yet reproduced; the parser needs targeted fixes before those two can retire.
Restored storage/backups/sangita_grantha_20260830_post_v56.dump into a scratch database sangita_grantha_rollforward (live sangita_grantha was left at V59). Original dump was not overwritten.
Restore gotcha. A one-shot pg_restore --exit-on-error fails: raga_aliases.match_key is GENERATED ALWAYS AS (raga_match_key(alias)), and raga_match_key calls unqualified strip_diacritics(). pg_restore uses an empty search_path, so the function is not found during COPY. Workaround: schema-only restore → ALTER FUNCTION public.raga_match_key(text) SET search_path = public → data-only restore with --disable-triggers. Optional later hygiene: pin search_path (or schema-qualify) in a real schema migration so future dumps restore without the ALTER.
Corpus shape in the dump (1,226 krithis):
| Title | Canonical sections | Variant lyric-section counts |
|---|---|---|
rAma sItA rAma |
10 | (V58 nonempty-guard would pass for this krithi alone) |
Rama Rama Rama Sita |
14 | 14 nonempty — V58 abort |
Raanidi Raadu |
4 | V59 target 3 |
ramA ramaNa rArA |
8 | en=8, ta=2, sa/te/kn/ml=1 |
Alakalallalaadaga |
4 | en=4, sa/ta/te/kn/ml=1 |
mAdhavO mAM pAtu |
0 sections | is_ragamalika=false, primary raga rAga mAlikA, 1 krithi_ragas row |
Flyway against the restored dump:
| Migration | Kind | Result on this dump |
|---|---|---|
V57 |
raga identity (out of TRACK-139 scope) | applies |
R__seed_04 (outdated checksum) + R__seed_06 |
reference | apply once V58–V62 are not blocking the run |
V58 |
corpus data-fix | aborts — "Rama Rama Rama Sita" has 14 non-empty sections, expected 6 (transaction rolled back; V57 stays) |
V59 |
corpus data-fix | would apply in isolation (Raanidi 4→3, ramA 8→7) |
V60 |
corpus data-fix | aborts — post-fix canonical=3, en=3, min-indic=1 (expected 3/3/3) |
V61 |
corpus data-fix | would apply in isolation (10 ordered ragas + is_ragamalika=true) |
V62 |
corpus data-fix | aborts — Indic variants lack oi5/oi6 (ml missing oi5/oi6 rows); dump Indic coverage is a single lyric-section, not the glued-charanam shape V62 assumes |
Dump-update conclusion: this backup cannot be rolled forward through V62. The honest updated artifact is schema/reference current through V57 + current R__, with corpus still as of 2026-08-30. Written to storage/backups/sangita_grantha_20260906_post_v57.dump (gitignored, alongside the original). Restoring that dump and running make migrate still aborts at V58 until Phase 1 deletes V58. After Phase 1, the next blocker is V60 until Phase 2.
Do not bake V59/V61 SQL into the dump — that would freeze corpus data-fixes into a backup while this track is trying to stop using SQL as the vehicle.
Status: Accepted Accepted by: User Accepted at: 2026-09-06
Corpus-data corrections are being delivered as Flyway V__ migrations. This (a) violates ADR-012/ADR-013 ownership, (b) makes migrations depend on exact corpus shape so they abort on any valid earlier/variant corpus (as V58 did on the post_v56 restore), and (c) hides the real defect — the parser — behind after-the-fact SQL.
V__ migrations from landing.post_v57 (or post_v56 + V57) and make migrate without corpus-shape aborts.structure_parser / HTML extraction worker (Phase 2 gaps).storage/backups/ (gitignored).V__ files to “make them no-op”; retire by delete + flyway repair, or leave until parser-superseded.AUDIT_LOG (re-import / curator path).make db-reset the live recovery database as part of exploration.sangita_grantha V58 row is a real apply or a skip-marker (checksum is present; TRACK-138 notes a skip). Repair procedure is the same either way.raga_match_key search_path so dumps restore without the ALTER workaround. Out of TRACK-139’s corpus-retirement goal; can be a follow-up.V39/40/48/49/50/53/55/57) — separate concern (ADR-016/017).sangita_grantha_20260830_post_v56.dump.Status: Accepted Accepted by: User Accepted at: 2026-09-06
post_v56 or post_v57 can apply every remaining versioned migration without aborting on corpus shape. Immediate meaning: V58/V59/V62 are gone from database/migrations/.rAma sItA rAma, Rama Rama Rama Sita, ramA ramaNa rArā) have correct structure from re-import + curator approval (6 / 6 / 7), not from SQL.V60 and V61 stay until structure_parser reproduces the adjudicated outcomes, then they retire the same way.flyway repair is run in every environment that had the deleted versions applied (live dev is V59; CI Testcontainers are from-scratch and need no repair).make agent-evals (or sibling lint) check fails when a new V__ file mutates krithi_* corpus tables, with an explicit override comment for the rare justified case.V__ file; Flyway reports missing; docker compose run --rm migrate repair marks deleted. Do not insert skip rows by hand.HtmlExtractionStrategy / structure_parser → Kotlin ingestion → curator approve. Junction tables (krithi_ragas, lyric sections) must be populated, not only parent FKs (verify-import).tools/krithi-extract-enrich-worker next to the existing structure_parser suite — two-line pallavi (Alakalallalaadaga); dasāvatāra ragamalika sequence (mAdhavO mAM pAtu)..claude/hooks/protect-migrations.py and/or evals/check.py to scan new versioned SQL for INSERT|UPDATE|DELETE against krithis, krithi_sections, krithi_lyric_%, krithi_ragas, krithi_revisions. Existing committed data-fix files are grandfathered until retired.post_v56 immutable. Use post_v57 as the recovery starting point for TRACK-138 once Phase 1 lands. storage/backups/ stays gitignored.V__ files is a coordinated ops step (repair everywhere). A lone rm on main will fail flyway validate on the live volume.protect-migrations.py currently denies mutating committed V__ files, including delete. Phase 1 will need an explicit allow (user-approved delete, or a track-scoped override) rather than fighting the hook.V59 SQL succeeds on the Aug 30 dump; retiring it is an ownership decision, not a “it won’t apply” decision. Re-import must land before delete so make db-reset corpora stay correct.V61 also succeeds on the dump; parser does not yet reproduce it — do not delete in Phase 1.search_path dump-restore hygiene as a separate schema migration.Status: Accepted Accepted by: User Accepted at: 2026-09-06
database/migrations/V58__*.sql, V59__*.sql, V62__*.sql — delete (Phase 1, after re-import).database/migrations/V60__*.sql, V61__*.sql — delete only in Phase 2.tools/krithi-extract-enrich-worker/ structure_parser + fixtures/tests (Phase 2)..claude/hooks/protect-migrations.py and/or evals/check.py + evals/cases/ (Phase 4).conductor/tracks/TRACK-139-*.md, conductor/tracks.md, a short ADR-014 / ops note under application_documentation/ for retirement + dump restore.storage/backups/sangita_grantha_20260906_post_v57.dump.verify-import.V58/V59/V62; flyway repair on live sangita_grantha and on sangita_grantha_rollforward if kept; confirm make migrate on a restore of post_v57 proceeds until V60.V__ fails CI.flyway validate fails in any environment that applied a deleted version and has not run repair.post_v57 (or post_v56 + V57 workaround) → make migrate does not abort at V58 after Phase 1.make agent-evals includes the corpus-mutation guard (Phase 4).make test / worker tests as touched.post_v57 backup without applying V58–V62amba nIlAyatAkshi Devanagari is complete, retire V46| Migration | Decision | Why |
|---|---|---|
| V38 | KEEP | Corpus-wide historical cleanup. Parser already demotes MKS / merges dual-format for new imports; make db-reset never replays this corpus. Deleting it does not re-import the whole catalogue (non-goal). |
| V45 | KEEP (curator form rule) | bRhannAyaki vara dAyaki Pallavi + Samashti Charanam. One UUID delete; not reproduced by a general parser rule. Convert to a curator correction if the parser re-introduces Anupallavi on re-import. |
| V46 | RETIRED 2026-09-20 | Live amba nIlAyatAkshi (0dd869d5-…) is Pallavi+Anupallavi+Charanam with six scripts including Devanagari at 3 lyric sections. Incomplete variant 4281b4e2-… is gone. Deleted V46__delete_incomplete_devanagari_amba_nilayatakshi.sql and the matching flyway_schema_history row (version=46 AND that description). make migrate-status reports schema version 60 with no missing V46. |
| V47 | KEEP | Identity demerge of two Dikshitar works sharing a title. Matcher/title-collision, not structure_parser. SQL already applied; deleting would not prevent a future bad merge. |
sangita_grantha_rollforward. V57 + current R__ apply; V58/V60/V62 abort; V59/V61 would apply. Wrote storage/backups/sangita_grantha_20260906_post_v57.dump. Spec/Plan drafted. No migrations deleted.is_ragamalika + krithi_ragas. R__seed_06 aliases for nATa/gauLa/kEdAra/saurAshTra. Deleted V58–V62; live history aligned to V57; make agent-evals green. Phase 3 keep V38/V45/V46/V47.NameNormalizationService.normalizeRaga must not gate ragamalika names — it strips honorific sri and would drop raga Sri. Dual-URL krithis: last reingest wins; the 6-section Balahamsa / Saveri payloads were applied last so canon is 6/6. The sibling Huseni / Sankarabharanam URLs still parse to 14 and 10 nonempty sections (matcher collision, not empty phantoms).storage/backups/sangita_grantha_20260906_post_track139.dump from live sangita_grantha (V57 + reingested five krithis). post_v56 / post_v57 left immutable. Restore still needs the raga_match_key search_path workaround in migrations.md §5.amba nIlAyatAkshi Devanagari is 3/3; retired V46 by file delete + history-row delete keyed on version and description. V38/V45/V47 remain. Track completed.