# Agent gateway के बारे में force quit tests क्या साबित करते हैं?

जिस gateway में AI agent के credentials रखे हों, उसे उस सबसे खराब क्षण में भी सुरक्षित रहना चाहिए जब उसका process अचानक गायब हो जाए। Happy-path test यह दिखा सकता है कि approval दिखाई देती है, request सफल होती है और बाद में audit record मौजूद रहता है। लेकिन इससे यह पता नहीं चलता कि interrupted request के दौरान credential बाहर निकला या नहीं, या audit trail ऐसे काम के बारे में गलत दावा कर रहा है जो कभी पूरा नहीं हुआ।

Force quit tests «request started», «credential was attached», «remote system received it» और «record is durable» जैसे दावों के बीच का अंतर दिखाते हैं। ये अलग-अलग तथ्य हैं। इन्हें एक ही event मानना ऐसा action gateway बनाने का तरीका है जो crash के बाद सामान्य retry को duplicate deployment या बिना trace वाली outbound call में बदल सकता है।

macOS gateway के लिए normal quit और process death को अलग रखें। Apple normal app termination को ऐसा lifecycle path बताता है जिसमें ऐप state save कर सकता है और termination handling चला सकता है। Forced termination में यह अवसर मिलना ज़रूरी नहीं। Apple SIGKILL को भी उस termination type के रूप में पहचानता है जो user द्वारा app force quit करने पर हो सकती है। आपकी test plan को मानकर चलना चाहिए कि cleanup handlers, deferred writes और best-effort telemetry नहीं चलेंगे।

सही सवाल «क्या app restart हो जाता है?» नहीं है। सही सवाल यह है: हर interruption point पर क्या आप स्पष्ट बता सकते हैं कि कौन से side effects संभव हैं, कौन से असंभव हैं और कौन सा evidence बचा रहता है?

## Force quit को cleanup से होकर गुजरना चाहिए

Force quit test तभी valid है जब वह gateway को व्यवस्थित cleanup करने का अवसर छीन ले। अगर test किसी विनम्र shutdown function को call करता है, queues के खाली होने का इंतज़ार करता है, logs flush करता है और फिर exit होता है, तो आपने orderly termination की जाँच की है। वह भी महत्वपूर्ण है, लेकिन उससे वह failure case नहीं मिलता जो security engineers को चिंतित रखता है।

दो termination modes इस्तेमाल करें और उन्हें सही नाम दें:

- Normal terminate request cancellation handling, orderly file closure और interrupted session को UI में दिखाने की जाँच करती है।
- Hard kill उस समय memory, file buffers, open sockets और in-flight work की स्थिति जाँचता है जब application cleanup नहीं चलता।

macOS पर test harness पहले मामले के लिए `kill -TERM` और दूसरे के लिए `kill -KILL` इस्तेमाल कर सकता है। `SIGTERM` process को termination संभालने का अवसर देता है। `SIGKILL` नहीं देता। Result table में दोनों को «force quit» न कहें, क्योंकि वे अलग सवालों के जवाब देते हैं।

```sh
# Find the gateway process you started for the test.
pgrep -fl Sallyport

# Graceful termination case.
kill -TERM 48192

# Abrupt termination case.
kill -KILL 48192

# Confirm the process is gone.
ps -p 48192
# PID TTY           TIME CMD
# 48192 ttys003    0:00.42 gateway-test
# After SIGKILL, ps should print no process row.
```

Process ID test identifier नहीं है। हर test run को run ID, action ID और remote request ID दें। इन IDs को fixture records और gateway records दोनों में रखें। Shared identifiers के बिना आप timestamps मिलाते रहेंगे और अनुमान लगाएँगे कि remote POST उसी run का था जिसे आपने kill किया था या नहीं।

Production endpoint पर test न करें, भले ही आपको call harmless लगे। इस काम का उद्देश्य जानबूझकर ambiguous outcomes बनाना है। ऐसा receiver बनाएँ जो body स्वीकार करने से पहले, headers पढ़ने के बाद, body record करने के बाद या response छोड़ने से पहले रुक सके। वास्तविक third-party API ये boundaries भरोसेमंद ढंग से नहीं देगी और वह ऐसे retries या caching लागू कर सकती है जिनकी आपने माँग नहीं की।

