# cerbero v0.1.0 - Table of Contents

> Offline safety checks for Ecto migrations, judged against a committed snapshot of production database catalog metadata (PostgreSQL and CockroachDB).

## Pages

- [Cerbero](readme.md)
- [Changelog](changelog.md)
- [Contributing to cerbero](contributing.md)
- [Security Policy](security.md)

## Modules

- [Cerbero](Cerbero.md): Offline safety checks for Ecto migrations, judged against a committed
snapshot of production catalog metadata (Postgres and CockroachDB).
- [Cerbero.CLI.Check](Cerbero.CLI.Check.md): argv -> findings -> formatted output -> exit code. Injectable clock and IO.
- [Cerbero.CLI.Format.Human](Cerbero.CLI.Format.Human.md): Severity-first human output: findings are grouped into an errors section,
then warnings, then (only with `--verbose`) informational notes, each
section headed by a count so the reader knows what to fix first. Color is
applied via `IO.ANSI` and disabled automatically when output is not a
terminal or `NO_COLOR` is set, so piped/CI logs stay plain and the golden
tests stay stable.

- [Cerbero.CLI.Format.JSON](Cerbero.CLI.Format.JSON.md): Stable, versioned, canonically-encoded JSON output.
- [Cerbero.CLI.Format.SARIF](Cerbero.CLI.Format.SARIF.md): SARIF 2.1.0 output: a mechanical adapter over the findings list for
GitHub code-scanning annotations. Global findings (no file) anchor to
the committed snapshot artifact — the file they are actually about.

- [Cerbero.CLI.GenConfig](Cerbero.CLI.GenConfig.md): argv for mix cerbero.gen.config: `--out` (default `.cerbero.exs`), `--force`.
- [Cerbero.CLI.Snapshot](Cerbero.CLI.Snapshot.md): argv for mix cerbero.snapshot: --url | --emit-sql | --from-file, --out,
--config, --migration-source, --precision, --engine (postgres |
cockroachdb; steers --emit-sql only — the live path detects the engine
from the connection and --from-file from the file's own sections).
- [Cerbero.Catalog](Cerbero.Catalog.md): The queryable in-memory model the checks run against. Row-estimate
policy: max(reltuples, n_live_tup) when reltuples >= 0, else n_live_tup;
partitioned parents are the sum of their partitions, never the parent
row; unknown scale is unbounded, never small.

- [Cerbero.Check](Cerbero.Check.md): Behaviour for migration checks. Internal rules are its first consumers;
it is public API by design (spec constraint, born from real Credo-check
pain).
- [Cerbero.Check.CRDBTransactionalDDL](Cerbero.Check.CRDBTransactionalDDL.md): Rule 7: CockroachDB transactional schema-change restrictions.
- [Cerbero.Check.ColumnDefaultRewrite](Cerbero.Check.ColumnDefaultRewrite.md): Rule 3: volatile defaults and GENERATED STORED columns rewrite the table.
- [Cerbero.Check.ColumnTypeChange](Cerbero.Check.ColumnTypeChange.md): Rule 4: type changes — rewrite + index rebuilds on PG; engine-rejection table on CRDB.
- [Cerbero.Check.ConcurrentIndexRequiresAttributes](Cerbero.Check.ConcurrentIndexRequiresAttributes.md): Rule 10: concurrently: true without both @disable_ddl_transaction and
@disable_migration_lock fails at deploy time and leaves an invalid
index. Rule 1's own advice must not produce that.

- [Cerbero.Check.DMLInMigration](Cerbero.Check.DMLInMigration.md): Rule 9: classifier-detected UPDATE/DELETE/INSERT..SELECT against a table above threshold.
- [Cerbero.Check.FKMissingIndex](Cerbero.Check.FKMissingIndex.md): Rule 6: a new FK whose referencing column has no covering index in catalog ∪ overlay.
- [Cerbero.Check.FKValidationScan](Cerbero.Check.FKValidationScan.md): Rule 5: ADD FK scans the referencing table while blocking writes on BOTH tables.
- [Cerbero.Check.Helpers](Cerbero.Check.Helpers.md): Shared rule scaffolding: the migration-local fold, message construction, finding assembly.
- [Cerbero.Check.Judgment](Cerbero.Check.Judgment.md): The shared judgment spine every scale-gated rule walks:
born-silence -> scale/traffic -> `Cerbero.Severity.assess/6` -> finding
assembly (default relations, judged-lock metadata).
- [Cerbero.Check.MetaFindings](Cerbero.Check.MetaFindings.md): The escape routes for the worst migrations must be reachable by
--fail-on: unclassified SQL, unknown operations, and unmapped lock
entries all warn by default, never silence.

- [Cerbero.Check.NotNullOnPopulatedTable](Cerbero.Check.NotNullOnPopulatedTable.md): Rule 2: SET NOT NULL scans under AEL unless a validated IS NOT NULL CHECK exists (PG >= 12).
- [Cerbero.Check.RawDDLSafety](Cerbero.Check.RawDDLSafety.md): Catch-all for classified raw SQL whose class no other rule consumes.
- [Cerbero.Check.Runner](Cerbero.Check.Runner.md): Orders pending migrations, threads the overlay, applies skips and severity overrides.
- [Cerbero.Check.SnapshotHealth](Cerbero.Check.SnapshotHealth.md): Rule 8: the snapshot's own health, surfaced as findings — never silent
decay, never exit 2. Staleness degrades confidence; absence is never
safety.

- [Cerbero.Check.UnsafeIndexCreation](Cerbero.Check.UnsafeIndexCreation.md): Rule 1: non-concurrent create/drop index. Severity scales with size and traffic.
- [Cerbero.Config](Cerbero.Config.md): Checker configuration, loaded from `.cerbero.exs` (a keyword list).
- [Cerbero.DDL.CRDB](Cerbero.DDL.CRDB.md): CockroachDB limitation table, keyed by class and version. Same
data-not-conditionals rigor as Locks. Scoped to the facts rules 4 and 7
consume; layer 4 asserts the observable behaviors behind it.

- [Cerbero.DDL.Effect](Cerbero.DDL.Effect.md): What one operation does to the database: lock mode, cost class, touched relations.
- [Cerbero.DDL.Effects](Cerbero.DDL.Effects.md): Operation -> [Effect]. Total: unmapped classes get the conservative default.
- [Cerbero.DDL.Locks](Cerbero.DDL.Locks.md): The (operation class, engine, version range) -> {lock, cost} mapping.
This is DATA, not conditionals; layer 4's lock-verification suite is
its empirical anchor. Anything absent returns :unmapped and Effects
applies the conservative default (AEL + rewrite + tripwire finding).

- [Cerbero.Finding](Cerbero.Finding.md): One judged fact: mechanism + scale + provenance, with source location.
- [Cerbero.Migration](Cerbero.Migration.md): A parsed migration file: attributes + ordered operations. `operations`
holds the deploy direction (`up`/`change`); `down_operations` holds the
rollback direction (`down` bodies, plus the down leg of two-arg
`execute`), judged only when `mix cerbero.check --down` asks.

- [Cerbero.Migration.Parser](Cerbero.Migration.Parser.md): Static AST analysis of migration source. Never compiles or executes
user code: Ecto's DSL macros call the private, repo-bound
Ecto.Migration.Runner, so interception would mean replicating private
API. Cost: dynamically-generated operations are invisible — they are
emitted as %Unknown{}, never silence.

- [Cerbero.Operation](Cerbero.Operation.md): Typed operations mirroring Ecto migration DSL semantics, with source lines.
- [Cerbero.Operation.AlterTable](Cerbero.Operation.AlterTable.md): `alter table(...)` with its add/modify/remove column ops.
- [Cerbero.Operation.CreateConstraint](Cerbero.Operation.CreateConstraint.md): `create constraint(...)`, including CHECK body and validate option.
- [Cerbero.Operation.CreateIndex](Cerbero.Operation.CreateIndex.md): `create index(...)`, including unique/concurrently options.
- [Cerbero.Operation.CreateTable](Cerbero.Operation.CreateTable.md): `create table(...)` with its column definitions.
- [Cerbero.Operation.DropIndex](Cerbero.Operation.DropIndex.md): `drop index(...)`, including the concurrently option.
- [Cerbero.Operation.DropTable](Cerbero.Operation.DropTable.md): `drop table(...)`.
- [Cerbero.Operation.RawSQL](Cerbero.Operation.RawSQL.md): `execute "..."` with the classifier's reading of each statement.
- [Cerbero.Operation.RenameOp](Cerbero.Operation.RenameOp.md): `rename table(...), to: ...` (table or column rename).
- [Cerbero.Operation.Unknown](Cerbero.Operation.Unknown.md): A dynamically-built operation the static parser cannot read — never silence.
- [Cerbero.SQL.Classifier](Cerbero.SQL.Classifier.md): Keyword-heuristic classification of raw SQL in `execute/1,2`. This is
deliberately NOT a SQL parser: anchored patterns over normalized text,
with `:unknown` as the honest fallback (surfaced as `unclassified_sql`).
DML is *detected* (target table), never analyzed.
- [Cerbero.SQL.Classifier.Classified](Cerbero.SQL.Classifier.Classified.md): One classified raw SQL statement: its class plus the identifiers the patterns captured.
- [Cerbero.Severity](Cerbero.Severity.md): severity(lock, cost, scale, traffic, config, multiplier).
- [Cerbero.Snapshot](Cerbero.Snapshot.md): The snapshot artifact: decode, verify, canonically re-encode.
- [Cerbero.Snapshot.Bucketing](Cerbero.Snapshot.Bucketing.md): Opt-in `precision: :order_of_magnitude` export mode: buckets counts and
bytes to their power-of-ten floor so a committed snapshot does not reveal
exact business metrics (a `subscriptions` row count is a revenue proxy).
- [Cerbero.Snapshot.Canonical](Cerbero.Snapshot.Canonical.md): Canonical JSON: object keys sorted lexicographically, 2-space indent,
LF endings, trailing newline. The checksum is computed over these bytes,
and stable ordering is what makes PR diffs reviewable, so this encoder
must stay byte-stable across Elixir/OTP versions.

- [Cerbero.Snapshot.Exporter](Cerbero.Snapshot.Exporter.md): Builds a raw snapshot map from a live connection (or a DBA-returned
file). Session is read-only with a short statement_timeout — defense in
depth, not the privacy mechanism (that is the Queries allowlist).
- [Cerbero.Snapshot.Exporter.Queries](Cerbero.Snapshot.Exporter.Queries.md): EVERY SQL statement the exporter can run, on one reviewable screen.
No dynamic SQL beyond schema-name parameters and the quoted
migrations-table identifier. The only non-catalog read is the versions
column of the migrations table. This module is the privacy allowlist's
first layer — review it like one.
- [Cerbero.Snapshot.Signature](Cerbero.Snapshot.Signature.md): Optional Ed25519 tamper-proofing for snapshots.
- [Cerbero.Snapshot.Staleness](Cerbero.Snapshot.Staleness.md): Staleness degrades confidence, never fails unrelated PRs. Past the
headroom window, severity thresholds shrink (a table at 600k rows three
weeks ago is judged as if at the 1M tier). Past the degrade age, every
row count becomes unknown → unbounded, so a stale snapshot cannot
silently certify anything — but PRs with no pending migrations still
pass. The age findings themselves are emitted by the snapshot_health
rule, which consumes this struct.

- [Cerbero.Snapshot.Table](Cerbero.Snapshot.Table.md): One table's decoded catalog metadata: stats, columns, indexes, constraints.

## Mix Tasks

- [mix cerbero.check](Mix.Tasks.Cerbero.Check.md): Parse pending Ecto migrations (static AST — your code never runs), fold
their effects into the catalog model recorded by `mix cerbero.snapshot`,
and judge each operation by lock mode × cost class × your production scale
and traffic. Meant to run in CI, where no database is reachable: the exit
code is the verdict.
- [mix cerbero.gen.config](Mix.Tasks.Cerbero.Gen.Config.md): Write a `.cerbero.exs` config file with every setting spelled out at its
built-in default and commented, so the full surface is visible in one
place. Deleting any line falls back to the same default — the file is a
starting point to edit, not a required manifest. See `Cerbero.Config` for
the meaning of each key.
- [mix cerbero.snapshot](Mix.Tasks.Cerbero.Snapshot.md): Export a snapshot of your database's catalog metadata — schema shapes,
row/byte estimates, traffic counters, index/constraint validity — to a
canonical, checksummed JSON artifact you commit. `mix cerbero.check`
judges pending migrations against it. No row data, expression text, or
literals are ever exported (see the privacy boundary in the README).

