@private.me/xbind 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +55 -14
  2. package/dist-standalone/_deps/mldsa-wasm/dist/mldsa.js +1920 -1
  3. package/dist-standalone/_deps/shared/cjs/errors.js +729 -1
  4. package/dist-standalone/_deps/shared/cjs/index.js +463 -1
  5. package/dist-standalone/_deps/shared/cjs/types.js +315 -1
  6. package/dist-standalone/_deps/shared/errors.js +244 -1
  7. package/dist-standalone/_deps/shared/index.js +72 -1
  8. package/dist-standalone/_deps/shared/types.js +86 -1
  9. package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
  10. package/dist-standalone/_deps/ux-helpers/cjs/index.js +1 -1
  11. package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -1
  12. package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -1
  13. package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -1
  14. package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -1
  15. package/dist-standalone/_deps/ux-helpers/errors.js +1 -1
  16. package/dist-standalone/_deps/ux-helpers/index.js +1 -1
  17. package/dist-standalone/_deps/ux-helpers/pagination.js +1 -1
  18. package/dist-standalone/_deps/ux-helpers/progress.js +1 -1
  19. package/dist-standalone/_deps/ux-helpers/search.js +1 -1
  20. package/dist-standalone/_deps/xchange/auto-accept.js +1 -1
  21. package/dist-standalone/_deps/xchange/cjs/auto-accept.js +1 -1
  22. package/dist-standalone/_deps/xchange/cjs/errors.js +1 -1
  23. package/dist-standalone/_deps/xchange/cjs/index.js +1 -1
  24. package/dist-standalone/_deps/xchange/cjs/invite-client.js +1 -1
  25. package/dist-standalone/_deps/xchange/cjs/lazy-init.js +1 -1
  26. package/dist-standalone/_deps/xchange/cjs/trust-integration.js +1 -1
  27. package/dist-standalone/_deps/xchange/cjs/xchange.js +1 -1
  28. package/dist-standalone/_deps/xchange/errors.js +1 -1
  29. package/dist-standalone/_deps/xchange/index.js +1 -1
  30. package/dist-standalone/_deps/xchange/invite-client.js +1 -1
  31. package/dist-standalone/_deps/xchange/lazy-init.js +1 -1
  32. package/dist-standalone/_deps/xchange/trust-integration.js +1 -1
  33. package/dist-standalone/_deps/xchange/xchange.js +1 -1
  34. package/dist-standalone/_deps/xregistry/cjs/discovery.js +1 -1
  35. package/dist-standalone/_deps/xregistry/cjs/errors.js +1 -1
  36. package/dist-standalone/_deps/xregistry/cjs/index.js +1 -1
  37. package/dist-standalone/_deps/xregistry/cjs/registry.js +1 -1
  38. package/dist-standalone/_deps/xregistry/cjs/schema.js +1 -1
  39. package/dist-standalone/_deps/xregistry/cjs/types.js +1 -1
  40. package/dist-standalone/_deps/xregistry/discovery.js +1 -1
  41. package/dist-standalone/_deps/xregistry/errors.js +1 -1
  42. package/dist-standalone/_deps/xregistry/index.js +1 -1
  43. package/dist-standalone/_deps/xregistry/registry.js +1 -1
  44. package/dist-standalone/_deps/xregistry/schema.js +1 -1
  45. package/dist-standalone/_deps/xregistry/types.js +1 -1
  46. package/dist-standalone/agent-call.js +659 -1
  47. package/dist-standalone/agent-sdk.js +328 -1
  48. package/dist-standalone/agent.js +1800 -1
  49. package/dist-standalone/approval.js +193 -1
  50. package/dist-standalone/async-iterators.js +382 -1
  51. package/dist-standalone/auth.js +219 -1
  52. package/dist-standalone/auto-accept.js +229 -1
  53. package/dist-standalone/backup-config.js +201 -1
  54. package/dist-standalone/backup.js +326 -1
  55. package/dist-standalone/batch-operations.js +388 -1
  56. package/dist-standalone/cancellation.js +477 -1
  57. package/dist-standalone/checkpoint.js +186 -1
  58. package/dist-standalone/circuit-breaker.js +468 -1
  59. package/dist-standalone/cjs/agent-call.js +701 -1
  60. package/dist-standalone/cjs/agent-sdk.js +332 -1
  61. package/dist-standalone/cjs/agent.js +1837 -1
  62. package/dist-standalone/cjs/approval.js +199 -1
  63. package/dist-standalone/cjs/async-iterators.js +392 -1
  64. package/dist-standalone/cjs/auth.js +225 -1
  65. package/dist-standalone/cjs/auto-accept.js +233 -1
  66. package/dist-standalone/cjs/backup-config.js +207 -1
  67. package/dist-standalone/cjs/backup.js +330 -1
  68. package/dist-standalone/cjs/batch-operations.js +397 -1
  69. package/dist-standalone/cjs/cancellation.js +490 -1
  70. package/dist-standalone/cjs/checkpoint.js +193 -1
  71. package/dist-standalone/cjs/circuit-breaker.js +476 -1
  72. package/dist-standalone/cjs/cli/init.js +492 -1
  73. package/dist-standalone/cjs/config-validation.js +522 -1
  74. package/dist-standalone/cjs/connect.js +312 -1
  75. package/dist-standalone/cjs/connection-pool.js +506 -1
  76. package/dist-standalone/cjs/correlation-id.js +339 -1
  77. package/dist-standalone/cjs/crypto-utils.js +176 -1
  78. package/dist-standalone/cjs/debug-mode.js +534 -1
  79. package/dist-standalone/cjs/did-document.js +101 -1
  80. package/dist-standalone/cjs/did-privateme.js +130 -1
  81. package/dist-standalone/cjs/did-web.js +201 -1
  82. package/dist-standalone/cjs/discovery.js +462 -1
  83. package/dist-standalone/cjs/dual-mode.js +251 -1
  84. package/dist-standalone/cjs/email-templates.js +313 -1
  85. package/dist-standalone/cjs/email-transport.js +239 -1
  86. package/dist-standalone/cjs/envelope.js +538 -1
  87. package/dist-standalone/cjs/errors.js +913 -1
  88. package/dist-standalone/cjs/event-emitter.js +461 -1
  89. package/dist-standalone/cjs/gateway-state.js +55 -1
  90. package/dist-standalone/cjs/gateway-transport.js +120 -1
  91. package/dist-standalone/cjs/graceful-degradation.js +403 -1
  92. package/dist-standalone/cjs/guardrails.js +223 -1
  93. package/dist-standalone/cjs/health-check.js +336 -1
  94. package/dist-standalone/cjs/http-compat.js +272 -1
  95. package/dist-standalone/cjs/http-status-map.js +571 -1
  96. package/dist-standalone/cjs/identity.js +645 -1
  97. package/dist-standalone/cjs/index.js +406 -1
  98. package/dist-standalone/cjs/invitation.js +421 -1
  99. package/dist-standalone/cjs/invite.js +328 -1
  100. package/dist-standalone/cjs/key-agreement.js +335 -1
  101. package/dist-standalone/cjs/lazy-init.js +300 -1
  102. package/dist-standalone/cjs/logger.js +291 -1
  103. package/dist-standalone/cjs/mdns-discovery.js +202 -1
  104. package/dist-standalone/cjs/nonce-store.js +80 -1
  105. package/dist-standalone/cjs/pairing-manager.js +223 -1
  106. package/dist-standalone/cjs/plugin-system.js +264 -1
  107. package/dist-standalone/cjs/plugins/logging.js +168 -1
  108. package/dist-standalone/cjs/plugins/metrics.js +181 -1
  109. package/dist-standalone/cjs/plugins/validation.js +302 -1
  110. package/dist-standalone/cjs/policy.js +320 -1
  111. package/dist-standalone/cjs/progress-callbacks.js +583 -1
  112. package/dist-standalone/cjs/redis-nonce-store.js +76 -1
  113. package/dist-standalone/cjs/registry-middleware.js +50 -1
  114. package/dist-standalone/cjs/retry-strategies.js +544 -1
  115. package/dist-standalone/cjs/retry-transport.js +102 -1
  116. package/dist-standalone/cjs/runtime/browser.js +533 -1
  117. package/dist-standalone/cjs/runtime/edge.js +526 -1
  118. package/dist-standalone/cjs/runtime/react-native.js +394 -1
  119. package/dist-standalone/cjs/security-policy.js +245 -1
  120. package/dist-standalone/cjs/serialization.js +1040 -1
  121. package/dist-standalone/cjs/split-channel.js +225 -1
  122. package/dist-standalone/cjs/subscription-proof.js +230 -1
  123. package/dist-standalone/cjs/succession.js +148 -1
  124. package/dist-standalone/cjs/timeouts.js +412 -1
  125. package/dist-standalone/cjs/trace-context.js +424 -1
  126. package/dist-standalone/cjs/trace-spans.js +495 -1
  127. package/dist-standalone/cjs/transport.js +63 -1
  128. package/dist-standalone/cjs/trust-registry.js +991 -1
  129. package/dist-standalone/cjs/types/error-response.js +56 -1
  130. package/dist-standalone/cjs/vault-auth.js +178 -1
  131. package/dist-standalone/cjs/vault-store-loader.js +194 -1
  132. package/dist-standalone/cjs/verify.js +25 -1
  133. package/dist-standalone/cjs/version-info.js +543 -1
  134. package/dist-standalone/cjs/xfetch.js +340 -1
  135. package/dist-standalone/cli/init.js +455 -1
  136. package/dist-standalone/cli/setup.js +514 -1
  137. package/dist-standalone/cli/types.js +27 -1
  138. package/dist-standalone/cli/xbind.js +148 -1
  139. package/dist-standalone/config-validation.js +513 -1
  140. package/dist-standalone/connect.js +274 -1
  141. package/dist-standalone/connection-pool.js +500 -1
  142. package/dist-standalone/correlation-id.js +326 -1
  143. package/dist-standalone/crypto-utils.js +157 -1
  144. package/dist-standalone/debug-mode.js +510 -1
  145. package/dist-standalone/did-document.js +96 -1
  146. package/dist-standalone/did-privateme.js +121 -1
  147. package/dist-standalone/did-web.js +196 -1
  148. package/dist-standalone/discovery.js +458 -1
  149. package/dist-standalone/dual-mode.js +247 -1
  150. package/dist-standalone/email-templates.js +309 -1
  151. package/dist-standalone/email-transport.js +232 -1
  152. package/dist-standalone/envelope.js +525 -1
  153. package/dist-standalone/errors.js +896 -1
  154. package/dist-standalone/event-emitter.js +456 -1
  155. package/dist-standalone/gateway-state.js +51 -1
  156. package/dist-standalone/gateway-transport.js +116 -1
  157. package/dist-standalone/graceful-degradation.js +396 -1
  158. package/dist-standalone/guardrails.js +216 -1
  159. package/dist-standalone/health-check.js +332 -1
  160. package/dist-standalone/http-compat.js +267 -1
  161. package/dist-standalone/http-status-map.js +561 -1
  162. package/dist-standalone/identity.js +619 -1
  163. package/dist-standalone/index.js +78 -1
  164. package/dist-standalone/invitation.js +415 -1
  165. package/dist-standalone/invite.js +324 -1
  166. package/dist-standalone/key-agreement.js +325 -1
  167. package/dist-standalone/lazy-init.js +295 -1
  168. package/dist-standalone/logger.js +285 -1
  169. package/dist-standalone/mdns-discovery.js +195 -1
  170. package/dist-standalone/nonce-store.js +76 -1
  171. package/dist-standalone/pairing-manager.js +219 -1
  172. package/dist-standalone/plugin-system.js +257 -1
  173. package/dist-standalone/plugins/logging.js +163 -1
  174. package/dist-standalone/plugins/metrics.js +176 -1
  175. package/dist-standalone/plugins/validation.js +297 -1
  176. package/dist-standalone/policy.js +315 -1
  177. package/dist-standalone/progress-callbacks.js +576 -1
  178. package/dist-standalone/redis-nonce-store.js +72 -1
  179. package/dist-standalone/registry-middleware.js +47 -1
  180. package/dist-standalone/retry-strategies.js +534 -1
  181. package/dist-standalone/retry-transport.js +98 -1
  182. package/dist-standalone/runtime/browser.js +516 -1
  183. package/dist-standalone/runtime/edge.js +511 -1
  184. package/dist-standalone/runtime/react-native.js +383 -1
  185. package/dist-standalone/security-policy.js +239 -1
  186. package/dist-standalone/serialization.js +1031 -1
  187. package/dist-standalone/split-channel.js +219 -1
  188. package/dist-standalone/subscription-proof.js +224 -1
  189. package/dist-standalone/succession.js +142 -1
  190. package/dist-standalone/timeouts.js +398 -1
  191. package/dist-standalone/trace-context.js +414 -1
  192. package/dist-standalone/trace-spans.js +488 -1
  193. package/dist-standalone/transport.js +59 -1
  194. package/dist-standalone/trust-registry.js +950 -1
  195. package/dist-standalone/types/error-response.js +52 -1
  196. package/dist-standalone/vault-auth.js +174 -1
  197. package/dist-standalone/vault-store-loader.js +187 -1
  198. package/dist-standalone/verify.js +16 -1
  199. package/dist-standalone/version-info.js +530 -1
  200. package/dist-standalone/xfetch.js +335 -1
  201. package/package.json +4 -13
  202. package/share1.dat +0 -0
  203. package/dist-standalone/_deps/mldsa-wasm/LICENSE +0 -24
  204. package/dist-standalone/_deps/mldsa-wasm/package.json +0 -46
  205. package/dist-standalone/_deps/shared/cjs/package.json +0 -1
  206. package/dist-standalone/_deps/ux-helpers/cjs/package.json +0 -1
  207. package/dist-standalone/_deps/xchange/cjs/package.json +0 -1
  208. package/dist-standalone/_deps/xregistry/cjs/package.json +0 -1
  209. package/dist-standalone/cjs/package.json +0 -3
  210. package/dist-standalone/package.json +0 -10
