8 मिनट पढ़ें

सीक्रेट ब्रोकर error में retry से पहले प्रमाण चाहिए

सीक्रेट ब्रोकर errors को execution evidence के आधार पर design करें, ताकि agents सुरक्षित preflight retry और अज्ञात remote outcome अलग कर सकें।

सीक्रेट ब्रोकर error में retry से पहले प्रमाण चाहिए

किसी सीक्रेट ब्रोकर को वही बताना चाहिए जिसे उसके रिकॉर्ड साबित करते हैं, न कि वह जिसे कोई exception संकेत करता है। अगर वह यह साबित नहीं कर सकता कि रिमोट ऑपरेशन का प्रयास ही नहीं हुआ, तो उसे कॉल को preflight failure नहीं कहना चाहिए। जैसे ही request bytes या SSH exec request दूसरी ओर पहुंच चुके होने की संभावना बने, रिमोट परिणाम अज्ञात हो सकता है, भले ही स्थानीय error में "timeout" या "connection reset" लिखा हो।

यही अंतर तय करता है कि कोई agent दूसरी बार भुगतान करेगा, उसी credential को दो बार rotate करेगा, वही release फिर deploy करेगा, या उस काम को सुरक्षित रूप से दोहराएगा जो मशीन से बाहर गया ही नहीं। इसलिए उपयोगी error contract में दो अलग तथ्य होने चाहिए: ब्रोकर किस चरण पर रुका और वह रिमोट execution के बारे में क्या जानता है। "transient" नाम का एक field दोनों बातें नहीं बता सकता।

नीचे दिया गया design HTTP calls और SSH commands, दोनों पर लागू होता है। यह भी मानता है कि ब्रोकर के पास टिकाऊ action record है। केवल memory में मौजूद state error message बेहतर कर सकती है, लेकिन ब्रोकर या caller के restart होने के बाद वह retry को सही ठहराने का आधार नहीं बन सकती।

कारण और परिणाम अलग बातें हैं

हर failure में phase, outcome और retry disposition होना चाहिए। Phase operator को बताता है कि जांच कहां करनी है। Outcome caller को बताता है कि रिमोट पक्ष ने state बदली हो सकती है या नहीं। Retry disposition, दर्ज evidence और operation semantics के आधार पर, automation को बताता है कि वह अभी क्या कर सकती है।

पांच public phases रखें:

  • validation: ब्रोकर ने secret चुनने या इस्तेमाल करने से पहले action अस्वीकार कर दिया।
  • credential_injection: ब्रोकर credential पा नहीं सका, authorization नहीं ले सका या उसे जोड़ नहीं सका।
  • connection_setup: name resolution, routing, TCP, TLS, SSH transport, host verification या remote authentication dispatch से पहले विफल हुआ।
  • remote_execution: ब्रोकर operation dispatch कर चुका है और रिमोट परिणाम की प्रतीक्षा कर रहा है या उसे पा चुका है।
  • result_delivery: ब्रोकर ने रिमोट परिणाम दर्ज कर लिया, लेकिन caller को पूरा परिणाम लौटा नहीं सका।

ये phases diagnosis के लिए हैं, retry policy के लिए नहीं। connection_setup error साबित कर सकता है कि application request भेजा ही नहीं गया, जबकि टूटा हुआ reused connection ब्रोकर को इस बारे में अनिश्चित छोड़ सकता है कि peer को write मिला या नहीं। result_delivery error के साथ रिमोट सफलता का ज्ञात परिणाम भी हो सकता है। दोनों को सामान्य network error कहने से वह तथ्य मिट जाता है जिसकी caller को सबसे ज्यादा जरूरत है।

चार outcome states रखें:

  • not_attempted: टिकाऊ evidence दिखाता है कि रिमोट operation ने dispatch boundary पार नहीं की।
  • rejected: रिमोट system ने पूरा और authoritative इनकार लौटाया और सफलता का दावा नहीं किया।
  • committed: ब्रोकर के पास operation का पूरा और authoritative परिणाम है।
  • unknown: dispatch हुआ हो सकता है, लेकिन ब्रोकर के पास operation का फैसला करने वाला पूरा परिणाम नहीं है।

committed का अर्थ success नहीं है। Status 23 के साथ exit हुई command या पूरा HTTP 500 response पाने वाली request का परिणाम ज्ञात है। रिमोट application उत्तर देने की स्थिति तक चला। इसे "execute नहीं हुआ" कहना duplicate को बुलावा देता है।

