migration

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 18 Imported by: 0

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

View Source
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
)

func Probe

func Probe(path string) (Kind, error)

Probe classifies the schema generation of the database at path without mutating anything. A missing file is KindFresh (the store creates it).

func (Kind) String

func (k Kind) String() string

String renders the kind for logs and errors.

type Report

type Report struct {
	Sections []Section
}

Report is the full projection-equality verdict.

func Verify

func Verify(ctx context.Context, fromPath, toPath string) (Report, error)

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.

func (Report) OK

func (report Report) OK() bool

OK reports whether every section passed.

func (Report) Summary

func (report Report) Summary() string

Summary renders the report for terminal output.

type Section

type Section struct {
	Name   string
	OK     bool
	Detail string
}

Section is one projection-equality verdict.

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.

func Migrate

func Migrate(ctx context.Context, fromPath, toPath string) (Stats, error)

Migrate replays the source journal into a fresh engine store at toPath. toPath must not already exist: a fresh store is part of the definition (no in-place surgery on a live engine DB).

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL