migration

package
v2.3.4 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 18 Imported by: 0

README

Migration Framework

Database migration framework used by tests and the legacy SQLite migrator. Local startup uses the separate embedded forward-only v2 baseline in migrations/v2/001_init.sql; the root migrations/001-014 files do not drive startup. PostgreSQL uses migrations/v2/100_server.sql and its own ledger.

Features

  • Version Tracking: Automatically tracks applied migrations in _migrations table
  • Transaction Safety: Each migration runs in a transaction for atomicity
  • Rollback Support: Available to registered/legacy migrations; the v2 baseline is forward-only
  • Status Reporting: Query which migrations are applied/pending
  • Flexible Loading: Load migrations from disk or register programmatically
  • Concurrent Safe: Thread-safe operations with proper locking

Installation

import "github.com/lleontor705/cortex/internal/migration"

Quick Start

1. Load Migrations from Disk
package main

import (
    "context"
    "database/sql"
    "log"

    "github.com/lleontor705/cortex/internal/migration"
    _ "modernc.org/sqlite"
)

func main() {
    // Open database
    db, err := sql.Open("sqlite", "cortex.db")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Create migrator pointing to migrations directory
    migrator, err := migration.NewMigrator(db, "./migrations")
    if err != nil {
        log.Fatal(err)
    }

    // Apply all pending migrations
    ctx := context.Background()
    if err := migrator.Up(ctx); err != nil {
        log.Fatal(err)
    }

    log.Printf("Migrations applied. Current version: %d", migrator.Version())
}
2. Programmatic Migrations
// Create migrator (no directory needed)
migrator, err := migration.NewMigrator(db, "")
if err != nil {
    log.Fatal(err)
}

// Register migrations programmatically
migrator.Register(migration.Migration{
    Version:     1,
    Name:        "create_users",
    Description: "Create users table",
    UpSQL: `CREATE TABLE users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL UNIQUE,
        email TEXT NOT NULL
    );`,
    DownSQL: "DROP TABLE IF EXISTS users;",
})

// Apply migrations
ctx := context.Background()
if err := migrator.Up(ctx); err != nil {
    log.Fatal(err)
}
3. Check Migration Status
statuses, err := migrator.Status(ctx)
if err != nil {
    log.Fatal(err)
}

for _, status := range statuses {
    if status.Applied {
        fmt.Printf("✓ Version %d: %s (applied at %s)\n",
            status.Version, status.Name, status.AppliedAt)
    } else {
        fmt.Printf("⏳ Version %d: %s (pending)\n",
            status.Version, status.Name)
    }
}
4. Rollback Migrations
// Rollback to version 1 (removes versions 2+)
if err := migrator.Down(ctx, 1); err != nil {
    log.Fatal(err)
}

// Rollback all migrations
if err := migrator.Down(ctx, 0); err != nil {
    log.Fatal(err)
}

Migration File Format

Migration files are SQL files with a specific format:

-- +migrate Up
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT NOT NULL,
    email TEXT NOT NULL
);

CREATE INDEX idx_users_email ON users(email);

-- +migrate Down
DROP INDEX IF EXISTS idx_users_email;
DROP TABLE IF EXISTS users;
File Naming Convention

Files must follow the pattern: NNN_description.sql

  • NNN: Version number (zero-padded, e.g., 001, 002, 010)
  • description: Brief description using underscores
  • .sql: File extension

Examples:

  • 001_init.sql - Initial schema
  • 002_add_fts.sql - Add full-text search
  • 003_add_sync.sql - Add sync support
File Structure
  1. Up Section (required): SQL to apply migration

    • Starts with -- +migrate Up
    • Contains CREATE TABLE, ALTER TABLE, etc.
  2. Down Section (optional): SQL to rollback migration

    • Starts with -- +migrate Down
    • Should reverse the Up section
    • Required if you want rollback support

API Reference

Types
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
}

type MigrationStatus struct {
    Version   int    // Migration version
    Name      string // Migration name
    Applied   bool   // Whether migration was applied
    AppliedAt string // When migration was applied (empty if not applied)
}

type Migrator struct {
    // ... internal fields
}
Functions
// Create a new migrator
func NewMigrator(db *sql.DB, dir string) (*Migrator, error)

// Register a migration programmatically
func (m *Migrator) Register(migration Migration)

// Apply all pending migrations
func (m *Migrator) Up(ctx context.Context) error

// Rollback migrations to specified version (0 = all)
func (m *Migrator) Down(ctx context.Context, version int) error

// Get status of all migrations
func (m *Migrator) Status(ctx context.Context) ([]MigrationStatus, error)

// Get current migration version (highest applied)
func (m *Migrator) Version() int

Database Schema

The migrator automatically creates a _migrations table:

