From 21c0c54b8343b1c254e1b4aa424737954ea4e936 Mon Sep 17 00:00:00 2001 From: Urban Date: Sun, 19 Jul 2026 16:20:11 +0200 Subject: [PATCH] Document reference database read contract --- docs/reference-database-read-contract.md | 159 +++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 docs/reference-database-read-contract.md diff --git a/docs/reference-database-read-contract.md b/docs/reference-database-read-contract.md new file mode 100644 index 0000000..7b44a46 --- /dev/null +++ b/docs/reference-database-read-contract.md @@ -0,0 +1,159 @@ +# Read contract for the WCX reference database + +## Purpose and ownership + +This document defines the current SQLite read contract used by +`scripts/match_filenames.py` when matching local video filenames against the +WCX reference database. The contract identifies the database objects that the +file-management code depends on and the compatibility boundary that the +reference-data project must preserve. + +The reference-data project owns the database schema, canonical metadata, +history, and aliases. It is the sole writer to the reference database. +File-management code is a consumer only and must open the database through a +SQLite URI with `mode=ro` and `uri=True`. + +Local file paths, local processing state, matching decisions, and collection +state are outside this contract and must not be written to the reference +database. + +## Connection contract + +`scripts/match_filenames.py` resolves the configured database path, converts +it to a file URI, appends `?mode=ro`, and opens it as follows: + +```python +database_uri = f"{database_file.resolve().as_uri()}?mode=ro" + +with sqlite3.connect(database_uri, uri=True) as connection: + connection.row_factory = sqlite3.Row +``` + +The database file must already exist. The consumer must not fall back to a +normal writable SQLite connection. + +## Tables and columns read + +The current contract contains exactly three tables and ten selected columns: + +| Table | Columns read | +| --- | --- | +| `movie` | `id`, `name`, `duration_seconds` | +| `movie_name_alias` | `movie_id`, `alias`, `normalized_alias`, `source` | +| `movie_history` | `movie_id`, `duration_seconds`, `archived_at` | + +No other table or column is read by `load_movies()`. + +### Column meanings for matching + +| Column | Meaning in the file matcher | +| --- | --- | +| `movie.id` | Stable external identity for a movie. It links the current movie to aliases and historical durations and is the identifier that file-management data may reference externally. | +| `movie.name` | Current canonical display name and the primary name used to generate filename candidates. Empty names are excluded. | +| `movie.duration_seconds` | Current known duration. It supplies the current duration version used to support or distinguish name matches. | +| `movie_name_alias.movie_id` | Associates an alias with `movie.id`. | +| `movie_name_alias.alias` | Human-readable alternative name used to generate additional filename candidates. Empty aliases are excluded. | +| `movie_name_alias.normalized_alias` | Stored normalized form used directly for name comparison and duplicate suppression. | +| `movie_name_alias.source` | Describes the alias source and is retained in match results so the reported match can identify how the alias was obtained. | +| `movie_history.movie_id` | Associates a historical duration with `movie.id`. | +| `movie_history.duration_seconds` | Earlier known duration. Positive values are used as historical duration versions so an older local release can still support a match. | +| `movie_history.archived_at` | Identifies when the historical version was archived and is retained with the historical duration for diagnostics and reporting. | + +`movie.id` is the stable external identity in this contract. `movie.name` is +not an external identity: the site importer can update the current name and +archives the previous movie state in `movie_history`. File-management records +must therefore refer to `movie.id`, never to a name as an identifier. + +## SQL queries + +The current implementation executes exactly these three queries. + +### Current movies + +```sql +SELECT + id, + name, + duration_seconds +FROM movie +WHERE name IS NOT NULL + AND TRIM(name) <> '' +ORDER BY name, id +``` + +### Aliases + +```sql +SELECT + movie_id, + alias, + normalized_alias, + source +FROM movie_name_alias +WHERE alias IS NOT NULL + AND TRIM(alias) <> '' +ORDER BY movie_id, alias +``` + +### Historical durations + +```sql +SELECT + movie_id, + duration_seconds, + archived_at +FROM movie_history +WHERE duration_seconds IS NOT NULL + AND duration_seconds > 0 +ORDER BY movie_id, archived_at +``` + +These are read-only `SELECT` statements. The matcher does not issue database +writes or invoke another database-writing component. + +## Backward compatibility + +The following schema or semantic changes would be backward-incompatible for +the current file-management code: + +- removing or renaming any of the three contracted tables, +- removing or renaming any selected column, +- changing a selected value so it can no longer be converted as currently + expected (`id`, names, alias source, and archive time to text; durations to + positive integer seconds), +- making `movie.id` unstable or reusing an ID for another movie, +- breaking the relationships from `movie_name_alias.movie_id` or + `movie_history.movie_id` to `movie.id`, +- changing `duration_seconds` to another unit or meaning, +- changing `normalized_alias` so it no longer represents the normalized alias + expected by filename comparison, +- changing the meaning of `source` such that it can no longer identify the + alias provenance shown in match output, or +- preventing the three queries from running through a SQLite `mode=ro` + connection. + +Adding unrelated tables or columns is compatible. Adding rows, updating a +movie name while retaining the same `movie.id`, adding aliases, and appending +historical durations are also compatible with the current query contract. + +## Process for contract changes + +Before the reference-data project makes an incompatible change: + +1. Identify the affected table, column, value semantics, or relationship and + document the proposed replacement. +2. Update the file-management consumer to support the new contract, preferably + with a transition period in which it can read both forms. +3. Create a disposable database containing representative current names, + aliases, current durations, and historical durations under the proposed + schema. +4. Verify all three queries and representative filename matching against that + database through `mode=ro`. +5. Verify that a write through the same connection is rejected. +6. Release or deploy the compatible file-management version before removing + the old contract from the reference database. +7. Remove the old contract only after both projects explicitly agree that no + active consumer depends on it. + +The projects must coordinate any incompatible change; the reference-data +project must not silently break or reinterpret the documented read contract.