## Request में स्पष्ट interruption points चाहिए

जब तक implementation यह परिभाषित न करे कि «during credential injection» का अर्थ क्या है, आप process को उस चरण में kill नहीं कर सकते। Action pipeline के आसपास observable hold points जोड़ें। ये test controls हैं, policy language या production authorization mechanism नहीं।

HTTP action के लिए क्रम इस तरह रखें:

1. Gateway authorized action description स्वीकार करता है और action ID देता है।
2. अगर design में network work से पहले intent record ज़रूरी है, तो वह लिखता है।
3. Protected process के भीतर credential resolve करता है।
4. Outbound request बनाता है और credential inject करता है।
5. Connection खोलता है और request लिखता है।
6. Remote outcome को classify करने लायक response प्राप्त करता है।
7. Completion या unknown-outcome record लिखता है, फिर agent को result देता है।

हर numbered operation के बाद boundary पर pause लगाएँ। Pause अलग process से दिखाई देना चाहिए। Test-only local socket, named pipe या harness द्वारा नियंत्रित file descriptor काम करेगा। Sleep call अच्छा विकल्प नहीं है, क्योंकि timing drift boundary test को race में बदल देती है।

एक छोटा fixture protocol ऐसा हो सकता है:

```json
{
  "action_id": "fq-2026-07-22-017",
  "hold_after": "request_headers_built",
  "release": false
}
```

Gateway headers बनाने के बाद, किसी connection attempt से पहले रुकता है। Harness उस स्थिति की पुष्टि करता है, process को kill करता है और receiver से पूछता है कि क्या उसने connection देखा। अपेक्षित परिणाम zero inbound requests और zero credential-bearing headers है। अगर receiver ने इस चरण पर request देखी, तो आपका checkpoint बहुत देर से है या किसी background task ने सीमा पार की है और उसे हिसाब में नहीं लिया गया।

Checkpoints को संकीर्ण रखें। «Before network I/O» बहुत व्यापक है, अगर DNS resolution, connection creation, TLS negotiation और body transmission अलग-अलग tasks में चलते हों। हर library call के भीतर checkpoint की ज़रूरत नहीं, लेकिन इतना ढाँचा ज़रूर चाहिए कि आप बता सकें remote server credentials या state-changing body प्राप्त कर सकता था या नहीं।

SSH में भी यही विचार लागू होता है, केवल evidence अलग होगा। `sp-ssh` के credential-backed connection request पाने से पहले, helper शुरू होने के बाद लेकिन authentication से पहले, authentication के बाद लेकिन command शुरू होने से पहले और remote command लौटने के बाद लेकिन local completion commit होने से पहले hold लगाएँ। Disposable test host पर remote command start और exit दर्ज करें। Open TCP connection देखकर command execution का अनुमान न लगाएँ।

## Injection से पहले kill करने पर कोई remote trace नहीं रहना चाहिए

सबसे शुरुआती failure case का अपेक्षित परिणाम सबसे साफ़ है: अगर gateway credential attach करने या transport शुरू करने से पहले बंद हो जाता है, तो किसी external service को उस action के बारे में कुछ नहीं दिखना चाहिए।

यह स्पष्ट लगता है, लेकिन implementations अक्सर preparation और dispatch को मिला देती हैं। कोई client library उस समय connection शुरू कर सकती है जब दूसरा task header fetch या format कर रहा हो। Retry wrapper intended journal record लिखने वाले code से पहले outbound request allocate कर सकता है। Metrics callback ऐसा action description लिख सकता है जिसमें URL query parameter हो और जिसे आपका सामान्य redaction path कभी न देखे।

चार pre-dispatch cuts जाँचें:

- Authorization के बाद, किसी credential lookup से पहले।
- Credential lookup के बाद, request construction से पहले।
- Request construction के बाद, socket connection शुरू होने से पहले।
- Connection setup शुरू होने के बाद, लेकिन credential-bearing bytes process से बाहर जाने से पहले।

