Designing Permission Tiers for a Database Migration Agent
You are specifying the harness for an agent that helps engineers write and apply database migrations. It can read the schema, read the application code, write migration files, run migrations against a local database, and — on request — apply a migration to staging. Engineers run it interactively; a nightly job also runs it unattended to keep a shadow schema in sync.
- Define the permission tiers, saying precisely what each tier can do and what it takes to enter it.
- Identify which actions must be gated regardless of tier, and justify the criterion you used to pick them.
- The nightly unattended run cannot prompt a human. Explain how it gets enough authority to be useful without becoming the weakest link in the whole design.
1. The tiers
- Read-only / plan. Read schema, read application code, read existing migration files; produce a proposed migration as text in its answer, writing nothing. No execution, no network. This is the entry tier and requires no authorization — nothing it can do changes state.
- Sandboxed-edit. Adds writing migration files inside the
repository working tree, and running migrations against a
disposable local database — a throwaway instance created for
this run, seeded from an anonymized fixture, destroyed afterwards.
The
run_commandsurface is allow-listed to the migration tool, the test runner, and the linter. Entered by the engineer starting an interactive session; logged. - Staging-apply. Adds applying a migration to the shared staging database, using a credential scoped to that database only. Entered per action, not per session: the engineer confirms this specific migration against this specific target, having been shown the exact SQL that will run. The tier ratchets back down immediately afterwards.
Note that the tiers are ordered by blast radius, not by convenience, and that escalation is always an explicit human act while de-escalation is free.
2. Gated regardless of tier
The criterion is irreversibility and reach outside the run's own disposable environment, not "how likely the model is to get it wrong." By that criterion:
- Any statement touching production, in any form — the production credential should simply not exist in the agent's environment, so this is enforced by absence rather than by policy.
DROP/TRUNCATE/destructiveALTERon any non-disposable database, including staging: data loss is not undone by a revert of the migration file.- Writing outside the repository working tree.
- Network egress to anything not on the allow-list, since exfiltration of a schema or a fixture is irreversible once it has happened.
The point to make explicit: reversibility, not risk-of-error, is the right axis. A reversible mistake made often is cheaper to absorb than an irreversible mistake made rarely.
3. The unattended nightly run
The wrong answer is "give the nightly job the staging-apply tier with prompts disabled," which makes the unattended path the most powerful one in the system and removes the only human in it. Three things fix it instead:
- Narrow the task, not the controls. The nightly job's job is the shadow schema. Give it a credential scoped to the shadow database only — so the highest tier it can reach is, by construction, the least consequential one.
- Run it inside a real sandbox. Unattended autonomy is only acceptable when the environment is disposable: a fresh container per run, no engineer credentials mounted, egress denied except the database and the model API. The harness should structurally refuse the autonomous tier outside such an environment rather than warn about it.
- Replace the human gate with a deterministic one. Where a human would have reviewed, put a computational sensor instead: the migration must apply cleanly to a fresh copy, the post-migration schema must diff exactly against the expected target, and the test suite must pass — otherwise the run stops and files the proposed migration for human review in the morning rather than applying it. A gate that a human cannot answer should become a check that code can answer, not a gate that is simply removed.
This also satisfies the Rule of Two heuristic: the nightly run changes state and touches a sensitive system, so it must not also be processing untrusted input — its inputs are the repository and the schema, both under the team's control.
Share this question