Retry disposition भी साफ होना चाहिए: never, after_correction, backoff, same_idempotency_key, reconcile या fetch_result। ब्रोकर इसे evidence, method semantics, remote guarantees और result journal की state से निकालता है। Caller को error string से इसका अनुमान कभी नहीं लगाना चाहिए।

Public contract में rejected और committed को एक known value में न मिलाएं। Rejection सही की गई request को अनुमति दे सकता है, जबकि committed result caller से उस result को इस्तेमाल करने या उसकी जांच करने को कहता है। दोनों attempt को लेकर निश्चित हैं, लेकिन उनके बाद के workflows अलग हैं।

Asynchronous acceptance के लिए एक और evidence item चाहिए, नया outcome enum नहीं। पूरा HTTP 202 response submission का committed result है, queued job के पूरा होने का प्रमाण नहीं। Service से मिला job identifier और status resource दर्ज करें, फिर job को अलग operation के रूप में track करें। Job के pending रहने पर submission दोहराने से वह दो बार queue हो सकता है।

यह अलगाव उस भेद को स्पष्ट करता है जिसे कई SDK मिला देते हैं। Error cause का प्रश्न है, "स्थानीय रूप से क्या टूटा?" Remote outcome का प्रश्न है, "क्या पहले ही हो चुका हो सकता है?" केवल cause पढ़ने वाला retry engine असुरक्षित है।

Validation और injection dispatch से पहले विफल होने चाहिए

Validation error तभी वास्तविक preflight failure है जब ब्रोकर ने action ले जाने वाला channel नहीं खोला हो। Host resolve करने या secret तक पहुंचने से पहले malformed targets, unsupported methods, missing action fields, oversized payloads, unknown credential references और forbidden header overrides अस्वीकार करें। phase=validation, outcome=not_attempted और आम तौर पर retry=after_correction दर्ज करें।

Automatic retry किसी deterministic validation failure को ठीक नहीं करती। उसी invalid payload को दोहराने से capacity बर्बाद होती है और agent loop छिप सकता है। INVALID_TARGET, UNSUPPORTED_ACTION या PAYLOAD_LIMIT जैसा स्थिर code और वह field path लौटाएं जिसे caller ठीक कर सकता है। अगर rejected secret reference के नाम में संवेदनशील जानकारी है, तो उसे न लौटाएं।

जब ब्रोकर कोई remote request byte छोड़ने से पहले विफल होता है, तब credential injection भी preflight है। Locked vault, denied approval, absent credential, unsupported injection mode और local key decryption failure इस phase में आते हैं। Outcome not_attempted ही रहता है, लेकिन retry advice बदलती है। Locked vault user action के बाद retry की अनुमति दे सकता है; approval denial को आम तौर पर उस invocation के लिए never होना चाहिए; missing secret को correction चाहिए।

Error और action record से credential material बाहर रखें। Credential identifier या nonsecret version tag, injection mode और call रोकने वाला decision दर्ज करें। Injection साबित करने के लिए rendered Authorization header log करना ब्रोकर का मकसद ही खत्म कर देता है।

यहां cutoff थोड़ा सूक्ष्म है। अगर ब्रोकर private buffer में credential के साथ पूरा HTTP request बनाता है और लिखने से पहले विफल होता है, तो remote action अभी भी unattempted है। अगर वह buffer transport API को देता है और API partial या ambiguous write लौटाती है, तो injection सफल हुआ और dispatch शुरू हो चुका हो सकता है। Failure को आखिरी proven boundary के आधार पर classify करें, exception पकड़ने वाले function के stack frame से नहीं।

Preflight के लिए configuration snapshot भी चाहिए। अगर validation एक route definition इस्तेमाल करे और dispatch बाद में बदली हुई definition पढ़े, तो evidence उस action का वर्णन नहीं करता जो चला। Authorization से पहले normalized target, credential version, permitted injection mode और request fingerprint को invocation से बांधें। कोई bound value बदले तो पुराने record को बदलने के बजाय नया invocation बनाएं।

Human approval के दौरान यह खास तौर पर जरूरी है। Approval card खुला रह सकता है और इसी बीच agent या configuration reload body, host या selected secret बदल सकता है। ब्रोकर को उसी fingerprint को approve करना चाहिए जिसे वह dispatch करेगा, और write से ठीक पहले उसे फिर verify करना चाहिए। Mismatch validation/not_attempted है; पुराने approval से नई request भेजने का आधार नहीं।