हर cut का assertion अलग होगा। Credential lookup से पहले memory-safe diagnostics और logs में उन values या placeholders की अनुपस्थिति जाँचें जिन्हें बाद में substitute किया जा सकता है। Lookup के बाद वही outputs जाँचें और सुनिश्चित करें कि secret process boundary से बाहर नहीं गया। Connection से पहले receiver के पास कोई connection record नहीं होना चाहिए। Connection setup के दौरान receiver failed handshake या connection attempt देख सकता है, लेकिन उसे authorization header, basic-auth field, custom credential header या SSH authentication attempt नहीं मिलना चाहिए।

यह अंतर महत्वपूर्ण है, क्योंकि connection attempt authenticated action नहीं है। अगर remote edge ने attempted connection दर्ज की है तो «no action occurred» न लिखें। सीमित और सही बात लिखें: कोई credential नहीं भेजा गया और कोई application request प्राप्त नहीं हुई। Incident review में operators को बाद में जिस evidence की ज़रूरत पड़ सकती है, उसे smooth करके security records कम उपयोगी न बनाएँ।

Sallyport का design इस boundary को सीधे जाँचने लायक बनाता है: agent को plaintext credential कभी नहीं रखना चाहिए, जबकि app credential-backed HTTP या SSH action चलाकर agent को result लौटाती है। Test केवल इसलिए पूरा नहीं हो जाता कि agent के पास secret नहीं है। यह तब पूरा होता है जब killed gateway भी आधी तैयार action को secret-bearing outbound request में न बदल सके।

## Injection स्थानीय घटना है, dispatch का प्रमाण नहीं

Credential injection वह जगह है जहाँ teams सबसे नुकसानदेह bookkeeping mistake करती हैं। Code header बनाने या identity को SSH library को देने पर वे «credential used» log कर देती हैं। इससे पता चलता है कि gateway authentication की तैयारी कर चुका था। इससे यह साबित नहीं होता कि किसी peer ने credential प्राप्त किया।

कम से कम इन चार local states को अलग रखें:

| State | आप ईमानदारी से क्या कह सकते हैं | आप क्या नहीं कह सकते |
|---|---|---|
| Credential resolved | Protected process ने इस action के लिए credential पढ़ा | Peer ने उसे प्राप्त किया |
| Request assembled | Process ने memory में credential-bearing request बनाई | Connection खुला |
| Transport write started | Process ने bytes भेजने का प्रयास किया | Remote application ने उन्हें process किया |
| Remote outcome observed | Process को remote side से evidence मिला | Completion record durable है |

Credential resolve होने के बाद और transport write शुरू होने से पहले request object बन जाने पर एक-एक kill चलाएँ। दोनों मामलों में agent को कोई secret नहीं मिलना चाहिए और remote fixture को कोई credential-bearing request दर्ज नहीं करनी चाहिए। Action journal में interrupted preparation state या कोई persisted record नहीं होना चाहिए, यह आपके durability boundary पर निर्भर है। Journal को साफ़ दिखाने के लिए completed action न बनाएँ।

फिर उस पहले बिंदु पर kill करें जहाँ transport library write कर सकती है। यह असुविधाजनक test है। Local process ने write function call किया हो सकता है, लेकिन kernel, proxy, TLS layer या remote service को पूरी request मिली हो या नहीं, यह स्पष्ट नहीं होगा। जब तक receiver के पास request मिलने या न मिलने का positive evidence न हो, आपका expected result `outcome=unknown` होना चाहिए।

इस test को आसान बनाने के लिए full request headers log न करें। इससे secret का दूसरा रास्ता बन जाएगा। इसके बजाय fixture प्राप्त credential value से एक nonreversible test marker बनाए और केवल वह marker store करे। Gateway credential reference या internal credential identifier store कर सकता है। Test के दौरान credential print किए बिना identifiers और markers मिलाएँ।

एक उपयोगी fixture record इस रूप में हो सकता है:

```json
{
  "action_id": "fq-2026-07-22-017",
  "connection_seen": true,
  "headers_complete": true,
  "credential_marker": "sha256:7b8c...",
  "body_complete": false,
  "response_sent": false
}
```

