463 lines
19 KiB
Markdown
463 lines
19 KiB
Markdown
# Gap-analys för reproducerbart facitdatabasschema
|
||
|
||
## 1. Omfattning och metod
|
||
|
||
Analysen är avgränsad till om facitdatabasens struktur kan återskapas från
|
||
versionshanterade filer. Följande har inspekterats:
|
||
|
||
- `docs/project-split-analysis.md` och
|
||
`docs/project-split-analysis-summary.md`,
|
||
- `scripts/schema.sql`,
|
||
- databasberoendena i `import_site.py`, `migrate_csv.py`, `check_ocr.py`,
|
||
`process_pending_ocr.py`, `match_filenames.py` och
|
||
`test_duration_matching.py`,
|
||
- `sqlite_schema`, `PRAGMA table_xinfo`, `PRAGMA foreign_key_list`,
|
||
`PRAGMA index_list` och `PRAGMA index_xinfo` i
|
||
`/storage/disk1/WCX/database/wcx.db`, öppnad med `sqlite3 -readonly`, och
|
||
- resultatet av att läsa `scripts/schema.sql` i en separat SQLite-databas i
|
||
minnet.
|
||
|
||
**Verifierat faktum:** Produktionsdatabasen öppnades endast med
|
||
`sqlite3 -readonly`. Ingen kontroll eller verifiering skrev till den.
|
||
|
||
**Verifierat faktum:** `PRAGMA integrity_check` gav `ok` och
|
||
`PRAGMA foreign_key_check` gav inga rader. Både `PRAGMA user_version` och
|
||
`PRAGMA application_id` är `0`.
|
||
|
||
**Antagande:** Schemat som finns i den inspekterade `wcx.db` är den avsedda
|
||
produktionsmodellen. Analysen kan verifiera faktisk struktur och kodberoenden,
|
||
men inte om varje historiskt schemaingrepp var avsiktligt.
|
||
|
||
## 2. Fullständigt faktiskt produktionsschema
|
||
|
||
### 2.1 Tabeller
|
||
|
||
**Verifierat faktum:** Följande användardefinierade tabeller finns i
|
||
produktionen. SQL-definitionerna nedan är hämtade från `sqlite_schema`. Den
|
||
kompakta placeringen av OCR-kolumnerna på samma rad som `modified_at` är bara
|
||
hur SQLite lagrade den stegvis ändrade tabellens SQL-text; den ändrar inte
|
||
kolumnernas semantik.
|
||
|
||
```sql
|
||
CREATE TABLE movie (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
aka TEXT,
|
||
nationality TEXT,
|
||
age INTEGER,
|
||
shoot_location TEXT,
|
||
shoot_date TEXT,
|
||
duration_seconds INTEGER,
|
||
description TEXT,
|
||
rating REAL,
|
||
web_url TEXT,
|
||
thumbnail TEXT,
|
||
published TEXT,
|
||
updated TEXT,
|
||
|
||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
modified_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
ocr_status TEXT NOT NULL DEFAULT 'pending',
|
||
ocr_raw_text TEXT,
|
||
ocr_error TEXT,
|
||
ocr_processed_at TEXT,
|
||
|
||
CHECK (age IS NULL OR age >= 0),
|
||
CHECK (rating IS NULL OR rating BETWEEN 0 AND 10),
|
||
CHECK (duration_seconds IS NULL OR duration_seconds >= 0)
|
||
);
|
||
|
||
CREATE TABLE movie_history (
|
||
history_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
||
movie_id TEXT NOT NULL,
|
||
|
||
name TEXT NOT NULL,
|
||
aka TEXT,
|
||
nationality TEXT,
|
||
age INTEGER,
|
||
shoot_location TEXT,
|
||
shoot_date TEXT,
|
||
duration_seconds INTEGER,
|
||
description TEXT,
|
||
rating REAL,
|
||
web_url TEXT,
|
||
thumbnail TEXT,
|
||
published TEXT,
|
||
updated TEXT,
|
||
|
||
created_at TEXT,
|
||
modified_at TEXT,
|
||
|
||
ocr_status TEXT,
|
||
ocr_raw_text TEXT,
|
||
ocr_error TEXT,
|
||
ocr_processed_at TEXT,
|
||
|
||
archived_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
change_source TEXT NOT NULL DEFAULT 'site_import',
|
||
change_summary TEXT NOT NULL,
|
||
|
||
FOREIGN KEY (movie_id) REFERENCES movie(id)
|
||
);
|
||
|
||
CREATE TABLE movie_name_alias (
|
||
alias_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
|
||
movie_id TEXT NOT NULL,
|
||
alias TEXT NOT NULL,
|
||
normalized_alias TEXT NOT NULL,
|
||
source TEXT NOT NULL DEFAULT 'manual',
|
||
|
||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
FOREIGN KEY (movie_id) REFERENCES movie(id),
|
||
|
||
UNIQUE (movie_id, normalized_alias)
|
||
);
|
||
```
|
||
|
||
**Verifierat faktum:** SQLite har dessutom skapat den interna tabellen
|
||
`sqlite_sequence(name, seq)` eftersom `movie_history.history_id` och
|
||
`movie_name_alias.alias_id` använder `AUTOINCREMENT`. Den ska inte deklareras
|
||
manuellt i projektets schema.
|
||
|
||
### 2.2 Index
|
||
|
||
**Verifierat faktum:** Produktionen innehåller följande explicita index:
|
||
|
||
```sql
|
||
CREATE INDEX idx_movie_history_movie_id
|
||
ON movie_history(movie_id);
|
||
|
||
CREATE INDEX idx_movie_history_archived_at
|
||
ON movie_history(archived_at);
|
||
|
||
CREATE INDEX idx_movie_name_alias_movie_id
|
||
ON movie_name_alias(movie_id);
|
||
|
||
CREATE INDEX idx_movie_name_alias_normalized
|
||
ON movie_name_alias(normalized_alias);
|
||
```
|
||
|
||
**Verifierat faktum:** SQLite har också skapat två unika automatiska index:
|
||
|
||
| Index | Ursprung | Nyckelkolumner | Partiellt |
|
||
| --- | --- | --- | --- |
|
||
| `sqlite_autoindex_movie_1` | `PRIMARY KEY` | `movie.id` | nej |
|
||
| `sqlite_autoindex_movie_name_alias_1` | `UNIQUE` | `movie_id`, `normalized_alias` | nej |
|
||
|
||
Alla sex index använder stigande ordning och `BINARY`-kollation. De fyra
|
||
explicita indexen är icke-unika och icke-partiella.
|
||
|
||
### 2.3 Constraints
|
||
|
||
**Verifierat faktum:** Produktionsschemat deklarerar:
|
||
|
||
- primärnyckel på `movie.id`,
|
||
- primärnyckel med `AUTOINCREMENT` på `movie_history.history_id`,
|
||
- primärnyckel med `AUTOINCREMENT` på `movie_name_alias.alias_id`,
|
||
- `NOT NULL` enligt SQL-definitionerna ovan,
|
||
- standardvärdena `CURRENT_TIMESTAMP`, `'pending'`, `'site_import'` och
|
||
`'manual'` enligt SQL-definitionerna ovan,
|
||
- `CHECK` för icke-negativ `age` och `duration_seconds` samt `rating` i
|
||
intervallet 0–10,
|
||
- främmande nyckel från `movie_history.movie_id` till `movie.id`, med
|
||
`NO ACTION` för både update och delete,
|
||
- främmande nyckel från `movie_name_alias.movie_id` till `movie.id`, med
|
||
`NO ACTION` för både update och delete, och
|
||
- unikhet för kombinationen
|
||
`movie_name_alias(movie_id, normalized_alias)`.
|
||
|
||
**Verifierat faktum:** Det finns ingen `CHECK`-constraint som begränsar
|
||
`ocr_status` till de statusvärden som koden använder (`pending`, `completed`,
|
||
`manual_review` och `failed`).
|
||
|
||
**Verifierat faktum:** Främmande nycklar är del av schemat, men SQLite kräver
|
||
att `PRAGMA foreign_keys = ON` aktiveras per anslutning för enforcement.
|
||
`import_site.py` aktiverar detta. De övriga inspekterade Python-skripten gör
|
||
det inte. Detta är ett runtime-beteende, inte en strukturell skillnad mellan
|
||
produktionsschemat och Git-schemat.
|
||
|
||
### 2.4 Triggers och views
|
||
|
||
**Verifierat faktum:** Produktionsdatabasen innehåller inga triggers och inga
|
||
views. Historisering görs uttryckligen av `import_site.py`, inte av en trigger.
|
||
|
||
## 3. Schema som kan återskapas från Git i dag
|
||
|
||
**Verifierat faktum:** När `scripts/schema.sql` läses i en tom SQLite-databas
|
||
skapas `movie`, `movie_history`, `movie_name_alias`, den interna
|
||
`sqlite_sequence`, samma fyra explicita index och samma två automatiska index
|
||
som i produktionen. Inga triggers eller views skapas.
|
||
|
||
Det återskapade Git-schemats `movie` är:
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS movie (
|
||
id TEXT PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
aka TEXT,
|
||
nationality TEXT,
|
||
age INTEGER,
|
||
shoot_location TEXT,
|
||
shoot_date TEXT,
|
||
duration_seconds INTEGER,
|
||
description TEXT,
|
||
rating REAL,
|
||
web_url TEXT,
|
||
thumbnail TEXT,
|
||
published TEXT,
|
||
updated TEXT,
|
||
|
||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
modified_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
||
CHECK (age IS NULL OR age >= 0),
|
||
CHECK (rating IS NULL OR rating BETWEEN 0 AND 10),
|
||
CHECK (duration_seconds IS NULL OR duration_seconds >= 0)
|
||
);
|
||
```
|
||
|
||
Git-schemats `movie_history` och `movie_name_alias` är semantiskt identiska
|
||
med definitionerna i avsnitt 2.1. De omfattar redan samtliga fyra
|
||
OCR-kolumner i `movie_history`. Git skapar även exakt de fyra explicita index
|
||
som visas i avsnitt 2.2.
|
||
|
||
**Verifierat faktum:** `IF NOT EXISTS` i Git-filen men inte i den lagrade
|
||
produktionsdefinitionen är ingen strukturell skillnad i den skapade databasen.
|
||
|
||
**Verifierat faktum:** Git innehåller ingen versionsmarkör eller
|
||
migrationshistorik. En nyskapad databas får `user_version = 0`, precis som den
|
||
inspekterade produktionsdatabasen.
|
||
|
||
## 4. Exakt jämförelse
|
||
|
||
### 4.1 Skillnader
|
||
|
||
**Verifierat faktum:** Den fullständiga strukturella skillnaden är fyra
|
||
kolumner som finns i produktionens `movie` men saknas i Git-schemats `movie`:
|
||
|
||
| Position i produktion | Kolumn | Typ | `NOT NULL` | Standardvärde | Saknas från Git |
|
||
| ---: | --- | --- | --- | --- | --- |
|
||
| 16 | `ocr_status` | `TEXT` | ja | `'pending'` | ja |
|
||
| 17 | `ocr_raw_text` | `TEXT` | nej | inget | ja |
|
||
| 18 | `ocr_error` | `TEXT` | nej | inget | ja |
|
||
| 19 | `ocr_processed_at` | `TEXT` | nej | inget | ja |
|
||
|
||
Konsekvenserna är konkreta:
|
||
|
||
- nya `movie`-rader från `import_site.py` förlitar sig på standardvärdet
|
||
`'pending'`,
|
||
- OCR-kön kan inte läsa `ocr_status` eller `ocr_error`,
|
||
- OCR-resultat kan inte lagras, och
|
||
- `import_site.py` kan inte läsa en komplett rad för arkivering till
|
||
`movie_history`.
|
||
|
||
### 4.2 Element som inte skiljer sig
|
||
|
||
**Verifierat faktum:** Följande matchar exakt semantiskt mellan produktion och
|
||
en tom databas skapad från Git:
|
||
|
||
- tabellerna `movie_history` och `movie_name_alias`, inklusive samtliga
|
||
kolumner, ordning, typer, nullbarhet och standardvärden,
|
||
- alla `movie`-kolumner som föregår OCR-kolumnerna,
|
||
- samtliga tre `CHECK`-constraints på `movie`,
|
||
- båda främmande nycklarna och deras `NO ACTION`-beteende,
|
||
- unikhetskravet på `(movie_id, normalized_alias)`,
|
||
- samtliga fyra explicita index,
|
||
- båda automatiska unika indexen,
|
||
- avsaknaden av triggers, och
|
||
- avsaknaden av views.
|
||
|
||
**Verifierat faktum:** Inga tabeller, index, constraints, triggers eller views
|
||
finns endast i Git-schemat. Den interna tabellen `sqlite_sequence` uppstår i
|
||
båda fallen och är inte ett gap.
|
||
|
||
## 5. Skriptberoenden per schemaelement
|
||
|
||
### 5.1 `movie`
|
||
|
||
| Skript | Lästa eller skrivna element | Förutsättning |
|
||
| --- | --- | --- |
|
||
| `import_site.py` | Alla 20 produktionskolumner läses för befintliga poster; webbägda fält och `modified_at` uppdateras; nya poster infogas. | Kräver de fyra OCR-kolumnerna för `SELECT` och historisering. Nya poster förlitar sig på `ocr_status DEFAULT 'pending'`. Förlitar sig även på `movie_history`, dess OCR-kolumner och obligatoriska `change_summary`. Aktiverar främmande nycklar. |
|
||
| `migrate_csv.py` | Läser `id`; infogar eller uppdaterar alla metadatafält samt `ocr_status`; uppdaterar `modified_at`. | Kräver `ocr_status`. Anger status explicit och förlitar sig därför inte på dess default i sina egna inserts. Skriptet aktiverar inte främmande nycklar. |
|
||
| `check_ocr.py` | Läser `name`, `thumbnail`; skriver `nationality`, `shoot_location`, `shoot_date`, samtliga fyra OCR-kolumner och `modified_at`. | Kräver alla fyra OCR-kolumnerna. Använder statusvärdena `completed`, `manual_review` och `failed`, men schemat validerar inte statusdomänen. |
|
||
| `process_pending_ocr.py` | Läser `id`, `name`, `published`, `ocr_status`, `ocr_error`. | Kräver `ocr_status` och `ocr_error`; förutsätter att nya obehandlade poster får status `pending`. |
|
||
| `match_filenames.py` | Läser `id`, `name`, `duration_seconds`. | Fungerar mot både produktions- och Git-schemat. |
|
||
| `test_duration_matching.py` | Läser `id`, `name`, `duration_seconds`. | Fungerar mot både produktions- och Git-schemat. |
|
||
|
||
### 5.2 `movie_history`
|
||
|
||
| Skript | Beroende |
|
||
| --- | --- |
|
||
| `import_site.py` | Infogar den föregående kompletta `movie`-raden, inklusive samtliga OCR-fält, samt `change_source` och `change_summary`. `archived_at` lämnas till standardvärdet. |
|
||
| `match_filenames.py` | Läser `movie_id`, `duration_seconds` och `archived_at`. |
|
||
| `test_duration_matching.py` | Läser `movie_id` och `duration_seconds`. |
|
||
|
||
**Verifierat faktum:** Inget skript förutsätter en historiseringstrigger;
|
||
`import_site.py` gör historik-insert och film-update i samma anslutningskontext.
|
||
|
||
### 5.3 `movie_name_alias`
|
||
|
||
| Skript | Beroende |
|
||
| --- | --- |
|
||
| `match_filenames.py` | Läser `movie_id`, `alias`, `normalized_alias` och `source`. |
|
||
|
||
**Verifierat faktum:** Inget inspekterat skript skriver alias. Constraints och
|
||
index på aliastabellen finns ändå likadant i produktion och Git.
|
||
|
||
## 6. Minsta säkra åtgärd
|
||
|
||
**Rekommendation:** Gör en enda avgränsad schemaändring: lägg till de fyra
|
||
verifierade produktionskolumnerna i `movie` i `scripts/schema.sql`, direkt
|
||
efter `modified_at`, med exakt följande definitioner:
|
||
|
||
```sql
|
||
ocr_status TEXT NOT NULL DEFAULT 'pending',
|
||
ocr_raw_text TEXT,
|
||
ocr_error TEXT,
|
||
ocr_processed_at TEXT,
|
||
```
|
||
|
||
Detta är den minsta åtgärden eftersom alla andra tabeller, index och
|
||
constraints redan reproduceras. Lägg inte samtidigt till triggers, views,
|
||
nya index, status-`CHECK`, ändrad främmande-nyckelpolicy eller andra
|
||
modellförbättringar; sådana ändringar skulle gå utöver att återskapa dagens
|
||
produktion.
|
||
|
||
**Rekommendation:** Behandla den korrigerade `schema.sql` som ett komplett
|
||
grundschema för helt nya databaser. Verifiera det först mot en ny temporär
|
||
databas. Kör inte den kompletta filen som en påstådd reparationsmigration mot
|
||
produktion: `CREATE TABLE IF NOT EXISTS` kompletterar inte kolumner i en redan
|
||
existerande tabell.
|
||
|
||
**Antagande:** Produktionsdatabasen behöver ingen strukturändring för detta
|
||
gap, eftersom den redan har exakt de fyra föreslagna kolumnerna. Den behöver
|
||
endast adopteras av en framtida migrationsmodell efter separat kontroll.
|
||
|
||
## 7. Enkel migrationsmodell för SQLite
|
||
|
||
**Rekommendation:** Inför en liten, versionshanterad modell med två delar:
|
||
|
||
1. `schema.sql` är aktuell baseline för nya, tomma databaser.
|
||
2. En katalog med ordnade migrationer, exempelvis
|
||
`migrations/0001_add_movie_ocr_columns.sql`, beskriver övergångar för äldre
|
||
databaser.
|
||
|
||
Använd en liten Python-runner baserad på standardbibliotekets `sqlite3` och en
|
||
tabell som exempelvis:
|
||
|
||
```sql
|
||
CREATE TABLE schema_migration (
|
||
version INTEGER PRIMARY KEY,
|
||
name TEXT NOT NULL UNIQUE,
|
||
applied_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
Runnern bör:
|
||
|
||
- öppna en explicit vald databas och aldrig ha produktionssökvägen som dold
|
||
teststandard,
|
||
- aktivera `PRAGMA foreign_keys = ON`,
|
||
- kontrollera befintliga kolumner med `PRAGMA table_xinfo` före migration,
|
||
- köra varje ej registrerad migration atomärt i en transaktion,
|
||
- registrera versionen först när hela migrationen lyckats,
|
||
- avbryta om databasen har en okänd eller partiell struktur, och
|
||
- köra `PRAGMA foreign_key_check` efteråt.
|
||
|
||
**Rekommendation:** Eftersom SQLite saknar portabelt
|
||
`ALTER TABLE ... ADD COLUMN IF NOT EXISTS` ska migration `0001` inte bara
|
||
exekveras blint. Runnern ska skilja mellan tre tillstånd:
|
||
|
||
- inga OCR-kolumner finns: lägg till alla fyra och registrera migrationen,
|
||
- alla fyra finns med exakt rätt typ, nullbarhet och default: registrera
|
||
migrationen som adopterad utan att ändra tabellen, och
|
||
- endast några finns eller definitionerna avviker: avbryt och kräv manuell
|
||
analys.
|
||
|
||
Exempel på de faktiska övergångssatserna för ett äldre Git-skapat schema är:
|
||
|
||
```sql
|
||
ALTER TABLE movie
|
||
ADD COLUMN ocr_status TEXT NOT NULL DEFAULT 'pending';
|
||
ALTER TABLE movie
|
||
ADD COLUMN ocr_raw_text TEXT;
|
||
ALTER TABLE movie
|
||
ADD COLUMN ocr_error TEXT;
|
||
ALTER TABLE movie
|
||
ADD COLUMN ocr_processed_at TEXT;
|
||
```
|
||
|
||
**Rekommendation:** Använd antingen `schema_migration` eller konsekvent
|
||
`PRAGMA user_version` som versionskälla, inte två oberoende sanningar. Tabellen
|
||
är enklare att granska eftersom den även lagrar migrationsnamn och tidpunkt.
|
||
Nuvarande `user_version = 0` visar att ingen versionsmodell används i dag.
|
||
|
||
## 8. Verifieringsplan utan produktionsskrivningar
|
||
|
||
Alla steg nedan ska använda en ny katalog skapad med `mktemp -d` och en ny
|
||
databasfil där. Ingen testparameter får peka på
|
||
`/storage/disk1/WCX/database/wcx.db`.
|
||
|
||
1. **Skydda referensen.** Registrera produktionsdatabasens filstorlek, mtime
|
||
och kryptografiska hash före testet. Använd därefter endast
|
||
`sqlite3 -readonly` om referensmetadata behöver läsas igen.
|
||
2. **Skapa baseline.** Skapa `${TEMP_DIR}/wcx.db` från den korrigerade
|
||
`schema.sql`.
|
||
3. **Jämför struktur maskinellt.** Jämför tabeller, kolumner, ordning, typer,
|
||
nullbarhet, defaults, primärnyckelflaggor, främmande nycklar, explicita och
|
||
automatiska index, indexkolumner, triggers och views mot den dokumenterade
|
||
produktionsmodellen. Ignorera endast harmlösa skillnader i SQL-format och
|
||
`IF NOT EXISTS`.
|
||
4. **Kontrollera constraints.** Verifiera att ogiltig negativ `age` och
|
||
`duration_seconds`, rating utanför 0–10, dubblett av
|
||
`(movie_id, normalized_alias)` samt främmande nyckelbrott med
|
||
`PRAGMA foreign_keys = ON` nekas i temporärdatabasen.
|
||
5. **Kontrollera defaults.** Infoga en minimal `movie(id, name)` och verifiera
|
||
att `created_at` och `modified_at` sätts samt att `ocr_status = 'pending'`
|
||
och övriga OCR-fält är `NULL`.
|
||
6. **Testa import.** Kör `import_site.py` med explicit `--input` till en liten
|
||
temporär JSON-fil och explicit `--database` till temporärdatabasen. Kör en
|
||
andra import med nyare uppdateringsdatum och verifiera att
|
||
`movie_history` får hela den föregående raden inklusive OCR-fälten.
|
||
7. **Testa CSV-operationer.** Kör först `migrate_csv.py --dry-run`. Om apply
|
||
behöver provas, använd endast explicit `--database` till temporärdatabasen
|
||
och en isolerad test-CSV.
|
||
8. **Testa OCR:s databasberoenden utan API-anrop.** Anropa de rena
|
||
databasfunktionerna i `check_ocr.py` mot temporärdatabasen för statusfallen
|
||
failed, manual review och completed. Verifiera alla skrivna metadata- och
|
||
OCR-kolumner. Testa `process_pending_ocr.py`-frågorna separat så att
|
||
`pending` kan väljas och `ocr_error` läsas. Gör inget Vision API-anrop.
|
||
9. **Testa konsumentfrågorna.** Kör matchningskodens databasladdning och
|
||
längddiagnostik mot temporärdatabasen efter att film, alias och historik har
|
||
lagts in.
|
||
10. **Testa migrationsvägen separat.** Skapa ytterligare en ny temporär
|
||
databas från dagens ofullständiga Git-schema, kör migration `0001`, och
|
||
upprepa strukturjämförelsen. Testa även adoption mot en ny databas som
|
||
redan har alla fyra korrekta kolumner samt avbrott mot ett medvetet
|
||
partiellt schema.
|
||
11. **Kör integritetskontroller.** Kräv `PRAGMA integrity_check = 'ok'` och
|
||
tomt resultat från `PRAGMA foreign_key_check` i varje temporärdatabas.
|
||
12. **Bekräfta produktionsskyddet.** Kontrollera att produktionens storlek,
|
||
mtime och hash är identiska med värdena från steg 1. Radera därefter endast
|
||
den explicit skapade temporärkatalogen.
|
||
|
||
**Rekommendation:** Godkänn reproducerbarheten först när både baseline-vägen
|
||
och migrationsvägen ger samma semantiska schema som avsnitt 2 och samtliga
|
||
databasberoende skript klarar sina isolerade operationer mot temporärdata.
|
||
|
||
## 9. Slutsats
|
||
|
||
**Verifierat faktum:** Facitdatabasen är nästan, men inte helt,
|
||
reproducerbar från Git. Gapet består exakt av fyra OCR-kolumner i `movie`.
|
||
Inga tabeller, index, constraints, triggers eller views saknas utöver dessa
|
||
kolumndefinitioner, och inga sådana objekt skiljer sig i övrigt.
|
||
|
||
**Rekommendation:** Komplettera baselineschemat med de fyra exakta
|
||
produktionsdefinitionerna och inför en liten, transaktionell migrationsrunner
|
||
som kan migrera äldre Git-skapade databaser och adoptera en redan korrekt
|
||
produktion utan att ändra den. All utveckling och verifiering ska ske mot nya
|
||
temporära databaser.
|