@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.
- package/README.md +55 -14
- package/dist-standalone/_deps/mldsa-wasm/dist/mldsa.js +1920 -1
- package/dist-standalone/_deps/shared/cjs/errors.js +729 -1
- package/dist-standalone/_deps/shared/cjs/index.js +463 -1
- package/dist-standalone/_deps/shared/cjs/types.js +315 -1
- package/dist-standalone/_deps/shared/errors.js +244 -1
- package/dist-standalone/_deps/shared/index.js +72 -1
- package/dist-standalone/_deps/shared/types.js +86 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/search.js +1 -1
- package/dist-standalone/_deps/ux-helpers/cjs/types.js +1 -1
- package/dist-standalone/_deps/ux-helpers/errors.js +1 -1
- package/dist-standalone/_deps/ux-helpers/index.js +1 -1
- package/dist-standalone/_deps/ux-helpers/pagination.js +1 -1
- package/dist-standalone/_deps/ux-helpers/progress.js +1 -1
- package/dist-standalone/_deps/ux-helpers/search.js +1 -1
- package/dist-standalone/_deps/xchange/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/auto-accept.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/index.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/cjs/xchange.js +1 -1
- package/dist-standalone/_deps/xchange/errors.js +1 -1
- package/dist-standalone/_deps/xchange/index.js +1 -1
- package/dist-standalone/_deps/xchange/invite-client.js +1 -1
- package/dist-standalone/_deps/xchange/lazy-init.js +1 -1
- package/dist-standalone/_deps/xchange/trust-integration.js +1 -1
- package/dist-standalone/_deps/xchange/xchange.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/index.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/cjs/types.js +1 -1
- package/dist-standalone/_deps/xregistry/discovery.js +1 -1
- package/dist-standalone/_deps/xregistry/errors.js +1 -1
- package/dist-standalone/_deps/xregistry/index.js +1 -1
- package/dist-standalone/_deps/xregistry/registry.js +1 -1
- package/dist-standalone/_deps/xregistry/schema.js +1 -1
- package/dist-standalone/_deps/xregistry/types.js +1 -1
- package/dist-standalone/agent-call.js +659 -1
- package/dist-standalone/agent-sdk.js +328 -1
- package/dist-standalone/agent.js +1800 -1
- package/dist-standalone/approval.js +193 -1
- package/dist-standalone/async-iterators.js +382 -1
- package/dist-standalone/auth.js +219 -1
- package/dist-standalone/auto-accept.js +229 -1
- package/dist-standalone/backup-config.js +201 -1
- package/dist-standalone/backup.js +326 -1
- package/dist-standalone/batch-operations.js +388 -1
- package/dist-standalone/cancellation.js +477 -1
- package/dist-standalone/checkpoint.js +186 -1
- package/dist-standalone/circuit-breaker.js +468 -1
- package/dist-standalone/cjs/agent-call.js +701 -1
- package/dist-standalone/cjs/agent-sdk.js +332 -1
- package/dist-standalone/cjs/agent.js +1837 -1
- package/dist-standalone/cjs/approval.js +199 -1
- package/dist-standalone/cjs/async-iterators.js +392 -1
- package/dist-standalone/cjs/auth.js +225 -1
- package/dist-standalone/cjs/auto-accept.js +233 -1
- package/dist-standalone/cjs/backup-config.js +207 -1
- package/dist-standalone/cjs/backup.js +330 -1
- package/dist-standalone/cjs/batch-operations.js +397 -1
- package/dist-standalone/cjs/cancellation.js +490 -1
- package/dist-standalone/cjs/checkpoint.js +193 -1
- package/dist-standalone/cjs/circuit-breaker.js +476 -1
- package/dist-standalone/cjs/cli/init.js +492 -1
- package/dist-standalone/cjs/config-validation.js +522 -1
- package/dist-standalone/cjs/connect.js +312 -1
- package/dist-standalone/cjs/connection-pool.js +506 -1
- package/dist-standalone/cjs/correlation-id.js +339 -1
- package/dist-standalone/cjs/crypto-utils.js +176 -1
- package/dist-standalone/cjs/debug-mode.js +534 -1
- package/dist-standalone/cjs/did-document.js +101 -1
- package/dist-standalone/cjs/did-privateme.js +130 -1
- package/dist-standalone/cjs/did-web.js +201 -1
- package/dist-standalone/cjs/discovery.js +462 -1
- package/dist-standalone/cjs/dual-mode.js +251 -1
- package/dist-standalone/cjs/email-templates.js +313 -1
- package/dist-standalone/cjs/email-transport.js +239 -1
- package/dist-standalone/cjs/envelope.js +538 -1
- package/dist-standalone/cjs/errors.js +913 -1
- package/dist-standalone/cjs/event-emitter.js +461 -1
- package/dist-standalone/cjs/gateway-state.js +55 -1
- package/dist-standalone/cjs/gateway-transport.js +120 -1
- package/dist-standalone/cjs/graceful-degradation.js +403 -1
- package/dist-standalone/cjs/guardrails.js +223 -1
- package/dist-standalone/cjs/health-check.js +336 -1
- package/dist-standalone/cjs/http-compat.js +272 -1
- package/dist-standalone/cjs/http-status-map.js +571 -1
- package/dist-standalone/cjs/identity.js +645 -1
- package/dist-standalone/cjs/index.js +406 -1
- package/dist-standalone/cjs/invitation.js +421 -1
- package/dist-standalone/cjs/invite.js +328 -1
- package/dist-standalone/cjs/key-agreement.js +335 -1
- package/dist-standalone/cjs/lazy-init.js +300 -1
- package/dist-standalone/cjs/logger.js +291 -1
- package/dist-standalone/cjs/mdns-discovery.js +202 -1
- package/dist-standalone/cjs/nonce-store.js +80 -1
- package/dist-standalone/cjs/pairing-manager.js +223 -1
- package/dist-standalone/cjs/plugin-system.js +264 -1
- package/dist-standalone/cjs/plugins/logging.js +168 -1
- package/dist-standalone/cjs/plugins/metrics.js +181 -1
- package/dist-standalone/cjs/plugins/validation.js +302 -1
- package/dist-standalone/cjs/policy.js +320 -1
- package/dist-standalone/cjs/progress-callbacks.js +583 -1
- package/dist-standalone/cjs/redis-nonce-store.js +76 -1
- package/dist-standalone/cjs/registry-middleware.js +50 -1
- package/dist-standalone/cjs/retry-strategies.js +544 -1
- package/dist-standalone/cjs/retry-transport.js +102 -1
- package/dist-standalone/cjs/runtime/browser.js +533 -1
- package/dist-standalone/cjs/runtime/edge.js +526 -1
- package/dist-standalone/cjs/runtime/react-native.js +394 -1
- package/dist-standalone/cjs/security-policy.js +245 -1
- package/dist-standalone/cjs/serialization.js +1040 -1
- package/dist-standalone/cjs/split-channel.js +225 -1
- package/dist-standalone/cjs/subscription-proof.js +230 -1
- package/dist-standalone/cjs/succession.js +148 -1
- package/dist-standalone/cjs/timeouts.js +412 -1
- package/dist-standalone/cjs/trace-context.js +424 -1
- package/dist-standalone/cjs/trace-spans.js +495 -1
- package/dist-standalone/cjs/transport.js +63 -1
- package/dist-standalone/cjs/trust-registry.js +991 -1
- package/dist-standalone/cjs/types/error-response.js +56 -1
- package/dist-standalone/cjs/vault-auth.js +178 -1
- package/dist-standalone/cjs/vault-store-loader.js +194 -1
- package/dist-standalone/cjs/verify.js +25 -1
- package/dist-standalone/cjs/version-info.js +543 -1
- package/dist-standalone/cjs/xfetch.js +340 -1
- package/dist-standalone/cli/init.js +455 -1
- package/dist-standalone/cli/setup.js +514 -1
- package/dist-standalone/cli/types.js +27 -1
- package/dist-standalone/cli/xbind.js +148 -1
- package/dist-standalone/config-validation.js +513 -1
- package/dist-standalone/connect.js +274 -1
- package/dist-standalone/connection-pool.js +500 -1
- package/dist-standalone/correlation-id.js +326 -1
- package/dist-standalone/crypto-utils.js +157 -1
- package/dist-standalone/debug-mode.js +510 -1
- package/dist-standalone/did-document.js +96 -1
- package/dist-standalone/did-privateme.js +121 -1
- package/dist-standalone/did-web.js +196 -1
- package/dist-standalone/discovery.js +458 -1
- package/dist-standalone/dual-mode.js +247 -1
- package/dist-standalone/email-templates.js +309 -1
- package/dist-standalone/email-transport.js +232 -1
- package/dist-standalone/envelope.js +525 -1
- package/dist-standalone/errors.js +896 -1
- package/dist-standalone/event-emitter.js +456 -1
- package/dist-standalone/gateway-state.js +51 -1
- package/dist-standalone/gateway-transport.js +116 -1
- package/dist-standalone/graceful-degradation.js +396 -1
- package/dist-standalone/guardrails.js +216 -1
- package/dist-standalone/health-check.js +332 -1
- package/dist-standalone/http-compat.js +267 -1
- package/dist-standalone/http-status-map.js +561 -1
- package/dist-standalone/identity.js +619 -1
- package/dist-standalone/index.js +78 -1
- package/dist-standalone/invitation.js +415 -1
- package/dist-standalone/invite.js +324 -1
- package/dist-standalone/key-agreement.js +325 -1
- package/dist-standalone/lazy-init.js +295 -1
- package/dist-standalone/logger.js +285 -1
- package/dist-standalone/mdns-discovery.js +195 -1
- package/dist-standalone/nonce-store.js +76 -1
- package/dist-standalone/pairing-manager.js +219 -1
- package/dist-standalone/plugin-system.js +257 -1
- package/dist-standalone/plugins/logging.js +163 -1
- package/dist-standalone/plugins/metrics.js +176 -1
- package/dist-standalone/plugins/validation.js +297 -1
- package/dist-standalone/policy.js +315 -1
- package/dist-standalone/progress-callbacks.js +576 -1
- package/dist-standalone/redis-nonce-store.js +72 -1
- package/dist-standalone/registry-middleware.js +47 -1
- package/dist-standalone/retry-strategies.js +534 -1
- package/dist-standalone/retry-transport.js +98 -1
- package/dist-standalone/runtime/browser.js +516 -1
- package/dist-standalone/runtime/edge.js +511 -1
- package/dist-standalone/runtime/react-native.js +383 -1
- package/dist-standalone/security-policy.js +239 -1
- package/dist-standalone/serialization.js +1031 -1
- package/dist-standalone/split-channel.js +219 -1
- package/dist-standalone/subscription-proof.js +224 -1
- package/dist-standalone/succession.js +142 -1
- package/dist-standalone/timeouts.js +398 -1
- package/dist-standalone/trace-context.js +414 -1
- package/dist-standalone/trace-spans.js +488 -1
- package/dist-standalone/transport.js +59 -1
- package/dist-standalone/trust-registry.js +950 -1
- package/dist-standalone/types/error-response.js +52 -1
- package/dist-standalone/vault-auth.js +174 -1
- package/dist-standalone/vault-store-loader.js +187 -1
- package/dist-standalone/verify.js +16 -1
- package/dist-standalone/version-info.js +530 -1
- package/dist-standalone/xfetch.js +335 -1
- package/package.json +4 -13
- package/share1.dat +0 -0
- package/dist-standalone/_deps/mldsa-wasm/LICENSE +0 -24
- package/dist-standalone/_deps/mldsa-wasm/package.json +0 -46
- package/dist-standalone/_deps/shared/cjs/package.json +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/package.json +0 -1
- package/dist-standalone/_deps/xchange/cjs/package.json +0 -1
- package/dist-standalone/_deps/xregistry/cjs/package.json +0 -1
- package/dist-standalone/cjs/package.json +0 -3
- package/dist-standalone/package.json +0 -10
|
@@ -1 +1,396 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
+
}
|