diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index df6b503..097d190 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,9 +3,6 @@ name: CI
on:
workflow_call:
pull_request:
- push:
- branches:
- - main
permissions:
contents: read
diff --git a/README.md b/README.md
index 73b8fd9..edb4a1c 100644
--- a/README.md
+++ b/README.md
@@ -1,60 +1,14 @@
# Augur
-## Overview
-
Discord bot for requesting movies and TV shows through [Seerr](https://github.com/seerr-team/seerr).
-## Quick start
-
-You need a Discord server where you can install an app, a reachable Seerr instance, Docker with Compose, and a Seerr API
-key. Create the key in Seerr's administration/settings area; the exact menu label can vary between Seerr releases.
-
-Clone or download this repository, then open its directory. With Git:
-
-```sh
-git clone https://github.com/mayvqt/Augur.git
-cd Augur
-```
-
-1. In the [Discord Developer Portal](https://discord.com/developers/applications), create an application and add a bot.
- Install it with the `bot` and `applications.commands` scopes. Grant the bot View Channel, Send Messages, Embed Links,
- and Manage Messages; administrators also need Manage Server to configure approval messages.
-2. Copy the example environment file and fill in the Discord token, Seerr API key, Seerr URLs, and any optional settings:
-
- ```sh
- cp .env.example .env
- $EDITOR .env
- ```
-
-3. Start Augur:
-
- ```sh
- docker compose up -d
- ```
-
-4. Check startup with `docker compose logs -f augur`. In Discord, run `/link`, open the settings page, and save the
- Discord ID shown by the bot in your Seerr notification settings. Then use `/request query:
` to search and
- submit a request. Server administrators can use `/approvals enable channel:#approvals` for approval cards.
-
-`AUGUR_SEERR_BASE_URL` must be reachable from the Augur container. The sample Seerr URL is intentionally a host
-placeholder: this Compose file starts Augur only, so `http://seerr:5055` works only when a container named `seerr` is on
-the same Docker network. For a separate Seerr installation, use its LAN hostname or IP and port instead. The public URL
-is the browser-facing address that Augur sends to users for account linking.
-
-Approval cards cover all Seerr request sources, notify linked requesters of the decision, and are removed two minutes
-after approval or decline. State is stored in `./data`.
-
-See [commands and approval permissions](docs/features.md) before enabling approval cards, and
-[upgrades and backups](docs/releases.md#upgrading-and-rolling-back) before updating an existing installation.
-
## Documentation
-- [Configuration](docs/configuration.md)
-- [Features](docs/features.md)
-- [Unraid](docs/unraid.md)
-- [Releases](docs/releases.md)
-- [Security](SECURITY.md)
-- [Development](docs/development.md)
+- Get started: [Setup](docs/setup.md) and [Configuration](docs/configuration.md)
+- Run Augur: [Operations](docs/development/operations.md) and [Unraid](docs/unraid.md)
+- Use the bot: [Capabilities](docs/capabilities.md) and [Commands and approvals](docs/features.md)
+- Contribute: [Development](docs/development/README.md) and [Security](SECURITY.md)
+- Follow releases: [Release notes](docs/releases.md)
- [License](LICENSE)
- [Issues](https://github.com/mayvqt/Augur/issues)
diff --git a/docs/capabilities.md b/docs/capabilities.md
new file mode 100644
index 0000000..35dc3ab
--- /dev/null
+++ b/docs/capabilities.md
@@ -0,0 +1,14 @@
+# Capabilities
+
+Augur lets Discord members search Seerr, preview a movie or show, choose TV
+seasons, submit a request, and review their recent requests without leaving
+Discord. Members can opt in or out of direct messages for approvals, declines,
+and availability.
+
+Server administrators can send pending Seerr requests to a Discord channel as
+approval cards. Those cards also cover requests created outside Discord. Augur
+polls Seerr for request decisions and availability, remembers delivery state
+across restarts, and can expose private health, readiness, and metrics endpoints.
+
+For exact commands and permissions, see [Commands and approvals](features.md).
+For deployment settings, see [Configuration](configuration.md).
diff --git a/docs/development.md b/docs/development.md
index 4674bf1..f09fccf 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -1,26 +1,11 @@
# Development
-Use the Go version declared in [`go.mod`](../go.mod). Run automated checks from
-the repository root:
-
-```sh
-gofmt -l ./cmd ./internal
-go mod tidy -diff
-go test ./...
-go vet ./...
-go test -race ./...
-CGO_ENABLED=0 go build ./cmd/augur
-```
-
-The formatting command should print no paths. Tests use temporary SQLite files
-and fake clients/transports; they do not need live Discord or Seerr credentials.
-[CI](../.github/workflows/ci.yml) also runs static analysis, dependency vulnerability
-checks, and a container build with entrypoint smoke tests. Image publishing reuses
-these same gates. CI pins action commits and analysis tool versions; update the
-pins deliberately and validate their checks before release.
-
-For manual integration testing, follow the [setup instructions](../README.md#quick-start)
-with a dedicated Discord application/server, Seerr instance, and isolated data.
-`docker compose up --build` starts a live bot, reads local configuration/secrets,
-and mounts `./data`; it is not an automated test. Stop the test deployment with
-`docker compose down` when finished.
+Start with the [development task index](development/README.md). It links to the
+code map, architecture, storage, validation, operations, and documentation
+guides.
+
+For a manual integration test, follow the [setup guide](setup.md)
+with a dedicated Discord application and server, a test Seerr instance, and an
+isolated data directory. `docker compose up --build` starts a real bot and uses
+the secrets in your local environment; stop it with `docker compose down` when
+you finish.
diff --git a/docs/development/README.md b/docs/development/README.md
new file mode 100644
index 0000000..7f3ccef
--- /dev/null
+++ b/docs/development/README.md
@@ -0,0 +1,13 @@
+# Development task index
+
+Pick the page that matches the work:
+
+- [Codebase map](codebase-map.md) — packages, entry points, and ownership.
+- [Architecture](architecture.md) — Discord, Seerr, background work, and trust boundaries.
+- [Data](data.md) — SQLite tables, migrations, identifiers, and retention.
+- [Validation](validation.md) — focused checks, manual interaction checks, and CI.
+- [Operations](operations.md) — deployment, backups, health, releases, and rollback.
+- [Documentation conventions](documentation-conventions.md) — where public information belongs.
+
+The repository [README](../../README.md) indexes user and operator guides.
+Security-sensitive work also needs [Security](../../SECURITY.md).
diff --git a/docs/development/architecture.md b/docs/development/architecture.md
new file mode 100644
index 0000000..fbc6c2d
--- /dev/null
+++ b/docs/development/architecture.md
@@ -0,0 +1,36 @@
+# Architecture
+
+`cmd/augur` loads configuration and creates an `app.Runner`. The runner owns
+the Discord connection, Seerr client, SQLite store, optional health server, and
+background monitor. Discord handlers ask the runner to perform application
+operations; protocol details stay in `internal/discordbot` and `internal/seer`,
+and durable state stays in `internal/storage`.
+
+## Sources of truth
+
+- Discord owns server membership, channel permissions, interaction identity,
+ messages, and component events.
+- Seerr owns linked users, request permissions, quotas, request decisions, and
+ media availability.
+- SQLite owns Augur's subscriptions, delivery decisions, approval-channel
+ settings, rendered-message references, and notification preferences.
+- `config.json` supplies defaults; environment variables override supported
+ fields at startup. [Configuration](../configuration.md) is the public contract.
+
+Commands and component IDs are public interaction contracts. Seerr endpoints and
+Discord payloads are external contracts: check their current official
+documentation, then cover changes with fake clients or transports before using a
+dedicated live test environment.
+
+## Trust and concurrency
+
+The Discord token, Seerr API key, user IDs, guild/channel IDs, request details,
+and database are sensitive. Approval actions use the configured Seerr API key,
+so the Manage Server or Administrator permission check is a security boundary.
+Account-name matching must never replace explicit Seerr linking.
+
+The runner serializes lifecycle changes and approval reconciliation separately.
+Background polling, Discord callbacks, and shutdown can overlap. Keep operations
+idempotent, make state changes durable before sending notifications where
+practical, bound retries and waits, and honor context cancellation. SQLite uses
+one connection and WAL mode; do not add parallel database owners.
diff --git a/docs/development/codebase-map.md b/docs/development/codebase-map.md
new file mode 100644
index 0000000..a33fee4
--- /dev/null
+++ b/docs/development/codebase-map.md
@@ -0,0 +1,17 @@
+# Codebase map
+
+| Path | What lives here |
+| --- | --- |
+| `cmd/augur` | Process entry point, signal handling, configuration loading, and logging setup. |
+| `internal/app` | Application lifecycle, request/approval coordination, polling, health endpoints, and metrics. |
+| `internal/config` | JSON and environment configuration, defaults, normalization, and validation. |
+| `internal/discordbot` | Discord session lifecycle, slash commands, buttons, previews, approval cards, formatting, and delivery cache. |
+| `internal/seer` | Seerr HTTP client, account links, requests, status, and user lookup. |
+| `internal/storage` | SQLite schema, migrations, subscriptions, approvals, notification preferences, and scans. |
+| `internal/safelog` | Log redaction and safe error output. |
+| `Dockerfile`, `docker-entrypoint.sh`, `docker-compose.yml` | Container build, startup, ownership, and example deployment. |
+| `unraid` | Unraid application template. |
+| `.github/workflows` | CI, dependency review, image publishing, and release announcements. |
+
+Tests sit beside the packages they cover. Update this map when a package takes
+on a new responsibility or a top-level area is added.
diff --git a/docs/development/data.md b/docs/development/data.md
new file mode 100644
index 0000000..ab70fde
--- /dev/null
+++ b/docs/development/data.md
@@ -0,0 +1,27 @@
+# Data and migrations
+
+`internal/storage/store.go` is the schema and migration source of truth. The
+`schema_migrations` ledger records applied versions. Current tables store:
+
+- request subscriptions and availability completion;
+- approval settings per Discord server;
+- approval-message IDs and decisions;
+- notification preferences per Discord user; and
+- decision-notification deduplication.
+
+Discord, guild, channel, message, Seerr request, and media IDs are identifiers,
+not proof of authorization. Re-check permissions at the action boundary. The
+database may reveal account relationships and request history, so treat it and
+its backups as private.
+
+Migrations run in transactions and move forward only. Add a new numbered
+migration; never edit or delete a version that may already be applied. Prefer an
+expand/contract sequence when a rolling transition needs old and new code to
+coexist. Startup rejects a migration version newer than the binary understands
+instead of risking changes to a future schema.
+
+Each schema change needs tests for a fresh database, upgrade from every affected
+supported version, preservation of existing rows, repeat startup, and failure
+behavior. Keep SQL and migration compatibility in `internal/storage`. Restore
+the full data directory for rollback; an older binary may not understand a newer
+schema.
diff --git a/docs/development/documentation-conventions.md b/docs/development/documentation-conventions.md
new file mode 100644
index 0000000..8f0357c
--- /dev/null
+++ b/docs/development/documentation-conventions.md
@@ -0,0 +1,27 @@
+# Documentation conventions
+
+Write for the person using the page. Keep installation steps in
+[Setup](../setup.md), settings in [Configuration](../configuration.md), command help in [Features](../features.md), shipped scope in
+[Capabilities](../capabilities.md), release changes in [Releases](../releases.md),
+and contributor details in this directory. Link instead of repeating a procedure.
+Do not add empty roadmap or known-issues pages; add one only when there is accepted
+future work or a reproduced issue worth publishing.
+
+For a risky integration change, keep the agreed design stable while work is in
+progress and record test evidence separately. List the external operations,
+check Discord and Seerr behavior against their official documentation, cover it
+with deterministic fakes, and make any live test clearly opt-in.
+
+## Documentation impact map
+
+| If you change... | Also update... |
+| --- | --- |
+| A command, message, approval, or notification | [Features](../features.md), [Capabilities](../capabilities.md) when scope changes, and the README if navigation changes |
+| Configuration or setup | [Setup](../setup.md), [Configuration](../configuration.md), Unraid docs when affected, and their nearest index |
+| Package ownership or architecture | [Codebase map](codebase-map.md), [Architecture](architecture.md), and this index |
+| Schema, identifiers, or retained state | [Data](data.md), [Operations](operations.md) when upgrades change, and this index |
+| Tests, tools, interaction states, or CI | [Validation](validation.md) and this index |
+| Credentials, permissions, or disclosure | [Security](../../SECURITY.md), the relevant architecture/data/operations page, and the nearest index |
+| Deployment, backup, health, rollback, or release | [Operations](operations.md), [Releases](../releases.md), and public setup/configuration pages that changed |
+
+Add new documentation domains here and to the nearest index.
diff --git a/docs/development/operations.md b/docs/development/operations.md
new file mode 100644
index 0000000..0b9dc95
--- /dev/null
+++ b/docs/development/operations.md
@@ -0,0 +1,34 @@
+# Operations
+
+Run Augur with the smallest practical permissions: one writable `/data`
+mount, outbound access to Discord and Seerr, and no public inbound port unless
+the optional health server is deliberately exposed to a private monitoring
+network. Keep the Discord token, Seerr API key, `.env`, `config.json`, data
+directory, and diagnostics private. Redact IDs and request details when they are
+not needed to diagnose a problem.
+
+## Back up, upgrade, and roll back
+
+1. Record the current immutable image tag or digest and read the release notes.
+2. Stop Augur, then copy the entire data directory and deployment configuration
+ to restricted off-host storage.
+3. Restore that backup into a separate test location and confirm the bot starts,
+ retains approval settings and subscriptions, and can read the database.
+4. Deploy a published version tag or digest, then check logs, a harmless command,
+ a synthetic request, and any configured approval channel.
+
+`latest` moves and is not a rollback reference. Migrations run at startup and
+are forward-only. To roll back across a schema change, stop Augur, restore the
+matching pre-upgrade data, and start the previous immutable image. Local state
+created after the backup is lost; requests already sent to Seerr are not undone.
+
+When enabled, `/healthz` reports that the health process is running, `/readyz`
+checks readiness and SQLite access, and `/metrics` exposes counters. These
+endpoints are unauthenticated, so bind them privately. A release is complete only
+after the exact revision passes CI, the published artifact is tied to that
+revision, and post-deploy health and Discord/Seerr checks succeed.
+
+The release sequence is: update [release notes](../releases.md), run the final CI
+gate, create an annotated `vX.Y.Z` tag, publish the matching GitHub release,
+verify the versioned image, and announce any operator action. Repairs that change
+Discord messages, Seerr requests, or SQLite state must be explicit and opt-in.
diff --git a/docs/development/validation.md b/docs/development/validation.md
new file mode 100644
index 0000000..8a5c24e
--- /dev/null
+++ b/docs/development/validation.md
@@ -0,0 +1,33 @@
+# Validation
+
+Start with the narrowest useful check:
+
+| Change | Focused check |
+| --- | --- |
+| Application coordination or health | `go test ./internal/app` |
+| Discord commands, cards, or formatting | `go test ./internal/discordbot` |
+| Seerr protocol | `go test ./internal/seer` |
+| SQLite or migrations | `go test ./internal/storage` |
+| Configuration or redaction | `go test ./internal/config ./internal/safelog` |
+| CLI startup | `go test ./cmd/augur` |
+| Release binary | `CGO_ENABLED=0 go build -trimpath -o /tmp/augur ./cmd/augur` |
+
+Run `gofmt -w` on touched Go files. For performance work, include a
+representative before/after benchmark or trace and a regression limit.
+
+Discord changes also need a manual pass in a dedicated server with synthetic
+requests. Check command discovery, keyboard use in Discord clients, ephemeral and
+public responses, missing links, empty search/results, API errors, expired
+components, permission denial, DM-disabled users, and shutdown during polling.
+Check narrow and desktop Discord layouts when card content changes. Live Discord
+or Seerr tests are opt-in and must never use production tokens or data.
+
+Use the Go version in `go.mod`. Do not install tools or download dependencies
+without approval. Reuse a module cache only when `go.sum`, the Go toolchain,
+OS/architecture, and installed module tree match the revision; otherwise use a
+clean locked environment or stop.
+
+The final gate is the complete `CI` workflow in `.github/workflows/ci.yml` on
+the exact revision. It checks formatting, `go mod tidy -diff`, whitespace,
+tests, vet, the race detector, pinned Staticcheck and govulncheck versions, the
+release build, Docker build, entrypoint behavior, and runtime ownership.
diff --git a/docs/releases.md b/docs/releases.md
index ac28268..73dff39 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -7,34 +7,9 @@ tag is published. Pin production deployments to a published semver tag; use `lat
- Standardize project documentation and harden release automation.
+- Refuse to open database state created by a newer Augur version.
- Reject zero-padded root user/group IDs in container configuration.
- Clarify setup, account linking, approval permissions, and backup procedures.
-## Release procedure
-
-Release checklist:
-
-1. Preflight: run the full checks in [`development.md`](development.md), review migration notes, and build and
- smoke-test the image.
-2. Tag: create an annotated semver tag, for example `git tag -a v1.2.3 -m "v1.2.3"`.
-3. Publish: push the tag and create the matching GitHub release, then verify the published container image.
-4. Announce the release with upgrade notes and supported changes.
-
-## Upgrading and rolling back
-
-1. Read the release notes and record the image tag you currently use. Choose an
- image tag listed on the [published release](https://github.com/mayvqt/Augur/releases);
- Git tags include a `v` prefix, while versioned container tags omit it.
-2. Stop Augur with `docker compose stop augur`, then back up the entire `data/`
- directory and your deployment configuration. Keep the backup private: it
- contains account IDs, request state, and possibly credentials.
-3. Set `image:` in `docker-compose.yml` to the chosen container tag, run
- `docker compose pull augur`, then `docker compose up -d augur`.
-4. Check `docker compose logs --tail=100 augur`, then verify `/request` and any
- approval channel you use.
-
-Database migrations run on startup. Do not edit or delete applied migrations.
-An older image may not support a newer database schema. To roll back, stop Augur,
-restore the matching pre-upgrade data backup when required by the release notes,
-and start the previous image tag. Restoring a backup loses local changes made
-since that backup; Seerr requests already submitted are not undone.
+Release and upgrade procedures live in [Operations](development/operations.md).
+This page is only for changes that shipped or are ready for the next release.
diff --git a/docs/setup.md b/docs/setup.md
new file mode 100644
index 0000000..500dcec
--- /dev/null
+++ b/docs/setup.md
@@ -0,0 +1,44 @@
+# Setup
+
+You need a Discord server where you can install an app, a reachable Seerr
+instance, Docker with Compose, and a Seerr API key.
+
+1. Create an application in the
+ [Discord Developer Portal](https://discord.com/developers/applications) and
+ add a bot. Install it with the `bot` and `applications.commands` scopes.
+ Grant View Channel, Send Messages, Embed Links, and Manage Messages.
+2. Clone the repository and open it:
+
+ ```sh
+ git clone https://github.com/mayvqt/Augur.git
+ cd Augur
+ ```
+
+3. Copy the example environment file and add the Discord token, Seerr API key,
+ internal Seerr URL, and public Seerr URL:
+
+ ```sh
+ cp .env.example .env
+ $EDITOR .env
+ ```
+
+4. Start Augur and check its logs:
+
+ ```sh
+ docker compose up -d
+ docker compose logs -f augur
+ ```
+
+5. Run `/link` in Discord. Open the Seerr settings link and save the Discord ID
+ shown by the bot in your Seerr notification settings. Then try
+ `/request query:`.
+
+`AUGUR_SEERR_BASE_URL` must be reachable from inside the Augur container. This
+Compose file starts Augur only, so `http://seerr:5055` works only when a
+container named `seerr` shares its Docker network. Otherwise, use the Seerr
+host's LAN name or address. `AUGUR_SEERR_PUBLIC_URL` is the browser-facing URL
+sent to users.
+
+State is stored in `./data`. Before enabling approval cards, read the
+[permissions and behavior](features.md#approval-cards). Before an upgrade, follow
+the [backup and rollback guide](development/operations.md).
diff --git a/internal/storage/store.go b/internal/storage/store.go
index d7a03c1..573a90d 100644
--- a/internal/storage/store.go
+++ b/internal/storage/store.go
@@ -55,6 +55,8 @@ type NotificationPreferences struct {
const subscriptionColumnList = "request_id, discord_id, title, media_type, overview, poster_path, release_year, language, rating, created_at, completed_at"
+const currentMigrationVersion = 5
+
func Open(path string) (*Store, error) {
dbPath, err := storagePath(path)
if err != nil {
@@ -448,6 +450,13 @@ func (s *Store) initialize(ctx context.Context) error {
if _, err := s.db.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`); err != nil {
return fmt.Errorf("create schema ledger: %w", err)
}
+ var latestVersion sql.NullInt64
+ if err := s.db.QueryRowContext(ctx, `SELECT max(version) FROM schema_migrations`).Scan(&latestVersion); err != nil {
+ return fmt.Errorf("inspect schema version: %w", err)
+ }
+ if latestVersion.Valid && latestVersion.Int64 > currentMigrationVersion {
+ return fmt.Errorf("storage schema version %d is newer than supported version %d", latestVersion.Int64, currentMigrationVersion)
+ }
var migrationCount int
if err := s.db.QueryRowContext(ctx, `SELECT count(*) FROM schema_migrations`).Scan(&migrationCount); err != nil {
return fmt.Errorf("inspect schema ledger: %w", err)
@@ -463,7 +472,7 @@ func (s *Store) initialize(ctx context.Context) error {
}
}
}
- for version := 1; version <= 5; version++ {
+ for version := 1; version <= currentMigrationVersion; version++ {
var applied int
if err := s.db.QueryRowContext(ctx, `SELECT count(*) FROM schema_migrations WHERE version = ?`, version).Scan(&applied); err != nil {
return fmt.Errorf("inspect migration %d: %w", version, err)
diff --git a/internal/storage/store_test.go b/internal/storage/store_test.go
index bf4d12f..22d83c0 100644
--- a/internal/storage/store_test.go
+++ b/internal/storage/store_test.go
@@ -6,6 +6,7 @@ import (
"os"
"path/filepath"
"runtime"
+ "strings"
"testing"
"time"
@@ -34,16 +35,45 @@ func TestFreshDatabaseRecordsOrderedMigrations(t *testing.T) {
if err := rows.Err(); err != nil {
t.Fatal(err)
}
- if got := len(versions); got != 5 {
- t.Fatalf("migration versions = %v, want 1..5", versions)
+ if got := len(versions); got != currentMigrationVersion {
+ t.Fatalf("migration versions = %v, want 1..%d", versions, currentMigrationVersion)
}
for i, version := range versions {
if version != i+1 {
- t.Fatalf("migration versions = %v, want 1..5", versions)
+ t.Fatalf("migration versions = %v, want 1..%d", versions, currentMigrationVersion)
}
}
}
+func TestOpenRejectsNewerMigrationVersion(t *testing.T) {
+ path := filepath.Join(t.TempDir(), "state.db")
+ db, err := sql.Open("sqlite", path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := db.Exec(`
+ CREATE TABLE schema_migrations (version INTEGER PRIMARY KEY, applied_at TEXT NOT NULL);
+ INSERT INTO schema_migrations(version, applied_at) VALUES (?, '2026-01-01T00:00:00Z')
+ `, currentMigrationVersion+1); err != nil {
+ db.Close()
+ t.Fatal(err)
+ }
+ if err := db.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = Open(path)
+ if err == nil {
+ t.Fatal("Open accepted a database with a newer migration version")
+ }
+ if !strings.Contains(err.Error(), "newer than supported") {
+ t.Fatalf("Open error = %q, want a clear unsupported-version error", err)
+ }
+ if strings.Contains(err.Error(), path) {
+ t.Fatalf("Open error exposed database path: %q", err)
+ }
+}
+
func TestExistingMainDatabaseUpgradesWithoutLosingRows(t *testing.T) {
path := filepath.Join(t.TempDir(), "state.db")
db, err := sql.Open("sqlite", path)
@@ -100,8 +130,8 @@ func TestExistingMainDatabaseUpgradesWithoutLosingRows(t *testing.T) {
if err := store.db.QueryRow(`SELECT count(*) FROM schema_migrations`).Scan(&versions); err != nil {
t.Fatal(err)
}
- if versions != 5 {
- t.Fatalf("migration count = %d, want 5", versions)
+ if versions != currentMigrationVersion {
+ t.Fatalf("migration count = %d, want %d", versions, currentMigrationVersion)
}
}