Connection setup का अंत साफ तय होना चाहिए

Application channel बनने से पहले विफल हुआ नया connection आम तौर पर not_attempted साबित करता है। DNS failure, route failure, refused TCP connection, TLS certificate rejection, SSH host-key rejection और SSH user-authentication failure, सभी HTTP request या SSH command के चल सकने से पहले होते हैं। पूरा हुआ सटीक stage दर्ज करें, ताकि caller secret देखे बिना bad hostname और rejected credentials में अंतर कर सके।

Pooled HTTP client के लिए "connection failed" बहुत व्यापक वाक्य है। जब ब्रोकर existing connection लेता है, setup पहले ही पूरा हो चुका होता है। Write इसलिए विफल हो सकता है क्योंकि peer ने idle socket बंद कर दिया। Operating system broken pipe तब report कर सकता है जब कुछ bytes peer तक पहुंच गए हों, या तब जब peer पूरा request पा चुका हो लेकिन client ने close अभी देखा न हो। जब तक transport अधिक मजबूत प्रमाण न दे, यह remote_execution है और outcome=unknown है।

"Local write call ने zero bytes acknowledge किए" को इस बात का प्रमाण न मानें कि remote को कुछ नहीं मिला। Buffered API बाद में failure बताने से पहले bytes को locally accept कर सकती है, और failed write यह बहुत कम बताता है कि peer ने पहले क्या पढ़ लिया। उपयोगी dispatch boundary उस transport को पहली handoff पर है जो application data पहुंचा सकता है। इसे पार करते ही default outcome unknown हो जाता है।

Connection setup HTTP और SSH के लिए अलग बिंदु पर खत्म होता है। HTTPS के लिए dispatch से पहले DNS, TCP, TLS, certificate verification और कोई proxy tunnel पूरा करें। SSH के लिए transport negotiation, host verification, user authentication, session channel creation और जरूरी environment setup पूरा करें। इनमें से कोई भी बाद की command के शुरू होने या न होने का प्रमाण नहीं है, लेकिन इनमें हुई failure साबित कर सकती है कि यह command कभी request नहीं हुई।

Redirect दूसरी dispatch boundary बनाता है। पूरा 307 या 308 पहले HTTP exchange का फैसला करता है, लेकिन उसे follow करने पर नए target के लिए नया request बनता है। उस request से पहले destination, credential scope और method को फिर validate करें। केवल इसलिए authorization credential को origins के बीच forward न करें कि client library redirects अपने आप follow करती है।

HTTP proxy एक और observer जोड़ता है, uncertainty नहीं हटाता। सफल tunnel केवल यह साबित करता है कि proxy ने path खोला। Forwarding proxy अपने attempt के बारे में पूरा error लौटा सकता है और RFC 9209 बता सकता है कि forwarding कहां विफल हुई, फिर भी origin outcome unknown हो सकता है। Evidence देने वाला hop दर्ज करें और intermediary की अपने response को लेकर certainty को origin के effects पर certainty की तरह पेश न करें।

जब हर attempt का अपना record हो और कोई attempt dispatch पार न करे, तो ब्रोकर connection setup को internally retry कर सकता है। उसे attempts सीमित करने चाहिए और उन्हें एक action record में दिखाना चाहिए:

{"attempts":[{"n":1,"stage":"tcp_connect","outcome":"not_attempted","code":"ECONNREFUSED"},{"n":2,"stage":"tls_handshake","outcome":"not_attempted","code":"CERT_EXPIRED"}]}

Final error पुराने evidence को मिटाए नहीं। उसे कहना चाहिए कि दोनों attempts में operation unattempted रहा और correction चाहिए।

Dispatch के बाद प्रमाण का दायित्व बदल जाता है

Dispatch का क्षण explicit, durable state transition होना चाहिए। ब्रोकर पहली transport handoff से पहले action record में dispatch_started append करे और system की durability promise के अनुसार उसे flush करे। अगर process write के बाद लेकिन transition record करने से पहले बंद हो जाए, तो restart operation को गलत ढंग से unattempted कह सकता है।

Strict write-ahead recording latency बढ़ाती है, इसलिए teams भेजने के बाद record करने के लिए ललचाती हैं। यह recommendation लोकप्रिय है क्योंकि सामान्य path तेज होता है और code आसान दिखता है। Non-idempotent actions के लिए यह गलत है। दुर्लभ crash ठीक उस gap में आता है जहां ब्रोकर को काम खोने और duplicate करने के बीच चुनना पड़ता है।