ऐसा test-only credential लें जिसका fixture के बाहर कोई value न हो। फिर भी उसे screenshots, shell history और सामान्य logs से दूर रखें। Disposable secret भी secret-जैसी वस्तु है और test की आदतें production code तक पहुँच सकती हैं।

## Network I/O एक ईमानदार unknown state बनाता है

जैसे ही state-changing request machine से बाहर जा सकती है, local certainty उस बिंदु पर समाप्त हो जाती है जहाँ remote system ने शायद अभी जवाब न दिया हो। यही स्थिति retry behavior, UI wording और audit semantics को प्रभावित करनी चाहिए।

मान लें कोई POST deployment service से release शुरू करने को कहती है। Gateway पूरी request लिखता है। Service deployment job को persist करती है। Response gateway तक पहुँचने से पहले आप gateway process को kill कर देते हैं। Restart के बाद तीन वास्तविकताएँ संभव हैं:

1. Service को request कभी मिली ही नहीं।
2. Service को request मिली, लेकिन उसने उसे reject कर दिया।
3. Service ने request स्वीकार कर deployment बना दिया।

Local gateway को शायद पता न हो कि इनमें से कौन सी स्थिति हुई। Case three में `failed` record गलत है। Cases one और two में `succeeded` record गलत है। इसे `unknown` दर्ज करें और action ID, remote request ID, destination, method तथा वह बिंदु सुरक्षित रखें जहाँ local observation रुक गई।

यहीं blind retries नुकसान करती हैं। Transient network failure आम है और सफल demonstrations smooth दिखती हैं, इसलिए teams automatic retry पसंद करती हैं। GET के लिए retry आम तौर पर स्वीकार्य है, अगर उससे server-side accounting या rate-limit की समस्या न हो। POST, PATCH, remote command या write-capable API call के लिए retry में idempotency mechanism या बाद में reconciliation ज़रूरी है।

जहाँ remote system की idempotency सुविधा हो, उसका इस्तेमाल करें। पूरे agent session में reuse होने वाले token के बजाय action-specific idempotency token दें। अगर remote API इसे support नहीं करती, तो ऐसा remote lookup path बनाएँ जो retry से पहले बता सके कि action ID स्वीकार की गई थी या नहीं। अगर इनमें से कुछ भी उपलब्ध नहीं है, तो निर्णय किसी human से लें। Automatic recovery से यह कम elegant है, लेकिन double execution से कहीं सुरक्षित है।

Receiver के साथ यह sequence ठीक इसी तरह जाँचें:

```text
Gateway writes intent record
Gateway sends POST with action ID fq-2026-07-22-017
Receiver stores action ID and body
Receiver delays its HTTP response
Harness kills gateway with SIGKILL
Gateway restarts
Agent requests retry
Gateway checks receiver for action ID before another POST
```

परिणाम में एक stored remote action और एक local record होना चाहिए, जिसका outcome interrupted या unknown हो और जो बाद में remote state से reconcile हो। अगर reconciliation check से पहले दूसरा POST दिखाई देता है, तो test ने retry bug पकड़ लिया है। अगर journal uncertainty मिटाकर केवल clean success record छोड़ देता है, तो यह audit bug है।

TCP close events को इस बात का प्रमाण न मानें कि remote application ने काम नहीं किया। Peer द्वारा request application code को सौंपने के बाद भी socket बंद हो सकता है। Receiver का durable action record ही महत्वपूर्ण evidence है।

## Audit commit के लिए स्पष्ट durability promise चाहिए

Audit log केवल hash chain होने से trustworthy नहीं बन जाता। Hash chain surviving sequence में alteration या removal दिखा सकती है। Process खत्म होने से पहले durable storage तक न पहुँची event को वह वापस नहीं ला सकती।

Promise को एक वाक्य में लिखें। उदाहरण के लिए: «Gateway state-changing external action शुरू करने से पहले action intent को durably record करता है; outcome देखने के बाद उसे agent को लौटाने से पहले उस outcome को durably record करता है।» फिर timestamp वाले in-memory object को record मानने के बजाय «begins» और «durably» दोनों verbs की जाँच करें।

