Join 1242 keyword-split statement lines (COMMENT/CREATE/ALTER/LOAD), move the dc3_identity_audit_log table section and the schema fingerprint INSERT so every script orders DDL before seed data, and drop one duplicated COMMENT ON COLUMN. Statement multiset verified identical apart from the intended dedup. The canonical fingerprint changes to e81e3562... and is synchronized via schema_fingerprint.py --sync into the embedded ddl_hash and all four distribution points; existing initialized databases require re-initialization (flag day).
- migrate auth, manager, data, and agentic persistence to R2DBC
- hard cut pagination, facade, gateway, web, and CLI contracts
- remove legacy database and TSDB adapters and document validation gates
BREAKING CHANGE: platform persistence now requires PostgreSQL 18 with TimescaleDB and the new reactive API contracts.
R2c of docs/design/storage-abstraction.md §3. dc3-db-tck runs one set of
mapper-level assertions against both engines with the real mapper XML —
the databaseIdProvider routing is part of what gets certified, not just
the SQL: the fenced latest-value upsert's three-level tiebreak (fencing
token, then time, then sequence, then message id), the state upsert +
same-transaction re-select shape, the three-step expired-lease claim
(lock-and-read FOR UPDATE SKIP LOCKED, flip offline, derive the
post-update view), the lease upserts' row-local increments (token moves
only on ownership change), the row-level revision triggers (insert and
meaningful update bump, trivial update does not), and the tool-catalog
query's encoded-JSON double-hop.
Harness notes worth keeping: one SqlSessionFactory per database mirrors
the production @DS wiring (state/latest/leases/catalog span four
databases with unqualified table names); the mapper XML loads through
XMLMapperBuilder with the affectData mybatis-plus DTD extension
stripped; postgres registers the timestamptz type handler. Both
harnesses learned to distrust podman: log-following never fires, so
mysql waits on the listening TCP port and postgres polls a real JDBC
handshake (the port opens during initdb's local-only phase and closes
again for the restart). The postgres harness mounts seed files 01..07
individually — 00's age/pgvector extensions exist only in the
production base image — with 05 trimmed at runtime to the
dc3_point_latest projection (the hypertable half needs the base
image's TimescaleDB function set; the dialect contract doesn't touch
it).
The catalog fixture plants its api row through a parameterized insert:
the escaped-JSON value must not travel through SQL text (MySQL's
default backslash escapes eat the \" the value depends on) nor bind
uniformly (PG rejects varchar->json, MySQL rejects the binary
Types.OTHER binding) — the fixture branches on the engine.
Selection guide published at docs/db-dialects.md (dialect matrix,
swap procedure, the semantic decisions the suite locks); the storage
design's status header moves to Implemented with an R1/R2 closeout
note.
Gates: db-tck 16/16 (8 postgres + 8 mysql), data/auth/manager suites
green (274 in data), e2e 26/26, full-repo compile green.
R2b of docs/design/storage-abstraction.md §3:
- PostgreSQL seed: the three driver-lease sequences retire. The fencing
token and assignment version default to 1 and advance row-locally —
per-device / per-driver monotonicity is the property the checks rely
on, which the global sequences only delivered incidentally, and MySQL
has no sequence objects. The three statement-level revision triggers
(transition tables + INSERT .. ON CONFLICT, none of which MySQL can
express) collapse into one row-level function with row-local +1 upserts
— a shape both engines share.
- MySQL seed under dc3/dependencies/mysql/initdb/ (00-06), derived by
dc3/dependencies/mysql/pg2mysql_seed.py: schemas become databases,
TIMESTAMPTZ becomes DATETIME(6), TEXT/JSON literal defaults become
expression defaults (MySQL 8 refuses literal defaults on BLOB/TEXT/
JSON), operate-time plpgsql triggers become the ON UPDATE column
attribute (explicit SETs keep winning), the revision family becomes
row-level DELIMITER triggers, keyed TEXT columns widen to VARCHAR(191),
partial indexes drop, and the embedded-JSON seed values load under
NO_BACKSLASH_ESCAPES (MySQL string literals eat the backslashes the
escaped-JSON content values depend on; PostgreSQL does not). 05 is
hand-maintained: only the dc3_point_latest projection — a MySQL core
requires an external time-series store, so the hypertable and cagg DDL
have no counterpart by design.
- Compose: the optional stack gains a mysql:8.4 service (utf8mb4, seed
mounted at docker-entrypoint-initdb.d, healthcheck); the app stack
passes DC3_DB_TYPE through.
Verified by loading all seven files into a fresh mysql:8.4 container —
zero errors, five databases, table counts aligned with the PostgreSQL
seed (manager 24 incl. common, auth 24, data 12, agentic 6, history 1),
and the revision trigger proven live: insert bumps the owning driver's
revision to 1, an enable_flag flip bumps it again to 2.
Use PostgreSQL leases and fencing for distributed ownership, require a durable SQLite outbox before RabbitMQ publication, and make Data Center ingestion transactional and idempotent.
BREAKING CHANGE: drivers require mandatory durable outbox configuration and use lease-aware ownership and telemetry contracts.