केवल write-ahead state यह साबित नहीं करती कि remote को request मिली। यह जानबूझकर uncertainty को सुरक्षित दिशा में रखती है। dispatch_started के बाद outcome unknown से शुरू होता है। बाद का authoritative response इसे rejected या committed में बदल सकता है। ब्रोकर इसे वापस not_attempted नहीं कर सकता।

HTTP में dispatch पहले request byte के connection में जाने से पहले शुरू होता है। Track करें कि ब्रोकर ने headers भेजे, पूरा body भेजा, response headers पाए और पूरा response body पाया या नहीं। ये markers diagnosis में मदद करते हैं, पर request_body_sent=true अभी भी यह साबित नहीं करता कि application ने request process किया। इसी तरह request_body_sent=false यह साबित नहीं करता कि application ने कुछ नहीं किया; server पूरा body पढ़ने से पहले headers के आधार पर reject या act कर सकता है।

SSH में dispatch authenticated transport में exec channel request के जाने से पहले शुरू होता है। want reply=true रखें। RFC 4254 कहता है कि server channel success या failure से जवाब देता है, पर channel success का अर्थ केवल इतना है कि उसने command शुरू करने का request स्वीकार किया। इससे command के पूरा होने या उसके effects को दोहराने की सुरक्षा साबित नहीं होती।

Dispatch के बाद cancellation preflight failure नहीं है। अगर caller timeout के बाद channel बंद करता है, तो remote process चलता रह सकता है। Local cause CALLER_CANCELLED report करें और recorded remote result मिलने तक outcome=unknown रखें। Cancellation caller की रुचि बताती है, remote state नहीं।

Batch requests को हर member के लिए outcome चाहिए। अगर ब्रोकर एक HTTP request में पांच changes भेजता है और पूरा response केवल चार का फैसला करता है, तो पूरे batch को एक retry value देना सुरक्षित नहीं है। Parent transport result और पांच child outcomes दर्ज करें। केवल उस child को retry करें जिसके records और remote contract अनुमति देते हैं, या remote API changes को atomic बनाती हो तो पूरे batch को reconcile करें।

SSH से भेजी shell script पर भी यही नियम लागू होता है। एक exit status script process को cover करता है, जरूरी नहीं कि उसके हर side effect को। Callers को action-level retry decisions चाहिए तो हर operation को remote identifier और result record दें, stdout से progress का अनुमान न लगाएं।

पूरा remote result execution का फैसला करता है

HTTP actions को एक gateway दें
Agents credentials रखे बिना bearer, basic या custom-header calls Sallyport से route करते हैं।

Authoritative और पूरी तरह framed response uncertainty को ज्ञात result में बदलता है। HTTP के लिए final status, चुने हुए nonsecret headers, पूरा body या body digest और framing completion दर्ज करें। SSH के लिए command acceptance, stdout और stderr completion status, उपलब्ध exit status या exit signal और channel closure दर्ज करें।

RFC 9112 client से मांग करता है कि connection जल्दी बंद होने या chunked decoding विफल होने पर HTTP response को incomplete record करे। Secret broker में यह नियम और कड़ाई से लागू होना चाहिए: partial body को complete remote result न दिखाएं, भले ही उसके पहले bytes सही JSON जैसे लगें। Partial content केवल साफ तौर पर marked diagnostic field में लौटाएं, या sensitive data होने की संभावना पर उसे discard करें।

HTTP status अकेला यह तय नहीं करता कि application action दोहराना सुरक्षित है। पूरा 401 साबित करता है कि server ने उस request के credentials अस्वीकार किए, इसलिए ब्रोकर result को rejected कर सकता है; उन्हीं credentials से retry बेकार है। पूरा 429 या 503 backoff की अनुमति तब दे सकता है जब method सुरक्षित रूप से repeat हो और response सही timing दे। पूरा 500 ज्ञात है, पर application उसे बनाने से पहले state बदल चुका हो सकता है। हर 5xx को POST replay की अनुमति न बनाएं।

RFC 9110 idempotency को कई समान requests के intended effect से परिभाषित करता है और communication failure के बाद idempotent methods की automatic retry की अनुमति देता है। जरूरी qualification "known to be idempotent" है। Method names evidence हैं, जादू नहीं। Deployment trigger करने वाला गलत design का GET method token के बावजूद unsafe है; ठीक implemented PUT state बदलते हुए भी repeatable हो सकता है।

Informational HTTP responses action का फैसला नहीं करते। 100 Continue client को request body भेजने देता है; यह final application result के बारे में कुछ नहीं कहता। दूसरे 1xx responses भी invocation को in progress रखते हैं। केवल पूरा final response, या ऐसा मजबूत application receipt जिसका contract ब्रोकर समझता है, outcome को unknown से बाहर ला सकता है।

Response completeness और authenticity साथ चाहिए। गलत TLS identity, untrusted SSH host या unexpected proxy से आया perfectly framed response intended target के बारे में authoritative evidence नहीं है। Identity verification आम तौर पर connection setup में पूरा होता है, लेकिन resumed sessions और connection pools में verified peer identity को action record से बांधना फिर भी जरूरी है।

RFC 9209 उस intermediary के लिए http_response_incomplete परिभाषित करता है जिसे next hop से partial response मिला। उसका recommended 502 HTTP compatibility के लिए उपयोगी है, लेकिन अकेला 502 outcome evidence खो देता है। किसी mapped status code के साथ ब्रोकर का structured outcome=unknown रखें।

SSH में वैसा ही जाल है। RFC 4254 server को exit-status लौटाने की recommendation देता है, requirement नहीं। अगर channel stdout के बाद exit status या exit signal के बिना बंद हो, तो ब्रोकर stream का अंत जानता है, command की सफलता नहीं। REMOTE_RESULT_INCOMPLETE लौटाएं और unknown चुनें, जब तक action contract कोई दूसरा authoritative completion marker न दे।

Result delivery दोबारा execution नहीं करनी चाहिए

Result delivery केवल तब शुरू होती है जब ब्रोकर settled remote result को durably store कर चुका हो। Caller के लिए serialization fail हो, MCP pipe बंद हो या caller process exit करे, remote operation फिर unknown नहीं बनता। phase=result_delivery report करें, outcome=committed या rejected बनाए रखें और retry=fetch_result रखें।

इस phase को invocation identifier चाहिए जिससे caller stored result पा सके। दोहराया action submission result retrieval नहीं है। दोनों operations अलग रखें ताकि generic client library broken response pipe को गलती से दूसरी remote call न बना दे।

Writes का क्रम मायने रखता है:

  1. Remote response पूरा और validate करें।
  2. Settled outcome और result digest durable record में append करें।
  3. Invocation identifier के नीचे retrievable result commit करें।
  4. Result caller तक पहुंचाएं।

Step 4 विफल हो तो steps 2 और 3 घटना का प्रमाण देते हैं। ब्रोकर पहले deliver और बाद में journal करे तो crash caller को success दे सकता है जबकि audit record unknown कहे। भले ही तुरंत retry न हो, यह audit defect है।

Large या streaming results पर भी यही नियम लागू है। Chunks को sequence numbers और final completeness marker के साथ store करें। Caller last verified chunk से delivery resume कर सकता है, लेकिन terminator, declared length या protocol-specific closure मिलने से पहले ब्रोकर result को complete न कहे।

Caller acknowledgement retention के लिए उपयोगी है, remote outcome के लिए नहीं। Caller-facing protocol से complete handoff की पुष्टि के बाद ही RESULT_DELIVERED mark करें। Acknowledgement न हो तो retention policy खत्म होने तक result उपलब्ध रखें और repeated reads को reads ही मानें। ब्रोकर ने result retain न करने का निर्णय लिया हो तो उसे फिर बनाने के लिए action दोबारा न चलाएं।

Remote completion के बाद result storage fail हो सकता है। ब्रोकर के memory में पूरा response हो लेकिन वह commit न कर पाए, तो वह plain unknown से ज्यादा जानता है, फिर भी evidence crash के बाद नहीं बचेगा। phase=result_delivery लौटाएं, outcome=committed केवल तब दें जब durability contract current record से उस claim की अनुमति देता हो, और तुरंत operator attention मांगें। सही fix reserved capacity और failure-tested storage है, remote action की retry नहीं।

Idempotency एक remote contract है

Agent runs और calls अलग रखें
Sessions और Activity journals agent run तथा उसकी हर requested action को सुरक्षित रखते हैं।

Idempotency key unknown outcome को तभी retryable बनाती है जब remote service उस key को एक logical operation से बांधने का वादा करे। ब्रोकर में UUID बनाकर log करने से अपने आप कुछ नहीं होता। Remote endpoint को key accept करनी चाहिए, request fingerprint compare करना चाहिए, पहले settled result को पर्याप्त समय रखना चाहिए और repeat होने पर वही result लौटाना चाहिए।

ब्रोकर dispatch से पहले चार facts store करे: idempotency key, request fingerprint, remote scope और service द्वारा प्रकाशित expiry या retention information। Retry में वही key और identical fingerprint इस्तेमाल हों। बदली body के साथ key reuse होने पर local IDEMPOTENCY_MISMATCH होना चाहिए।

ऐसे endpoints पर idempotency header चुपचाप न जोड़ें जिनका इसके लिए declared semantics नहीं है। कुछ services unknown headers ignore करती हैं। दूसरी keys को account या route के भीतर scope करती हैं। Retry policy को configured और reviewed remote contract knowledge चाहिए, header name पर आशा नहीं।

Retention windows भी contract का हिस्सा हैं। Service एक दिन बाद keys भूलती है तो उसके बाद retry नया effect बना सकती है, जबकि ब्रोकर को वही operation दिखे। Earliest safe expiry store करें, उससे पहले automatic retries रोकें और बाद में reconcile करें। Service retention guarantee न दे तो key को केवल conservative configured window में उपयोगी मानें।

Concurrency सही single-threaded design को भी हरा सकती है। दो workers एक unknown record पढ़कर उसी key से retry करने का निर्णय ले सकते हैं। सही remote deduplication contract उन्हें collapse कर देगा, फिर भी ब्रोकर invocation पर lease ले, retry generation record करे और केवल एक active attempt चलने दे। इससे load घटता है और journal समझने लायक रहता है।

unknown से केवल तीन safe paths हैं:

  • ऐसा operation दोहराएं जिसकी semantics idempotent मानी जाती है।
  • Verified remote deduplication contract के तहत उसी idempotency key से retry करें।
  • Stable operation identifier से remote state query करके reconcile करें, फिर तय करें कि नया action चाहिए या नहीं।

बाकी हर स्थिति review के लिए रुकती है। Agent के इंतजार में यह बहुत conservative लग सकता है, लेकिन duplicate side effects की कीमत visible pause से ज्यादा है।

Conditional HTTP requests contract मजबूत कर सकते हैं। Known entity tag के साथ If-Match resource बदल जाने पर update को fail कर सकता है, जबकि If-None-Match: * उसी target पर दूसरा resource बनने से रोक सकता है। Endpoint का resource model अभी भी मायने रखता है, इसलिए ये हर duplicate नहीं रोकते, लेकिन client guesswork की जगह server-enforced evidence देते हैं।

SSH commands में protocol-level idempotency key बहुत कम मिलती है। Repeatability को command के application contract में रखें: unique release identifier के तहत deployment बनाएं, atomic compare के साथ write करें, या intended state confirm करने वाली query चलाएं। Shell command ने output नहीं दिया, इसलिए उसे safe न मानें।

Error envelope में evidence होना चाहिए

हर remote call दर्ज करें
Activity journal हर call दर्ज करता है और uncertain operations के लिए durable audit trail रखता है।

Caller को stable machine contract और छोटा human message चाहिए। Transport-library exceptions को internal diagnostic field में रखें क्योंकि उनके names platforms पर बदलते हैं और implementation detail खोलते हैं। Public envelope ऐसा दिखना चाहिए:

{"invocation_id":"act_01J...","error":{"code":"REMOTE_OUTCOME_UNKNOWN","phase":"remote_execution","outcome":"unknown","retry":"same_idempotency_key","message":"Connection closed before a complete response was recorded."},"evidence":{"dispatch_started":true,"request_complete":true,"response_headers_received":false,"response_complete":false,"idempotency":{"key":"req_01J...","scope":"payments.create","fingerprint":"sha256:8b1...","remote_contract":"configured"}}}

code, phase, outcome और retry को closed enums रखें। नए evidence fields जोड़ें, पुराने का अर्थ न बदलें। Callers enums पर branch कर सकते हैं और message किसी व्यक्ति को दिखा सकते हैं। उन्हें message parse नहीं करना चाहिए।

Evidence बताए कि ब्रोकर निष्कर्ष कैसे जानता है, निष्कर्ष को दोहराए नहीं। उपयोगी fields में attempt number, connection identifier, dispatch journal sequence, request fingerprint, protocol completion marker, remote request identifier, response digest, exit status और result-record identifier शामिल हैं। Secrets, full authorization headers, private keys और unfiltered remote bodies छोड़ दें।

Transitions को append-only events की तरह persist करें, फिर current status project करें:

ACTION_ACCEPTED
PREFLIGHT_VALIDATED
CREDENTIAL_AUTHORIZED
DISPATCH_STARTED
REQUEST_SENT
REMOTE_RESPONSE_STARTED
REMOTE_RESPONSE_COMPLETE
RESULT_COMMITTED
RESULT_DELIVERED

PREFLIGHT_VALIDATED के बाद समाप्त action provably unattempted है। DISPATCH_STARTED के बाद लेकिन authoritative completion से पहले समाप्त action unknown रहता है। RESULT_COMMITTED वाला action failed delivery के बाद दूसरी remote execution के बिना बच सकता है।

Projection impossible regressions reject करे। Late evidence unknown को rejected या committed बना सकता है, लेकिन committed को not_attempted नहीं। दूसरा observer reconciliation result जोड़ सकता है, पर original attempt को इस तरह rewrite नहीं कर सकता जैसे dispatch कभी हुआ ही नहीं।

Wall-clock order पर निर्भर रहने के बजाय monotonic sequence numbers record करें। Clocks बदल सकती हैं और concurrent components के events देर से आ सकते हैं। Timestamps operators को systems correlate करने में मदद करते हैं, लेकिन journal sequence durable transitions का क्रम तय करता है। Imported remote evidence में source और local sequence जोड़ें, उसे history के बीच न घुसाएं।

Evidence को confidence source भी बताना चाहिए। transport_observed, remote_response, remote_query और operator_attested बाद के code को बताते हैं कि outcome क्यों बदला। Remote system देखने के बाद operator किसी unknown attempt को सही रूप से settle कर सकता है, लेकिन उस fact को original connection में ब्रोकर को मिले response के रूप में न दिखाएं।

Audit integrity और outcome evidence अलग समस्याएं हल करते हैं। Hash chain साबित कर सकती है कि recorded events बाद में नहीं बदले, पर यह साबित नहीं कर सकती कि ब्रोकर ने हर event record किया या remote application ने request मानी। Sallyport का write-blind encrypted, hash-chained audit log और अलग session तथा activity views इन transitions को रखने की durable जगह देते हैं, जबकि action result को यहां बताया phase और outcome contract फिर भी चाहिए।

Retry code साधारण और testable होना चाहिए

Retry engine evidence से पहले ही निकाली गई disposition को consume करे। वह rate limits और attempt caps जोड़ सकता है, पर किसी exception के temporary दिखने के कारण unsafe disposition को upgrade नहीं कर सकता।

decide(record, operation):
  if record.retry == "fetch_result":
    return FETCH(record.invocation_id)

  if record.outcome == "not_attempted":
    if record.retry == "backoff":
      return RETRY_NEW_ATTEMPT
    return STOP_FOR_CORRECTION

  if record.outcome == "unknown":
    if operation.idempotent:
      return RETRY_NEW_ATTEMPT
    if record.retry == "same_idempotency_key" and
       operation.fingerprint == record.fingerprint:
      return RETRY_SAME_KEY
    return RECONCILE

  if record.outcome == "rejected" and record.retry == "backoff":
    return RETRY_WHEN_ALLOWED

  return RETURN_RECORDED_RESULT

Exception classes नहीं, transitions test करें। Credential access से पहले, TLS के दौरान, first request write से पहले, complete request write के बाद, response headers के बीच, framed body के बीच, result commit के बाद और caller delivery के दौरान failure inject करें। Durable transitions की हर जोड़ी के बीच ब्रोकर kill करें और verify करें कि recovery DISPATCH_STARTED के बाद कभी not_attempted claim न करे।

Adversarial remote behaviors भी जोड़ें। Server side effect apply करके response के बिना close करे। Commit के बाद 500 लौटाए। Idempotency key को honor करे, ignore करे और changed payload के साथ reused key reject करे। SSH के लिए exec accept करने के बाद close करे, exit-status छोड़े और broken output stream से पहले exit status भेजे। Expected outcome हर बार evidence के अनुसार होना चाहिए।

Metrics में phase और outcome अलग count हों। connection_setup/not_attempted का spike routing, certificates या authentication की ओर इशारा करता है। remote_execution/unknown का spike reconciliation मांगता है और remote reliability problem दिखा सकता है। उन्हें "broker failures" में मिलाने से operational cause और duplicate-action risk, दोनों छिपते हैं।

Friendly SDK को contract मिटाने न दें। उसे exceptions throw करनी हों तो पूरा envelope attach करे और automatic retry केवल backoff या same_idempotency_key के लिए opt in हो। unknown/reconcile action दोहराने के लिए caller को साफ तौर पर unsafe code लिखना पड़े।

Recovery tests में competing workers और stale leases शामिल करें। एक worker को retry lease मिलने के बाद रोकें, lease expire होने दें और दूसरा शुरू करें। पहला resume हो तो generation check उसे dispatch से पहले रोके। उस check के बिना पूरी तरह सही outcome classification भी concurrent duplicates बना सकती है।

Retry budgets को action record में रखें, process-local counters में नहीं। Restart attempt count या remote key expiry reset न करे। Budget खत्म हो तो last evidence लौटाएं और reconciliation मांगें; error code को generic "max retries exceeded" बनाने से सुरक्षित diagnosis खो जाएगा।

सबसे कठिन state का असहज रहना सही है। Record कहता है कि dispatch शुरू हुआ और authoritative completion नहीं मिला, तो ब्रोकर remote outcome नहीं जानता। उस fact को बचाएं, उसे reconcile करें और missing evidence को permission में बदलने से इनकार करें।

सामान्य प्रश्न

क्या timeout साबित कर सकता है कि remote action नहीं चला?

Timeout केवल deadline खत्म होना साबित करता है। यह preflight failure तभी है जब durable records दिखाएं कि dispatch शुरू नहीं हुआ; dispatch के बाद दूसरे evidence से फैसला होने तक outcome unknown रखें।

क्या broker को हर failed GET request retry करनी चाहिए?

नहीं। RFC 9110 intended semantics के अनुसार GET को idempotent मानता है, लेकिन खराब design का endpoint side effects कर सकता है। Retry तभी करें जब endpoint contract और broker evidence दोनों उसका समर्थन करें।

क्या HTTP 500 response को सुरक्षित रूप से retry किया जा सकता है?

केवल status code के आधार पर नहीं। पूरा 500 ज्ञात remote result है, लेकिन application इसे बनाने से पहले change commit कर सकती है, इसलिए retry को method semantics या remote deduplication से सही ठहराना होगा।

क्या idempotency key हमेशा duplicate actions रोकती है?

नहीं। Remote service को key पहचाननी, उसे request fingerprint से बांधना और repeat पर stored result लौटाना होगा। केवल broker log में मौजूद key correlation देती है, deduplication नहीं।

Unknown remote outcome पर सबसे सुरक्षित response क्या है?

पहले stable operation identifier से remote state query करें। केवल तब repeat करें जब reconciliation कोई effect न दिखाए, operation idempotent हो, या verified remote contract उसी key और fingerprint को स्वीकार करे।

Connection setup को remote execution से अलग क्यों रखें?

यह split बताता है कि application request peer तक पहुंचा हो सकता है या नहीं। नए connection में TLS failure no attempt साबित कर सकती है, जबकि reused connection में write के बाद reset outcome को unknown छोड़ सकता है।

Result लौटाते समय failure को broker कैसे report करे?

इसे phase=result_delivery के रूप में record करें और settled remote outcome बनाए रखें। Caller action फिर submit करने के बजाय invocation identifier से stored result fetch करे।

क्या SSH channel close सफल command completion माना जा सकता है?

अपने आप नहीं। RFC 4254 exit status की recommendation देता है, requirement नहीं, इसलिए exit status, exit signal या application completion marker के बिना close result को unknown छोड़ सकता है।

Secret broker error में कौन सा evidence होना चाहिए?

Invocation identifier, phase, outcome, retry disposition, dispatch state, request fingerprint, completion markers और उपलब्ध result record identifier शामिल करें। Credentials और unfiltered sensitive response data शामिल न करें।

Team कैसे test करे कि retries सुरक्षित हैं?

हर durable transition पर broker kill करें और dispatch से पहले, writes में, response framing में, result commit के बाद और delivery में failures inject करें। Assert करें कि कोई recovered record dispatched से वापस unattempted न जाए।

Sallyport

Sallyport आपके AI एजेंट के लिए API कॉल और SSH कमांड चलाता है। कुंजियाँ आपके Mac पर एक लोकल वॉल्ट में रहती हैं; आप हर रन को स्वीकृत करते हैं और हर क्रिया एक सीलबंद जर्नल में दर्ज होती है।

© 2026 Sallyport · Apache-2.0 के तहत ओपन सोर्स · Oleg Sotnikov