The "upstream_is_stream" JSON key flows from the AI execution report
context producer (aether-ai-serving::report_context) through several
consumers — usage runtime metadata copy/move, gateway watchdog, sync
execution decision, observability handlers, and the per-driver usage
repositories. Each site spelled the key as a bare string literal, so a
producer-side rename would silently degrade every consumer to its
fallback (typically assuming streaming) with no compile-time signal.
Introduce a single pub const UPSTREAM_IS_STREAM_KEY in
aether-ai-formats (the lowest crate every consumer already depends on),
re-export from the crate root, and route producer + all map-style
consumers through it. The change is purely a string-literal → constant
swap; behaviour is identical.
Sites left as literals (intentional):
- `json!({"upstream_is_stream": ...})` macro keys, which must be string
literals at the macro layer; these are also API-response payload
field names (an external contract that should not silently track
internal report-context renames).
- SQL column accessors (`try_get::<...>("upstream_is_stream")`), which
refer to the database schema column, not the JSON key.
- Test fixtures and assertions, which validate the on-the-wire contract
and should keep verifying the actual string.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
aether-data
aether-data is the runtime data-access crate. It owns concrete SQL/database
drivers, concrete repository implementations, migration/backfill/export
workflows, and the composition layer that wires those pieces into the rest of
the application.
It does not own the cross-crate DTO contracts. Shared repository records and
errors that are consumed by scheduler, billing, admin, usage runtime, and video
task crates live in ../aether-data-contracts.
Directory Map
| Path | Responsibility |
|---|---|
src/database.rs |
Logical SQL driver selection and shared pool configuration. |
src/config.rs |
Data-layer config for SQL drivers and repository wiring. |
src/maintenance.rs |
Maintenance DTOs and aggregation summaries used by backend dispatch and runtime maintenance entrypoints. |
src/driver/{postgres,mysql,sqlite} |
Low-level SQL driver primitives such as pools, transactions, and leases. These modules should not contain domain repository logic. |
src/repository |
Domain repository traits/types re-exported from contracts plus concrete in-memory/Postgres/MySQL/SQLite implementations. |
src/backend |
Composition root. Builds concrete driver backends and exposes app-facing read/write/worker/lock/lease handles. |
src/backend/{maintenance,stats,wallet,system}.rs |
Backend-owned maintenance, aggregation, wallet ledger, and system config workflows that are not normal request-path repositories. |
src/lifecycle/migrate.rs and src/lifecycle/migrate/* |
Runtime migration entry points and migration-specific tests/helpers. |
src/lifecycle/backfill.rs |
Backfill entry points and backfill discovery. |
src/lifecycle/export.rs |
Cross-database export/import workflows. |
migrations/{postgres,mysql,sqlite} |
Executable sqlx migrations embedded at compile time. |
schema |
Schema maintenance workspace for logical definitions, driver fragments, and generated output. |
schema/logical |
Human-maintained logical table definitions used by aether-data-schema. |
schema/drivers/{postgres,mysql,sqlite} |
Human-maintained driver fragments that compose back into executable SQL while generation is being promoted. |
schema/bootstrap/postgres |
Human-maintained source fragments for the Postgres bootstrap snapshot. build.rs composes them into the runtime embedded artifact during crate builds. |
schema/generated |
Machine-written SQL generated from logical schema for audit and drift detection only. |
schema/overrides |
Rare driver-specific SQL escape hatch. Keep README-only until a real override is needed. |
backfills/{postgres,mysql,sqlite} |
Executable backfill SQL grouped by driver. |
Layering Rules
The crate is easiest to read as five layers:
- Contracts: DTOs, input structs, repository traits, and
DataLayerError. Preferaether-data-contractsfor anything that another crate needs to compile against. - Driver primitives:
driver/postgres,driver/mysql, anddriver/sqliteconnect to infrastructure and expose pools/runners. - Repository implementations:
repository/<domain>/{sql,mysql,sqlite,memory}translate contract types to driver-specific SQL. - Backend composition:
backendchooses one SQL driver from config and wires repository implementations into app-facing handles. Backend-owned runtime maintenance workflows live in focused backend modules rather than in the driver pool files. - Maintenance workflows:
lifecycle/migrate,lifecycle/backfill,lifecycle/export, andschemamanage database lifecycle outside normal request handling.
Do not add domain queries to low-level pool modules. Do not add driver selection
logic inside individual repository implementations. Keep cross-crate contracts
out of aether-data unless they are implementation-only.
Repository Layout
Most domain repositories use this shape:
src/repository/<domain>/
mod.rs # exports trait/type names and concrete implementations
types.rs # implementation-local DTOs when they are not already in contracts
postgres.rs # Postgres implementation
mysql.rs # MySQL implementation
sqlite.rs # SQLite implementation
memory.rs # tests/dev in-memory implementation
Use explicit driver filenames for repository implementations. Do not introduce
new generic sql.rs modules for driver-specific code.
SQL Driver Policy
The project supports three SQL drivers at the repository/backend boundary: Postgres, MySQL, and SQLite. That does not mean every raw SQL file is shared. The portable contract is the Rust shape and behavior; the physical SQL stays driver-specific where syntax, indexes, JSON support, timestamps, locking, or upsert semantics differ.
Use logical types in design docs and reviews:
| Logical type | Postgres | MySQL | SQLite |
|---|---|---|---|
json |
json or jsonb |
json or text JSON |
text JSON |
bool |
boolean |
boolean / tinyint(1) |
integer |
time_unix |
bigint or legacy timestamp |
bigint |
integer |
money_decimal |
numeric / legacy double |
double |
real |
jsonb is acceptable only in Postgres SQL. MySQL and SQLite migrations must not
contain jsonb; this is guarded by migration tests. Prefer serde_json::Value
or typed Rust structs at the repository boundary so callers do not depend on the
physical storage type.
Schema Maintenance
Executable migrations stay under migrations/{postgres,mysql,sqlite} because
sqlx::migrate! embeds those paths and existing deployments record those file
versions.
The large baseline SQL files are maintained through schema fragments. New
table-structure work should start in schema/logical/*.toml and be generated
into driver-specific SQL before it is composed into executable migrations:
bash crates/aether-data/schema/compose_schema.sh generate
bash crates/aether-data/schema/compose_schema.sh compose
bash crates/aether-data/schema/compose_schema.sh check
schema/generated/** is machine-written by aether-data-schema; it is checked
in to make generator drift reviewable, not because runtime reads it. Edit
schema/logical/*.toml instead. Use schema/overrides/** only as an exception
bucket for driver-specific SQL that cannot live cleanly in logical schema or the
normal driver fragments.
compose_schema.sh check also verifies that required baseline/portable
table-creation SQL is represented in schema/logical. This is the guardrail
that keeps table structure from drifting back into three manually maintained
definitions.
For executable fragments that have not been promoted to generated output yet,
edit fragments under schema/drivers/{postgres,mysql,sqlite} directly, run
compose, then run check. Do not edit baseline executable SQL and fragments
independently.
When adding a table:
- Add or update
schema/logical/*.tomlfirst for the table structure. - Run
schema/compose_schema.sh generateand inspect the generated driver SQL. - Add or update the executable driver-specific migration/fragments only for deployment compatibility or generator gaps.
- Add or update repository contracts in
aether-data-contractsif other crates need the new shape. - Add driver repository implementations only for the drivers that are actually supported for that domain.
- Wire new repositories through
src/backend/read.rsorsrc/backend/write.rsonly after the implementation exists for each selected driver. - Update
docs/architecture/data-schema-inventory.mdfor new tables or logical type changes.
Known Cleanup Targets
These are intentionally staged to keep the multi-database refactor reviewable:
- Group repository domains once file-level names are stable. Likely groups: identity, auth config, provider catalog, runtime tasks, wallet/billing, usage, stats, and proxy nodes.
- Continue shrinking Postgres stats SQL modules where useful by moving shared
SQL fragments and row-mapping helpers behind focused
backend/stats/*modules. - Consider a later crate split only after module boundaries are stable. The likely split is schema/migration tooling versus runtime repository backends, not an ORM rewrite.