The mariadb profile
Status (2026-11-01): a selectable app profile; local evidence only. kit template:prune --profile mariadb selects it: it sets PROFILE in config/src/env.ts, copies this folder's conusai.json,
compose.yaml and compose.dev.yaml to the app root and moves its migrations/ to migrations/mariadb/.
Every process then reads the one MARIADB_URL login and REDIS_URL (config/src/env.ts); the web
(apps/web/src/server/dialect/) and the worker and migrate job (apps/worker/src/dialect/) compose the
mariadb data source, BullMQ on Valkey and the kit's outbox relay. The modules account, notes and
documents ship mariadb migrations and adapters, so self-service account export and erasure work here;
agent and knowledge declare Postgres only and are not part of an app on this profile. Live collections
run (the migrate job adds the live log and a row trigger per collection; the web polls it every second). Module cron: the worker installs the modules' schedules that this profile ships as BullMQ job
schedulers when it starts (apps/worker/src/dialect/schedules.ts: files.recover and the account
sweeps, commit 589a06079; the agent module's run retention is Postgres-only). Source and local
tests only; no live worker has run them yet. No generated app on this profile has been deployed on a
ConusAI install. Adapter, migration and live-conformance tests do not establish a deployment.
The persistence profile for an app whose primary store is MariaDB instead of Postgres
(spec §5.1): the same procedures, the same modules, the same migrations discipline, on the
Kysely MySQL dialect (@conusai-kit/data-kysely/mysql) with jobs on BullMQ over Valkey
(@conusai-kit/jobs-bullmq).
| postgres (default) | mariadb (this profile) | |
|---|---|---|
| Primary store | Postgres 18 | MariaDB LTS |
| Data adapter | @conusai-kit/data-kysely |
@conusai-kit/data-kysely/mysql |
| Jobs | pg-boss (Postgres) | BullMQ on Valkey (@conusai-kit/jobs-bullmq) |
| Tenant wall | repository predicate + RLS (three roles, AT-D20) | repository predicate alone — MariaDB has no RLS |
| Links | postgres, identity |
mariadb, redis, identity |
| Process env | POSTGRES_URL, POSTGRES_READER_URL, POSTGRES_MIGRATE_URL |
MARIADB_URL, REDIS_URL |
Registered modules must support the selected persistence profile. The web, worker and migrate compositions refuse unsupported modules by name before connecting to services; their data-rights hooks and adoption history are preserved. Fresh backend conformance fixtures explicitly author a compatible module set for both the registry and data source. These fixtures do not migrate an adopted starter or establish a public scaffold command; the existing CLI custody refusal still applies.
What this folder carries
conusai.json— the profile's deployment contract: the same three services, linksmariadb+redis+identity. There is nomariadb.migrateflag: the three-role split it declares on Postgres (data-services DS21) exists to make row-level security bind, and MariaDB has none, so every process holds the oneMARIADB_URLand ordering is compose'sservice_completed_successfullyalone (AT-D14).compose.yaml,compose.dev.yaml— the profile's compose files. The dev overlay runsmariadb:ltsandvalkey/valkey:9-alpine, the images the platform's catalog offers.migrations/— the kernel tables (tenancy, sign-in, files, proposals, outbox, idempotency keys) as MariaDB DDL, generated bymysqlSchemaStatementsfrom the same entity definitions as the postgres files and committed, forward-only. A module's own MariaDB migrations live with the module:modules/<id>/adapters/kysely/mysql/migrations/, declared in its manifest asmigrations: { postgres: "migrations", mariadb: "adapters/kysely/mysql/migrations" }.
The composition (wired)
The profile is chosen at generation time (.conusai/template.json profile), and
kit template:prune --profile mariadb removes the other dialects. What it selects:
config/src/env.tsreadsMARIADB_URL(every process) instead of the three Postgres URLs -- one login, because there is no RLS to bind (AT-D20 is Postgres-only) -- andREDIS_URLfor the queue.apps/web(src/server/dialect/stores.ts) andapps/worker(src/dialect/runtime.ts) composecreateMysqlDataSource(KyselyMysqlDialect+mysql2) and the queue iscreateBullmqJobQueue/createBullmqWorkeroverREDIS_URL, with the kit's outbox relay over the same data source.- The migrate job (
apps/worker/src/migrate.ts,migrateDialect) runsrunMysqlMigrationsFromEnvovermigrations/mariadb/(moved there from this folder) and the modules'adapters/kysely/mysql/migrations/, then the job adapter's table and the live capture of each collection. - This folder's
conusai.json,compose.yamlandcompose.dev.yamlreplace the root ones.
The dialect differences are explicit, never generated at run time (spec §5.3): a module's MariaDB schema is its own numbered SQL folder, reviewed like any migration.
Proof
@conusai-kit/data-kysely's MySQL dialect passes the shared repository and capability contract suites againstmariadb:ltsin testcontainers — the tenant predicate is the wall, and the contract is what proves it.@conusai-kit/jobs-bullmqpasses the shared JobQueue contract againstvalkey/valkey:9in testcontainers.scripts/vendor/test-template.sh app --profile mariadbruns the template's gate and then applies this folder's and the modules' MariaDB migrations to a real MariaDB LTS container, so the committed DDL is proven against the engine, not just generated.