POSIX `fsync()` को file data को associated storage तक पहुँचाने के request के रूप में परिभाषित करता है और कहता है कि action पूरी होने या error report होने तक यह return नहीं करता। Standard यह भी चेतावनी देता है कि storage guarantees implementation और configuration पर निर्भर करती हैं। यह warning call छोड़ने का बहाना नहीं है। इसका अर्थ है कि software क्या promise कर सकता है, उसे सटीक रखें और अपने नियंत्रण में आने वाले recovery behavior की जाँच करें।

Hash-chained encrypted log के लिए कम से कम ये cuts जाँचें:

- Proposed entry लिखने के बाद, durability barrier से पहले kill।
- Durability barrier के बाद, in-memory index update से पहले kill।
- Completion entry durable होने के बाद, agent को response मिलने से पहले kill।
- Compaction, rotation या किसी journal projection process के दौरान kill।

हर restart के बाद offline verification command चलाएँ और raw audit sequence तथा user-facing projections दोनों देखें। Sallyport encrypted hash-chained audit log की जाँच के लिए `sp audit verify` उपलब्ध कराता है और इसके लिए vault key की आवश्यकता नहीं होती। यह इस test set में उपयोगी है, लेकिन verification कई assertions में से केवल एक होना चाहिए, पूरा test नहीं।

Expected results में बारीकी ज़रूरी है। Durability boundary से पहले kill होने पर record का गायब होना स्वीकार्य हो सकता है, अगर कोई external action शुरू नहीं हुई थी। Gateway ने state-changing request शुरू कर दी हो और फिर record गायब हो, तो यह स्वीकार्य नहीं है, अगर design write-ahead intent का promise करता है। Unknown result वाला record अक्सर सही होता है। Remote response observe होने से पहले completion का दावा करने वाला record गलत है, भले ही test आम तौर पर सफल होता हो।

Recovery देखते समय agent-run history और action-call history को अलग रखें। एक session अचानक समाप्त हो सकता है, जबकि कई actions के परिणाम अलग-अलग हों। Run-level «terminated» entry उस request के per-call record की जगह नहीं लेती जो remote system तक पहुँची थी, न ही उस request की जो memory से बाहर नहीं गई।

## Approval state अपने subject से आगे नहीं बचनी चाहिए

Restart durability जितना ही authorization state की भी जाँच करता है। अगर gateway किसी session के लिए किसी खास agent process को authorize करता है, तो gateway को kill करने पर वह authorization ऐसे नए process के लिए reusable permit नहीं बननी चाहिए जो केवल मिलती-जुलती request करता हो।

सबसे सुरक्षित सरल नियम यह है कि session approval observed process identity से जुड़ी हो और run के साथ समाप्त हो। Restart के बाद नए agent process को नई session authorization चाहिए। Gateway restart के दौरान जीवित रहे current process को भी नई decision चाहिए, जब तक gateway exact identity binding फिर से स्थापित न कर सके और product इस behavior को स्पष्ट रूप से document न करता हो। Command name, working directory या friendly process label जैसे loose cache record से approval restore न करें।

Restart boundary को दो ऐसे agents के साथ जाँचें जो command line से समान दिखते हों, लेकिन signing authority या executable origin में अलग हों। पहले को approve करें। Action के दौरान gateway रोकें। उसे restart करें। फिर दूसरे agent से वही action करवाएँ। Gateway को पहले agent की session inherit करने के बजाय नई approval decision दिखानी चाहिए।

Per-call approval का test अलग है। Approval card दिखाई देते समय gateway को kill करें, फिर restart करके action दोहराएँ। पुराना UI event नई call को authorize नहीं करना चाहिए। User के approve करने के बाद लेकिन credential resolution से पहले kill करें और पुष्टि करें कि नया process पुरानी approval reuse नहीं कर सकता। ये tests उस आम गलती को पकड़ते हैं जिसमें action ID, specific session और expiry condition से बाँधे बिना केवल boolean «approved» persist कर दिया जाता है।

Sallyport के fixed controls इस test को स्पष्ट रूप देते हैं। Locked vault में इसका vault gate actions को deny करता है, session authorization नए agent process से जुड़ी होती है और चुने हुए credentials के लिए हर उपयोग पर approval आवश्यक हो सकती है। Force quit testing को साबित करना चाहिए कि UI, vault state और action pipeline अलग-अलग समय पर restart होने पर भी ये boundaries बनी रहती हैं।

