Metadata-Version: 2.4
Name: novyx-blast-radius
Version: 0.1.0
Summary: Review AI-authored SQL migrations and post a self-explaining Change Ticket that gates only the irreversible, high-blast-radius changes.
Author-email: Novyx Labs <blake@novyxlabs.com>
License: MIT
Project-URL: Homepage, https://github.com/novyxlabs/novyx-blast-radius
Project-URL: Repository, https://github.com/novyxlabs/novyx-blast-radius
Project-URL: Issues, https://github.com/novyxlabs/novyx-blast-radius/issues
Keywords: database-migrations,sql,change-management,github-actions,ai-agents,code-review,guardrails,terraform
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0
Requires-Dist: sqlglot>=20.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: ruff>=0.5.0; extra == "dev"
Provides-Extra: postgres
Requires-Dist: psycopg[binary]>=3.1; extra == "postgres"
Dynamic: license-file

# novyx-blast-radius

**A GitHub Action that reviews AI-authored SQL migrations and posts a self-explaining Change Ticket on the pull request — gating only the irreversible changes that actually destroy data.**

Coding agents open PRs with database migrations now. A blanket "a human must approve all migrations" rule trains people to rubber-stamp; a regex content policy can't tell `DROP COLUMN` on an empty table from `DROP COLUMN` on 2.3M rows. novyx-blast-radius grades each statement on two axes — **reversibility** (structural) and **blast radius** (live row impact) — and only stops the statements that are both irreversible *and* material. Everything additive passes silently.

The output is a **Change Ticket**: one readable, hash-verifiable comment that says what the change does, what it would destroy, and what an approver must check.

---

## Quick start (GitHub Action)

Add `.github/workflows/novyx-change-ticket.yml`:

```yaml
name: Novyx Change Ticket
on:
  pull_request:
    paths:
      - "**/*.sql"

permissions:
  contents: read
  pull-requests: write

jobs:
  change-ticket:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: novyxlabs/novyx-blast-radius@v1
```

That's it. On every PR that touches a `.sql` file, the Action posts (and keeps updated) a Change Ticket comment, and **fails the check only when a statement is irreversible and destroys data** (configurable — see [Options](#options)).

### What the comment looks like

For a migration that adds one column and drops another:

```sql
ALTER TABLE users ADD COLUMN nickname text;
ALTER TABLE users DROP COLUMN legacy_token;
```

> **Verdict:** `requires_approval`
>
> **Impact:** statement 2 of 2 drops column `users.legacy_token`, which holds
> 2,300,000 rows — irreversible; 2,300,000 rows destroyed. The other 1 are additive.
>
> **Operator Review** — decision `approve_or_revise`, owner `platform-lead`
> - approval reason cites the concrete blast radius
> - backup or rollback reference
> - dry-run output or live row-count evidence
>
> **Safer alternative:** replace statement 2 with a nullable deprecation marker
> or a staged backfill before dropping `users.legacy_token`.

The additive `ADD COLUMN` is never flagged. Only the destructive drop gates.

---

## How the verdict is decided

Every statement lands in one of three buckets:

| Verdict | Meaning | Default check result |
| --- | --- | --- |
| `auto_allow` | Additive/reversible, or irreversible with zero data impact | ✅ pass |
| `requires_approval` | Irreversible and destroys data below the block threshold | ✅ pass, flagged in ticket |
| `block` | Irreversible and destroys ≥ 10M rows (catastrophic) | ❌ fail |

Anything the parser doesn't recognize **fails closed** to `requires_approval` — a gate that treated unknown SQL as safe would be worse than no gate.

### Where the row counts come from

By default the Action runs with **no database connection**, so it knows structure but not live row counts — every destructive statement fails closed to approval. To get precise blast radius (and let genuinely-empty drops auto-allow), give it live facts one of two ways:

- **Connect a read-only Postgres probe** (`pip install "novyx-blast-radius[postgres]"`) and point it at a replica.
- **Seed counts** for no-DB analysis: `--column users.legacy_token=2300000` or `--table events=50000000`.

---

## Options

```yaml
- uses: novyxlabs/novyx-blast-radius@v1
  with:
    fail-on: block        # block (default) | approval | never
    comment: "true"       # post/update the PR comment
    python-version: "3.11"
    version-spec: ""      # default: install from the action's pinned source
```

> `version-spec` defaults to installing the checker from the Action's own
> pinned source, so `@v1` works with no PyPI dependency. Once the package is on
> PyPI you can set `version-spec: novyx-blast-radius==0.1.0` to pin a release.

- `fail-on: block` — fail only on catastrophic (≥10M row) drops. **Default.**
- `fail-on: approval` — fail on anything that isn't auto-allowed (strict).
- `fail-on: never` — never fail the check; just post the ticket (advisory mode).

---

## CLI

The same engine runs locally:

```bash
pip install novyx-blast-radius

# One ticket across changed SQL, GitHub workflow, and Terraform plan files:
novyx-pr-check db/migrations/0007_drop_legacy.sql --format markdown

# Single file, JSON, with a seeded row count:
novyx-change-ticket migration.sql --column users.legacy_token=2300000 --format json --out ticket.json

# Verify a ticket's hash is self-consistent:
novyx-change-ticket --verify-ticket ticket.json
```

Exit codes: `0` auto-allow, `2` requires approval, `3` block.

---

## Beyond SQL

The same Change Ticket also classifies two other high-blast-radius change types when they appear in a PR:

- **GitHub Actions workflows** — production deploys reachable from PRs, write-token escalation, secret exposure, unpinned third-party actions.
- **Terraform plans** (`terraform show -json`) — admin IAM grants, public sensitive ingress, public bucket exposure, sensitive-infra destroys.

Point the Action at those paths and they fold into the same ticket. SQL migrations are the sharp edge; the rest is coverage.

---

## Trust boundary (read this)

`ticket_hash` is an **unsigned SHA-256 checksum**. It detects a ticket whose body was altered without recomputing the hash — it is **not an authenticity signature**. A caller that controls the ticket can recompute a matching hash. Today the gate is an **advisory, self-explaining review aid**, not an adversarial-safe enforcement boundary. Cryptographically-signed tickets are on the roadmap. Use it to make dangerous migrations *visible and reviewable*, not to stop a motivated insider.

---

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check .
```

## License

MIT — see [LICENSE](LICENSE).

Built by [Novyx Labs](https://novyxlabs.com). Questions: blake@novyxlabs.com.
