# सशर्त API अनुरोध एजेंट के पुराने overwrite को कैसे रोकते हैं

एक एजेंट पूरी तरह वैध API अनुरोध बना सकता है और फिर भी वास्तविक नुकसान कर सकता है। समस्या तब आती है जब वह कोई record पढ़ता है, कोई दूसरा actor उसे बदल देता है और एजेंट बाद में अपनी पुरानी copy लिखकर नई स्थिति को मिटा देता है। Authentication इसे नहीं रोकता। Authorization भी नहीं। अनुरोध किसी अधिकृत principal से आया था, लेकिन उसमें वास्तविकता का पुराना snapshot था।

सशर्त API अनुरोध इस खास समस्या को ठीक करते हैं। क्लाइंट मूल रूप से कहता है, «यह बदलाव तभी लागू करें जब resource अभी भी वही version हो जिसे मैंने देखा था।» सर्वर write के हिस्से के रूप में इस बात की जांच करता है। अगर शर्त गलत हो, तो सर्वर कुछ बदलने से पहले कार्रवाई रोक देता है।

Coding agents के मामले में यह contract उस व्यक्ति की तुलना में अधिक जरूरी है जो form पर क्लिक कर रहा हो। एजेंट कई resources पढ़ सकते हैं, code देखने या tests चलाने के लिए रुक सकते हैं और दुनिया बदल जाने के बाद writes का batch भेज सकते हैं। हर महत्वपूर्ण update को read-modify-write operation मानें, जब तक API यह साबित न कर सके कि वह append या commutative command है।

## सामान्य read-modify-write flow में lost updates होते हैं

Lost update तब होता है जब दो writers एक ही पुरानी स्थिति से शुरू करते हैं और बाद वाला write पहले वाले बदलाव को मिटा देता है। इसके लिए database outage, malicious user या खराब network जरूरी नहीं है। एक unconditional replacement स्वीकार करने वाला server ही काफी है।

मान लीजिए deployment configuration JSON के रूप में उपलब्ध है:

```json
{
  "name": "billing-worker",
  "replicas": 3,
  "image": "registry.example/billing:2.4.0",
  "maintenanceMode": false
}
```

एक एजेंट load test से पहले `replicas` को 3 से 5 करने के लिए इसे पढ़ता है। उसके काम करने के दौरान एक operator queue की समस्या जांचने के लिए `maintenanceMode` को `true` कर देता है। अगर एजेंट बाद में अपने saved document के साथ पूरा `PUT` भेजता है, तो वह `maintenanceMode` को फिर `false` कर सकता है। अनुरोध ने replicas को सही तरह बदला, लेकिन उस safety decision को भी पलट दिया जिसे एजेंट ने कभी देखा ही नहीं था।

Partial update नुकसान का दायरा घटाता है, लेकिन race खत्म नहीं करता। अगर एजेंट `/replicas` को बदलने के लिए PATCH भेजता है, तो पढ़ने के बाद वह field फिर भी बदल सकती है। इससे भी महत्वपूर्ण बात यह है कि replicas को 5 करने का निर्णय दूसरे बदले हुए fields पर निर्भर हो सकता है। PATCH request body का आकार बताता है। यह नहीं बताता कि body resource के किस version पर आधारित है।

इसीलिए «हमारा UI केवल एक field बदलता है» concurrency design नहीं है। UI कुछ समय तक समस्या छिपा सकता है, क्योंकि लोग धीरे काम करते हैं और fresh pages देखते हैं। Autonomous process के पास ऐसे आकस्मिक safeguards नहीं होते।

## ETag उस representation की पहचान करता है जिसे क्लाइंट ने देखा

`ETag` response header एक HTTP validator है। Server जब कोई representation लौटाता है, तो उसके साथ ऐसा token जोड़ सकता है जो उस representation के version की पहचान करे:

```http
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "deploy-8f31c2"

{
  "name": "billing-worker",
  "replicas": 3,
  "image": "registry.example/billing:2.4.0",
  "maintenanceMode": false
}
```

