# AI coding agents के लिए सुरक्षित database migration workflows

Production database में बदलावों के लिए सामान्य code edits से अलग workflow चाहिए। AI coding agent schema देख सकता है, application queries का संबंध खोज सकता है और migration files अधिकतर teams के review schedule करने से पहले तैयार कर सकता है। लेकिन केवल prompt में "deploy" शब्द आ जाने से उसे production बदलने की अनुमति नहीं मिलनी चाहिए।

सुरक्षित database migration workflows planning को authority से अलग रखते हैं। Agent evidence और सटीक change set तैयार कर सकता है। मानव operational consequences की समीक्षा कर सकता है, recovery के काम करने का प्रमाण दे सकता है और एक सीमित execution को approve कर सकता है। यह अलगाव उस आम failure को रोकता है जिसमें harmless दिखने वाला `ALTER TABLE` checkout flow को block कर देता है, अनियंत्रित rewrite शुरू कर देता है या ऐसा अधूरा deployment छोड़ देता है जिसे कोई समझा नहीं सकता।

इस लेख में PostgreSQL commands का इस्तेमाल किया गया है, क्योंकि उसका locking और transaction behavior documentation में स्पष्ट है। यही workflow दूसरे relational databases पर भी लागू हो सकता है, लेकिन किसी दूसरे engine में PostgreSQL syntax या assumptions इस्तेमाल करने से पहले उसके manual को देखें।

## Production changes के लिए code generation से अलग authority चाहिए

Migration file और production migration दो अलग काम हैं। पहला intent बताता है। दूसरा active users, replication, backups और कभी-कभी एक-दूसरे से असंगत application versions वाले live system पर सीमित authority खर्च करता है।

Teams अक्सर local development आसान बनाने के लिए agent को एक broad database connection दे देती हैं। फिर वही connection अलग-अलग environments में पहुंच जाता है और यदि credentials समान हों, तो agent staging hostname और production cluster में फर्क नहीं कर पाता। इससे भी खराब स्थिति तब होती है जब agent arbitrary SQL चला सकता है। Table rewrite से पहले रुकने का उसके पास कोई स्वाभाविक कारण नहीं होता। उसके सामने बस task और tool होते हैं।

काम को अलग phases में बांटें और हर phase के inputs तथा permissions अलग रखें:

1. Discovery schema metadata, migration history, query code और operational constraints पढ़ता है।
2. Planning SQL, expected lock behavior, data effects, preflight checks, verification queries और recovery decision तैयार करता है।
3. Review यह पक्का करता है कि plan वास्तविक production state से मेल खाता है और organization इस risk को स्वीकार करती है।
4. Execution एक approved artifact को एक नामित target पर चलाता है।
5. Verification साबित करता है कि deployment पूरा कहने से पहले application और database intended state में पहुंच गए हैं।

उपयोगी अंतर *reversibility* और *recoverability* के बीच है। Nullable column जोड़ना आम तौर पर reversible होता है: यदि कोई code उस पर निर्भर नहीं है, तो बाद में उसे हटाया जा सकता है। Flawed expression के आधार पर लाखों rows update करना reversible नहीं भी हो सकता, भले कोई reverse update लिख दिया जाए। यह पता नहीं चलेगा कि कौन-सी original values overwrite हुई थीं। Recoverability का अर्थ है कि damage होने पर restore या containment का tested तरीका मौजूद हो। हर migration plan में इन दोनों को अलग fields की तरह रखें।

Agent को repository branch, ticket label या chat prompt में दिए environment variable से production authority का अनुमान कभी नहीं लगाना चाहिए। ये signals केवल intent बताते हैं और intent अक्सर गलत होता है। Execution के लिए agent के text context से बाहर चुना गया target, recorded digest वाला prepared artifact और ऐसा व्यक्ति चाहिए जिसे साफ दिखे कि क्या चलने वाला है।

## Migration plan को review योग्य artifact बनाएं

Review योग्य plan operator को database तक पहुंचने से पहले migration अस्वीकार करने के लिए पर्याप्त जानकारी देता है। "Add an index" plan नहीं है। Table size, query का उद्देश्य, index form, transaction constraint, lock exposure और verification query मिलकर plan बनाते हैं।

Agent से pull request में paragraph लिखवाने के बजाय fixed files वाली directory तैयार करवाएं। उदाहरण के लिए:

```text
migrations/2025-04-add-orders-status-index/
  up.sql
  verify.sql
  preflight.sql
  recovery.md
  manifest.json
```

Manifest files को intended target से जोड़ना चाहिए और agent ने जो सीखा है उसे बताना चाहिए। इस example में credentials उजागर नहीं होते और केवल backup मांगे जाने से backup मौजूद होने का दावा भी नहीं किया जाता।

```json
{
  "migration_id": "2025-04-add-orders-status-index",
  "engine": "postgresql",
  "target": "production-orders",
  "change": "create index for filtered order status query",
  "transaction_mode": "outside_transaction",
  "requires_backup_restore_check": true,
  "expected_write_blocking": "none during index build",
  "stop_conditions": [
    "target schema differs from preflight result",
    "backup restore check fails",
    "index is invalid after execution"
  ]
}
```

मानव reviewer को इस artifact से पांच सवालों के जवाब मिलने चाहिए:

- बदलाव किस database और schema में जाएगा?
- कौन-सा exact SQL चलेगा और क्या runner उसे transaction में wrap करता है?
- कौन-सा operation block करेगा, rewrite करेगा, scan करेगा या extra storage लेगा?
- बदलाव गलत होने पर database recover कर सकता है, इसका क्या evidence है?
- Success साबित करने वाली queries या application signals कौन-से हैं?

Agent से uncertainty भी बताने को कहें। यदि वह PostgreSQL version, table size, current migration version या calling application की compatibility range निश्चित नहीं कर सकता, तो इसे stop condition के रूप में लिखना चाहिए। Repository की अधूरी access से confidence गढ़ना, item खुला छोड़ने से अधिक खराब है।

Review के बाद generated SQL को immutable रखें। यदि reviewer `up.sql` बदलता है, तो checksum फिर से बनाएं और artifact को दोबारा review में भेजें। आम failure reviewed migration से शुरू होता है और deployment shell में जल्दी-जल्दी किए गए "छोटे fix" पर खत्म होता है। Live command reviewed command नहीं रहती, जबकि audit record एक भरोसा देने वाली लेकिन झूठी कहानी बताता है।

## चलाने का तरीका तय करने से पहले operation को classify करें

SQL verb operational risk के बारे में पर्याप्त जानकारी नहीं देता। `ALTER TABLE` में ऐसे बदलाव भी आते हैं जो जल्दी पूरे हो जाते हैं और ऐसे भी जो locks लेते हैं या इतना data rewrite करते हैं कि storage भर सकता है। Execution path तय करने से पहले actual operation, database version, table size और concurrent workload को classify करें।

PostgreSQL का `ALTER TABLE` documentation एक असुविधाजनक बात स्पष्ट करता है: कई forms `ACCESS EXCLUSIVE` lock लेते हैं, जब तक manual कुछ और न कहे। यह lock reads और writes दोनों से टकराता है। "Staging में जल्दी हो गया" का अर्थ कम है, यदि staging में long-running transactions, report traffic और production जितना data नहीं है।

चार व्यावहारिक classes इस्तेमाल करें:

| Class | सामान्य उदाहरण | Execution की अपेक्षा |
|---|---|---|
| Metadata-only | बिना default के nullable column जोड़ना | छोटा operation, फिर भी अपने version में lock behavior verify करें |
| Concurrent build | नया index बनाना | विशेष command form और अलग transaction handling |
| Batched data change | नए column को backfill करना | छोटे commits, मापी हुई rate और resumable progress |
| Rewrite या destructive change | बड़े column type को बदलना या data हटाना | स्पष्ट recovery plan के साथ maintenance decision |

हर online operation को safe न कहें। PostgreSQL का `CREATE INDEX CONCURRENTLY`, `CREATE INDEX` में बताए अनुसार, build के दौरान writes को block नहीं करता। लेकिन इसमें अधिक समय लगता है, यह transaction block में नहीं चल सकता और failure के बाद invalid index छोड़ सकता है। "Always use concurrently" वाली सलाह इन conditions को अनदेखा करती है। जब write availability महत्वपूर्ण हो और आपका migration runner इसके नियम संभाल सके, तभी इसका इस्तेमाल करें।

Planning query table और index size का पहला उपयोगी अनुमान दे सकती है:

```sql
SELECT
  pg_size_pretty(pg_total_relation_size('public.orders')) AS total_size,
  pg_size_pretty(pg_relation_size('public.orders')) AS table_size,
  pg_size_pretty(pg_indexes_size('public.orders')) AS indexes_size;
```

Expected output में तीन readable size values वाली एक row होगी। इसे sizing signal मानें, duration का promise नहीं। Row width, cache state, concurrent writes, replication, disk throughput और active transactions सब result बदलते हैं।

Meaningful lock exposure वाले operation से पहले agent को blockers भी देखना चाहिए। PostgreSQL active sessions को `pg_stat_activity` में दिखाता है, लेकिन इससे उन्हें terminate करने की permission नहीं मिलती। Plan में किसी long-running workload का owner, और migration के wait करने, reschedule होने या maintenance window मिलने की स्थिति साफ लिखी होनी चाहिए।

## Restored backup entry ticket है

Successful backup command केवल यह साबित करती है कि किसी program ने file लिखी। यह साबित नहीं करती कि file restore होगी, उसमें ज़रूरी objects हैं या pressure में restoration procedure काम करेगा। Destructive या hard-to-reverse काम से पहले restore check चलाएं।

PostgreSQL database के लिए custom-format logical dump ऐसा दिख सकता है:

```sh
pg_dump -Fc -d "$SOURCE_DATABASE" -f "orders-preflight.dump"
pg_restore -l "orders-preflight.dump" | sed -n '1,12p'
createdb migration_restore_check
pg_restore -d migration_restore_check "orders-preflight.dump"
psql -d migration_restore_check -c "SELECT count(*) FROM public.orders;"
```

Listing command को archive entries दिखानी चाहिए, जैसे table definition, table data entry और index definitions। अंतिम query को restored row count वाली one-column result दिखानी चाहिए। Command outcome, dump identifier, restore target और check result migration record में रखें। Database URLs या passwords उस record में न डालें।

Logical restore check physical recovery test, point-in-time recovery, replica promotion drill या provider की backup assurance का विकल्प नहीं है। यह केवल संकरा सवाल पूछता है: क्या यह dump database में restore होकर अपेक्षित objects और data shape दे सकता है? फिर भी यह damaged archives, missing extensions, role issues और ऐसी procedures पकड़ लेता है जो केवल उसे लिखने वाले व्यक्ति के laptop पर चली थीं।

Recovery method execution से पहले तय करें, error के बाद नहीं। Plan में इन विकल्पों में से लागू विकल्प साफ होना चाहिए:

- Reverse migration सुरक्षित है, क्योंकि कोई data discard नहीं हुआ और application reversal संभाल सकती है।
- Replacement database में restore recovery route है और cutover का निर्णय लेने वाला owner नामित है।
- Replica या provider recovery procedure route है और recovery point objective documented है।
- बदलाव साफ तौर पर recover नहीं किया जा सकता, इसलिए maintenance window और explicit acceptance चाहिए।

आखिरी category वैध है। Deployment template में field भरने के लिए rollback होने का दिखावा करना वैध नहीं। मैंने teams को customer data बदलने वाले backfill के rollback के रूप में `DROP COLUMN` लिखते देखा है। बाद में पता चला कि नया column हटाने से केवल evidence मिटा, पुराने values फिर भी गलत रहे।

## पहले expand करें, बाद में remove करें

अधिकांश application-facing schema changes को mixed application versions के बीच compatibility बनाए रखनी चाहिए। Deployment के दौरान पुराने और नए processes साथ चल सकते हैं, क्योंकि workers धीरे drain होते हैं, users requests खुली रखते हैं या rollback से पुराना build फिर शुरू हो जाता है। एक ही instantaneous application version मानने वाला migration सामान्य deployment behavior को outage में बदल देता है।

मान लें कि `orders.status_text` को constrained `orders.status_code` से बदलना है। Unsafe तरीका है नई column जोड़ना, हर row rewrite करना, application code बदलना और एक ही release में पुरानी column हटाना। इसमें failure के कई points हैं और रुकने की कोई शांत जगह नहीं।

इसके बजाय expand और contract sequence अपनाएं:

1. `status_code` को nullable जोड़ें और ऐसी supporting structures बनाएं जो current application को न तोड़ें।
2. ऐसा application code deploy करें जो नई field मौजूद होने पर उसे पढ़े और दोनों fields लिखे, या एक से दूसरे को सुरक्षित रूप से derive करे।
3. Existing rows को bounded batches में backfill करें और progress को agent के transient chat context से बाहर record करें।
4. Verify करें कि सभी rows नया invariant पूरा करती हैं और readers नई field इस्तेमाल कर रहे हैं।
5. बाद के release में पुरानी field लिखना बंद करें, agreed retention period का इंतज़ार करें और फिर उसे हटाएं।

Batch loop महत्वपूर्ण है। एक विशाल `UPDATE` resources को बहुत देर तक पकड़े रखता है, write-ahead log का burst बनाता है, replicas पर दबाव डालता है और recovery को समझना कठिन करता है। Bounded query commits के बीच operator को replication lag, error rate और database load देखने का मौका देती है।

यह PostgreSQL pattern primary key के आधार पर चुने हुए batch को update करता है और बदली हुई rows लौटाता है:

```sql
WITH batch AS (
  SELECT id
  FROM public.orders
  WHERE status_code IS NULL
  ORDER BY id
  FOR UPDATE SKIP LOCKED
  LIMIT 500
)
UPDATE public.orders AS o
SET status_code = CASE o.status_text
  WHEN 'new' THEN 10
  WHEN 'paid' THEN 20
  WHEN 'shipped' THEN 30
  ELSE NULL
END
FROM batch
WHERE o.id = batch.id
RETURNING o.id;
```

Runner इसे तभी दोहराए जब returned rows मौजूद हों और mapping acceptable values दे रही हो। `SKIP LOCKED` controlled worker के लिए उपयोगी हो सकता है, क्योंकि इससे किसी दूसरे transaction द्वारा पकड़ी गई rows के लिए इंतज़ार नहीं करना पड़ता। यह इस बात का प्रमाण नहीं है कि हर row process हो गई। Verification query को remaining nulls और unexpected source values की जांच करनी होगी:

```sql
SELECT status_text, count(*)
FROM public.orders
WHERE status_code IS NULL
GROUP BY status_text
ORDER BY count(*) DESC;
```

यदि query कोई अपरिचित value लौटाए, तो रुकें। Agent को यह तय न करने दें कि unknown production state को default enum value बना दिया जाए, केवल इसलिए कि task पूरा करना है।

## Execution के लिए hard stop conditions और bounded commands चाहिए

Approved migration भी planner द्वारा देखी गई production state से अलग state पा सकती है। Runner को execution से तुरंत पहले preflight checks चलाने और mismatch पर रुकने की जरूरत है। यही वह जगह है जहां workflow memory से follow किए जाने वाले runbook से सुरक्षित बनता है।

ऐसा dedicated executor रखें जो केवल artifact identifier और target selector स्वीकार करे। उसे terminal से paste किए गए inline SQL को अस्वीकार करना चाहिए, बदले हुए checksum वाले artifact को reject करना चाहिए और शुरू होने से पहले target identity print करनी चाहिए। Executor पहले read-only preflight SQL चला सकता है, फिर mutating portion के लिए अलग approval मांग सकता है।

Session time limits स्पष्ट रूप से सेट करें। PostgreSQL `lock_timeout` और `statement_timeout` को अलग controls के रूप में document करता है। Short metadata operation के लिए reasonable preamble ऐसा हो सकता है:

```sql
SET lock_timeout = '5s';
SET statement_timeout = '60s';
SELECT current_database(), current_user, now();
```

इन numbers को index builds या backfills पर बिना सोचे लागू न करें। Timeout एक budget है जिसे plan को justify करना चाहिए। यदि expected load में command को तीस मिनट चाहिए, तो sixty-second statement timeout केवल predictable failure बनाएगा। Concurrent index creation के लिए ऐसा runner path इस्तेमाल करें जो command को transaction में wrap न करे:

```sql
CREATE INDEX CONCURRENTLY IF NOT EXISTS orders_status_code_idx
ON public.orders (status_code);
```

Command के बाद process exit को success मानने के बजाय index verify करें। PostgreSQL catalog metadata में validity दर्ज करता है और failed concurrent build invalid index छोड़ सकता है। Retry से पहले उसे inspect और remove करना पड़ सकता है। यह check `verify.sql` में रखें और जहां उचित हो, application-facing query plan भी जोड़ें।

सच्चे abort decision के लिए named signals चाहिए। यदि preflight को unexpected migration version, insufficient storage headroom, allowed window से बाहर active blocker, failed backup restore, failed checksum या verification predicate से अलग result मिले, तो execution रोकें। Lock wait बढ़ते समय operator को agent से बहस नहीं करनी चाहिए।

## Approval को एक व्यक्ति और एक live run से जोड़ें

Approval fatigue तब आती है जब systems हर SQL call पर approval मांगते हैं। लोग reflex में approve करने लगते हैं या prompts बंद कर देते हैं। Defined agent process की शुरुआत में एक approval उपयोगी है, यदि वह process की पहचान करे, process खत्म होते ही expire हो और बाद के किसी अलग session को चुपचाप cover न करे।

Approval में इतना context होना चाहिए कि व्यक्ति run अस्वीकार कर सके: signed process identity, selected target, artifact identifier, intended action class और यह जानकारी कि action channel write कर सकता है या नहीं। Raw password न दिखाएं और agent से उसे संभालने को न कहें। व्यक्ति action को authorize करता है, secret disclosure को नहीं।

Sallyport इस सीमा में API और SSH credentials को encrypted vault में रखता है, MCP-capable agent को `sp mcp` के ज़रिए actions मांगने देता है और plaintext secrets के बजाय action results लौटाता है। इसका अलग session approval और optional per-key approval production database action के लिए human decision मांग सकता है, बिना reusable credential agent context में रखे।

Approval को execution phase तक सीमित रखें। Discovery agent read-only path इस्तेमाल कर सकता है। Execution agent को ऐसा session मिल सकता है जो process के साथ expire हो और केवल approved action route तक पहुंच सके। Instant revocation महत्वपूर्ण है, क्योंकि unexpected preflight result मिलने पर operator को run रोकने का तरीका चाहिए, केवल deployment script खत्म होने के बाद नहीं।

यदि आपकी team pressure में policy समझा नहीं सकती, तो clear operational design की जगह policy language न रखें। भरोसेमंद boundary सरल है: locked access सभी actions रोकता है, नए execution process को authorization चाहिए और विशेष रूप से sensitive credentials के लिए हर use पर fresh decision लिया जा सकता है। यह inferred rules के ढेर से अधिक आसानी से audit होता है, जिन्हें लिखने की बात किसी को याद नहीं।

## Audit records में secrets के बिना change reconstruct होना चाहिए

Failed migration के बाद लोग सरल सवाल पूछते हैं: इसे किसने चलाया, किस target पर, कौन-सा exact artifact चला, यह कब रुका और क्या इसने data को छुआ? सामान्य terminal transcript अक्सर इन सभी का जवाब नहीं देता। उसमें target छूट सकता है, shell history साफ होने पर command खो सकती है या ऐसे secrets शामिल हो सकते हैं जिन्हें कभी log नहीं करना चाहिए।

Planning, approval, preflight, execution, verification, revocation और failure के लिए structured events दर्ज करें। हर event में migration identifier और artifact digest होना चाहिए, ताकि investigator review को live run से जोड़ सके। Caller द्वारा मांगे गए नाम के बजाय preflight से लौटाई गई database identity भी शामिल करें।

Minimal event shape ऐसा दिख सकता है:

```json
{
  "event": "migration.verify",
  "migration_id": "2025-04-add-orders-status-index",
  "artifact_sha256": "recorded-digest",
  "target_identity": "production-orders",
  "result": "passed",
  "observed": "index valid; query returned expected columns"
}
```

जब SQL parameters में personal data, tokens या customer content आ सकता हो, तो उन्हें log न करें। इसके बजाय artifact digest और safe statement identifier दर्ज करें। Execution system defined retention policy के तहत protected diagnostics रख सकता है, लेकिन audit journal बिना secret store बने भी उपयोगी रहना चाहिए।

Tamper evidence investigation की quality बदल देता है। Bad deployment के बाद जिसे कोई edit कर सकता है, ऐसा record केवल यह साबित करता है कि किसी को record तक access था। Hash-chained audit log इस्तेमाल करते हैं, तो incident review के दौरान उसे independently verify करें। Sallyport का `sp audit verify` vault access के बिना offline encrypted audit chain check करता है। यह उस reviewer के लिए सही property है जिसे production credentials नहीं मिलने चाहिए।

## Verification को केवल DDL नहीं, workload को test करना चाहिए

Migration तब सफल होता है जब intended workload सही चलता है, न कि केवल database statement स्वीकार करता है। नई column मौजूद हो सकती है जबकि application writes उसे भरती ही न हों। Index valid हो सकता है, लेकिन predicate या data type अलग होने से target query उसका इस्तेमाल न कर पाए। Constraint validate हो सकता है, जबकि पुराना worker अभी भी नए application contract को तोड़ने वाला data भेज रहा हो।

Execution से पहले verification queries लिखें, जब reviewers उनकी assumptions को चुनौती दे सकते हों। Index migration में catalog state जांचें और उस query का निरीक्षण करें जिसके कारण index बनाया गया। Backfill में remaining rows गिनें, unexpected source values को group करें और verify करें कि नई application write path अपेक्षित representation बना रही है। Constraint के लिए production पर लागू करने से पहले disposable database में valid और invalid writes test करें।

PostgreSQL का `EXPLAIN` उपयोगी है, लेकिन उसका गलत इस्तेमाल आसान है। Planner का decision statistics, parameter values, data distribution और configuration पर निर्भर करता है। `EXPLAIN (ANALYZE, BUFFERS)` query चलाकर measurement करता है, इसलिए costly production query पर इसे casually न चलाएं। Representative safe query पर इसका इस्तेमाल करें और output देखने से पहले acceptable result तय करें। वरना हर plan ऐसा बन जाता है जिसे थका हुआ operator अपने पक्ष में समझा सकता है।

बदलाव के दौरान और उसके बाद live application indicators देखें: request error rate, प्रभावित path की query latency, connection pressure, replication health और worker failures। Agent readings collect करके दिखा सकता है, लेकिन stated exit condition पूरी हुई या नहीं, इसका निर्णय release owner करे।

Expansion deployment पास हो जाने पर destructive contraction केवल इसलिए schedule न करें। तब तक इंतज़ार करें जब तक observability और release history न दिखाएं कि पुराने application processes पुराने schema पर निर्भर नहीं हैं। फिर removal को अपनी reviewed migration के रूप में चलाएं। यह extra change उस स्थिति से सस्ता है जिसमें rollback के दौरान पता चले कि पुराने build को वह column चाहिए जिसे आपने एक घंटे पहले हटा दिया।

## पहला production run जानबूझकर उबाऊ होना चाहिए

पहला agent-assisted production migration low-risk और observable change होना चाहिए, load के बीच table redesign नहीं। Nullable additive column, comment या ऐसा operation चुनें जिसका behavior आप पहले से समझते हों। इसका इस्तेमाल boundaries test करने के लिए करें: artifact generation, target selection, approval, backup evidence, audit events, preflight failure, execution और verification।

Exercise से यह साबित करवाएं कि system काम से इनकार कर सकता है। Executor को गलत schema fingerprint वाले target पर चलाएं और देखें कि वह रुकता है। Reviewed file बदलकर देखें कि digest check उसे reject करता है। Harmless staged run के दौरान authorization revoke करें और देखें कि बाद के actions fail होते हैं। इससे पता चलेगा कि controls उस समय काम करते हैं जब उनकी जरूरत हो, केवल design document में समझदार दिखते हैं या नहीं।

इसके बाद allowed migration classes को एक-एक करके बढ़ाएं। जो team column सुरक्षित रूप से जोड़ सकती है, उसने अभी यह साबित नहीं किया कि वह बड़ी table backfill कर सकती है, concurrent index बना सकती है या खराब data transformation से recover कर सकती है। हर class को अपना observed run और अपने failure criteria चाहिए।

Production authority को ऐसा अधिकार मानें जिसे agent एक सीमित काम के लिए उधार लेता है और फिर खो देता है। यह operational आदत किसी भी clever prompt instruction से अधिक damage रोकेगी।
