Skip to content

feat(cli): Add review subcommand with eight migration risk rules#91

Merged
mhiro2 merged 14 commits into
mainfrom
feat/review-phase1
Apr 28, 2026
Merged

feat(cli): Add review subcommand with eight migration risk rules#91
mhiro2 merged 14 commits into
mainfrom
feat/review-phase1

Conversation

@mhiro2

@mhiro2 mhiro2 commented Apr 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Introduce a new relune review subcommand that diffs two SQL/JSON inputs and emits migration risk findings classified as info | warning | caution | breaking.
  • Add a relune-core::review engine implementing eight built-in rules: risk/drop-column-referenced, risk/drop-table-referenced, risk/drop-pk-or-unique, risk/add-not-null-on-existing, risk/type-narrow, risk/fk-without-index, risk/add-unique-on-existing, risk/add-cascade-delete.
  • Extend SchemaDiff with on_delete / on_update action diffs so cascade-related rules can reason about FK action changes.
  • Surface text / markdown / JSON output formats, --rules / --except-rule / --except-table filtering, --deny <severity> gating with exit code 10, and a [review] section in relune.toml.
  • Normalize inline UNIQUE column options and table-level UNIQUE constraints as Index entries so review rules can reason about uniqueness regardless of declaration form.

Changes

  • 936e475 : feat(core): detect FK on_delete and on_update changes in SchemaDiff
    • Track referential action transitions in the FK diff so downstream rules can flag risky cascade introductions.
  • 35bfbd2 : feat(core): add review engine with eight migration risk rules
    • Introduce ReviewSeverity, ReviewRuleId (with risk/* serde namespace), RiskFinding, and the run_rules orchestrator that runs each rule against the diff plus the before/after schemas.
  • 3db595f : test(core): add unit tests for the eight review rules
    • Cover happy paths and severity escalations (e.g. PK drop with referencing FK is breaking, FK without supporting index is info).
  • 773ef45 : feat(app): add review use case orchestrating diff and risk rules
    • Wire up ReviewRequest / ReviewResult and provide text / markdown / JSON formatters that include summary counts and per-rule findings.
  • 863b9c6 : test(app): add review fixture suite covering all eight rules
    • Add fixtures/review/<rule-name>/{before.sql,after.sql,expected.json} cases plus paired safe/unsafe variants for the rules with severity escalation logic.
  • 6b7d8f4 : feat(cli): add review subcommand
    • Plumb relune review through the CLI with --before/--after selectors, format flag, output path, and --rules / --except-rule / --except-table filtering.
  • b03cc79 : feat(cli): wire review config section into relune.toml
    • Allow review defaults (rules, exceptions, deny threshold) to be set in relune.toml and merged with command-line flags.
  • 14b46a4 : test(cli): add review command smoke tests
    • Exercise the CLI end-to-end across formats and verify severity-driven exit codes.
  • 1a7f7e1 : docs: document review subcommand
    • Add docs covering rule semantics, severity model, configuration, and exit-code conventions.
  • df6842a : docs: surface review subcommand in README and skill guide
    • Add quick-start usage and link the docs into README plus the skill guide.
  • 92fe692 : feat(parser-sql): normalize inline and table-level UNIQUE as Index entries
    • Materialize ColumnOption::Unique and TableConstraint::Unique into Index { is_unique: true } entries so review rules can detect uniqueness loss; dedupe by name and column set.
  • 988b7d3 : fix(core): tighten drop-pk-or-unique and drop-column-referenced detection
    • Escalate risk/drop-pk-or-unique to breaking when an incoming FK references the dropped UNIQUE column set, mirroring the existing PK escalation.
    • Stop skipping same-table FKs in risk/drop-column-referenced so self-referencing FK target columns are reported, while filtering the overlap with the outgoing-FK pass to avoid duplicate findings.
  • b81ded7 : fix(cli): exit 10 on review --deny and suppress findings under --quiet
    • Add a dedicated ReviewDenied CliError variant that maps to exit code 10, distinguishing a deny-threshold failure from a generic error.
    • Honour --quiet for review by emitting only the summary line in text and markdown formats.
  • f0580f0 : fix(core): flag PK widening that breaks existing FK references
    • Add a per-table check that fires when the after PK is a strict superset of the before PK and no UNIQUE index in after covers the original column set; severity escalates to breaking when an incoming FK targets the original column set.
    • Tighten the column-path equality check to use exact set comparison rather than superset coverage.

mhiro2 added 14 commits April 27, 2026 23:23
Foreign keys whose only modification was a referential action change
were previously reported as Unchanged because fks_differ ignored
on_delete and on_update. Compare those fields so cascade behavior
shifts surface in diff output, which Phase 1 review rules
(risk/add-cascade-delete) will rely on.
Introduces the relune-core::review module that compares before/after
schemas and surfaces migration-time risks distinct from the existing
lint engine. Implements eight rules covering dropped references,
narrowing type changes, NOT NULL on existing data, dropped PK/UNIQUE,
new UNIQUE on existing data, ON DELETE CASCADE additions, and new
foreign keys without supporting indexes. ReviewSeverity is intentionally
separated from the lint Severity so migration safety can be reasoned
about independently from schema quality.
Add positive and negative test cases for each rule in `risk/<id>`
namespace, asserting both severity escalation and intentional-disconnect
silencing behavior.
Wires `parse → diff → run_rules → suppression → summary` into a single
`run_review` entry point with text / markdown / json output helpers.
Handles `--rules` / `--except-rule` / `--except-table` / `--deny`
semantics in the application layer.
Each rule gets a positive case; rules with notable false-positive
boundaries get a paired negative fixture (FK also dropped, indexed FK,
NOT NULL on a brand-new table). The integration test golden-compares
findings against `expected.json`; set `UPDATE_FIXTURES=1` to refresh.
Wire up the relune-app review pipeline into a new `relune review`
subcommand with --before/--after inputs (file/text/JSON variants),
--format text|markdown|json, --rules/--except-rule/--except-table,
--deny <severity>, --exit-code, and -o/--out support.
Add a [review] table to ReluneConfig with format, dialect, rules,
except_rules, except_tables, and deny fields, plus the merge_review_args
helper used by the review command. CLI flags continue to take precedence
over config values.
Cover text, markdown, and JSON formatting paths along with --deny,
--exit-code, --except-rule, --except-table, --out, and config-driven
defaults. Capture the review --help output as an insta snapshot so the
CLI surface stays stable.
Describe the review CLI surface, configuration keys, and crate roles so
docs/cli-reference.md, docs/configuration.md, and ARCHITECTURE.md cover
the new schema review workflow.
Promote the new schema review workflow alongside lint and diff in the
top-level README and the relune skill so quick-start examples and
migration-review playbooks include the new command.
…tries

Inline `UNIQUE` column options and `UNIQUE` table constraints were previously
discarded during CREATE TABLE parsing, which left review rules unable to detect
that a column drop also removes a unique guarantee. Materialize both forms as
`Index { is_unique: true }` entries and dedupe by name + column set so the
schema model reflects every uniqueness contract.
…tion

`risk/drop-pk-or-unique` now escalates the UNIQUE-index removal path to
`breaking` when an incoming foreign key targets the unique column set,
mirroring the primary-key escalation rule.

`risk/drop-column-referenced` no longer skips same-table foreign keys, so
self-referencing FK target columns are reported correctly. The same-table
overlap with the outgoing-FK pass is filtered out to avoid duplicate
findings.
- Add a dedicated `ReviewDenied` `CliError` variant that maps to exit code
  `10`, matching the documented "review denied" gate semantics so CI can
  distinguish a deny-threshold failure from a generic error.
- Honour `--quiet` for `review` by emitting only the summary line in text
  and markdown formats; the findings body and per-rule details are
  suppressed when the flag is set.
- Update review CLI smoke tests: rename the deny test to assert exit code
  10 and add a `--quiet` suppression test.
`risk/drop-pk-or-unique` previously missed the case where a primary key is
widened in place — e.g. `PRIMARY KEY (id)` becoming `PRIMARY KEY (id,
tenant_id)` — because no individual column loses its PK status, so the
column-path check is never entered. The original column set is no longer
guaranteed unique once the PK is composite, and an FK that still references
just `(id)` is unsupportable in most engines.

Add a per-table check that fires when the after PK is a strict superset of
the before PK and no UNIQUE index in `after` covers the original column
set. The severity escalates to `breaking` when an incoming FK targets the
exact original column set, mirroring the existing PK-drop and UNIQUE-drop
escalations. Tighten the column-path equality check to use exact set
comparison rather than superset coverage as a defensive measure.
@mhiro2 mhiro2 self-assigned this Apr 28, 2026
@mhiro2 mhiro2 added the enhancement New feature or request label Apr 28, 2026
@github-actions

Copy link
Copy Markdown

Code Metrics Report

main (2a4d39f) #91 (3dae7e9) +/-
Coverage 94.7% 94.6% -0.2%
Test Execution Time 1m26s 1m32s +6s
Details
  |                     | main (2a4d39f) | #91 (3dae7e9) |  +/-  |
  |---------------------|----------------|---------------|-------|
- | Coverage            |          94.7% |         94.6% | -0.2% |
  |   Files             |             77 |            81 |    +4 |
  |   Lines             |          34529 |         36827 | +2298 |
+ |   Covered           |          32726 |         34864 | +2138 |
- | Test Execution Time |          1m26s |         1m32s |   +6s |

Code coverage of files in pull request scope (93.3% → 93.2%)

Files Coverage +/- Status
crates/relune-app/src/lib.rs 96.2% 0.0% modified
crates/relune-app/src/request.rs 80.9% -0.9% modified
crates/relune-app/src/result.rs 96.0% -3.4% modified
crates/relune-app/src/usecases/review.rs 94.5% +94.5% added
crates/relune-cli/src/cli.rs 88.0% 0.0% modified
crates/relune-cli/src/commands/input.rs 85.0% +1.2% modified
crates/relune-cli/src/commands/review.rs 92.1% +92.1% added
crates/relune-cli/src/config.rs 96.1% -0.6% modified
crates/relune-cli/src/error.rs 97.9% +0.4% modified
crates/relune-cli/src/main.rs 95.0% +0.1% modified
crates/relune-core/src/diff.rs 94.3% +3.3% modified
crates/relune-core/src/review.rs 85.5% +85.5% added
crates/relune-core/src/review/rules.rs 92.2% +92.2% added
crates/relune-parser-sql/src/lib.rs 93.5% -0.1% modified

Reported by octocov

@mhiro2
mhiro2 merged commit c74d3e2 into main Apr 28, 2026
5 checks passed
@mhiro2
mhiro2 deleted the feat/review-phase1 branch April 28, 2026 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant