# जब agent records आपस में न मिलें, तब API audit log की जांच कैसे करें

एक API provider कहता है कि किसी request ने production data बदल दिया। Agent का रिकॉर्ड कहता है कि वह वहां तक पहुंचा ही नहीं। दोनों बातें सही हो सकती हैं। किसी एक log को अंतिम फैसला मान लेने से teams अक्सर एक सीमित discrepancy को खराब incident response में बदल देती हैं।

कार्रवाई की जांच observations की एक chain के रूप में करें। पता लगाएं कि run किसने शुरू किया, agent ने क्या करने की कोशिश की, credential boundary के पार क्या गया, provider ने क्या स्वीकार किया और बाद में क्या बदला। Timestamps इस chain का क्रम तय करने में मदद करते हैं। Request identifiers इसे जोड़ते हैं। Results और state बताते हैं कि इसका असर पड़ा या नहीं। Missing events भी evidence हैं, लेकिन पहले उन सामान्य कारणों को खारिज करें जिनसे records गायब हो सकते हैं।

मैंने लोगों को spreadsheet से शुरुआत करके तुरंत समय के अनुसार sort करते देखा है। यह उलटा तरीका है। Timestamp अक्सर उपलब्ध join fields में सबसे कमजोर होता है। पहले stable identifiers और immutable exports से शुरुआत करें, फिर समय का उपयोग यह जांचने के लिए करें कि प्रस्तावित sequence समझ में आता है या नहीं।

## Dashboard refresh होने से पहले records सुरक्षित करें

Filter करने, retry करने, access revoke करने या provider की support team से जांच कहने से पहले original evidence capture करें। Interactive dashboards बदलते हैं, retention jobs चलती हैं और retry ऐसा दूसरा event बना सकता है जो पहले event को समझना मुश्किल कर दे।

Case ID के साथ एक case folder बनाएं और केवल screenshots नहीं, raw exports भी इकट्ठा करें। इसमें agent session record, individual action records, provider audit export, प्रभावित सिस्टम के application logs और team के पास उपलब्ध कोई भी egress record शामिल करें। Collection time UTC में, collector का नाम, इस्तेमाल किया गया account या role और हर export बनाने के लिए इस्तेमाल किया गया filter लिखें।

Collection के बाद हर file का hash बनाएं। अगर आपका operating system standard SHA-256 utility देता है, तो एक shell command पर्याप्त है:

```
$ shasum -a 256 provider-events.json agent-activity.json
81b5777b8416320fe26cb8a8dddb6a9e736fab4f5e7aa5812bf6afeffc5f4e82  provider-events.json
a1e98c01992b51104fbc8c5fcbaa78e65db31f1edb3e546f4c14d0e6d3673ba  agent-activity.json
```

Hashes को साधारण case note में रखें। Hash यह साबित नहीं करता कि provider export पूरा था। यह केवल साबित करता है कि collection के बाद आपकी working copy चुपचाप नहीं बदली। ये दो अलग दावे हैं, लेकिन incident reports अक्सर इन्हें मिला देती हैं।

पहली copy के रूप में JSON को spreadsheet में “साफ” न करें। Normalization duplicate fields, array order, fractional seconds, empty values और exact request body हटा सकता है, जबकि बाद में यही mismatch समझा सकती है। Untouched export रखें और अलग parsed working file बनाएं।

अगर discrepancy में credential leak या unauthorized use शामिल हो सकता है, तो sequence सुरक्षित रखते हुए access रोकें। संभव हो तो live agent session revoke करें या action path lock करें। Provider credential को rotate करने से पहले provider के हाल के audit records collect करें, जब तक active abuse के कारण तुरंत rotation जरूरी न हो। Rotation कभी-कभी आवश्यक होती है, लेकिन इससे attribution का बचा हुआ एकमात्र रास्ता मिट सकता है।

## Request identifier, timestamp से अधिक महत्वपूर्ण है

ऐसे identifiers पर records जोड़ें जो boundaries के पार बने रहते हैं: provider request ID, client-supplied correlation ID, idempotency key, write से मिला object ID और provider द्वारा documented trace ID। हर identifier रखें, क्योंकि provider headers, audit events, support exports और error bodies में अलग-अलग IDs दिखा सकता है।

सबसे आसान स्थिति कुछ ऐसी होती है। Action record कहता है कि agent ने `POST /v1/invoices` call किया; response headers में `x-request-id: req_72M...` है; provider export में `req_72M...` शामिल है; और बनाए गए invoice का ID `inv_4P...` है। अब आपके पास intent, delivery, provider handling और durable state को जोड़ने वाली कड़ी है।

मुश्किल स्थितियां ज्यादा आम हैं। Provider request को parse करने के बाद ही request ID दे सकता है। TLS failure में provider request ID नहीं होगा, क्योंकि request application तक पहुंची ही नहीं। Gateway एक ID और downstream service दूसरी ID बना सकती है। Asynchronous API job ID लौटा सकती है और requested object कई मिनट बाद लिख सकती है। हर ID किस boundary ने जारी की, यह रिकॉर्ड करें। इन्हें एक ही `request_id` column में न समेटें।

ऐसी reconciliation table इस्तेमाल करें जो uncertainty को स्पष्ट रखे:

| Field | Local action record | Provider record | Affected system |
| --- | --- | --- | --- |
| Client correlation ID | `run-18-call-42` | `run-18-call-42` | absent |
| Provider request ID | response में `req_72M...` | `req_72M...` | absent |
| Method and path | `POST /v1/invoices` | `POST /v1/invoices` | invoice created |
| Result | `504 timeout` | `202 accepted` | job `job_91...` completed |
| Event time | `10:04:03.219Z` | `10:04:03Z` | `10:04:11.802Z` |

यह table एक परिचित failure दिखाती है: caller का timeout हो गया, लेकिन provider ने write स्वीकार कर लिया और caller के हार मानने के बाद उसे process किया। Caller के result के आधार पर action को “failed” कहना गलत होगा। Provider log को “agent के intent का proof” कहना भी गलत होगा। Evidence यह बताता है कि agent ने request भेजी, provider ने उसे स्वीकार किया और caller को समय पर response नहीं मिला।

अगर provider writes के लिए idempotency key देता है, तो उसका उपयोग करें। IETF Idempotency-Key Internet-Draft इसका व्यावहारिक उद्देश्य अच्छी तरह बताता है: client unsafe HTTP operation को दोबारा चला सकता है और गलती से वही effect दो बार पैदा नहीं करता। Provider-specific behavior अलग होता है, इसलिए retention और matching rules की documentation पढ़ें। यह न मानें कि केवल endpoint का मिलना पर्याप्त है।

Custom headers स्वीकार करने वाली APIs के लिए call से पहले correlation ID बनाएं और उसे documented header, जैसे `X-Client-Request-ID`, में भेजें। इसे local event के साथ store करें। इस ID में secrets, prompts, user data या raw tokens कभी न रखें। सुरक्षित value का case के बाहर कोई अर्थ नहीं होना चाहिए, जैसे `case-2025-041-run7-call18`।

## समय किसी कहानी को गलत साबित कर सकता है, लेकिन आमतौर पर सही साबित नहीं करता

Events की सीमाएं तय करने और असंभव ordering पकड़ने के लिए timestamps इस्तेमाल करें। जब तक हर source में इससे बेहतर identifier न हो, timestamp को primary identity field न बनाएं।

RFC 3339 एक सामान्य Internet timestamp profile तय करता है और uppercase UTC form की सलाह देता है, जिसका अंत `Z` से होता है, जैसे `2025-03-08T10:04:03.219Z`। Parse करने के बाद भी original string रखें। `10:04:03Z` और `10:04:03.219Z` के बीच अंतर महत्वपूर्ण है, जब एक source seconds तक round करता है और दूसरा milliseconds देता है।

हर relevant event के लिए चार time fields बनाएं:

- source timestamp जैसा export हुआ है
- normalized UTC timestamp
- event type, जैसे sent, accepted, completed या logged
- clock owner, जैसे local Mac, provider edge, provider worker या database

Provider edge timestamp का local “response received” timestamp से पहले होना विरोधाभास नहीं है। Provider worker completion timestamp agent process exit के बाद आ सकता है। Drift वाली local clock किसी action को session शुरू होने से पहले दिखा सकती है। ये सामान्य technical घटनाएं हैं, tampering का proof नहीं।

किसी known anchor, आमतौर पर request ID या session start, के आसपास एक window बनाएं। Accidental joins रोकने के लिए शुरुआत में window छोटी रखें। इसे तभी बढ़ाएं जब कारण बता सकें: provider केवल seconds record करता है, operation asynchronous है या आपने trusted reference से clock drift मापी है। चुनी गई window case note में लिखें। “हमने समय के आसपास search किया” कोई method नहीं है।

Log ingestion time को लेकर सावधान रहें। कई systems `event_time` और `created_at` दोनों दिखाते हैं। पहला emitting system के अनुसार event होने का समय बताता है। दूसरा aggregator द्वारा event receive या index किए जाने का समय बता सकता है। देर से पहुंचने का अर्थ देर से execute होना नहीं है। अगर कोई event incident शुरू होने के बाद दिखाई देता है, तो narrative बनाने से पहले दोनों fields देखें।

एक उपयोगी ordering test केवल यह पूछता है कि प्रस्तावित कहानी संभव है या नहीं। Provider event 10:04:03 पर और local send 10:04:03.219 पर होना संभव है, अगर clocks अलग हों या provider नीचे की ओर round करता हो। अगर provider कहता है कि job 10:04 पर स्वीकार हुई, तो 10:02 पर completion का दावा संभव नहीं है, जब तक आपने दो events मिला न दिए हों या field को गलत न समझा हो।

## Attempted, delivered, accepted और completed को अलग रखें

Teams अक्सर चार अलग states को “called” शब्द में समेट देती हैं। यही shortcut अधिकांश log disputes का कारण है।

Agent request बनाकर action attempt कर सकता है। Local component bytes को remote endpoint तक deliver कर सकता है। Provider request स्वीकार कर सकता है। Downstream worker effect पूरा कर सकता है। हर stage का record और failure mode अलग होता है।

HTTP Semantics specification, RFC 9110, status code को server के response का बयान मानती है, caller के पूरे experience का इतिहास नहीं। `202 Accepted` साफ तौर पर कहता है कि processing स्वीकार हुई है, पूरी नहीं हुई। `204 No Content` बताता है कि server ने request सफलतापूर्वक पूरी की, लेकिन इससे हर downstream effect स्पष्ट नहीं होता। Network timeout में HTTP response न मिले, फिर भी server request process कर सकता है।

हर disputed event को इन जैसे status से classify करें:

- **Attempted only**: local action record है, लेकिन network delivery का evidence नहीं।
- **Delivered, outcome unknown**: request local boundary से बाहर गई, लेकिन caller को reliable response नहीं मिला और provider में अभी searchable record नहीं है।
- **Accepted, effect pending**: provider ने acceptance या job reference लौटाया, लेकिन completed state अभी नहीं है।
- **Completed**: provider result और observed state change एक-दूसरे से मेल खाते हैं।
- **Contradicted**: fields के अर्थ समझने के बाद भी sources ऐसे दावे करते हैं जो दोनों एक साथ सही नहीं हो सकते।

“Outcome unknown” एक वैध निष्कर्ष है। केवल इसलिए इसे failure न कहें कि agent को exception मिला। Write operation में यह exception automatic retries रोकने के लिए पर्याप्त होना चाहिए, जब तक idempotency mechanism या read-back check retry को सुरक्षित न बना दे।

उलटी गलती भी उतनी ही खराब है: `200` response का मतलब यह नहीं कि intended business result हुआ। Endpoint syntactically valid request के लिए success लौटा सकता है, जबकि बाद में validation, async job या downstream dependency intended change को अस्वीकार कर दे। Returned object, job status या target-system event देखें, जिसे API contract completion का संकेत मानती है।

## Missing events के लिए सीमित और स्पष्ट explanation चाहिए

Record न मिलना यह बता सकता है कि request कभी हुई ही नहीं। लेकिन यह भी हो सकता है कि आपने गलत service से पूछा, गलत account scope इस्तेमाल किया, गलत retention tier में search की या ऐसे record की अपेक्षा की जिसे provider emit करने का वादा ही नहीं करता।

Missing events की जांच एक तय क्रम में करें। पहले exact account, project, region, environment और API product की पुष्टि करें। Providers अक्सर audit views को इनमें से एक या अधिक fields के आधार पर अलग करते हैं। दूसरा, हर identifier से, फिर documented time window और endpoint से search करें। तीसरा, देखें कि provider accepted requests, rejected requests, data-plane calls, control-plane calls या केवल administrative actions record करता है। चौथा, retention और export delay जांचें। पांचवां, पूछें कि proxy, SDK या asynchronous queue आपके अपेक्षित event से अलग provider event बनाती है या नहीं।

एक ठोस failure याद रखने लायक है। Agent `POST /exports` भेजता है और connection timeout मिलता है। Team local client ID से provider audit logs search करती है और कुछ नहीं मिलता। वे retry करते हैं, फिर दो export completion notifications मिलती हैं।

पहली request regional ingestion endpoint पर गई थी। उन्होंने जिस audit screen को देखा, उसमें केवल control-plane events थे। Provider ने job को client header के बजाय generated export ID के तहत record किया, और job service ने timeout के बाद उसे पूरा किया। इस sequence में malicious activity की कोई जरूरत नहीं थी। Duplicate इसलिए बना क्योंकि idempotency key, job query endpoint या business-level marker जांचे बिना write को retry किया गया।

यह failure यह भी दिखाता है कि absence को सावधानी से लिखना जरूरी है। कहें, “हमारे collect किए गए export में इस window के लिए matching data-plane event नहीं है,” न कि, “Provider के पास कोई record नहीं है।” पहला statement evidence और उसकी सीमा दोनों बताता है। दूसरा ऐसा दावा करता है जिसे आप अक्सर साबित नहीं कर सकते।

अगर कोई log अपेक्षित था लेकिन नहीं मिला, तो query parameters सुरक्षित रखें और provider documentation capture करें, जिसमें expected event coverage लिखी हो। Exact request ID, account scope, UTC window, endpoint और evidence hashes के बिना support request भेजने से कई दिन बर्बाद होंगे।

## Results को status codes से आगे जाकर जांचें

Request के stated intent की तुलना response payload और दिखाई देने वाले effect से करें। Status codes protocol exchange के बारे में बताते हैं। वे यह नहीं बताते कि request का scope सही था या नहीं, provider ने कोई default लागू किया या agent ने stale identifier भेजा।

हर action के लिए, जहां API उपलब्ध कराती हो, ये fields capture करें: HTTP method, normalized path, request ID, idempotency key, actor या credential identity, status code, response body hash, returned object ID और asynchronous job ID। व्यापक sharing से पहले credentials और sensitive payload data redact करें, लेकिन policy अनुमति दे तो protected original रखें।

Response body hash दो ऊपर से समान दिखने वाले `200` records में अंतर बता सकता है। Pretty-print करने से पहले raw response bytes पर hash निकालें। अगर API JSON लौटाती है और layers के बीच field order बदलता है, तो raw bytes और canonical parsed copy दोनों रखें। समान status codes को समान responses न मानें।

फिर उस resource को query करें जो बनना या बदलना चाहिए था। Create operation में returned object ID retrieve करें और उसके creator, creation time और attributes की तुलना करें। Update में version, revision या audit entry retrieve करें, अगर service यह सुविधा देती है। Delete में देखें कि object अनुपस्थित है या नहीं और provider audit record deletion को उसी credential से जोड़ता है या नहीं।

यहीं broad credentials investigation को नुकसान पहुंचाते हैं। अगर कई tools एक ही API token साझा करते हैं, तो provider अक्सर बता सकता है कि token ने action किया, लेकिन यह नहीं कि कौन-से local process या human ने action शुरू किया। Credential identity को boundary marker मानें, actor identity नहीं।

## Gateway record तभी उपयोगी है जब वह boundary record करे

Action gateway agent और credential वाली operation के बीच एक साफ observation point देता है। उसे invoking process या run, approved authorization state, requested operation, agent को लौटाया गया result और provider records से जोड़ने के लिए पर्याप्त identifiers record करने चाहिए। उसे credential agent को देकर बाद की local telemetry को audit trail नहीं कहना चाहिए।

Sallyport API और SSH credentials को अपने encrypted vault में रखता है, action खुद execute करता है और secret के बजाय result agent को लौटाता है। इसके Sessions और Activity journals write-blind encrypted, hash-chained audit log से project होते हैं, जिससे investigator को run-level और call-level दोनों records मिलते हैं। `sp audit verify` vault key के बिना ciphertext पर उस chain को offline verify कर सकता है।

यह design एक खास gap को दूर करता है। Provider log credential और API request की पहचान कर सकता है। वह यह नहीं बताता कि किस agent process को उस credential के उपयोग की अनुमति मिली थी और न ही यह साबित करता है कि agent ने secret कभी देखा ही नहीं। Local audit record इस सवाल का केवल कुछ हिस्सा तब जवाब दे सकता है, जब credential boundary वास्तव में record बनाने वाले component के भीतर हो।

Gateway record को बढ़ा-चढ़ाकर न बताएं। जो request उसे bypass करके गई हो, वह उसे report नहीं कर सकता। वह अस्पष्ट provider API को सटीक भी नहीं बना सकता। वह evidence की तुलना के लिए मजबूत जगह देता है और जांच जारी रहते किसी ज्ञात agent run को revoke करने की सुविधा देता है।

## Finding को evidence और limits वाले claims के रूप में लिखें

अच्छा finding किसी दूसरे engineer को आपकी reasoning दोहराने देता है, बिना आपकी assumptions अपनाए। Invocation, permission, request delivery, provider handling और observed effect के लिए अलग claims लिखें। हर claim के साथ उससे जुड़े identifiers, timestamps, source files और fields के अर्थ दें।

Confidence के अनुरूप भाषा इस्तेमाल करें। “Action journal में दर्ज है कि process X ने इस समय `POST /v1/invoices` का अनुरोध किया।” “Provider export में उसी provider request ID वाला request है।” “Invoice मौजूद है और उसके attributes recorded response से मेल खाते हैं।” ये testable statements हैं। “Agent ने निश्चित रूप से invoice बनाया” तभी उचित हो सकता है, जब joins और credential boundary इसे support करें।

जब records अलग हों, तो final report में disagreement दिखने दें। Timestamps का औसत न निकालें और असुविधाजनक source हटाएं नहीं। सबसे संभावित explanation, खारिज किए गए विकल्प और अभी missing evidence लिखें। अगर यह तय नहीं कर सकते कि write पूरी हुई या नहीं, तो उसे unknown दर्ज करें और automatic retries की अनुमति देने से पहले API path ठीक करें।

Incident के बाद व्यावहारिक बदलाव अक्सर छोटे और साधारण होते हैं: correlation ID अनिवार्य करें, सही provider event class retain करें, fractional UTC timestamps सुरक्षित रखें और writes के लिए idempotency इस्तेमाल करें। ये controls अगली disagreement को forensic बहस के बजाय छोटी reconciliation में बदल देते हैं।
