Documentation
¶
Overview ¶
Package migration implements the ADR-0019 S1 data migration and the endgame P1 on-open auto-upgrade: it replays or converges a tq fact journal — the hand-rolled store's database — onto the go-cqrs-lite engine store and verifies projection equality (StatusCounts, ProjectCounts, the full fact stream, head seq, per-task fact tails, DLQ contents, watermarks, priority scores). It is the dogfood cutover's definition-of-done gate (ADR-0019 §Data migration; C12/M056 in docs/planning/archived/2026-09-22_23-49_go-cqrs-lite-platform- migration.md) and the safety net under UpgradeIfNeeded.
C12 replay-design decision (resolving the memo's open question): the applier is a VERBATIM projection copy, not an operation-by-operation transition replay. The journal alone cannot reconstruct task rows — task.enqueued details carry project/type/priority/dedup key but NOT the payload, deps, or max attempts — so a pure transition replay would silently drop every prompt. The copy preserves task IDs, dedup keys, fact seq numbers, timestamps, and detail bytes exactly, which makes the equality gate meaningful: any divergence between the old store's projections and the engine-backed adapter's read paths is a real defect, not replay fuzz. The enqueued fact has since grown the full task snapshot (EnqueueDetail payload/deps/max_attempts/not_before/ created_at, 2026-09-24), so post-growth journals could drive a transition applier — but every pre-growth journal (including the production dogfood journal's history) still needs this copy, and the upstream engine's own enqueued detail stays thin until the M4 ratification lands. The copy remains until both are true.
Known shape conversions (the two schemas differ deliberately):
- tasks.payload: old TEXT → engine BLOB (identity codec, byte-equal)
- tasks.lease_token: absent in the old schema → NULL. Running tasks therefore migrate without a claim-fencing token (upstream ADR-0134 finalizes); a lapsed-lease reclaim re-mints one. Divergence D1 of the S1 conformance report covers the finalize semantics.
- facts_archive and journal_meta (old-only tables) are recreated verbatim so no history is lost; the engine does not read them.
Usage (the CLI front-end lives in the sibling replay/ directory):
go run ./replay --from <old.db> --to <new.db> [--verify-only]
Exit 0 on a green report, 1 on any projection mismatch, 2 on setup errors. The tool never writes to the source database.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrAutoUpgradeDisabled = errors.New(
"migration: legacy database needs the on-open auto-upgrade, refused by TQ_NO_AUTO_UPGRADE=1" +
" — run the replay tool (go run ./replay --from <db> --to <new.db>) or unset the variable",
)
ErrAutoUpgradeDisabled is returned when a legacy database needs the auto-upgrade but TQ_NO_AUTO_UPGRADE=1 refused it.
Functions ¶
This section is empty.
Types ¶
type Kind ¶
type Kind int
Kind classifies a database file's schema generation.
const ( // KindFresh is a file with no tasks table: new or empty — the engine // migrations create everything from scratch. KindFresh Kind = iota // KindEngine is the go-cqrs-lite engine generation: the tasks table // carries the ADR-0134 lease_token column. KindEngine // KindLegacy is the pre-flip hand-rolled generation: tasks without // lease_token (the deployed production journal's shape until cutover). KindLegacy )
type Report ¶
type Report struct {
Sections []Section
}
Report is the full projection-equality verdict.
func Verify ¶
Verify compares the source journal's projections against the target engine store's read paths. The source side is read with the same SQL the hand-rolled store runs (that store is frozen history at cutover); the target side goes through the real queue.Store API — the gate proves what consumers see after the flip.
type Stats ¶
type Stats struct {
Tasks int
Deps int
Facts int
Watermarks int
PriorityScores int
FactsArchive int
JournalMeta int
}
Stats reports how many rows the migration copied per table.
type UpgradeResult ¶
type UpgradeResult struct {
// Kind is the probed generation before the upgrade (always KindLegacy
// when an upgrade ran).
Kind Kind
// BackupPath is the VACUUM INTO snapshot of the legacy file, kept for
// audit/rollback after a successful upgrade.
BackupPath string
// Report is the projection-equality verdict of the converged store
// against the snapshot.
Report Report
}
UpgradeResult reports what the auto-upgrade did.
func UpgradeIfNeeded ¶
func UpgradeIfNeeded(ctx context.Context, path string) (*UpgradeResult, error)
UpgradeIfNeeded probes path and, when it is a legacy hand-rolled database, upgrades it in place under the verified safety net. Fresh and engine files return (nil, nil): nothing to do.