## Suite चलाने से पहले result matrix बनाएँ

जो test केवल «stage X पर kill» कहता है, उसमें interpretation की बहुत गुंजाइश रहती है। Interruption points की rows और observable outcomes के columns वाली matrix बनाएँ। Test code लिखने से पहले expected result की समीक्षा करें। अगर team इस बात पर सहमत नहीं हो सकती कि क्या होना चाहिए, तो implementation में failure contract अभी परिभाषित नहीं है।

इन columns का इस्तेमाल करें:

| Cut point | क्या remote connection देख सकता है? | क्या remote credentials देख सकता है? | अनुमत local action status | Recovery के बाद agent result | आवश्यक evidence |
|---|---:|---:|---|---|---|
| Credential lookup से पहले | नहीं | नहीं | Not started या interrupted | New approval या retry path | केवल gateway trace |
| Credential resolution के बाद | नहीं | नहीं | Interrupted preparation | New approval या retry path | केवल gateway trace |
| Request write के दौरान | हाँ | संभवतः | Unknown | Retry से पहले reconcile करें | Receiver record और local record |
| Remote acceptance के बाद | हाँ | हाँ | Observed या reconciled होने तक unknown | Retry से पहले reconcile करें | Remote durable record |
| Durable completion के बाद | हाँ | हाँ | Completed | Cached या reconciled result लौटाएँ | Verified audit record |

«Possibly» और «unknown» कमजोरी नहीं हैं। ये distributed work का ईमानदार वर्णन हैं। खतरनाक शब्द «failed» है, जब gateway के पास यह evidence नहीं कि remote service ने काम करने में असफलता पाई।

हर row को बार-बार चलाएँ, लेकिन repetition को control का विकल्प न बनाएँ। सौ random kills journal append और durability barrier के बीच की रेखा चूक सकते हैं। Hold point पर एक controlled kill यह स्पष्ट कर सकता है कि वह सीमा कैसे काम करती है। इसके बाद races, scheduling mistakes और checkpoint के अनजाने स्थान बदलने को पकड़ने के लिए दोहराएँ।

हर run में दोनों ओर से artifacts लें: harness event timeline, remote fixture की durable action list, process exit information, gateway recovery diagnostics, audit verification output और agent-visible output। Filename में run ID रखें। Test fail होने पर दोबारा चलाने से पहले evidence सुरक्षित करें। दूसरी run अक्सर एकमात्र उपयोगी clue मिटा देती है।

## Discrepancies को test noise नहीं, design work मानें

जब force quit test gateway journal और remote fixture के बीच असहमति दिखाए, तो assertion को pass कराने के लिए तुरंत patch न करें। आम तौर पर यही असहमति test की खोज होती है।

अगर fixture accepted request बताता है लेकिन gateway में intent नहीं है, तो durable intent boundary को पहले ले जाएँ या उस boundary से पहले action रोकें। अगर gateway completion बताता है लेकिन fixture में action नहीं है, तो पता लगाएँ कि gateway ने local write को remote result समझ लिया था या नहीं। अगर restarted agent fresh approval के बिना action कर सकता है, तो लंबा timeout जोड़ने के बजाय identity binding ठीक करें।

सबसे अच्छा परिणाम green tests से भरा dashboard नहीं है। सबसे अच्छा परिणाम ऐसा failure contract है जिसे operator दबाव में इस्तेमाल कर सके: यह action निश्चित रूप से machine से बाहर नहीं गई; यह action remote system तक पहुँच सकती है और reconciliation चाहिए; यह action पूरी हुई और इसका record verification के बाद भी बचा है। Crash के बाद निर्णय को defensible बनाने वाली categories यही हैं।

Credential plumbing, transport libraries, journaling, retry behavior, process supervision या approval handling बदलने पर यह suite चलाएँ। Gateway तब भरोसा कमाता है जब उसे अपनी सफाई देने का कोई अवसर नहीं मिलता और फिर भी वह predictable ढंग से व्यवहार करता है।
