Documentation
¶
Overview ¶
Package migration provides a database migration framework for SQLite.
It supports version-tracked migrations with Up/Down capabilities, transaction safety, and status reporting.
Package migration — read-only compatibility probe (W3.2, REQ-DB-002).
ProbeCompatibility inspects a candidate database file STRICTLY READ-ONLY before any write-capable open, and classifies it as Fresh, Compatible, or refused (INCOMPATIBLE_DATABASE). It exists to satisfy REQ-DB-002: Cortex MUST detect old Cortex, Engram, corrupt, ambiguous, or partially-initialized databases and refuse to mutate, auto-convert, or journal them.
Read-only guarantees (belt and suspenders):
- DSN opens with mode=ro (VFS-level read-only).
- immutable=1 (SQLite never creates/touches -wal/-shm for the probe).
- _pragma=query_only(1) (SQL compiler rejects any write statement).
The probe issues ONLY SELECTs against sqlite_master and cortex_meta. It never runs PRAGMA journal_mode or any other mutating pragma.
Design grounding: ADR-03 (clean v2 DB + read-only old-DB refusal), spec REQ-DB-002 (old database immutability on refusal).
Package migration provides a database migration framework for SQLite.
Package migration — v2 clean baseline runner (W3.1, REQ-DB-001).
This file implements the forward-only v2 schema baseline: it applies the embedded SQL bundle inside a single transaction, records the schema identity (family + version + SHA-256 checksum) in cortex_meta within that SAME transaction, and only commits after PRAGMA integrity_check passes.
Design grounding: ADR-03 (clean v2 DB + read-only old-DB refusal), spec REQ-DB-001 (clean v2 database at a new path with version identity).
Key semantics (see v2_test.go for the full scenario matrix):
- UP: fresh DB → full schema + identity + integrity pass.
- DOWN: forward-only guard; returns ErrForwardOnly, never mutates.
- FRESH: absent path → clean baseline; re-init is idempotent no-op.
- INTEGRITY: only "ready" after integrity_check + checksum match pass.
- PATH: unwritable path → fail before mutation, no partial file.
- V1-RETIRED: v1 migrations 001-014 are absent from the v2 line.
Index ¶
- Constants
- Variables
- func ApplyPostgresServerMigrations(ctx context.Context, db *sql.DB) error
- func DefaultV2DBPath() string
- func InitV2Database(ctx context.Context, path string) (*sql.DB, error)
- type IncompatibleDatabaseError
- type LedgerPreflight
- type Migration
- type MigrationStatus
- type Migrator
- type PostgresServerMigration
- func (m *PostgresServerMigration) Apply(ctx context.Context, db *sql.DB) error
- func (m *PostgresServerMigration) Checksum() string
- func (m *PostgresServerMigration) Down(ctx context.Context, db *sql.DB) error
- func (m *PostgresServerMigration) MatchesChecksum(recorded string) bool
- func (m *PostgresServerMigration) Name() string
- func (m *PostgresServerMigration) Preflight(ctx context.Context, db *sql.DB) (LedgerPreflight, error)
- func (m *PostgresServerMigration) SQL() string
- func (m *PostgresServerMigration) VerifyApplied(ctx context.Context, db *sql.DB) error
- func (m *PostgresServerMigration) Version() int
- type ProbeReport
- type ProbeStatus
- type Registry
- type SchemaIdentity
- type V2Baseline
- func (b *V2Baseline) Apply(ctx context.Context, db *sql.DB) error
- func (b *V2Baseline) Down(ctx context.Context, db *sql.DB) error
- func (b *V2Baseline) Identity() SchemaIdentity
- func (b *V2Baseline) IsV2(ctx context.Context, db *sql.DB) (bool, error)
- func (b *V2Baseline) PreflightFollowUp(ctx context.Context, db *sql.DB, version int) (LedgerPreflight, error)
- func (b *V2Baseline) VerifyFollowUpApplied(ctx context.Context, db *sql.DB, version int) error
- func (b *V2Baseline) VerifyIntegrity(ctx context.Context, db *sql.DB) error
- type V2Registry
Constants ¶
const ( // SchemaFamilyCortexV2 is the schema family identifier for Cortex v2. SchemaFamilyCortexV2 = "cortex-v2" // V2BaselineVersion is the version label of the initial v2 baseline // (corresponds to migrations/v2/001_init.sql). V2BaselineVersion = "001" )
Schema identity constants recorded in cortex_meta.
const CodeIncompatibleDatabase = "INCOMPATIBLE_DATABASE"
CodeIncompatibleDatabase is the stable, operator-facing string error code surfaced when the probe refuses to operate on an incompatible database. It is intentionally a string constant so logs/CLI output are grep-stable across versions and wrapper layers.
const V2BaselineMigrationVersion = 2001
V2BaselineMigrationVersion is the numeric version of the v2 baseline in the migration registry. v2 migrations use versions >= 2000 so they never collide with the retired v1 set (1-14). 2001 = "v2 line, migration 001".
const V2HandoffReceiptsMigrationVersion = 2002
V2HandoffReceiptsMigrationVersion is the numeric version of the additive SQLite follow-up migration 002 (handoff receipts) in the v2 line.
const V2ProjectArtifactsMigrationVersion = 2003
V2ProjectArtifactsMigrationVersion is the numeric version of the additive SQLite follow-up migration 003 (project context artifacts) in the v2 line.
Variables ¶
var ( // ErrForwardOnly is returned by Down(). The v2 baseline is forward-only: // rolling it back would destroy user data. Down never mutates the database. ErrForwardOnly = errors.New("migration: v2 baseline is forward-only; Down is not supported") // ErrSchemaTampered is returned when the recorded schema checksum does not // match the expected baseline checksum (tampering or incompatible schema). ErrSchemaTampered = errors.New("migration: schema identity tampered or checksum mismatch") // ErrIncompatibleDatabase is returned when the DB has a schema identity // from a different family (e.g., a v1 or foreign database). ErrIncompatibleDatabase = errors.New("migration: incompatible database schema family") // ErrFutureMigration is returned when a migration ledger records a // version NEWER than any migration this runtime knows. Such a database // was created by a newer runtime; an older runtime must fail closed on // Apply and verification instead of silently forking the migration line // (REM-ROLLOUT-001). ErrFutureMigration = errors.New("migration: ledger records a future migration version") // ErrPreflightStop is returned by the read-only rollout preflight when // the ledger is NOT in the expected unledgered state for the target // migration (SQLite follow-up 2003, PostgreSQL 106). It tells operators // to STOP the rollout and escalate instead of applying; it is never // returned by Apply/VerifyIntegrity paths (IDP-T05). ErrPreflightStop = errors.New("migration: rollout preflight stop") )
Sentinel errors for the v2 baseline.
Functions ¶
func DefaultV2DBPath ¶
func DefaultV2DBPath() string
DefaultV2DBPath returns the default filesystem path for the Cortex v2 database: ~/.cortex/v2/cortex.db. It is deliberately DISTINCT from any v1/Engram path (~/.cortex/cortex.db) to ensure a clean major-version cutover without touching legacy data (ADR-03, REQ-DB-001).
When this path is absent, InitV2Database creates it cleanly (parent directory and baseline in one operation), preserving local fresh-install behavior.
func InitV2Database ¶
InitV2Database creates a v2 database at the given filesystem path. If the path does not exist, the parent directory is created and the full baseline is applied. If the path already holds a valid v2 DB, it is opened without re-running the baseline (idempotent).
If the path is not writable (parent cannot be created), it fails BEFORE any mutation and creates no partial database file.
Types ¶
type IncompatibleDatabaseError ¶
type IncompatibleDatabaseError struct {
Path string // the configured database path that was refused
Detail string // why it was refused
// contains filtered or unexported fields
}
IncompatibleDatabaseError wraps the W3.1 sentinel ErrIncompatibleDatabase so refusal is errors.Is-checkable, while also exposing the stable code and an operator-facing message that names the clean v2 default path.
func (*IncompatibleDatabaseError) Code ¶
func (e *IncompatibleDatabaseError) Code() string
Code returns the stable string error code.
func (*IncompatibleDatabaseError) Error ¶
func (e *IncompatibleDatabaseError) Error() string
Error implements error.
func (*IncompatibleDatabaseError) Unwrap ¶
func (e *IncompatibleDatabaseError) Unwrap() error
Unwrap allows errors.Is(err, ErrIncompatibleDatabase).
type LedgerPreflight ¶
type LedgerPreflight struct {
// Version is the preflight target version (e.g. 2003 or 106).
Version int
// LedgerTable reports whether the migration ledger table exists at all.
LedgerTable bool
// Ledgered reports whether the ledger records a row for Version.
Ledgered bool
// RecordedChecksum is the checksum ledgered for Version ("" when
// unledgered). ANY recorded value — a prior pre-release checksum or the
// current one — stops a rollout that expects the unledgered state.
RecordedChecksum string
// ExpectedChecksum is this runtime's embedded checksum for Version.
ExpectedChecksum string
// Head is the newest migration version this runtime knows.
Head int
// FutureLedgerVersion is a ledgered version beyond Head (0 when none):
// the database was created by a NEWER runtime.
FutureLedgerVersion int
}
LedgerPreflight is the read-only ledger preflight result for ONE target migration version (the SQLite follow-up 2003 or the PostgreSQL server migration 106). It is filled exclusively by SELECT probes: running a preflight never creates the ledger table, never writes rows, and never takes advisory locks. The expected state for a rollout is UNLEDGERED; see docs/project-context-protocol-identity-privilege.md for the runbook.
func (LedgerPreflight) Verdict ¶
func (p LedgerPreflight) Verdict() error
Verdict returns nil ONLY for the expected unledgered state (no ledger row for the target version and no newer-runtime ledger row). Every other state is a rollout stop: the returned error always wraps ErrPreflightStop and carries the precise escalation reason — already applied with the current checksum, a prior checksum (tamper-class), or a future version (newer runtime). It is a pure function over the reported state.
type Migration ¶
type Migration struct {
Version int // Migration version number
Name string // Migration name (from filename)
Description string // Human-readable description
UpSQL string // SQL to apply migration
DownSQL string // SQL to rollback migration
}
Migration represents a single database migration.
type MigrationStatus ¶
type MigrationStatus struct {
Version int // Migration version
Name string // Migration name
Applied bool // Whether the migration has been applied
AppliedAt string // When the migration was applied (empty if not applied)
}
MigrationStatus represents the status of a migration.
type Migrator ¶
type Migrator struct {
// contains filtered or unexported fields
}
Migrator manages database migrations.
func NewMigrator ¶
NewMigrator creates a new migrator instance. The dir parameter specifies the filesystem path to the migrations directory.
func (*Migrator) Down ¶
Down rolls back migrations to the specified version. If version is 0, all migrations are rolled back. Migrations are rolled back in reverse order (highest to lowest).
func (*Migrator) Register ¶
Register adds a migration to the in-memory registry. This is useful for programmatically defining migrations instead of loading from disk.
func (*Migrator) Status ¶
func (m *Migrator) Status(ctx context.Context) ([]MigrationStatus, error)
Status returns the status of all migrations (both applied and pending).
v2-aware (W3, REQ-DB-001): on a cortex-v2 database the v1 migrations 001-014 are consolidated into the v2 baseline, so they are reported as applied (the schema they define is present). On a non-v2 database, status reflects the _migrations tracking table as before.
func (*Migrator) Up ¶
Up applies all pending migrations. Migrations are applied in version order (lowest to highest).
v2-aware (W3, REQ-DB-001): on a cortex-v2 database the v1 migrations 001-014 are RETIRED — they are consolidated into the v2 baseline applied by the app bootstrap. Running them here would conflict ("table already exists"), so on a v2 database Up is an idempotent no-op. On a non-v2 database Up behaves as before (legacy v1 line).
type PostgresServerMigration ¶
type PostgresServerMigration struct {
// contains filtered or unexported fields
}
PostgresServerMigration is the isolated server-wave migration. It must not be registered with the SQLite migrator: the SQL uses PostgreSQL-only DDL.
func NewPostgresServerMigration ¶
func NewPostgresServerMigration() (*PostgresServerMigration, error)
NewPostgresServerMigration loads the embedded, checksummed server baseline (version 100). It carries the full runtime head so a standalone Apply also refuses databases ledgered by a newer runtime.
func NewPostgresServerMigrations ¶
func NewPostgresServerMigrations() ([]*PostgresServerMigration, error)
NewPostgresServerMigrations returns every immutable server migration in application order. Existing databases apply only versions missing from the ledger; checksum mismatches and ledgered versions beyond the runtime head fail closed.
func (*PostgresServerMigration) Apply ¶
Apply runs the server migration atomically. A migration record stores the exact embedded checksum, so changing an applied migration fails closed. The transaction-scoped advisory lock is safe with transaction poolers.
func (*PostgresServerMigration) Checksum ¶
func (m *PostgresServerMigration) Checksum() string
func (*PostgresServerMigration) Down ¶
Down is the forward-only guard for the PostgreSQL server migration line. For EVERY version — 100 through 106, ledgered or unledgered — it returns an ErrForwardOnly-wrapped error and executes NO DDL/DML: no transaction, no query; schema, data, and the migration ledger remain untouched. There is no artifact-cleanup exception: stale unledgered artifacts and newer-runtime ledgers are handled by reviewed compensating migrations, never by destructive rollback (REM-MIG-001, R1F review). The behavioral matrix with real schema/ledger/data snapshots runs in postgres_integration.
func (*PostgresServerMigration) MatchesChecksum ¶
func (m *PostgresServerMigration) MatchesChecksum(recorded string) bool
MatchesChecksum checks if the recorded checksum matches the migration checksum, including cross-platform line ending normalization (LF vs CRLF).
func (*PostgresServerMigration) Name ¶
func (m *PostgresServerMigration) Name() string
func (*PostgresServerMigration) Preflight ¶
func (m *PostgresServerMigration) Preflight(ctx context.Context, db *sql.DB) (LedgerPreflight, error)
Preflight runs the READ-ONLY rollout preflight for this migration (the 106 project_artifacts train) against cortex_server_migrations. It issues SELECTs only: ledger presence is probed with to_regclass (never DDL), no row is written, and no advisory lock is taken. The expected rollout state is unledgered; any recorded checksum or any newer-runtime ledger row yields an ErrPreflightStop verdict for operator escalation (IDP-T05). Behavioral coverage runs in postgres_integration.
func (*PostgresServerMigration) SQL ¶
func (m *PostgresServerMigration) SQL() string
func (*PostgresServerMigration) VerifyApplied ¶
VerifyApplied is the POST-APPLY check for this migration: the ledger must record a row whose checksum matches the embedded SQL (exactly the acceptance Apply's idempotent path uses). A missing row means the migration was not applied; a mismatched checksum fails closed. It is read-only (a single SELECT). Behavioral coverage runs in postgres_integration.
func (*PostgresServerMigration) Version ¶
func (m *PostgresServerMigration) Version() int
type ProbeReport ¶
type ProbeReport struct {
Status ProbeStatus // Fresh or Compatible
Path string // the probed filesystem path
Identity SchemaIdentity // populated for Compatible (family/version/checksum)
Detail string // human-readable classification note
}
ProbeReport describes a non-refusal probe outcome.
func ProbeCompatibility ¶
func ProbeCompatibility(ctx context.Context, path string) (*ProbeReport, error)
ProbeCompatibility opens the database at path STRICTLY READ-ONLY and classifies it. It MUST NOT mutate the file: it only issues SELECTs against sqlite_master and cortex_meta under query_only(1).
Outcomes:
- Fresh: path absent, or an empty/uninitialized SQLite file.
- Compatible: valid cortex-v2 database, checksum matches baseline.
- refused: old Cortex v1, Engram, corrupt, ambiguous-family, partially-initialized, or foreign/unknown. The returned error wraps ErrIncompatibleDatabase and carries CodeIncompatibleDatabase.
type ProbeStatus ¶
type ProbeStatus string
ProbeStatus is the non-refusal outcome of a compatibility probe. Refusals are conveyed via the returned error (see CodeIncompatibleDatabase).
const ( // ProbeStatusFresh means the path is absent (or an empty/uninitialized // SQLite file): the caller may create a clean v2 database here. ProbeStatusFresh ProbeStatus = "fresh" // ProbeStatusCompatible means the file is a valid cortex-v2 database whose // recorded schema checksum matches the expected v2 baseline. The caller may // proceed (re-initialization is idempotent). ProbeStatusCompatible ProbeStatus = "compatible" )
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maintains an in-memory registry of migrations. This allows programmatically registering migrations instead of loading from disk.
func (*Registry) Get ¶
Get retrieves a migration by version. Returns false if the migration doesn't exist.
func (*Registry) HasMigration ¶
HasMigration checks if a migration with the given version exists.
type SchemaIdentity ¶
type SchemaIdentity struct {
Family string // e.g. "cortex-v2"
Version string // e.g. "001"
Checksum string // hex SHA-256 of the baseline SQL
}
SchemaIdentity is the version+checksum tuple recorded in cortex_meta.
type V2Baseline ¶
type V2Baseline struct {
// contains filtered or unexported fields
}
V2Baseline is the forward-only v2 schema baseline.
func NewV2Baseline ¶
func NewV2Baseline() (*V2Baseline, error)
NewV2Baseline constructs the v2 baseline from the embedded SQL bundle, including the follow-up migration line (2002 handoff receipts, 2003 project context artifacts). The baseline identity (family/version/checksum) still refers ONLY to the immutable 001 SQL: follow-ups carry their own ledger checksums.
func (*V2Baseline) Apply ¶
Apply creates the v2 baseline schema inside a single transaction, records the schema identity, runs PRAGMA integrity_check, and commits. It is IDEMPOTENT: a second Apply on a DB whose cortex_meta carries a matching identity is a silent no-op. A mismatched checksum returns ErrSchemaTampered.
func (*V2Baseline) Down ¶
Down is the forward-only guard for the v2 baseline. It returns ErrForwardOnly and does NOT mutate the database under any circumstances. The v2 baseline creates the sole location for v2 data; rolling it back would destroy user data, which is explicitly a non-goal of the v2 major release (issue #49).
func (*V2Baseline) Identity ¶
func (b *V2Baseline) Identity() SchemaIdentity
Identity returns the schema identity of this baseline.
func (*V2Baseline) IsV2 ¶
IsV2 reports whether db has a valid cortex-v2 schema identity with a matching checksum and passing integrity check. Returns (false, err) if the DB is not a valid v2 database (missing identity, tampered, or corrupt).
func (*V2Baseline) PreflightFollowUp ¶
func (b *V2Baseline) PreflightFollowUp(ctx context.Context, db *sql.DB, version int) (LedgerPreflight, error)
PreflightFollowUp runs the READ-ONLY rollout preflight for one embedded v2 follow-up version (e.g. 2003, the project artifacts migration) against the follow-up ledger (cortex_v2_migrations). It issues SELECTs only: the ledger table is probed via sqlite_master and never created, and no row is written. The expected rollout state is unledgered; any recorded checksum or any newer-runtime ledger row yields an ErrPreflightStop verdict for operator escalation (IDP-T05). Operational failures (unreadable ledger) return a plain error, never a verdict.
func (*V2Baseline) VerifyFollowUpApplied ¶
VerifyFollowUpApplied is the POST-APPLY check for one embedded v2 follow-up version: the ledger must record a row whose checksum equals the EXACT embedded checksum. A missing row means the follow-up was not applied; a drifted checksum fails closed with ErrSchemaTampered. It is read-only (a single SELECT).
func (*V2Baseline) VerifyIntegrity ¶
VerifyIntegrity runs PRAGMA integrity_check and verifies the recorded schema checksum matches the expected baseline checksum. Returns ErrSchemaTampered if either check fails.
type V2Registry ¶
type V2Registry struct {
// contains filtered or unexported fields
}
V2Registry defines the v2 migration line and tracks which v1 migrations are retired from it. v1 migrations 001-014 MUST NOT run on a v2 database.
func NewV2Registry ¶
func NewV2Registry() (*V2Registry, error)
NewV2Registry creates the v2 registry: the embedded baseline plus the set of retired v1 versions (1 through 14).
func (*V2Registry) IsV1Retired ¶
func (r *V2Registry) IsV1Retired(version int) bool
IsV1Retired reports whether a given v1 migration version is retired in the v2 line. All versions 1-14 are retired.
func (*V2Registry) RetiredV1Versions ¶
func (r *V2Registry) RetiredV1Versions() []int
RetiredV1Versions returns the v1 migration versions retired from the v2 line (1 through 14). These MUST NOT run on a v2 database.
func (*V2Registry) V2Migrations ¶
func (r *V2Registry) V2Migrations() []Migration
V2Migrations returns the migrations in the v2 line: the immutable baseline (2001) followed by the checksummed follow-up 2002 (handoff receipts). None of these have versions in the retired v1 range, and every entry is forward-only (empty DownSQL; rollback returns ErrForwardOnly).