Check the reference model adoption map against main migrations

SI
5d ago
+784-0

Summary

This PR adds a public adoption map for the music business reference model: tests/reference_model_adoption.json, 24 rows. It also adds a suite on a throwaway PostgreSQL database that checks the map against the migration files on main. The reference model itself is still proposed and stays in private Business. The map only records which of its distinctions main migrations enforce (13 rows), which exist only on open PRs (5) and which are deliberately not adopted (6). Rows cover the ticket's identity, recording/composition/release, roster/catalog and scoped-evidence relationships, and each row names the tickets that own further adoption.

The suite also applies the ticket's rule that model corrections must arrive paired, on the product side. A migration fails the suite until the map is updated in the same PR if it:

  • changes a table the map checks;
  • redefines a mapped CHECK constraint; or
  • defines an open_pr or not_adopted object.

Refs recoupable/app#2147 (child of epic recoupable/app#2116).

No migration, no schema change, nothing deployed. Statuses describe the migration files on main; this is not an audit of a production schema. The reference model is still a proposed design, not an approved production migration.

What the suite checks

  • main_migration rows: checked on a throwaway cluster that reuses the release-case setup and adds the real subject-entry, catalog-member and professional-roster migrations.
    • Subject and resource kinds must match the mapped set exactly, so a new kind fails until it is mapped.
    • Each subject kind uses exactly one identity column; a company input cannot reuse an artist account.
    • Artist subjects reuse account IDs.
    • Songwriter, company, campaign and material inputs stay opaque.
    • Recordings require an existing ISRC.
    • Release subjects are anchored only to a resource, never to an ISRC, artist account or catalog. Track slots are observations.
    • Catalog subjects and catalog members never reference roster or rights tables.
    • Organization professionals are private to the organization, have no unique name and create no login accounts.
    • Sources are owner-scoped and carry withdrawal timestamps, with exact composite (…, owner_id) keys.
    • Row-level security is on, and anon/authenticated hold no privilege of any kind.
  • Behaviour tests:
    • A company subject with an artist account fails.
    • A recording without an ISRC fails.
    • A release anchored to an ISRC or artist account, or with no anchor, fails.
    • Two professionals with the same name both persist.
    • Browser roles get permission denied.
  • Migrations the setup does not apply: if one alters, drops, indexes, adds or drops a trigger or policy on, or grants or revokes privileges on a mapped table, the suite fails until that migration is added to EXTRA_MIGRATIONS, so the structural checks run against it.
  • open_pr rows (database #89, #90, #92, #94, #95) and not_adopted rows (business-subject bridge, identifier-less recordings, compositions/works/ISWCs, company relationships, rights/agreements/mandates, private document import/review): every main migration is scanned, with comments removed, for matching tables, functions, columns or string literals.
  • Constraint drift: each mapped CHECK constraint must name the latest migration that defines it.

Merge sequencing

This PR touches only the database repo; there are no docs, API or app changes. It adds only new files (its own workflow, test, map and note) and does not edit release-cases.yml, tests/README.md or any migration.

This PR is coupled to 8 open database PRs. I replayed every open database PR's migrations on top of this branch, at the PR heads current on 2026-10-10. Each of these fails the suite until its map row is updated:

PRWhat fails
#89Defines read_context_company_baseline (open_pr row)
#90Defines the attachment tables and function (open_pr row); adds a unique key to organization_professionals
#92Defines list_context_request_evidence_versions (open_pr row)
#94, #95Define the registration table and functions (open_pr rows); add an index to context_source_versions
#99Adds a trigger and unique key to context_documents
#111Redefines the subject/resource kind and subject identity checks; adds unreleased_recording (not_adopted row)
#112Defines context_company_relationships (not_adopted row)

The other 12 open database PRs pass unchanged: #88, #100–#102, #104, #106–#110, #114 and #115.

Neither merge order can turn main red. In pull-request CI the suite compares against the base branch:

  • A mismatch caused by a migration the PR itself adds fails, so the paired map change lands in that PR.
  • A mismatch caused by a migration already on the base branch prints a GitHub warning. This covers a PR whose green checks predate this workflow.
  • A PR that edits the map or the runner must match every migration.
  • Pushes to main only warn.
  • Local runs are strict.

Proposed sequence:

  • If this PR merges first: each coupled PR flips its row in its own PR. Its next CI run shows exactly which rows need flipping.
  • If a coupled PR merges first: rebase this PR and flip that row before merging.
  • If a coupled PR merges without re-running CI: the drift appears as warnings, and a small map-only PR fixes it.

Verification

All runs used throwaway local database clusters. LC_ALL=en_US.UTF-8 is needed on macOS only.

  • Suite at head cc6f50e (python3 -m unittest discover -s tests -p 'test_reference_model_adoption.py'):
    • PostgreSQL 17.7: 11 passed, 0 failed, 0 skipped.
    • PostgreSQL 15.10: 11 passed, 0 failed, 0 skipped.
    • PostgreSQL 17, CI-style with ADOPTION_MAP_BASE_REF=origin/main (strict, because the map is new): 11/11.
    • The inherited release-case lifecycle test runs inside this suite.
  • Red, then green, for the review fixes:
    • Before: the reviewer's probe migration still passed 9/9 at the old head. It grants select on context_sources to authenticated, adds a unique (organization_id, name) index on professionals, drops context_subjects_check and creates context_recording_works.
    • After, unharnessed: all four changes are caught. Three fail because they change mapped tables; the fourth is a works table under the not_adopted composition row.
    • After, added to EXTRA_MIGRATIONS: the structural and behaviour checks fail, covering server-only access, no_unique_on, the missing CHECK, and the company, recording, release and browser behaviour tests.
  • Sibling replay: I replayed the migrations from all 20 other open database PRs one at a time. 8 fail as shown in the table above, and 12 pass.
  • Simulated base comparison (in a throwaway git repo, using the migrations from #111 and #115):
    • This PR against main: OK.
    • #111 after this PR merges: FAILED (3 tests, 6 mismatches, all naming #111's migration).
    • A push to main after #111 merges without re-running CI: OK, with 6 warnings.
    • An unrelated PR (#115) on top of that stale map: OK, with warnings.
    • A map edit that leaves the drift in place: FAILED.
  • Earlier checks:
    • With an empty map, 4 map tests failed.
    • With a database #90 table labelled main_migration, 2 tests failed.
    • Of 10 injected wrong expectations, 10 were caught.
    • Narrowed and reordered composite keys were caught.
  • Other suites: I did not re-run them locally at this head. No existing file changed. At 9f31827, test_release_cases.py 1/1, test_company_assessment.py 3/3, test_professional_roster.py 11/11 and test_research_roster.py 27/27 passed on PostgreSQL 15 and 17.
  • CI at cc6f50e: reference-model-adoption passes on PostgreSQL 15 and 16. The log shows ADOPTION_MAP_BASE_REF: HEAD^1 resolving, and 11 tests ran OK. The release-cases, release-players, credit-charge-receipts, onboarding and storage checks also pass. Supabase Preview was skipped. The cubic review was still pending when this was written. PostgreSQL 16 is verified by CI only.
  • Not verified:
    • Hosted or production database state: no hosted access, no migration applied.
    • Supabase preview, deployment and live providers.
    • The GitHub warning-annotation path, for drift from migrations already on the base branch, is tested only by local simulation. This PR's own CI run used the strict path.
    • The private Business reference was neither read nor changed here.

Review follow-up (commit cc6f50e)

  • Major: coupling with open PRs, and main turning red. The coupling is now disclosed in the table above. The base-branch comparison stops an approved merge elsewhere from turning main or an unrelated PR red.
  • Major: new migrations were invisible to the structural checks. There is now a static guard on mapped tables. The README wording is corrected: it only promises that main matches the map in strict (local) runs.
  • Minor: the identifier-less recording row could not detect its own adoption. It now has an absent_literal guard. Kind CHECKs use exact sets, and column and literal scans are wider.
  • Minor: the check on release anchoring was weak. A behaviour test now proves it.
  • Minor: second public source of adoption status. Needs Sidney's decision. The README now says the private Business reference is canonical for intent, and this file only for what main migrations enforce. The file contains only synthetic fixtures and public migration facts. Keeping a public map is a placement decision for Sidney.

Out of scope / known gaps

  • No not_adopted concept is adopted. No new tables, RPCs, API or UI.
  • The static guards are regex scans of migration text. They miss dynamic SQL (execute format(...)), drop index by index name, and equivalent structures under other names.
  • The harness applies a subset of migrations over minimal stubs; it is not a full historical replay.
  • The private reference, relationships and scenarios update for this checkpoint is still a Business-side task.

🤖 Generated with Claude Code