इस string का कोई अनिवार्य internal format नहीं है। इसमें database revision, content hash या generated opaque value हो सकती है। Clients को इसे opaque मानना चाहिए। Revision number जानने के लिए tag को parse न करें और JSON body से tag न बनाएं। उसका अर्थ server तय करता है।

RFC 9110 entity tags को परिभाषित करता है और strong तथा weak tags में अंतर बताता है। Strong ETag का सामान्य quoted syntax होता है, जैसे `"deploy-8f31c2"`। इसका अर्थ है कि server की चुनी हुई representation semantics के अनुसार representations byte-for-byte समान हैं। Weak tag `W/` से शुरू होता है, जैसे `W/"deploy-8f31c2"`, और केवल इतना बताता है कि दोनों representations cache validation के लिए पर्याप्त रूप से समान हैं।

यह अंतर अक्सर धुंधला हो जाता है। कई GET cache checks के लिए weak validators ठीक हैं। Write को सुरक्षित रखने के लिए वे गलत साधन हैं, क्योंकि दो «काफी समान» representations में ऐसा field अलग हो सकता है जिसे write मिटा देगा। RFC 9110 के अनुसार `If-Match` में strong comparison होना चाहिए। अगर आपका API केवल weak ETags publish करता है, तो उसने optimistic concurrency के लिए उपयुक्त ETag दिया ही नहीं है।

अलग representations के लिए किसी resource के अलग ETags हो सकते हैं। Pretty-printed JSON, compact JSON या content-negotiated formats में हर एक को अपना validator मिल सकता है। यह HTTP का वैध व्यवहार है, लेकिन API clients के लिए असुविधाजनक है। जब संभव हो, write endpoints के लिए stable canonical representation रखें। तब GET पर मिला ETag PUT, PATCH और DELETE पर भी अर्थपूर्ण रहेगा।

## If-Match version check को server की जिम्मेदारी बनाता है

`If-Match` unsafe request में expected ETag रखता है। Server method तभी चलाता है जब current representation supplied tag से strongly match करे।

एजेंट record पढ़कर मिले header को रख सकता है:

```bash
curl -i \\
  -H 'Authorization: Bearer $TOKEN' \\
  https://api.example.test/v1/deployments/billing-worker
```

Response में यह शामिल होगा:

```http
ETag: "deploy-8f31c2"
```

इसके बाद वह अपने इच्छित सबसे छोटे बदलाव को पढ़ते समय मिले validator के साथ भेज सकता है:

```bash
curl -i -X PATCH \\
  -H 'Authorization: Bearer $TOKEN' \\
  -H 'Content-Type: application/json-patch+json' \\
  -H 'If-Match: "deploy-8f31c2"' \\
  --data '[{"op":"replace","path":"/replicas","value":5}]' \\
  https://api.example.test/v1/deployments/billing-worker
```

अगर resource उसी version पर है, तो server patch लागू करता है और नया ETag लौटाता है:

```http
HTTP/1.1 200 OK
ETag: "deploy-a19d77"
Content-Type: application/json

{
  "name": "billing-worker",
  "replicas": 5,
  "image": "registry.example/billing:2.4.0",
  "maintenanceMode": false
}
```

अगर operator के बदलाव से current version पहले ही बदल चुका है, तो server यह लौटाता है:

```http
HTTP/1.1 412 Precondition Failed
Content-Type: application/problem+json

{
  "type": "https://api.example.test/problems/precondition-failed",
  "title": "The deployment changed after it was read",
  "status": 412,
  "detail": "Fetch the current deployment before retrying this update."
}
```

RFC 9110 कहता है कि जब `If-Match` condition false हो, तो origin server को requested method नहीं चलाना चाहिए। यही वह सुरक्षा है जिसके लिए आप यह व्यवस्था बना रहे हैं। Check और mutation एक ही atomic operation में होने चाहिए। अगर handler row पढ़ता है, application memory में revision compare करता है और बाद में write करता है, तो comparison और write के बीच race बनी रहती है।

Relational database में implementation अक्सर conditional update जैसा दिखता है:

```sql
UPDATE deployments
SET replicas = :replicas,
    revision = revision + 1
WHERE id = :id
  AND revision = :expected_revision;
```

अगर प्रभावित rows की संख्या शून्य हो, तो API 412 लौटाता है। अगर संख्या एक हो, तो API updated document लौटाता है और नए revision से अगला ETag बनाता है। Test को `WHERE` clause या equivalent transactional compare-and-set primitive में रखें। इसे दो अलग queries में बांटकर सुरक्षित न मानें।

## Version fields application data में यही contract दिखाते हैं

Version field application-level validator है। यह clients को visible revision देता है, जिसे वे request body, query या dedicated header में वापस भेजते हैं। Generated SDKs, message queues या ऐसे protocols के साथ यह आसान हो सकता है जो HTTP response headers को अच्छी तरह preserve नहीं करते।

GET ऐसा response दे सकता है:

```json
{
  "id": "billing-worker",
  "revision": 42,
  "replicas": 3,
  "maintenanceMode": false
}
```

Update अपनी अपेक्षा साफ तौर पर बता सकता है:

```http
PATCH /v1/deployments/billing-worker HTTP/1.1
Content-Type: application/json

{
  "expectedRevision": 42,
  "replicas": 5
}
```

Server `expectedRevision` का stored revision से atomic मिलान करता है। सफलता पर revision बढ़ाता है। Mismatch होने पर documented response के साथ request अस्वीकार करता है, आम तौर पर जब field precondition की तरह काम करे तो 412 लौटाता है।

Version field को timestamp न समझें। Monotonic integer revision equality को स्पष्ट रखता है। Timestamps कई कठिन सवाल खड़े करते हैं: server कितनी precision रखता है? क्या दो writes एक ही precision bucket में आ सकती हैं? Serialization ने value बदली? क्या replica अलग समय assign करती है? इनमें से कुछ समस्याएं हल की जा सकती हैं, लेकिन revision counter को समझाना आसान है।

ETags और version fields एक-दूसरे के विरोधी नहीं हैं। API दोनों दे सकता है, जिसमें ETag standard HTTP semantics रखे और revision application code को बदलाव दिखाने या reconcile करने में मदद करे। दोनों एक ही committed state से आने चाहिए। अगर एक version 42 कहता है और दूसरा गलती से version 41 की ओर इशारा करता है, तो clients के पास सुरक्षित recovery का रास्ता नहीं रहता।

अगर ETag और revision field अलग हो सकते हैं, तो दोनों में से किसी एक को स्वीकार न करें। Route के लिए एक authoritative precondition चुनें या दोनों का मेल जरूरी करें। Flexible input contracts तब तक अच्छे लगते हैं जब तक कोई client stale body revision और fresh copied header साथ न भेज दे और किसी को पता न हो कि server ने किस claim को माना।

## If-None-Match creation को सुरक्षित करता है, stale replacement को नहीं

`If-None-Match` predicate को उलट देता है। इसका अर्थ है कि method तभी आगे बढ़े जब current representation supplied tags में से किसी से match न करे। Unsafe methods के लिए false condition पर 412 मिलता है।

इसका सबसे उपयोगी mutation form `If-None-Match: *` है, जिसका अर्थ है, «इसे तभी बनाएं जब अभी कोई current representation मौजूद न हो।» Client named resource creation सुरक्षित तरीके से आजमा सकता है:

```bash
curl -i -X PUT \\
  -H 'Authorization: Bearer $TOKEN' \\
  -H 'Content-Type: application/json' \\
  -H 'If-None-Match: *' \\
  --data '{"name":"nightly-export","schedule":"0 2 * * *"}' \\
  https://api.example.test/v1/jobs/nightly-export
```

अगर किसी दूसरे client ने वह job पहले ही बना दी है, तो server उसे चुपचाप replace करने के बजाय request अस्वीकार करता है। यह तब उपयोगी है जब एजेंट ने identifier बनाया हो और उसी नाम वाले existing object पर कब्जा न करना हो।

सामान्य optimistic concurrency के लिए `If-Match: *` न भेजें। यह केवल इतना मांगता है कि कोई current representation मौजूद हो। इससे एजेंट को किसी भी current version को overwrite करने की अनुमति मिल जाती है, जिसमें वह version भी शामिल है जिसे उसने कभी पढ़ा नहीं। यह existence protection है, lost-update protection नहीं।

GET और HEAD के लिए `If-None-Match` caching को support करता है। Matching tag पर आम तौर पर `304 Not Modified` मिलता है और response body नहीं होती। Developers अक्सर ETags से इसी cache behavior के कारण परिचित होते हैं। इसे देखकर validators को केवल cache plumbing न समझें। Writes पर इसी mechanism के परिणाम कहीं अधिक गंभीर हो सकते हैं।

## Stale-write response के बाद एजेंट के लिए स्पष्ट policy जरूरी है

412 को current mutation plan रोक देना चाहिए। एजेंट की पुरानी धारणा गलत साबित हो चुकी है, और वही request दोबारा भेजने से वह सही नहीं हो जाएगी।

सुरक्षित recovery sequence छोटा है:

1. Current representation और उसका नया validator प्राप्त करें।
2. केवल patch में नामित field नहीं, बल्कि इच्छित action के पीछे की fields और state assumptions की तुलना करें।
3. नए validator के साथ तभी retry करें जब intent बिना किसी नए अर्थ-निर्धारण के अभी भी सही हो।
4. जब current state action का अर्थ, दायरा या जोखिम बदल दे, तो मंजूरी मांगें या रुक जाएं।

दूसरा कदम वह जगह है जहां automated clients अक्सर shortcut लेते हैं। मान लीजिए एजेंट membership list पढ़ने के बाद किसी user को access group से हटाने की योजना बनाता है। फिर कोई व्यक्ति उस user की role को contractor से incident responder में बदल देता है। दोबारा fetch करने के बाद एजेंट अब भी syntactically legal removal कर सकता है। फिर भी उसे अपने-आप ऐसा नहीं करना चाहिए, क्योंकि role बदलने से मूल योजना संदिग्ध हो गई है।

Work record छोटा लेकिन पूरा रखें: resource URI, observed ETag या revision, पढ़ी गई fields, इच्छित mutation और response। Tool runner इसे छोटे task के दौरान memory में रख सकता है। लंबे autonomous workflow को इसे अपने audited task state में persist करना चाहिए। Model से केवल prose के आधार पर validator याद रखने को कभी न कहें। Quoted header values आसानी से छूट, बदल या गलत resource पर दोबारा इस्तेमाल हो सकती हैं।

Sallyport HTTP call चलाते समय एजेंट को API credential से दूर रख सकता है, लेकिन एजेंट को ETag को सामान्य request data की तरह सुरक्षित रखना और भेजना फिर भी जरूरी है। Credential isolation और concurrency control अलग-अलग failures को संबोधित करते हैं, इसलिए महत्वपूर्ण actions पर दोनों का इस्तेमाल करें।

## 412, 409 और 428 अलग failures बताते हैं

जब client ने conditional request header या equivalent documented precondition दी हो और वह condition false हो, तो `412 Precondition Failed` लौटाएं। Response सटीक रूप से बताता है कि resource अब उस state में नहीं है जिसका client ने दावा किया था।

जब server किसी route के लिए precondition जरूरी मानता हो और client ने उसे छोड़ा हो, तो `428 Precondition Required` लौटाएं। RFC 6585 ने lost updates रोकने के लिए यह status खास तौर पर परिभाषित किया है। Response कह सकता है कि PATCH के लिए `If-Match` जरूरी है और, अगर उसे उजागर करने में disclosure risk न हो, current ETag भी दे सकता है।

जब version condition पास होने के बाद भी request application state से conflict करे, तो `409 Conflict` लौटाएं। उदाहरण के लिए client का `If-Match` current invoice से match कर सकता है, फिर भी server cancellation रोक सकता है क्योंकि payment settlement शुरू हो चुका है। Version test पास हुआ, लेकिन business command invoice state से conflict करता है।

इन सबको एक generic error में न मिलाएं। Agent की प्रतिक्रिया अलग होनी चाहिए:

- 428 के बाद resource प्राप्त करें और आवश्यक condition के साथ retry करें।
- 412 के बाद दोबारा fetch करें और मूल intent का फिर आकलन करें।
- 409 के बाद domain conflict देखें और API के business resolution path का पालन करें।

उपयोगी error body resource का नाम दे, failed condition की पहचान करे लेकिन secrets दोहराए नहीं, और बताए कि fresh GET मदद कर सकता है या नहीं। उसे retry को harmless नहीं दिखाना चाहिए। HTTP status machine-readable category देता है, जबकि body operator को अगला कदम तय करने के लिए पर्याप्त context देती है।

## Last-modified dates compatibility fallback हैं

`Last-Modified` और `If-Unmodified-Since` इससे जुड़ी condition व्यक्त कर सकते हैं: method तभी चलाएं जब supplied date के बाद resource बदला न हो। जहां पुराना API modification times पहले से publish करता हो और tags जोड़ने में समय लगे, वहां ये उपयोगी रहते हैं।

महत्वपूर्ण writes के लिए ये कमजोर हैं। HTTP dates में one-second precision होती है। एक ही second में हुए दो बदलाव एक ही visible date दे सकते हैं, और client को पता नहीं हो सकता कि server का stored timestamp header से अधिक precision रखता है या नहीं। Replication, clocks और serialization भी अप्रत्याशित परिणाम ला सकते हैं।

अगर client `If-Match` और `If-Unmodified-Since` दोनों भेजता है, तो RFC 9110 के अनुसार `If-Match` को प्राथमिकता मिलती है। यह उचित है। Strong validator exact version test देता है, जबकि date एक approximation है।

जब तक standard headers किसी protocol requirement को पूरा न कर पाएं, अपना `X-If-Version` header न बनाएं। Custom headers SDKs और proxies में तेजी से फैलते हैं और फिर स्थायी compatibility work बन जाते हैं। `ETag` और `If-Match` के semantics स्पष्ट हैं, status codes ज्ञात हैं और सामान्य HTTP tooling में इनका support है।

## PATCH formats को भी अलग tests चाहिए

Conditional headers resource के version की रक्षा करते हैं। वे यह validate नहीं करते कि patch सुरक्षित transformation व्यक्त करता है या नहीं। सही ETag होने पर भी ऐसा JSON Merge Patch जिसमें पूरा nested object शामिल हो, sibling fields मिटा सकता है। JSON Patch गलत array position को target कर सकता है, अगर API ऐसी ordered list model करता हो जिसकी membership बदल चुकी हो।

Operation के अनुरूप patch format चुनें। RFC 6902 में परिभाषित JSON Patch `replace`, `add`, `remove` और `test` जैसे operations को specific paths पर व्यक्त करता है। उसका `test` operation document के भीतर किसी value की जांच कर सकता है, फिर बाद के operations चल सकते हैं। RFC 7396 में परिभाषित JSON Merge Patch desired partial document बताता है और `null` को deletion मानता है।

Document-level ETag को बाहरी guard बनाए रखें। जब operation में कोई field-specific assumption स्पष्ट करना उपयोगी हो, तब JSON Patch `test` जोड़ें:

```json
[
  {"op":"test","path":"/maintenanceMode","value":false},
  {"op":"replace","path":"/replicas","value":5}
]
```

अगर इस request से पहले किसी दूसरे writer ने `maintenanceMode` बदल दिया, तो request fail होनी चाहिए, ताकि maintenance के दौरान capacity न बढ़े। API को failed JSON Patch test के लिए लौटाए जाने वाले error को document करना चाहिए। कई implementations 409 का इस्तेमाल करते हैं, क्योंकि patch instruction current document से conflict करती है, जबकि बाहरी ETag mismatch 412 रहता है। यह अंतर तब उपयोगी है जब clients को जानना हो कि document पुराना था या state-dependent request ही गलत थी।

General concurrency scheme के रूप में केवल patch `test` पर निर्भर न रहें। यह केवल उन्हीं paths की रक्षा करता है जिन्हें आपने test करना याद रखा। Strong ETag उस representation version की रक्षा करता है जिस पर एजेंट ने वास्तव में अपनी योजना आधारित की थी।

## Servers को write boundary पर precondition लागू करनी चाहिए

जो API केवल `If-Match` की सलाह देता है, वह deadline pressure में विफल होगा। एक client इसे छोड़ देगा, दूसरा SDK इसे forward करना भूल जाएगा और vulnerable endpoint वही बन जाएगा जिसे agents examples के जरिए खोज लेंगे। जहां stale overwrite की महत्वपूर्ण कीमत हो, वहां updates के लिए इसे अनिवार्य करें।

Side effects करने से पहले handler को missing conditions अस्वीकार करनी चाहिए। फिर उसे expected validator को उस storage operation में भेजना चाहिए जो state बदलता है। अगर resource कई tables या external control plane पर आधारित है, तो comparison और state change को एक transaction में रखें या provider का compare-and-set operation इस्तेमाल करें। अगर provider ऐसा नहीं कर सकता, तो आपका API उस write के लिए lost-update protection का ईमानदार वादा नहीं कर सकता।

Race को जानबूझकर test करें। Resource को revision 7 पर रखें। Client A और client B दोनों से GET कराएं। A को `If-Match: "7"` के साथ PATCH करने दें और verify करें कि उसे revision 8 मिले। फिर B को `If-Match: "7"` के साथ PATCH करने दें और verify करें कि उसे 412 मिले तथा उसका इच्छित बदलाव दिखाई न दे। DELETE, full PUT और किसी भी ऐसे bulk action के साथ भी दोहराएं जो prior read के आधार पर resource लिखता है।

खतरनाक shortcuts भी test करें: protected routes पर missing `If-Match` को 428 मिलना चाहिए, `If-Match: *` को stale-write protection के रूप में प्रस्तुत नहीं करना चाहिए और weak ETag को strong comparison में पास नहीं होना चाहिए। ये tests उन regressions को पकड़ते हैं जो नया endpoint सामान्य repository method को bypass करने पर आते हैं।

## सुरक्षित रास्ते को overwrite से आसान बनाएं

API को mutable resources के हर GET पर ETags लौटाने चाहिए, हर unsafe operation के पास required conditions document करनी चाहिए और SDK methods को validators स्वाभाविक रूप से carry करना चाहिए। दूसरे writer का काम नुकसान से बचाने के लिए client को किसी obscure response object से raw headers निकालने की जरूरत नहीं होनी चाहिए।

Agents के लिए planning और execution अलग रखें। Target पढ़ें, उसका validator record करें, intended mutation लिखें और conditional call चलाएं। अगर कोई observation बदलती है, तो planned write छोड़ दें, जब तक एजेंट यह न दिखा सके कि बदलाव irrelevant है। यह नियम conservative लगता है, क्योंकि यह सचमुच conservative है। विकल्प automated process को उन facts के आधार पर कार्रवाई की authority देना है जिन्हें वह पुराना जानता है।

उन endpoints से शुरुआत करें जहां overwritten change किसी को तुरंत जगा देगा: deployment settings, access control, customer records, payment state और secrets metadata। `If-Match` जोड़ें, missing precondition को fail करें और route पर दो writers का test करें। जब server default रूप से stale writes रोकने लगेगा, तो agent की speed ordinary concurrency को silent damage में नहीं बदलेगी।