@@ -1 +1,396 @@
1
- import{ok,err}from"./_deps/shared/index.js";import{createDetailedError}from"./_deps/ux-helpers/index.js";const DEFAULT_CACHE_CONFIG={defaultTTL:3e5,maxSize:1e3,allowStale:!0,maxStaleMs:36e5},DEFAULT_RETRY_CONFIG={maxAttempts:3,initialDelayMs:1e3,backoffMultiplier:2,maxDelayMs:3e4,jitter:!0};export class GracefulDegradationManager{cacheConfig;retryConfig;cache=new Map;serviceStatus=new Map;constructor(e,t){this.cacheConfig={...DEFAULT_CACHE_CONFIG,...e},this.retryConfig={...DEFAULT_RETRY_CONFIG,...t}}getCached(e,t){const r=this.cache.get(e);if(!r)return;const a=Date.now()-r.cachedAt,i=a<=r.ttl,s=a>r.ttl&&a<=this.cacheConfig.maxStaleMs;return"CRITICAL"===t||"HIGH"===t?i?r.value:void 0:this.cacheConfig.allowStale&&(i||s)?(r.usageCount++,r.value):i?r.value:void 0}setCached(e,t,r){if(this.cache.size>=this.cacheConfig.maxSize){const e=this.cache.keys().next().value;e&&this.cache.delete(e)}this.cache.set(e,{value:t,cachedAt:Date.now(),ttl:r??this.cacheConfig.defaultTTL,usageCount:0})}invalidate(e){this.cache.delete(e)}clearCache(){this.cache.clear()}getCacheStats(){const e=Date.now(),t=Array.from(this.cache.entries()).map(([t,r])=>({key:t,age:e-r.cachedAt,usageCount:r.usageCount}));return{size:this.cache.size,maxSize:this.cacheConfig.maxSize,entries:t}}recordSuccess(e){const t=this.serviceStatus.get(e)||{health:"HEALTHY",lastSuccess:0,failureCount:0};t.health="HEALTHY",t.lastSuccess=Date.now(),t.failureCount=0,t.lastError=void 0,this.serviceStatus.set(e,t)}recordFailure(e,t){const r=this.serviceStatus.get(e)||{health:"HEALTHY",lastSuccess:0,failureCount:0};r.failureCount++,r.lastError=t,r.failureCount>=5?r.health="UNAVAILABLE":r.failureCount>=2&&(r.health="DEGRADED"),this.serviceStatus.set(e,r)}getServiceHealth(e){return this.serviceStatus.get(e)?.health??"HEALTHY"}getServiceStatus(e){return this.serviceStatus.get(e)}resetServiceHealth(e){e?this.serviceStatus.delete(e):this.serviceStatus.clear()}async withRetry(e,t){const r=this.getMaxAttempts(t.qos);let a;for(let t=1;t<=r;t++){const i=await e();if(i.ok)return i;if(a=i.error,t<r){const e=this.calculateBackoff(t);await this.sleep(e)}}return err(a)}getMaxAttempts(e){switch(e){case"CRITICAL":case"LOW":return 1;case"HIGH":return this.retryConfig.maxAttempts;case"NORMAL":return Math.max(1,this.retryConfig.maxAttempts-1)}}calculateBackoff(e){const t=this.retryConfig.initialDelayMs*Math.pow(this.retryConfig.backoffMultiplier,e-1),r=Math.min(t,this.retryConfig.maxDelayMs);if(!this.retryConfig.jitter)return r;const a=new Uint8Array(4);crypto.getRandomValues(a);const i=.75+.5*(new DataView(a.buffer).getUint32(0)/4294967295);return Math.floor(r*i)}sleep(e){return new Promise(t=>setTimeout(t,e))}}export async function registryLookupWithFallback(e,t,r,a){const i=`registry:${t}`;if("LOW"===r.qos){const e=a.getCached(i,r.qos);if(e)return ok(e)}const s=await a.withRetry(()=>e.getEntry(t),r);if(s.ok){a.recordSuccess("registry");const e={publicKey:Buffer.from(s.value.publicKey).toString("base64")};return a.setCached(i,e),ok(e)}if(a.recordFailure("registry",String(s.error)),"HIGH"===r.qos||"NORMAL"===r.qos){const e=a.getCached(i,r.qos);if(e)return ok(e)}return err(createDetailedError("REGISTRY_UNAVAILABLE",`Failed to resolve DID: ${t}`,{hint:"UNAVAILABLE"===a.getServiceHealth("registry")?"Registry service is currently unavailable. Using cached data may help.":"Registry lookup failed after retries. Check network connectivity.",suggested_action:"CRITICAL"===r.qos?"Wait for registry service to recover before retrying critical operations.":"Lower QoS level to NORMAL or LOW to use cached data.",docs:"https://private.me/docs/xbind/registry-fallback",severity:"CRITICAL"===r.qos?"critical":"error"}))}export async function sendWithTransportFallback(e,t,r,a,i){if(0===e.length)return err(createDetailedError("NO_TRANSPORT","No transport adapters configured",{hint:"Configure at least one transport adapter for envelope delivery.",suggested_action:"Add HttpsTransportAdapter or custom transport to agent configuration.",docs:"https://private.me/docs/xbind/transport",severity:"critical"}));const s=[];for(let o=0;o<e.length;o++){const c=e[o];if(!c)continue;const n=await i.withRetry(()=>c.send(t,r),a);if(n.ok)return i.recordSuccess(`transport:${o}`),ok(void 0);s.push({transport:o,error:n.error}),i.recordFailure(`transport:${o}`,n.error)}const o=s[0]?.error??"SEND_FAILED";return err(createDetailedError("TRANSPORT_EXHAUSTED",`All transport adapters failed: ${s.map(e=>e.error).join(", ")}`,{hint:"NETWORK_ERROR"===o?"Network connectivity issue detected. Check internet connection.":"TIMEOUT"===o?"Transport timeout occurred. Recipient may be unreachable or overloaded.":"RECIPIENT_UNREACHABLE"===o?"Recipient DID not found or offline.":"Transport layer failure. Check transport configuration.",suggested_action:"Verify recipient endpoint and network connectivity. Consider increasing timeout or adding fallback transports.",docs:"https://private.me/docs/xbind/transport-fallback",severity:"CRITICAL"===a.qos?"critical":"error"}))}export function enhanceError(e,t,r){const a=r.getServiceHealth(t.type.split(":")[0]??"unknown"),i="DEGRADED"===a?" Service is experiencing degraded performance.":"UNAVAILABLE"===a?" Service is currently unavailable.":"",s="CRITICAL"===t.qos?" Consider retrying when service recovers.":"HIGH"===t.qos?" Cached data may be used if available.":" Using cached data regardless of staleness.";return{...e,hint:(e.hint??"")+i+s}}
1
+ /**
2
+ * @module graceful-degradation
3
+ * Enhanced Reliability Capabilities for xBind
4
+ *
5
+ * Provides graceful degradation mechanisms to maintain service continuity
6
+ * when external services (registry, gateway, transport) experience issues.
7
+ *
8
+ * Features:
9
+ * - Quality-of-Service (QoS) tiers for operation prioritization
10
+ * - Fallback mechanisms with exponential backoff
11
+ * - Intelligent caching with TTL
12
+ * - User-friendly error messages with actionable recovery hints
13
+ */
14
+ import { ok, err } from"./_deps/shared/index.js";
15
+ import { createDetailedError } from"./_deps/ux-helpers/index.js";
16
+ const DEFAULT_CACHE_CONFIG = {
17
+ defaultTTL: 300_000, // 5 minutes
18
+ maxSize: 1000,
19
+ allowStale: true,
20
+ maxStaleMs: 3600_000, // 1 hour
21
+ };
22
+ const DEFAULT_RETRY_CONFIG = {
23
+ maxAttempts: 3,
24
+ initialDelayMs: 1000,
25
+ backoffMultiplier: 2,
26
+ maxDelayMs: 30_000,
27
+ jitter: true,
28
+ };
29
+ /**
30
+ * Graceful Degradation Manager
31
+ *
32
+ * Coordinates fallback mechanisms, caching, and retry logic across
33
+ * registry, gateway, and transport layers.
34
+ */
35
+ export class GracefulDegradationManager {
36
+ cacheConfig;
37
+ retryConfig;
38
+ cache = new Map();
39
+ serviceStatus = new Map();
40
+ constructor(cacheConfig, retryConfig) {
41
+ this.cacheConfig = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig };
42
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
43
+ }
44
+ /* ── Cache Operations ── */
45
+ /**
46
+ * Get cached value if available and valid for the given QoS level.
47
+ *
48
+ * @param key Cache key
49
+ * @param qos Quality of service level
50
+ * @returns Cached value or undefined
51
+ */
52
+ getCached(key, qos) {
53
+ const entry = this.cache.get(key);
54
+ if (!entry)
55
+ return undefined;
56
+ const now = Date.now();
57
+ const age = now - entry.cachedAt;
58
+ const isFresh = age <= entry.ttl;
59
+ const isStale = age > entry.ttl && age <= this.cacheConfig.maxStaleMs;
60
+ // CRITICAL/HIGH: Only use fresh cache
61
+ if (qos === 'CRITICAL' || qos === 'HIGH') {
62
+ return isFresh ? entry.value : undefined;
63
+ }
64
+ // NORMAL/LOW: Use stale cache if allowed
65
+ if (this.cacheConfig.allowStale && (isFresh || isStale)) {
66
+ entry.usageCount++;
67
+ return entry.value;
68
+ }
69
+ return isFresh ? entry.value : undefined;
70
+ }
71
+ /**
72
+ * Store value in cache with TTL.
73
+ *
74
+ * @param key Cache key
75
+ * @param value Value to cache
76
+ * @param ttl Time-to-live in milliseconds (uses default if omitted)
77
+ */
78
+ setCached(key, value, ttl) {
79
+ // Evict oldest entry if cache is full
80
+ if (this.cache.size >= this.cacheConfig.maxSize) {
81
+ const oldestKey = this.cache.keys().next().value;
82
+ if (oldestKey)
83
+ this.cache.delete(oldestKey);
84
+ }
85
+ this.cache.set(key, {
86
+ value,
87
+ cachedAt: Date.now(),
88
+ ttl: ttl ?? this.cacheConfig.defaultTTL,
89
+ usageCount: 0,
90
+ });
91
+ }
92
+ /**
93
+ * Invalidate cached entry.
94
+ *
95
+ * @param key Cache key
96
+ */
97
+ invalidate(key) {
98
+ this.cache.delete(key);
99
+ }
100
+ /**
101
+ * Clear all cached entries.
102
+ */
103
+ clearCache() {
104
+ this.cache.clear();
105
+ }
106
+ /**
107
+ * Get cache statistics.
108
+ */
109
+ getCacheStats() {
110
+ const now = Date.now();
111
+ const entries = Array.from(this.cache.entries()).map(([key, entry]) => ({
112
+ key,
113
+ age: now - entry.cachedAt,
114
+ usageCount: entry.usageCount,
115
+ }));
116
+ return {
117
+ size: this.cache.size,
118
+ maxSize: this.cacheConfig.maxSize,
119
+ entries,
120
+ };
121
+ }
122
+ /* ── Service Health Tracking ── */
123
+ /**
124
+ * Record successful operation for a service.
125
+ *
126
+ * @param serviceName Service identifier (e.g., 'registry', 'gateway', 'transport')
127
+ */
128
+ recordSuccess(serviceName) {
129
+ const existing = this.serviceStatus.get(serviceName);
130
+ const status = existing || {
131
+ health: 'HEALTHY',
132
+ lastSuccess: 0,
133
+ failureCount: 0,
134
+ };
135
+ status.health = 'HEALTHY';
136
+ status.lastSuccess = Date.now();
137
+ status.failureCount = 0;
138
+ status.lastError = undefined;
139
+ this.serviceStatus.set(serviceName, status);
140
+ }
141
+ /**
142
+ * Record failed operation for a service.
143
+ *
144
+ * @param serviceName Service identifier
145
+ * @param error Error message
146
+ */
147
+ recordFailure(serviceName, error) {
148
+ const existing = this.serviceStatus.get(serviceName);
149
+ const status = existing || {
150
+ health: 'HEALTHY',
151
+ lastSuccess: 0,
152
+ failureCount: 0,
153
+ };
154
+ status.failureCount++;
155
+ status.lastError = error;
156
+ // Update health based on consecutive failures
157
+ if (status.failureCount >= 5) {
158
+ status.health = 'UNAVAILABLE';
159
+ }
160
+ else if (status.failureCount >= 2) {
161
+ status.health = 'DEGRADED';
162
+ }
163
+ this.serviceStatus.set(serviceName, status);
164
+ }
165
+ /**
166
+ * Get service health status.
167
+ *
168
+ * @param serviceName Service identifier
169
+ * @returns Service health status
170
+ */
171
+ getServiceHealth(serviceName) {
172
+ return this.serviceStatus.get(serviceName)?.health ?? 'HEALTHY';
173
+ }
174
+ /**
175
+ * Get detailed service status.
176
+ *
177
+ * @param serviceName Service identifier
178
+ * @returns Service status or undefined if not tracked
179
+ */
180
+ getServiceStatus(serviceName) {
181
+ return this.serviceStatus.get(serviceName);
182
+ }
183
+ /**
184
+ * Reset service health tracking.
185
+ *
186
+ * @param serviceName Service identifier (all services if omitted)
187
+ */
188
+ resetServiceHealth(serviceName) {
189
+ if (serviceName) {
190
+ this.serviceStatus.delete(serviceName);
191
+ }
192
+ else {
193
+ this.serviceStatus.clear();
194
+ }
195
+ }
196
+ /* ── Retry Logic ── */
197
+ /**
198
+ * Execute operation with retry logic based on QoS level.
199
+ *
200
+ * @param operation Async operation to execute
201
+ * @param context Operation context with QoS level
202
+ * @returns Result of operation
203
+ */
204
+ async withRetry(operation, context) {
205
+ const maxAttempts = this.getMaxAttempts(context.qos);
206
+ let lastError;
207
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
208
+ const result = await operation();
209
+ if (result.ok) {
210
+ return result;
211
+ }
212
+ lastError = result.error;
213
+ // Don't retry on last attempt
214
+ if (attempt < maxAttempts) {
215
+ const delay = this.calculateBackoff(attempt);
216
+ await this.sleep(delay);
217
+ }
218
+ }
219
+ // All retries exhausted
220
+ return err(lastError);
221
+ }
222
+ /**
223
+ * Get maximum retry attempts for QoS level.
224
+ */
225
+ getMaxAttempts(qos) {
226
+ switch (qos) {
227
+ case 'CRITICAL':
228
+ return 1; // Fail fast
229
+ case 'HIGH':
230
+ return this.retryConfig.maxAttempts;
231
+ case 'NORMAL':
232
+ return Math.max(1, this.retryConfig.maxAttempts - 1);
233
+ case 'LOW':
234
+ return 1; // No retries
235
+ }
236
+ }
237
+ /**
238
+ * Calculate exponential backoff delay with optional jitter.
239
+ */
240
+ calculateBackoff(attempt) {
241
+ const base = this.retryConfig.initialDelayMs * Math.pow(this.retryConfig.backoffMultiplier, attempt - 1);
242
+ const capped = Math.min(base, this.retryConfig.maxDelayMs);
243
+ if (!this.retryConfig.jitter) {
244
+ return capped;
245
+ }
246
+ // Add ±25% jitter using cryptographically secure random
247
+ const randomBytes = new Uint8Array(4);
248
+ crypto.getRandomValues(randomBytes);
249
+ const randomValue = new DataView(randomBytes.buffer).getUint32(0) / 0xffffffff;
250
+ const jitterFactor = 0.75 + randomValue * 0.5;
251
+ return Math.floor(capped * jitterFactor);
252
+ }
253
+ /**
254
+ * Sleep for specified milliseconds.
255
+ */
256
+ sleep(ms) {
257
+ return new Promise((resolve) => setTimeout(resolve, ms));
258
+ }
259
+ }
260
+ /* ── Registry Fallback ── */
261
+ /**
262
+ * Registry lookup with graceful degradation.
263
+ *
264
+ * Provides intelligent fallback for DID resolution:
265
+ * 1. Try primary registry lookup
266
+ * 2. On failure, check cache based on QoS level
267
+ * 3. Return user-friendly error if all fallbacks exhausted
268
+ *
269
+ * @param registry Trust registry instance
270
+ * @param did DID to resolve
271
+ * @param context Operation context
272
+ * @param manager Degradation manager
273
+ * @returns Registry lookup result
274
+ */
275
+ export async function registryLookupWithFallback(registry, did, context, manager) {
276
+ const cacheKey = `registry:${did}`;
277
+ // Check cache first for LOW QoS
278
+ if (context.qos === 'LOW') {
279
+ const cached = manager.getCached(cacheKey, context.qos);
280
+ if (cached) {
281
+ return ok(cached);
282
+ }
283
+ }
284
+ // Attempt registry lookup with retry
285
+ const lookupResult = await manager.withRetry(() => registry.getEntry(did), context);
286
+ if (lookupResult.ok) {
287
+ manager.recordSuccess('registry');
288
+ // Convert Uint8Array publicKey to base64 string for caching
289
+ const publicKeyStr = Buffer.from(lookupResult.value.publicKey).toString('base64');
290
+ const entryValue = { publicKey: publicKeyStr };
291
+ manager.setCached(cacheKey, entryValue);
292
+ return ok(entryValue);
293
+ }
294
+ // Lookup failed - record failure
295
+ manager.recordFailure('registry', String(lookupResult.error));
296
+ // Try cache fallback for HIGH/NORMAL QoS
297
+ if (context.qos === 'HIGH' || context.qos === 'NORMAL') {
298
+ const cached = manager.getCached(cacheKey, context.qos);
299
+ if (cached) {
300
+ return ok(cached);
301
+ }
302
+ }
303
+ // All fallbacks exhausted - return detailed error
304
+ return err(createDetailedError('REGISTRY_UNAVAILABLE', `Failed to resolve DID: ${did}`, {
305
+ hint: manager.getServiceHealth('registry') === 'UNAVAILABLE'
306
+ ? 'Registry service is currently unavailable. Using cached data may help.'
307
+ : 'Registry lookup failed after retries. Check network connectivity.',
308
+ suggested_action: context.qos === 'CRITICAL'
309
+ ? 'Wait for registry service to recover before retrying critical operations.'
310
+ : 'Lower QoS level to NORMAL or LOW to use cached data.',
311
+ docs: 'https://private.me/docs/xbind/registry-fallback',
312
+ severity: context.qos === 'CRITICAL' ? 'critical' : 'error',
313
+ }));
314
+ }
315
+ /* ── Transport Fallback ── */
316
+ /**
317
+ * Send envelope with transport fallback.
318
+ *
319
+ * Tries multiple transports in order until one succeeds:
320
+ * 1. Primary transport with retry
321
+ * 2. Fallback transports (if configured)
322
+ * 3. Return detailed error if all transports fail
323
+ *
324
+ * @param transports Array of transport adapters
325
+ * @param envelope Envelope to send
326
+ * @param recipientDid Recipient DID
327
+ * @param context Operation context
328
+ * @param manager Degradation manager
329
+ * @returns Send result
330
+ */
331
+ export async function sendWithTransportFallback(transports, envelope, recipientDid, context, manager) {
332
+ if (transports.length === 0) {
333
+ return err(createDetailedError('NO_TRANSPORT', 'No transport adapters configured', {
334
+ hint: 'Configure at least one transport adapter for envelope delivery.',
335
+ suggested_action: 'Add HttpsTransportAdapter or custom transport to agent configuration.',
336
+ docs: 'https://private.me/docs/xbind/transport',
337
+ severity: 'critical',
338
+ }));
339
+ }
340
+ const errors = [];
341
+ for (let i = 0; i < transports.length; i++) {
342
+ const transport = transports[i];
343
+ if (!transport)
344
+ continue;
345
+ const sendResult = await manager.withRetry(() => transport.send(envelope, recipientDid), context);
346
+ if (sendResult.ok) {
347
+ manager.recordSuccess(`transport:${i}`);
348
+ return ok(undefined);
349
+ }
350
+ // Record failure and try next transport
351
+ errors.push({ transport: i, error: sendResult.error });
352
+ manager.recordFailure(`transport:${i}`, sendResult.error);
353
+ }
354
+ // All transports failed
355
+ const primaryError = errors[0]?.error ?? 'SEND_FAILED';
356
+ return err(createDetailedError('TRANSPORT_EXHAUSTED', `All transport adapters failed: ${errors.map(e => e.error).join(', ')}`, {
357
+ hint: primaryError === 'NETWORK_ERROR'
358
+ ? 'Network connectivity issue detected. Check internet connection.'
359
+ : primaryError === 'TIMEOUT'
360
+ ? 'Transport timeout occurred. Recipient may be unreachable or overloaded.'
361
+ : primaryError === 'RECIPIENT_UNREACHABLE'
362
+ ? 'Recipient DID not found or offline.'
363
+ : 'Transport layer failure. Check transport configuration.',
364
+ suggested_action: 'Verify recipient endpoint and network connectivity. Consider increasing timeout or adding fallback transports.',
365
+ docs: 'https://private.me/docs/xbind/transport-fallback',
366
+ severity: context.qos === 'CRITICAL' ? 'critical' : 'error',
367
+ }));
368
+ }
369
+ /* ── User-Friendly Error Messages ── */
370
+ /**
371
+ * Enhance error with QoS-appropriate messaging.
372
+ *
373
+ * @param error Original error
374
+ * @param context Operation context
375
+ * @param manager Degradation manager
376
+ * @returns Enhanced error with actionable hints
377
+ */
378
+ export function enhanceError(error, context, manager) {
379
+ const serviceHealth = manager.getServiceHealth(context.type.split(':')[0] ?? 'unknown');
380
+ // Add service health context to hint
381
+ const healthHint = serviceHealth === 'DEGRADED'
382
+ ? ' Service is experiencing degraded performance.'
383
+ : serviceHealth === 'UNAVAILABLE'
384
+ ? ' Service is currently unavailable.'
385
+ : '';
386
+ // Add QoS-specific suggestions
387
+ const qosHint = context.qos === 'CRITICAL'
388
+ ? ' Consider retrying when service recovers.'
389
+ : context.qos === 'HIGH'
390
+ ? ' Cached data may be used if available.'
391
+ : ' Using cached data regardless of staleness.';
392
+ return {
393
+ ...error,
394
+ hint: (error.hint ?? '') + healthHint + qosHint,
395
+ };
396
+ }
@@ -1 +1,216 @@
1
- export class PolicyDenied extends Error{rule;allowed;requested;suggestion;details;constructor(e,t,o,i,l){super(`Policy violation: ${e}`),this.rule=e,this.allowed=t,this.requested=o,this.suggestion=i,this.details=l,this.name="PolicyDenied"}toAgentFormat(){return[`POLICY_VIOLATION: ${this.rule}`,`REQUESTED: ${JSON.stringify(this.requested)}`,`ALLOWED: ${JSON.stringify(this.allowed)}`,`SUGGESTION: ${this.suggestion}`].join("\n")}toUserFormat(){return[`❌ Policy Violation: ${this.rule}`,"",`You requested: ${this.formatValue(this.requested)}`,`Policy allows: ${this.formatValue(this.allowed)}`,"",`💡 Suggestion: ${this.suggestion}`].join("\n")}toLogFormat(){return{error:"PolicyDenied",rule:this.rule,allowed:this.allowed,requested:this.requested,suggestion:this.suggestion,details:this.details,timestamp:(new Date).toISOString()}}formatValue(e){return"number"==typeof e?this.rule.includes("amount")||this.rule.includes("Amount")?`$${e.toLocaleString()}`:e.toLocaleString():Array.isArray(e)?e.map(e=>`"${String(e)}"`).join(", "):JSON.stringify(e)}}export class Guardrails{static amountExceeded(e){let t,o;switch(e.type){case"per-transaction":t="maxAmount",o=`Reduce amount to $${e.limit.toLocaleString()} or request policy update from admin`;break;case"daily":t="dailyAmount",o=`Reduce amount to stay within daily limit of $${e.limit.toLocaleString()}, or wait until tomorrow`;break;case"monthly":t="monthlyAmount",o=`Reduce amount to stay within monthly limit of $${e.limit.toLocaleString()}, or wait until next month`}return new PolicyDenied(t,e.limit,e.requested,o)}static rateLimitExceeded(e){const t=e.windowMs/1e3,o=t>=60?`Wait ${Math.ceil(t/60)} minutes before making additional calls`:`Wait ${t} seconds before making additional calls`;return new PolicyDenied("callsPerMinute",e.limit,e.current+1,o)}static toolDenied(e){const[t]=e.tool.split(":"),o=0===e.allowed.length?`No tools are currently allowed. Request approval from admin to enable "${e.tool}"`:`Request approval from admin to add "${e.tool}" or "${t}:*" to allowed tools`;return new PolicyDenied("allowedTools",e.allowed,e.tool,o)}static scopeDenied(e){const t=0===e.allowed.length?`No scopes are currently allowed. Request approval from admin to enable "${e.scope}"`:`Request approval from admin to add "${e.scope}" to allowed scopes`;return new PolicyDenied("allowedScopes",e.allowed,e.scope,t)}static fieldDenied(e){const t=`Field "${e.field}" is restricted. Contact admin to update policy.fieldFilters for "${e.tool}"`;return new PolicyDenied("fieldFilters",e.allowed,e.field,t)}static timeWindowDenied(e){const t=`Actions are only allowed between ${e.allowedStart} and ${e.allowedEnd}. Current time is ${e.current.toTimeString()}`;return new PolicyDenied("timeWindow",`${e.allowedStart} - ${e.allowedEnd}`,e.current.toTimeString(),t)}static custom(e){return new PolicyDenied(e.rule,e.allowed,e.requested,e.suggestion)}}export function extractSuggestion(e){return e.fix??"Contact administrator to update policy"}export function toPolicyDenied(e){let t;switch(e.constraint){case"amountPerTxn":t="maxAmount";break;case"dailyAmount":t="dailyAmount";break;case"callsPerMinute":t="callsPerMinute";break;case"scope":t="allowedScopes";break;case"tool":t="allowedTools";break;default:t=String(e.constraint)}return new PolicyDenied(t,e.allowed,e.requested,e.fix,e)}
1
+ /**
2
+ * @module guardrails
3
+ * Enhanced error messages with actionable suggestions for policy violations
4
+ *
5
+ * When policies deny requests, guardrails provide specific, actionable
6
+ * guidance on how to fix the issue or what to request from admins.
7
+ */
8
+ /**
9
+ * Policy violation error with actionable suggestions
10
+ */
11
+ export class PolicyDenied extends Error {
12
+ rule;
13
+ allowed;
14
+ requested;
15
+ suggestion;
16
+ details;
17
+ constructor(
18
+ /** Which policy rule was violated */
19
+ rule,
20
+ /** What the policy allows */
21
+ allowed,
22
+ /** What was requested */
23
+ requested,
24
+ /** Actionable suggestion for how to fix */
25
+ suggestion,
26
+ /** Full policy violation details */
27
+ details) {
28
+ super(`Policy violation: ${rule}`);
29
+ this.rule = rule;
30
+ this.allowed = allowed;
31
+ this.requested = requested;
32
+ this.suggestion = suggestion;
33
+ this.details = details;
34
+ this.name = 'PolicyDenied';
35
+ }
36
+ /**
37
+ * Format error for AI agent consumption
38
+ *
39
+ * Structured format optimized for LLM parsing
40
+ */
41
+ toAgentFormat() {
42
+ return [
43
+ `POLICY_VIOLATION: ${this.rule}`,
44
+ `REQUESTED: ${JSON.stringify(this.requested)}`,
45
+ `ALLOWED: ${JSON.stringify(this.allowed)}`,
46
+ `SUGGESTION: ${this.suggestion}`,
47
+ ].join('\n');
48
+ }
49
+ /**
50
+ * Format error for human consumption
51
+ *
52
+ * User-friendly format with clear next steps
53
+ */
54
+ toUserFormat() {
55
+ return [
56
+ `❌ Policy Violation: ${this.rule}`,
57
+ '',
58
+ `You requested: ${this.formatValue(this.requested)}`,
59
+ `Policy allows: ${this.formatValue(this.allowed)}`,
60
+ '',
61
+ `💡 Suggestion: ${this.suggestion}`,
62
+ ].join('\n');
63
+ }
64
+ /**
65
+ * Format error for logs (structured JSON)
66
+ */
67
+ toLogFormat() {
68
+ return {
69
+ error: 'PolicyDenied',
70
+ rule: this.rule,
71
+ allowed: this.allowed,
72
+ requested: this.requested,
73
+ suggestion: this.suggestion,
74
+ details: this.details,
75
+ timestamp: new Date().toISOString(),
76
+ };
77
+ }
78
+ formatValue(value) {
79
+ if (typeof value === 'number') {
80
+ // Format currency if it looks like money
81
+ if (this.rule.includes('amount') || this.rule.includes('Amount')) {
82
+ return `$${value.toLocaleString()}`;
83
+ }
84
+ return value.toLocaleString();
85
+ }
86
+ if (Array.isArray(value)) {
87
+ return value.map((v) => `"${String(v)}"`).join(', ');
88
+ }
89
+ return JSON.stringify(value);
90
+ }
91
+ }
92
+ /**
93
+ * Guardrail builder - fluent API for creating policy errors with suggestions
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const error = Guardrails.amountExceeded({
98
+ * requested: 5000,
99
+ * limit: 1000,
100
+ * type: 'per-transaction'
101
+ * });
102
+ *
103
+ * throw error;
104
+ * // ❌ Policy Violation: maxAmount
105
+ * // You requested: $5,000
106
+ * // Policy allows: $1,000
107
+ * // 💡 Suggestion: Reduce amount to $1,000 or request policy update from admin
108
+ * ```
109
+ */
110
+ export class Guardrails {
111
+ /**
112
+ * Amount exceeded (per-transaction limit)
113
+ */
114
+ static amountExceeded(options) {
115
+ let rule;
116
+ let suggestion;
117
+ switch (options.type) {
118
+ case 'per-transaction':
119
+ rule = 'maxAmount';
120
+ suggestion = `Reduce amount to $${options.limit.toLocaleString()} or request policy update from admin`;
121
+ break;
122
+ case 'daily':
123
+ rule = 'dailyAmount';
124
+ suggestion = `Reduce amount to stay within daily limit of $${options.limit.toLocaleString()}, or wait until tomorrow`;
125
+ break;
126
+ case 'monthly':
127
+ rule = 'monthlyAmount';
128
+ suggestion = `Reduce amount to stay within monthly limit of $${options.limit.toLocaleString()}, or wait until next month`;
129
+ break;
130
+ }
131
+ return new PolicyDenied(rule, options.limit, options.requested, suggestion);
132
+ }
133
+ /**
134
+ * Rate limit exceeded
135
+ */
136
+ static rateLimitExceeded(options) {
137
+ const windowSec = options.windowMs / 1000;
138
+ const suggestion = windowSec >= 60
139
+ ? `Wait ${Math.ceil(windowSec / 60)} minutes before making additional calls`
140
+ : `Wait ${windowSec} seconds before making additional calls`;
141
+ return new PolicyDenied('callsPerMinute', options.limit, options.current + 1, suggestion);
142
+ }
143
+ /**
144
+ * Tool not allowed
145
+ */
146
+ static toolDenied(options) {
147
+ const [service] = options.tool.split(':');
148
+ const suggestion = options.allowed.length === 0
149
+ ? `No tools are currently allowed. Request approval from admin to enable "${options.tool}"`
150
+ : `Request approval from admin to add "${options.tool}" or "${service}:*" to allowed tools`;
151
+ return new PolicyDenied('allowedTools', options.allowed, options.tool, suggestion);
152
+ }
153
+ /**
154
+ * Scope not allowed
155
+ */
156
+ static scopeDenied(options) {
157
+ const suggestion = options.allowed.length === 0
158
+ ? `No scopes are currently allowed. Request approval from admin to enable "${options.scope}"`
159
+ : `Request approval from admin to add "${options.scope}" to allowed scopes`;
160
+ return new PolicyDenied('allowedScopes', options.allowed, options.scope, suggestion);
161
+ }
162
+ /**
163
+ * Field filter denied (data access restriction)
164
+ */
165
+ static fieldDenied(options) {
166
+ const suggestion = `Field "${options.field}" is restricted. Contact admin to update policy.fieldFilters for "${options.tool}"`;
167
+ return new PolicyDenied('fieldFilters', options.allowed, options.field, suggestion);
168
+ }
169
+ /**
170
+ * Time window restriction
171
+ */
172
+ static timeWindowDenied(options) {
173
+ const suggestion = `Actions are only allowed between ${options.allowedStart} and ${options.allowedEnd}. Current time is ${options.current.toTimeString()}`;
174
+ return new PolicyDenied('timeWindow', `${options.allowedStart} - ${options.allowedEnd}`, options.current.toTimeString(), suggestion);
175
+ }
176
+ /**
177
+ * Generic policy violation
178
+ */
179
+ static custom(options) {
180
+ return new PolicyDenied(options.rule, options.allowed, options.requested, options.suggestion);
181
+ }
182
+ }
183
+ /**
184
+ * Helper: Extract actionable suggestion from AgentError details
185
+ *
186
+ * Used by agent.call() to convert PolicyViolationDetails into PolicyDenied
187
+ */
188
+ export function extractSuggestion(details) {
189
+ return details.fix ?? 'Contact administrator to update policy';
190
+ }
191
+ /**
192
+ * Helper: Convert PolicyViolationDetails to PolicyDenied
193
+ */
194
+ export function toPolicyDenied(details) {
195
+ let rule;
196
+ switch (details.constraint) {
197
+ case 'amountPerTxn':
198
+ rule = 'maxAmount';
199
+ break;
200
+ case 'dailyAmount':
201
+ rule = 'dailyAmount';
202
+ break;
203
+ case 'callsPerMinute':
204
+ rule = 'callsPerMinute';
205
+ break;
206
+ case 'scope':
207
+ rule = 'allowedScopes';
208
+ break;
209
+ case 'tool':
210
+ rule = 'allowedTools';
211
+ break;
212
+ default:
213
+ rule = String(details.constraint);
214
+ }
215
+ return new PolicyDenied(rule, details.allowed, details.requested, details.fix, details);
216
+ }