CREATE TABLE IF NOT EXISTS _migrations (
    version INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Best Practices

1. Always Include Down SQL
// Good
migrator.Register(migration.Migration{
    Version: 1,
    Name:    "create_users",
    UpSQL:   "CREATE TABLE users (id INTEGER PRIMARY KEY);",
    DownSQL: "DROP TABLE IF EXISTS users;", // ✓ Has rollback
})

// Bad
migrator.Register(migration.Migration{
    Version: 1,
    Name:    "create_users",
    UpSQL:   "CREATE TABLE users (id INTEGER PRIMARY KEY);",
    DownSQL: "", // ✗ No rollback
})
2. Use Transactions Wisely

Each migration runs in a transaction automatically. Don't add explicit transactions:

-- Bad: Don't add explicit transactions
-- +migrate Up
BEGIN;
CREATE TABLE users (id INTEGER);
COMMIT;

-- Good: Let the framework handle it
-- +migrate Up
CREATE TABLE users (id INTEGER);
3. Make Migrations Idempotent

Use IF NOT EXISTS and IF EXISTS:

-- Good
CREATE TABLE IF NOT EXISTS users (id INTEGER);
DROP TABLE IF EXISTS users;

-- Bad: Will fail on re-run
CREATE TABLE users (id INTEGER);
DROP TABLE users;
4. Version Numbering
  • Use sequential numbers starting from 1
  • Zero-pad to 3 digits (001, 002, ..., 010)
  • Never modify existing migration files
  • Always add new migrations with higher version numbers
5. Test Rollbacks

Always test that your Down SQL correctly reverses the Up SQL:

func TestMigration(t *testing.T) {
    db := testDB(t)
    m, _ := migration.NewMigrator(db, "")

    m.Register(migration.Migration{
        Version: 1,
        Name:    "test",
        UpSQL:   "CREATE TABLE test (id INTEGER);",
        DownSQL: "DROP TABLE IF EXISTS test;",
    })

    ctx := context.Background()

    // Apply
    m.Up(ctx)

    // Rollback
    m.Down(ctx, 0)

    // Verify table doesn't exist
    // ...
}

Error Handling

The migrator returns errors for:

  • Invalid migration files
  • SQL syntax errors
  • Missing Down SQL when rolling back
  • Database connection issues

All errors wrap the underlying cause:

if err := migrator.Up(ctx); err != nil {
    // err contains: "migration: apply version N: <cause>"
    log.Printf("Migration failed: %v", err)
}

Performance

  • Migrations are cached in memory after first load
  • Status checks are O(n) where n = number of migrations
  • Concurrent status checks are safe (thread-safe reads)
  • Migration application is sequential (cannot run Up concurrently)

Testing

The package includes comprehensive tests:

# Run all tests
go test ./internal/migration/... -v

# Run with coverage
go test ./internal/migration/... -cover

# Run benchmarks
go test ./internal/migration/... -bench=.

Examples

See the migrations/ directory for example migration files:

  • 001_init.sql - Initial schema setup
  • 002_add_fts.sql - Add full-text search support

License

MIT License - See LICENSE file for details.

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

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

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

View Source
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".

View Source
const V2HandoffReceiptsMigrationVersion = 2002

V2HandoffReceiptsMigrationVersion is the numeric version of the additive SQLite follow-up migration 002 (handoff receipts) in the v2 line.

View Source
const V2ProjectArtifactsMigrationVersion = 2003

V2ProjectArtifactsMigrationVersion is the numeric version of the additive SQLite follow-up migration 003 (project context artifacts) in the v2 line.

Variables

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

func ApplyPostgresServerMigrations(ctx context.Context, db *sql.DB) error

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

func InitV2Database(ctx context.Context, path string) (*sql.DB, error)

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

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

func NewMigrator(db *sql.DB, dir string) (*Migrator, error)

NewMigrator creates a new migrator instance. The dir parameter specifies the filesystem path to the migrations directory.

func (*Migrator) Down

func (m *Migrator) Down(ctx context.Context, version int) error

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

func (m *Migrator) Register(migration Migration)

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

func (m *Migrator) Up(ctx context.Context) error

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).

func (*Migrator) Version

func (m *Migrator) Version() int

Version returns the current migration version (highest applied version).

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

func (m *PostgresServerMigration) Apply(ctx context.Context, db *sql.DB) error

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

func (m *PostgresServerMigration) Down(ctx context.Context, db *sql.DB) error

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

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 (*PostgresServerMigration) VerifyApplied

func (m *PostgresServerMigration) VerifyApplied(ctx context.Context, db *sql.DB) error

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 NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new migration registry.

func (*Registry) Clear

func (r *Registry) Clear()

Clear removes all migrations from the registry.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of registered migrations.

func (*Registry) Get

func (r *Registry) Get(version int) (Migration, bool)

Get retrieves a migration by version. Returns false if the migration doesn't exist.

func (*Registry) GetAll

func (r *Registry) GetAll() []Migration

GetAll returns all registered migrations sorted by version.

func (*Registry) HasMigration

func (r *Registry) HasMigration(version int) bool

HasMigration checks if a migration with the given version exists.

func (*Registry) Register

func (r *Registry) Register(migration Migration)

Register adds a migration to the registry. If a migration with the same version already exists, it will be overwritten.

func (*Registry) Versions

func (r *Registry) Versions() []int

Versions returns all registered migration versions sorted.

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

func (b *V2Baseline) Apply(ctx context.Context, db *sql.DB) error

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

func (b *V2Baseline) Down(ctx context.Context, db *sql.DB) error

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

func (b *V2Baseline) IsV2(ctx context.Context, db *sql.DB) (bool, error)

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

func (b *V2Baseline) VerifyFollowUpApplied(ctx context.Context, db *sql.DB, version int) error

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

func (b *V2Baseline) VerifyIntegrity(ctx context.Context, db *sql.DB) error

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).

Jump to

Keyboard shortcuts

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