A migration can look small in a pull request and still carry a large operational question: what does this statement mean for the schema, the data behind it, and the services that depend on it? A line of SQL alone cannot answer all three.
Sentinel-DB is my Go prototype for exploring that review step. Its migration analyzer works offline: it recognizes a narrow set of SQL shapes, combines them with a seeded schema and dependency graph, then returns a heuristic score and review advice. It does not connect to PostgreSQL, execute a migration, or inspect a live workload.
ALTER TABLE core.orders
DROP COLUMN customer_id;Analyze the shape. Do not execute it.
- 01 / INPUTSanitize
Ignore comments and quoted text. Reject empty, oversized or multi-statement input.
- 02 / CLASSIFYMatch a narrow shape
Recognized patterns continue. Unsupported SQL becomes
UNKNOWN_DDL. - 03 / CONTEXTApply fixture rules
Seeded row counts and dependency nodes shape the heuristic factors.
- 04 / REVIEWBLOCKED · 95/100
Recorded sample result. Heuristic only; no live database facts.
The SQL is never run. The catalog, blast radius and score come from synthetic fixtures—not a PostgreSQL connection.
Recognize a small grammar on purpose
The analyzer accepts one statement at a time. Before classification, it blanks comments and quoted text so an operation mentioned inside a comment or string does not become the operation being reviewed. Multiple statements and inputs that do not fit the supported grammar become UNKNOWN_DDL.
The recognized shapes include selected forms of DROP COLUMN, DROP TABLE, ALTER COLUMN TYPE, nullable ADD COLUMN, CREATE INDEX, and UPDATE. This is deliberately narrower than PostgreSQL. The code uses a small sanitizer and regular-expression classifiers; it is not a PostgreSQL parser. Quoted identifiers, CTEs, compound scripts, and other unsupported forms should reach manual review rather than receive a reassuring result.
That boundary is a design decision. If the analyzer cannot explain the statement it received, it should say so instead of guessing that the change is safe.
Turn a known shape into review questions
For a recognized operation, the prototype applies deterministic rules and looks up context in a synthetic catalog and seeded lineage graph. The result can include an operation risk, a sample row-count factor, downstream fixture nodes, an estimated lock mode and a rollout playbook.
In the recorded example, ALTER TABLE core.orders DROP COLUMN customer_id returns BLOCKED with a score of 95/100. That number comes from the checked-in sample fixtures. The row count, dependent objects, lock duration and score are not observations from a real database, and the score is not a probability or a deployment approval.
The useful output is the explanation around the verdict: remove reads and writes first, deploy that application change, check for remaining references, then consider the contract migration in an appropriate window. The steps are a review prompt. They do not verify that every consumer has been updated.
Treat uncertainty as an outcome
The analyzer distinguishes “recognized but needs a canary” from “not understood.” A top-level WHERE makes an UPDATE recognizable, but the tool cannot estimate predicate selectivity. So WHERE TRUE still needs canary review; a missing top-level predicate is blocked. Unsupported statements and multiple-statement input are also blocked by the current rules.
This does not make the rule set complete. It makes one limitation visible: a syntactic signal is not evidence that a production change is bounded. Even CREATE INDEX CONCURRENTLY has operational details an offline pattern check cannot validate. PostgreSQL documents that concurrent index builds avoid locks that prevent ordinary writes, but they take extra work, wait for transactions, and cannot run inside a transaction block. A migration tool, target server version and live workload still matter. See the PostgreSQL CREATE INDEX documentation.
What I verified
At the reviewed source revision, the Go CI workflow passed. It runs go vet, race-enabled Go tests and a build, scans reachable Go vulnerabilities, validates the Compose file, and builds and smoke-tests the restricted demo container. The container check confirms that its health response identifies synthetic fixtures and that the sample DROP COLUMN request returns BLOCKED.
Those checks verify selected code paths and the demo runtime. They do not establish compatibility with a live PostgreSQL server, measure lock duration, or prove that a proposed migration is safe for a particular application.
Where I would take it next
The next useful step is not a more confident score. It is stronger evidence: replace the regex classifier with a PostgreSQL-aware parser, test supported versions against a disposable database, and compare the analyzer’s interpretation with PostgreSQL’s actual behavior. Only after that would a read-only connection to a real catalog add trustworthy context. Any live mode would need to be clearly separated from the current fixture-backed demonstration.
For other examples of bounded engineering decisions, read how Aurora quarantines and revalidates synthetic telemetry and how FieldCore handles an exact sync retry.