ADR 0006. Layer naming scheme

Status: accepted. Implemented 2026-08-24 in kevinvu184/up. Renames artifacts from ADR 0001 through 0004; those records keep their original names as history.

Context

The codebase had grown asymmetric names. The first cron and its state were unprefixed legacy (poller.cron.ts, lock, last_sync_at, last_run_rows, makeRunState), later jobs got verb prefixes (promote_*, and champion_* planned), while the tables and row types were layer-named all along (bronze_transactions, SilverRecord). Two candidate schemes were on the table: name everything by job verb (poll_*, promote_*, champion_*) or by medallion layer (bronze_*, silver_*, gold_*).

Decisions

The layer scheme

Tables and row types cannot sensibly become verbs, so the verb scheme would leave the data side permanently inconsistent. The layer scheme makes every artifact line up behind the layer it belongs to, and each cron maps one-to-one onto the layer it produces: bronze.cron.ts fills bronze_transactions, silver.cron.ts fills silver_transactions via silverTransform.ts, gold.cron.ts fills the gold_* tables via goldModels.ts.

What carries a layer prefix and what does not

Layer-owned artifacts take the prefix: the three crons, the two interpretation files, the state factories (makeBronzeState, makeSilverState, makeGoldState), the stores (bronzeStore, silverStore, rebuildGoldModel, and the three lock stores). Shared infrastructure keeps concern names, because it belongs to no layer: upClient.ts, lock.ts, state.ts, migrations.ts, sqliteStore.ts.

Symmetric meta keys

Every layer now records the same key shapes: <layer>_lock (seeded by migrations), <layer>_last_run (overwritten on success), <layer>_last_error (written on failure, cleared on the next success), plus one progress key where the layer needs one (bronze_cursor, silver_watermark; gold's skip memory lives inside gold_last_run). The old poller pair last_sync_at plus last_run_rows merged into a single bronze_last_run record so all three layers read alike in the panel.

The rename shipped as migration 5, not as edits to old migrations

The decisive fact, discovered by reading the live database before touching anything: the pipeline had already run. Schema stood at version 4, backfill had reached its sentinel, and the seeded lock rows existed under their old names. Rewriting the seed statements inside migrations 1 and 4 would have left the live rows orphaned and silently broken locking, because an applied migration never runs again. So migrations 1 through 4 are frozen exactly as applied, and migration 5 renames the live keys: UPDATE for each rename, one INSERT SELECT to merge the two poller keys into bronze_last_run, then a DELETE of the merged pair. A fresh database reaches the same end state by seeding the old names and renaming them, which is what a remixed val does on its first run.

Consequences

Naming is now mechanical: given any artifact, its prefix says which layer owns it, and given a layer, the full set of its artifacts is predictable. The general principle this change hardened is worth stating on its own: applied migrations are frozen, and history only grows by appending. The ADRs before this one keep the old names in their text; they were true when written, and this record is the bridge. The one residual asymmetry is deliberate: shared infrastructure stays unprefixed, because forcing a layer name onto a module every layer uses would be a lie.