The AI-Native SDLC Playbook Stops at the Database
Anthropic recently published The AI-Native SDLC playbook, and it's the most concrete writeup I've seen discussing the effects of cheaper code generation on a delivery process, written by a company where AI authors 80% of merged code. If you run engineering at any scale, I suggest you read it.
It consists of six stages from Plan to Maintain that work in a loop rather than a linear chain of handoffs. Each stage commits an artifact that the next stage reads. The chain of commits is the audit trail. Humans stop reading every line and start judging intent and risk.
GitLab's CEO responded with the line that will likely outlive the playbook itself: "when implementation becomes abundant, trust becomes scarce."
Code is abundant now. Your data is not. An agent can regenerate your code in an afternoon, but it cannot regenerate the production state your company accumulated over years. The playbook's recovery model quietly assumes otherwise, which leads to our claim: the loop does not close at the database.
The database is the one stateful component in your infrastructure. It's where the history lives, and you cannot roll back history by redeploying an older version of it.
Where Databases Differ
To better explain why databases need to be considered differently than the rest of your codebase, I'll describe two common scenarios that often break production in real applications.
A Session That "Does Everything Right"
A support ticket comes in stating that users are creating duplicate accounts with the same email. Someone brainstorms
with Claude and commits an intent.md to stop new duplicates at the source. The spec.md pass runs with the org's
security and compliance skills loaded, finding no new exposure. In plan mode, Claude writes a plan.md for the
PostgreSQL database:
# Plan: enforce unique emails (from intent.md 2026-08-24)
## Files that change
db/schema.sql, api/users/create.go, api/users/create_test.go
## Order of work
1. Add a unique constraint on users.email.
2. Return 409 on the duplicate path instead of 500.
## Proof
create_test.go covers the duplicate case.
The engineer reads and accepts it. Claude implements. The migration is one statement:
ALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE (email);
Tests pass against a development database with 40 rows in users. The review passes find nothing, because there is
nothing to find: the diff is three lines and does exactly what the plan says. A code owner approves, it merges, and
the pipeline ships it.
Say the ops team already cleaned up the old duplicates, so the constraint is valid. PostgreSQL still takes an
ACCESS EXCLUSIVE lock on users and scans the table to build the index behind the constraint. On the production
table, that scan runs for 90 seconds, and every new read and write on users queues behind it.
Logins hang, the connection pool drains, the incident spreads to services that never even touched this table.
That is the loud version. Here is the quiet one.
A Second Run, on MySQL
A product change adds an on_hold state to orders. This change must account for MySQL's behavior of comparing enum
values by their position in the list, so that queries like WHERE status < 'shipped' return the proper value.
So the spec.md asks to keep the values in lifecycle order, with on_hold between paid and shipped. Claude does
exactly that:
ALTER TABLE orders MODIFY COLUMN status
ENUM('pending','paid','on_hold','shipped','delivered','cancelled') NOT NULL;
Tests pass, the review sees a one-line enum edit, it merges.
While appending at the end of the enum list would have been a metadata-only change, inserting in the middle changes
what every stored row means. No ALGORITHM=INPLACE or INSTANT exists for this change, necessitating a rewrite for
every row.
The statement silently falls back to ALGORITHM=COPY, creating a full second copy of orders and blocking writes.
orders is 600 GB on a 1 TB volume. An hour in, the volume fills, the copy aborts, and a MySQL server that cannot
write to its data volume stops accepting writes altogether.
Appending on_hold at the end would have been instant, with only the comparison queries needing to change. This was
a trade-off nobody in the chain knew was being made.
Takeaways
These are just two common incidents: a lock held longer than the application can tolerate and a rewrite the storage cannot absorb. In both, every gate fired, every artifact was committed, and nobody made a mistake. Both changes still became incidents because nothing in the chain ever looked at the target database.
So for the database, the check cannot live after the merge. It has to run before the statement executes. Disciplined expand/contract migrations keep changes reversible, and that discipline is exactly what an agent will not apply unless something forces it. This is what we will cover in the following sections.
The Recovery Model Is Revert-Shaped
In the playbook, rollback is called "the most rehearsed path in the pipeline": a single command, exercised regularly
in staging, that even the agent is allowed to run. Branch protection, REVIEW.md, and tiered autonomy per environment,
all calibrated on the idea that when a bad change slips through, you redeploy the previous image to get back where you
started. Catching problems after the merge is acceptable because the cost of being wrong is a revert.
The two most-read postmortems of the last decade are both database incidents:
- GitHub's 2018 outage: 24 hours of untangling a MySQL failover that had accepted writes on two primaries.
- GitLab's 2017 outage: one command meant for the secondary ran on the primary, followed by the discovery that none of their five backup methods had worked.
Neither was a code problem. Neither had a revert. Because databases don't work this way.
DROP COLUMN has no lossless inverse. A backfill that ran for forty minutes does not un-run. We have written before
about why down migrations do not deliver on their promise. Once production traffic has
written data under the new schema, the down file no longer describes a state you can return to.
They are also the exact inverse of GitOps. Rolling back means deploying an earlier commit, and the earlier commit doesn't carry the down files for migrations that came after it. The two things a bad schema change actually costs you – downtime and data loss – are the two things no rollback can undo.
Plans Should Be Computed, Not Generated
plan.md works for code because the plan and its target both live in the repo. A schema change runs against a database,
and whether it is safe depends on what's there. The repo records how the schema got here, but it doesn't know how
production currently looks.
An agent writing DDL freehand is guessing at that state, so don't have it write migrations at all. Instead, have it change the schema code in your repo.
Using Atlas, your schema is code, whether written as ORM models, SQL files, or HCL:
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
- email text NOT NULL,
+ email text NOT NULL UNIQUE,
name text
);
Then, let Atlas compute the migration from it:
atlas migrate diff add_unique_email --env local
Every change to the schema code becomes a revision in the schema history: a migration file in the versioned workflow, or a plan in the declarative one. The schema at version X is the result of replaying all files up to X, and Atlas verifies that this result matches the schema code at that commit. The history and code cannot drift apart, and the plan produced is approved by a human, not a model.
How a change becomes a migration is a diff policy you set once. The
same schema change can be achieved by different migrations, as long as the database ends in the same state. For example,
making a column NOT NULL can be one blocking ALTER, or a lock-safe sequence with a
pre-migration check. The policy decides which one Atlas plans. Backfills and data migrations are still written by hand
(or by agent) and pass through the same gates below.
The playbook also recommends running several Claude Code sessions in parallel worktrees. Git merges their code, but migration directories don't merge because they are a linear history. Two sessions can add a migration on the same base and both look fine in their individual branches, but the merged directory registers the inconsistency. Atlas verifies the directory's integrity in CI, so the last branch to merge fails until it rebases. This is why many teams go declarative: the schema code merges like any other code.
The Feedback Loop the Playbook Asks For
Stage 4 has the best rule in the playbook: the agent always gets a way to verify its own work (tests, a build, a screenshot diff) and iterates until the check passes, before a human sees anything.
Agreed. But traditional testing is unaware of the database. A test suite checks what the application does, but says
nothing about what a migration does to the target it runs against. That is why the tests passed in both runs above:
on an empty development database, nearly every dangerous migration looks fine, because the danger lives in data
volume, lock behavior, and what a specific engine does with a specific ALTER. The agent knows the safe patterns
and ships the locking ALTER anyway.
Knowledge was never the gap. Enforcement is.
So the feedback signal has to come from database-aware tooling. Atlas validates the semantics of the change by replaying it on the dev database, introspecting the result, and classifying what it finds by how it breaks production:
atlas migrate lint --env ci --latest 1
-- analyzing version 20260824093214
-- data dependent changes detected:
-- L1: Adding a unique index "users_email_key" on table "users" might fail
in case column "email" contains duplicate entries
-- concurrent index violations detected:
-- L1: Adding a UNIQUE constraint "users_email_key" acquires an ACCESS EXCLUSIVE
lock on table "users", blocking all access during the operation
That is a verdict the agent can act on inside the session with no human in the loop. Give it that signal and it fixes its own migration by building the index concurrently, then attaching the constraint as a brief, metadata-only operation:
-- atlas:txmode none
CREATE UNIQUE INDEX CONCURRENTLY users_email_key ON users (email);
ALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE USING INDEX users_email_key;
atlas migrate test extends the same signal to behavior. If a migration carries backfill or
data-migration logic, a test case exercises it on an ephemeral database: seed data, migrate forward, assert on the
result. The same red/green loop the agent already iterates against, for the statements whose correctness depends on
what the data contains.
With Atlas, several analyzers warn by default. A warning is advice, and agents route around advice. error = true
turns the linter into a gate:
lint {
destructive { error = true }
data_depend { error = true }
concurrent_index { error = true }
non_linear { error = true }
// Enum insert that copies the table.
check "MY112" { error = true }
}
Wire this into CI the way the playbook wires evals, and the agent stops shipping these changes that look fine
in a diff and to a reviewer. The catalog behind it is engine-specific by design: which PostgreSQL operations take
an ACCESS EXCLUSIVE lock, which MySQL operations fall back to a table copy, where an enum edit turns from instant
into hours-long. The knowledge senior DBAs carry around is encoded as checks an agent has to pass.
A Hook in the Agent Is a Preference, Not a Boundary
The playbook is not naive about enforcement: permission rules, PreToolUse hooks, sandboxing, scoped credentials,
managed settings engineers cannot override. All of them govern the actor. None of them can see whether the database is
in the state the migration assumed, or what this DDL costs on this table at this size. A hook can stop Claude from
running psql, but it can't know that the ALTER it just permitted holds an exclusive lock for 90 seconds, or that
the target has drifted since the plan was computed.
And the database has a route to production via a connection string, which never passes through a merge. Any process that can open a connection can change the schema, and you cannot block every tool an agent might use to open one.
So the gate moves to the write path, and the write path gets narrowed until it is actually singular:
- No standing DDL credentials for engineers, agents, or application services. Application roles are DML-only.
- One deployment identity, held by the migration runner, can change schema, and it executes only approved artifacts.
- Every plan is computed against an assumed state: the database is at revision X, the data satisfies Y. Before executing, the assumption must hold. Atlas enforces it with a pre-deployment drift check and pre-migration checks, assertions evaluated on the live database in the same connection; if either fails, nothing executes and the deployment stops until the drift is resolved.
- Break-glass DDL still exists, because production. It is audited, and it surfaces as drift the moment it happens.
The playbook draws a sharp line between skills and hooks: skills make violations rare, hooks make them close to impossible. Same line, one layer down. Instructions and lint make bad migrations rare. A single governed identity with checks on the connection makes ungoverned ones close to impossible, for humans, pipelines, and agents alike.
Drift Closes the Loop
Stage 6 closes the playbook's loop: a script watches a metric, invokes Claude on a breach, and the diagnosis re-enters
Stage 1 as a new intent.md. For the database, the metric is drift. Drift means something wrote outside the governed
path, so the deployed schema no longer matches the declared one. Atlas monitors for it continuously and
checks for it right before an apply, so a migration planned against a stale picture of production fails loudly instead
of guessing. CI checks the code, and CD checks production.
But drift needs something to compare against, and that is where git's role ends. Git is the system of record for
declared intent, not for the deployed state, because a database's state is a fact about the world, not the repo. Git
cannot tell you which schema revision eu-west-1 is on, whether it matches us-east-1, or which of the last forty
agent-authored migrations actually applied.
That is the job of the schema registry: the versioned source of truth used for detecting drift and computing plans. It's also where an auditor looks when they ask who changed that column, under which approval, deployed when. The playbook's audit trail, extended to the one system that outlives every deploy.
The Database's Chapter of the Playbook
Nothing structural changes. The six stages are correct, the artifact chain is correct. The database just needs its own artifact and its own gate at each stage, a chapter we have been writing since long before agents could write DDL:
| Playbook control | Database equivalent |
|---|---|
spec.md, reviewed by the product owner | Schema as code in the repo, reviewed the same way |
plan.md, written in plan mode | Migration computed from authoritative state |
| Tests as the agent's feedback loop | Migration safeguards and testing, in the session and in CI |
PreToolUse hooks inside the agent | A governed identity with checks on the connection |
| Control bands on production metrics | Drift detection against the registry |
| Git as the audit trail | Schema registry as the system of record |
Wired together, the loop closes the same way the playbook's does:
If you are implementing the playbook, sequence matters. Schema-as-code plus lint in CI is a day of setup for your first service and removes the largest class of agent-caused database incidents. The governed write path comes second, parallel sessions third: gates before throughput, the ordering the playbook itself uses. None of this is speculative.
It is how our customers like Unico and EliseAI already run schema changes: through one pipeline that doesn't care whether the author was a human or an agent.
What Gets Harder from Here
Everything above ships today. Two questions remain: "Is this change destructive?" and "Is anything still reading this column?"
The former is answerable from the change itself, but the latter is not. Atlas's backward-compatibility checks warn on changes that can break deployed clients, but the full answer lives in runtime traffic and deployed application versions, and that contract needs stricter enforcement than a warning. It is the answer an agent needs before it drops anything.
And once many agents change shared state concurrently, rebase ordering stops being enough. The system has to know which changes commute, which depend on each other, and which conflict. Git merges text; it will not do this.
That is the direction we are building Atlas toward: not agents that write better SQL, but a system that makes autonomous database evolution safe enough for enterprises to allow.
The playbook signs off with the loop running and human judgment above it. Just make sure your database is inside the loop. It is the one part of your system an agent can change and cannot un-change.