# MariaDB standardisation and RD21 qualification MariaDB is the intended application database for development, integration testing, CI and production. SQLite remains only as an explicit, temporary preservation/legacy-test path. Missing MariaDB configuration and unknown provider names fail closed; they never select SQLite. This work starts at RD21 repair `0d16a691e0249fe1264bf960de5e59eaa9b87fed` on `codex/rd21-mariadb-standardisation`. The [final acceptance record](ACCEPTANCE.md) separates runtime, Docker/CI, retirement and release decisions. No main merge or production operation is authorized. ## Version and image identity The product owner verified production **12.2.2-MariaDB-ubu2404**, using floating `mariadb:latest`, with running image configuration ID `sha256:e0236fc6386e7eacd9359e59d0a078bd7aa0d18280d36d13061121bedeaee903`. This is not a pullable registry manifest digest. The repository previously advertised11.0.0 and used a floating11 client image; those facts did not identify production. The standalone Compose database uses the official **Linux/amd64** image: ```text mariadb:12.2.2@sha256:42a27baaba3e8f3f0159d16381d5a7156661aa91dcc65f663cabb6a6f9503c00 ``` The manifest, configuration and every image layer were downloaded from the official registry and SHA256/size verified. [Image provenance](image-provenance.json) records the corresponding config digest `sha256:dec309ca7f95ab98e85fcf56acb9c4f05da59125c09d9e60a521f15f12643d7b` and Ubuntu24.04 package version. It differs from production's reported image ID: same server patch is not exact production image equality. Other architectures need their own verified official manifest; do not reuse this amd64 digest while claiming another platform. Use explicit version plus verified digest for a future operator-reviewed production change. `latest` can move independently of application releases. This task does not change, pull into, restart, upgrade, downgrade or recreate production. [12.3 LTS investigation](MARIADB_12_3_PLAN.md) recommends a separate pinned12.3.3 rehearsal, including isolation, driver and recovery gates. ## Persistent local development Prerequisites: working local Docker engine/Compose, pinned .NET9.0.317 and existing frontend dependencies. From the repository root: ```powershell rtk proxy docker compose -f docker-compose.database.yml up -d --wait mariadb-dev rtk proxy C:/Users/Connor/.dotnet/dotnet.exe run --project JobTrackerApi/JobTrackerApi.csproj ``` On other hosts use the installed pinned `dotnet`. The database is loopback-only port33306, database/user `jobtracker_dev`, development password `jt-development-only`. The root password `jt-development-root-only` belongs only to this standalone development service. The API's Development settings match these values; authentication settings remain separately required. Use local user secrets for non-placeholder auth/admin configuration. Do not copy these credentials or volumes to production. Environment overrides such as `ConnectionStrings__JobTracker` take precedence. The standalone Compose project `jobtracker-local` owns the persistent `mariadb_dev` volume. It is separate from the production Compose file, networks, `.env` and application data volumes. A fresh database is initialized by the API's existing migration/reconciliation startup. Repeated/populated startup must preserve rows. Do not run `down -v` to resolve a schema problem, delete the dev volume, or point tests at an existing user database. Stop the service without removing data if needed: ```powershell rtk proxy docker compose -f docker-compose.database.yml stop mariadb-dev ``` There is intentionally no automatic destructive development reset. Export/backup and verify required data before an independently authorized reset. Existing SQLite files are not opened, imported, deleted or replaced by selecting the new default. To preserve/read an old database temporarily, explicitly set `Database__Provider=sqlite` and its existing `ConnectionStrings__JobTracker` path; do not confuse the Development MariaDB connection string with a SQLite connection string. ## Disposable MariaDB integration lane ```powershell rtk proxy python scripts/test-mariadb-integration.py rtk proxy python scripts/mariadb-integration.py ``` The second command creates a generated `jobtracker-it-` Compose project, allocates an available loopback port and starts only `mariadb-test` with the pinned image and health checks. Its data is private tmpfs, never the persistent development volume. The test administrator password is separately `jt-integration-only`; no production credentials are needed. Each test creates a new `jt_it_` schema and drops only that fixture's successfully-created schema. Tests initialize the actual startup schema; they do not patch missing tables with `EnsureCreated`. The runner performs locked restore and the complete `JobTrackerApi.MariaDb.Tests` project. Missing, unreachable or version-mismatched databases fail the explicit lane, never skip or fall back. Logs, native TRX and command/process outcomes go to `.artifacts/mariadb/integration/`. Cleanup removes only the generated test service, without `down -v`; a cleanup error fails the runner. To reset a manually started disposable service, first verify it is the test service with tmpfs and contains no needed data, then remove only that service: ```powershell rtk proxy docker compose -f docker-compose.database.yml --profile integration rm --stop --force mariadb-test ``` An isolated existing loopback test server can run the identical project: ```powershell $env:JOBTRACKER_MARIADB_TEST_ADMIN='Server=127.0.0.1;Port=33307;User ID=root;Password=jt-integration-only;SslMode=None' rtk proxy python scripts/mariadb-integration.py --existing-server --output .artifacts/mariadb/explicit-integration ``` The fixture rejects remote/non-TCP endpoints, requires exact12.2.2 by default and ignores any selected admin database in favor of a new schema. Loopback is a guard, never permission to tunnel production locally. Alternate `JOBTRACKER_MARIADB_TEST_VERSION` values label a separate compatibility run; they cannot be used to claim12.2.2 parity. The CI workflow uses the Compose command, requires a Docker daemon/Compose and preserves evidence. A checked-in workflow is not proof of a successful hosted run. The first hosted execution, [run843](https://git.cesnimda.uk/cesnimda/jobtrackingapp/actions/runs/843), failed before database tests because the runner job image has no `docker` executable. Hosted CI remains blocked pending an isolated Docker/Compose-capable test runner; local Compose32/32 remains separate evidence. Do not expose a production Docker socket to resolve this prerequisite. The ordinary backend project still contains explicitly transitional SQLite/InMemory unit/legacy contracts. The ordinary browser fixture explicitly selects SQLite until its SQL seed/proof helpers are ported. Those results remain useful but are never labelled MariaDB integration evidence. ## Native MariaDB browser recovery lane Build the API in Release and the UI static export first. Install the official MariaDB command-line client locally and set `JOBTRACKER_MARIADB_CLIENT` to its absolute executable path. This lane uses the pinned test service, never the persistent development database. For a fresh run, use an unused loopback port (33308 below) and a generated project/schema; preserve the resulting JSON report: ```powershell $project='jobtracker-it-browser-'+[Guid]::NewGuid().ToString('N') $db='jt_it_'+[Guid]::NewGuid().ToString('N') $env:MARIADB_TEST_PORT='33308' $env:JOBTRACKER_MARIADB_BROWSER_PORT='33308' $env:JOBTRACKER_MARIADB_CLIENT=(Get-Command mariadb).Source $env:MYSQL_PWD='jt-integration-only' rtk proxy docker compose --project-name $project -f docker-compose.database.yml --profile integration up -d --wait mariadb-test # Check each exit code before continuing. Only a successful CREATE establishes ownership. & rtk proxy $env:JOBTRACKER_MARIADB_CLIENT --host=127.0.0.1 --port=33308 --user=root "--execute=CREATE DATABASE $db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" $env:JOBTRACKER_MARIADB_BROWSER_CONNECTION="Server=127.0.0.1;Port=33308;Database=$db;User ID=root;Password=jt-integration-only;SslMode=None" $env:PLAYWRIGHT_STATIC_EXPORT='true' $env:PLAYWRIGHT_PREBUILT_EXPORT='true' $env:PLAYWRIGHT_JSON_OUTPUT_NAME='../.artifacts/mariadb/browser.json' Push-Location job-tracker-ui rtk proxy node node_modules/@playwright/test/cli.js test --config=playwright.mariadb.config.ts --reporter=list,json Pop-Location # Remove only this generated disposable container after retaining the test result and logs. rtk proxy docker compose --project-name $project -f docker-compose.database.yml --profile integration rm --stop --force mariadb-test ``` The service's tmpfs discards only this run's test schemas when removed. Never substitute an existing project/container or the dev service. The config accepts only the shown canonical connection format, requires the independent SQL reader's port to match and refuses server reuse. The test checks actual `SELECT VERSION()`, retains drafts across 409/network failures and checks durable SQL state before and after reviewed retries, refresh and a new login. Run its boundary checks with `rtk proxy node --test job-tracker-ui/scripts/mariadb-browser-connection.test.mjs`. ## Records - [Implementation plan](PLAN.md), [actual validation](validation.json), [acceptance](ACCEPTANCE.md). - [All baseline SQLite references/classification](sqlite-references.json), [audit summary](SQLITE_AUDIT.md). - [Safe staged retirement](SQLITE_RETIREMENT.md), [three fixture deltas](fixture-delta.json). - [12.3 LTS upgrade/recovery gates](MARIADB_12_3_PLAN.md). Historical Phase9 acceptance and the previous RD21 addendum remain historical records. The three active obsolete fixtures are intentionally corrected under the owner's new instruction; their old failures, exact deltas and before/after hashes are retained. No SQLite package, compatibility schema branch, database or migration history has been removed in this stage.