@private.me/xbind 1.3.0 → 2.3.4
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/LICENSES.md +212 -0
- package/README.md +388 -6
- package/dist-standalone/_deps/mldsa-wasm/dist/mldsa.js +1 -1920
- package/dist-standalone/_deps/shared/cjs/errors.js +1 -275
- package/dist-standalone/_deps/shared/cjs/index.js +1 -138
- package/dist-standalone/_deps/shared/cjs/types.js +1 -90
- package/dist-standalone/_deps/shared/errors.js +1 -262
- package/dist-standalone/_deps/shared/index.js +1 -77
- package/dist-standalone/_deps/shared/types.js +1 -91
- 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 +1 -642
- package/dist-standalone/agent-sdk.js +1 -328
- package/dist-standalone/agent.d.ts +95 -5
- package/dist-standalone/agent.js +1 -1545
- package/dist-standalone/approval.js +1 -193
- package/dist-standalone/async-iterators.d.ts +275 -0
- package/dist-standalone/async-iterators.js +1 -0
- package/dist-standalone/auth.js +1 -219
- package/dist-standalone/auto-accept.js +1 -229
- package/dist-standalone/backup-config.js +1 -201
- package/dist-standalone/backup.d.ts +114 -0
- package/dist-standalone/backup.js +1 -0
- package/dist-standalone/batch-operations.d.ts +297 -0
- package/dist-standalone/batch-operations.js +1 -0
- package/dist-standalone/cancellation.d.ts +301 -0
- package/dist-standalone/cancellation.js +1 -0
- package/dist-standalone/checkpoint.js +1 -186
- package/dist-standalone/circuit-breaker.d.ts +351 -0
- package/dist-standalone/circuit-breaker.js +1 -0
- package/dist-standalone/cjs/agent-call.js +1 -651
- package/dist-standalone/cjs/agent-sdk.js +1 -332
- package/dist-standalone/cjs/agent.js +1 -1582
- package/dist-standalone/cjs/approval.js +1 -199
- package/dist-standalone/cjs/async-iterators.js +1 -0
- package/dist-standalone/cjs/auth.js +1 -225
- package/dist-standalone/cjs/auto-accept.js +1 -233
- package/dist-standalone/cjs/backup-config.js +1 -207
- package/dist-standalone/cjs/backup.js +1 -0
- package/dist-standalone/cjs/batch-operations.js +1 -0
- package/dist-standalone/cjs/cancellation.js +1 -0
- package/dist-standalone/cjs/checkpoint.js +1 -193
- package/dist-standalone/cjs/circuit-breaker.js +1 -0
- package/dist-standalone/cjs/cli/init.js +1 -486
- package/dist-standalone/cjs/config-validation.js +1 -0
- package/dist-standalone/cjs/connect.js +1 -312
- package/dist-standalone/cjs/connection-pool.js +1 -0
- package/dist-standalone/cjs/correlation-id.js +1 -339
- package/dist-standalone/cjs/crypto-utils.js +1 -0
- package/dist-standalone/cjs/debug-mode.js +1 -0
- package/dist-standalone/cjs/did-document.js +1 -101
- package/dist-standalone/cjs/did-privateme.js +1 -130
- package/dist-standalone/cjs/did-web.js +1 -201
- package/dist-standalone/cjs/discovery.js +1 -462
- package/dist-standalone/cjs/dual-mode.js +1 -251
- package/dist-standalone/cjs/email-templates.js +1 -313
- package/dist-standalone/cjs/email-transport.js +1 -239
- package/dist-standalone/cjs/envelope.js +1 -510
- package/dist-standalone/cjs/errors.js +1 -826
- package/dist-standalone/cjs/event-emitter.js +1 -0
- package/dist-standalone/cjs/gateway-state.js +1 -55
- package/dist-standalone/cjs/gateway-transport.js +1 -120
- package/dist-standalone/cjs/graceful-degradation.js +1 -0
- package/dist-standalone/cjs/guardrails.js +1 -223
- package/dist-standalone/cjs/health-check.js +1 -0
- package/dist-standalone/cjs/http-compat.js +1 -272
- package/dist-standalone/cjs/http-status-map.js +1 -571
- package/dist-standalone/cjs/identity.js +1 -541
- package/dist-standalone/cjs/index.js +1 -237
- package/dist-standalone/cjs/invitation.js +1 -421
- package/dist-standalone/cjs/invite.js +1 -328
- package/dist-standalone/cjs/key-agreement.js +1 -246
- package/dist-standalone/cjs/lazy-init.js +1 -300
- package/dist-standalone/cjs/logger.js +1 -0
- package/dist-standalone/cjs/mdns-discovery.js +1 -202
- package/dist-standalone/cjs/nonce-store.js +1 -66
- package/dist-standalone/cjs/pairing-manager.js +1 -223
- package/dist-standalone/cjs/plugin-system.js +1 -0
- package/dist-standalone/cjs/plugins/logging.js +1 -0
- package/dist-standalone/cjs/plugins/metrics.js +1 -0
- package/dist-standalone/cjs/plugins/validation.js +1 -0
- package/dist-standalone/cjs/policy.js +1 -320
- package/dist-standalone/cjs/progress-callbacks.js +1 -0
- package/dist-standalone/cjs/redis-nonce-store.js +1 -76
- package/dist-standalone/cjs/registry-middleware.js +1 -50
- package/dist-standalone/cjs/retry-strategies.js +1 -0
- package/dist-standalone/cjs/retry-transport.js +1 -102
- package/dist-standalone/cjs/runtime/browser.js +1 -0
- package/dist-standalone/cjs/runtime/edge.js +1 -0
- package/dist-standalone/cjs/runtime/react-native.js +1 -0
- package/dist-standalone/cjs/security-policy.js +1 -245
- package/dist-standalone/cjs/serialization.js +1 -0
- package/dist-standalone/cjs/split-channel.js +1 -177
- package/dist-standalone/cjs/subscription-proof.js +1 -230
- package/dist-standalone/cjs/succession.js +1 -148
- package/dist-standalone/cjs/timeouts.js +1 -0
- package/dist-standalone/cjs/trace-context.js +1 -0
- package/dist-standalone/cjs/trace-spans.js +1 -0
- package/dist-standalone/cjs/transport.js +1 -63
- package/dist-standalone/cjs/trust-registry.js +1 -742
- package/dist-standalone/cjs/types/error-response.js +1 -56
- package/dist-standalone/cjs/vault-auth.js +1 -0
- package/dist-standalone/cjs/vault-store-loader.js +1 -0
- package/dist-standalone/cjs/verify.js +1 -25
- package/dist-standalone/cjs/version-info.js +1 -0
- package/dist-standalone/cjs/xfetch.js +1 -252
- package/dist-standalone/cli/init.js +1 -449
- package/dist-standalone/cli/setup.js +1 -514
- package/dist-standalone/cli/types.js +1 -27
- package/dist-standalone/cli/xbind.js +1 -148
- package/dist-standalone/config-validation.d.ts +185 -0
- package/dist-standalone/config-validation.js +1 -0
- package/dist-standalone/connect.js +1 -274
- package/dist-standalone/connection-pool.d.ts +251 -0
- package/dist-standalone/connection-pool.js +1 -0
- package/dist-standalone/correlation-id.js +1 -326
- package/dist-standalone/crypto-utils.d.ts +60 -0
- package/dist-standalone/crypto-utils.js +1 -0
- package/dist-standalone/debug-mode.d.ts +286 -0
- package/dist-standalone/debug-mode.js +1 -0
- package/dist-standalone/did-document.js +1 -96
- package/dist-standalone/did-privateme.js +1 -121
- package/dist-standalone/did-web.js +1 -196
- package/dist-standalone/discovery.js +1 -458
- package/dist-standalone/dual-mode.js +1 -247
- package/dist-standalone/email-templates.js +1 -309
- package/dist-standalone/email-transport.js +1 -232
- package/dist-standalone/envelope.d.ts +29 -1
- package/dist-standalone/envelope.js +1 -497
- package/dist-standalone/errors.d.ts +10 -0
- package/dist-standalone/errors.js +1 -811
- package/dist-standalone/event-emitter.d.ts +395 -0
- package/dist-standalone/event-emitter.js +1 -0
- package/dist-standalone/gateway-state.js +1 -51
- package/dist-standalone/gateway-transport.js +1 -116
- package/dist-standalone/graceful-degradation.d.ts +246 -0
- package/dist-standalone/graceful-degradation.js +1 -0
- package/dist-standalone/guardrails.js +1 -216
- package/dist-standalone/health-check.d.ts +150 -0
- package/dist-standalone/health-check.js +1 -0
- package/dist-standalone/http-compat.js +1 -267
- package/dist-standalone/http-status-map.js +1 -561
- package/dist-standalone/identity.d.ts +64 -1
- package/dist-standalone/identity.js +1 -516
- package/dist-standalone/index.d.ts +45 -3
- package/dist-standalone/index.js +1 -52
- package/dist-standalone/invitation.js +1 -415
- package/dist-standalone/invite.js +1 -324
- package/dist-standalone/key-agreement.d.ts +61 -13
- package/dist-standalone/key-agreement.js +1 -236
- package/dist-standalone/lazy-init.js +1 -295
- package/dist-standalone/logger.d.ts +77 -0
- package/dist-standalone/logger.js +1 -0
- package/dist-standalone/mdns-discovery.js +1 -195
- package/dist-standalone/nonce-store.d.ts +16 -3
- package/dist-standalone/nonce-store.js +1 -62
- package/dist-standalone/package.json +0 -1
- package/dist-standalone/pairing-manager.js +1 -219
- package/dist-standalone/plugin-system.d.ts +145 -0
- package/dist-standalone/plugin-system.js +1 -0
- package/dist-standalone/policy.js +1 -315
- package/dist-standalone/progress-callbacks.d.ts +394 -0
- package/dist-standalone/progress-callbacks.js +1 -0
- package/dist-standalone/redis-nonce-store.js +1 -72
- package/dist-standalone/registry-middleware.js +1 -47
- package/dist-standalone/retry-strategies.d.ts +382 -0
- package/dist-standalone/retry-strategies.js +1 -0
- package/dist-standalone/retry-transport.js +1 -98
- package/dist-standalone/security-policy.js +1 -239
- package/dist-standalone/serialization.d.ts +244 -0
- package/dist-standalone/serialization.js +1 -0
- package/dist-standalone/split-channel.d.ts +49 -1
- package/dist-standalone/split-channel.js +1 -171
- package/dist-standalone/subscription-proof.js +1 -224
- package/dist-standalone/succession.js +1 -142
- package/dist-standalone/timeouts.d.ts +275 -0
- package/dist-standalone/timeouts.js +1 -0
- package/dist-standalone/trace-context.d.ts +252 -0
- package/dist-standalone/trace-context.js +1 -0
- package/dist-standalone/trace-spans.d.ts +360 -0
- package/dist-standalone/trace-spans.js +1 -0
- package/dist-standalone/transport.js +1 -59
- package/dist-standalone/trust-registry.d.ts +106 -5
- package/dist-standalone/trust-registry.js +1 -702
- package/dist-standalone/vault-auth.d.ts +91 -0
- package/dist-standalone/vault-auth.js +1 -0
- package/dist-standalone/vault-store-loader.d.ts +110 -0
- package/dist-standalone/vault-store-loader.js +1 -0
- package/dist-standalone/verify.js +1 -16
- package/dist-standalone/version-info.d.ts +259 -0
- package/dist-standalone/version-info.js +1 -0
- package/dist-standalone/xfetch.js +1 -247
- package/llms.txt +1 -0
- package/package.json +66 -5
- package/share1.dat +0 -0
- package/dist-standalone/_deps/crypto/base64.d.ts +0 -29
- package/dist-standalone/_deps/crypto/base64.js +0 -209
- package/dist-standalone/_deps/crypto/cjs/base64.js +0 -103
- package/dist-standalone/_deps/crypto/cjs/errors.js +0 -119
- package/dist-standalone/_deps/crypto/cjs/hmac.js +0 -71
- package/dist-standalone/_deps/crypto/cjs/index.js +0 -86
- package/dist-standalone/_deps/crypto/cjs/padding.js +0 -57
- package/dist-standalone/_deps/crypto/cjs/share-header.js +0 -68
- package/dist-standalone/_deps/crypto/cjs/shares.js +0 -152
- package/dist-standalone/_deps/crypto/cjs/tlv.js +0 -199
- package/dist-standalone/_deps/crypto/cjs/uuid.js +0 -61
- package/dist-standalone/_deps/crypto/cjs/verify.js +0 -24
- package/dist-standalone/_deps/crypto/cjs/xorida.js +0 -221
- package/dist-standalone/_deps/crypto/errors.d.ts +0 -51
- package/dist-standalone/_deps/crypto/errors.js +0 -109
- package/dist-standalone/_deps/crypto/hmac.d.ts +0 -39
- package/dist-standalone/_deps/crypto/hmac.js +0 -66
- package/dist-standalone/_deps/crypto/index.d.ts +0 -20
- package/dist-standalone/_deps/crypto/index.js +0 -45
- package/dist-standalone/_deps/crypto/padding.d.ts +0 -19
- package/dist-standalone/_deps/crypto/padding.js +0 -53
- package/dist-standalone/_deps/crypto/share-header.d.ts +0 -44
- package/dist-standalone/_deps/crypto/share-header.js +0 -63
- package/dist-standalone/_deps/crypto/shares.d.ts +0 -27
- package/dist-standalone/_deps/crypto/shares.js +0 -148
- package/dist-standalone/_deps/crypto/tlv.d.ts +0 -26
- package/dist-standalone/_deps/crypto/tlv.js +0 -195
- package/dist-standalone/_deps/crypto/uuid.d.ts +0 -22
- package/dist-standalone/_deps/crypto/uuid.js +0 -56
- package/dist-standalone/_deps/crypto/verify.d.ts +0 -15
- package/dist-standalone/_deps/crypto/verify.js +0 -15
- package/dist-standalone/_deps/crypto/xorida.d.ts +0 -44
- package/dist-standalone/_deps/crypto/xorida.js +0 -215
- package/dist-standalone/_deps/shared/errors.d.ts.map +0 -1
- package/dist-standalone/_deps/shared/errors.js.map +0 -1
- package/dist-standalone/_deps/shared/index.d.ts.map +0 -1
- package/dist-standalone/_deps/shared/index.js.map +0 -1
- package/dist-standalone/_deps/shared/types.d.ts.map +0 -1
- package/dist-standalone/_deps/shared/types.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/errors.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/index.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/index.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/pagination.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/progress.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/progress.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/search.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/search.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/types.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/cjs/types.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/errors.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/errors.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/index.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/index.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/pagination.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/pagination.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/progress.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/progress.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/search.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/search.js.map +0 -1
- package/dist-standalone/_deps/ux-helpers/types.d.ts.map +0 -1
- package/dist-standalone/_deps/ux-helpers/types.js.map +0 -1
- package/dist-standalone/_deps/xregistry/discovery.d.ts.map +0 -1
- package/dist-standalone/_deps/xregistry/discovery.js.map +0 -1
- package/dist-standalone/_deps/xregistry/errors.d.ts.map +0 -1
- package/dist-standalone/_deps/xregistry/errors.js.map +0 -1
- package/dist-standalone/_deps/xregistry/index.d.ts.map +0 -1
- package/dist-standalone/_deps/xregistry/index.js.map +0 -1
- package/dist-standalone/_deps/xregistry/registry.d.ts.map +0 -1
- package/dist-standalone/_deps/xregistry/registry.js.map +0 -1
- package/dist-standalone/_deps/xregistry/schema.d.ts.map +0 -1
- package/dist-standalone/_deps/xregistry/schema.js.map +0 -1
- package/dist-standalone/_deps/xregistry/types.d.ts.map +0 -1
- package/dist-standalone/_deps/xregistry/types.js.map +0 -1
|
@@ -0,0 +1,246 @@
|
|
|
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 type { Result } from '@private.me/shared';
|
|
15
|
+
import type { ACIErrorDetail } from '@private.me/ux-helpers';
|
|
16
|
+
import type { TrustRegistry } from './trust-registry.js';
|
|
17
|
+
import type { XailTransportAdapter } from './transport.js';
|
|
18
|
+
/**
|
|
19
|
+
* Quality of Service levels for operations.
|
|
20
|
+
*
|
|
21
|
+
* Determines behavior when services are degraded:
|
|
22
|
+
* - CRITICAL: Must succeed, fail fast if unavailable
|
|
23
|
+
* - HIGH: Retry with aggressive backoff, use cache if fresh
|
|
24
|
+
* - NORMAL: Retry with standard backoff, use stale cache if necessary
|
|
25
|
+
* - LOW: Skip retries, use cache regardless of staleness
|
|
26
|
+
*/
|
|
27
|
+
export type QoSLevel = 'CRITICAL' | 'HIGH' | 'NORMAL' | 'LOW';
|
|
28
|
+
/**
|
|
29
|
+
* Operation metadata for QoS decisions.
|
|
30
|
+
*/
|
|
31
|
+
export interface OperationContext {
|
|
32
|
+
/** Unique operation identifier for correlation. */
|
|
33
|
+
readonly operationId: string;
|
|
34
|
+
/** Quality of service level. */
|
|
35
|
+
readonly qos: QoSLevel;
|
|
36
|
+
/** Operation type (e.g., 'registry:lookup', 'gateway:call', 'transport:send'). */
|
|
37
|
+
readonly type: string;
|
|
38
|
+
/** Optional timeout override in milliseconds. */
|
|
39
|
+
readonly timeoutMs?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Cache entry with TTL and staleness tracking.
|
|
43
|
+
*/
|
|
44
|
+
interface CacheEntry<T> {
|
|
45
|
+
/** Cached value. */
|
|
46
|
+
readonly value: T;
|
|
47
|
+
/** Timestamp when entry was created (ms since epoch). */
|
|
48
|
+
readonly cachedAt: number;
|
|
49
|
+
/** Time-to-live in milliseconds. */
|
|
50
|
+
readonly ttl: number;
|
|
51
|
+
/** Number of times this stale entry has been used. */
|
|
52
|
+
usageCount: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Cache configuration options.
|
|
56
|
+
*/
|
|
57
|
+
export interface CacheConfig {
|
|
58
|
+
/** Default TTL for cache entries (milliseconds). Default: 300000 (5 minutes). */
|
|
59
|
+
readonly defaultTTL?: number;
|
|
60
|
+
/** Maximum cache size (entries). Default: 1000. */
|
|
61
|
+
readonly maxSize?: number;
|
|
62
|
+
/** Allow stale cache usage for LOW/NORMAL QoS. Default: true. */
|
|
63
|
+
readonly allowStale?: boolean;
|
|
64
|
+
/** Maximum staleness allowed (milliseconds). Default: 3600000 (1 hour). */
|
|
65
|
+
readonly maxStaleMs?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Retry configuration for fallback operations.
|
|
69
|
+
*/
|
|
70
|
+
export interface RetryConfig {
|
|
71
|
+
/** Maximum retry attempts. Default: 3. */
|
|
72
|
+
readonly maxAttempts?: number;
|
|
73
|
+
/** Initial delay in milliseconds. Default: 1000. */
|
|
74
|
+
readonly initialDelayMs?: number;
|
|
75
|
+
/** Backoff multiplier. Default: 2. */
|
|
76
|
+
readonly backoffMultiplier?: number;
|
|
77
|
+
/** Maximum delay in milliseconds. Default: 30000. */
|
|
78
|
+
readonly maxDelayMs?: number;
|
|
79
|
+
/** Enable jitter (randomize delays). Default: true. */
|
|
80
|
+
readonly jitter?: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Service health status.
|
|
84
|
+
*/
|
|
85
|
+
type ServiceHealth = 'HEALTHY' | 'DEGRADED' | 'UNAVAILABLE';
|
|
86
|
+
/**
|
|
87
|
+
* Service health tracking.
|
|
88
|
+
*/
|
|
89
|
+
interface ServiceStatus {
|
|
90
|
+
/** Current health status. */
|
|
91
|
+
health: ServiceHealth;
|
|
92
|
+
/** Last successful operation timestamp. */
|
|
93
|
+
lastSuccess: number;
|
|
94
|
+
/** Consecutive failure count. */
|
|
95
|
+
failureCount: number;
|
|
96
|
+
/** Last error encountered. */
|
|
97
|
+
lastError?: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Graceful Degradation Manager
|
|
101
|
+
*
|
|
102
|
+
* Coordinates fallback mechanisms, caching, and retry logic across
|
|
103
|
+
* registry, gateway, and transport layers.
|
|
104
|
+
*/
|
|
105
|
+
export declare class GracefulDegradationManager {
|
|
106
|
+
private readonly cacheConfig;
|
|
107
|
+
private readonly retryConfig;
|
|
108
|
+
private readonly cache;
|
|
109
|
+
private readonly serviceStatus;
|
|
110
|
+
constructor(cacheConfig?: CacheConfig, retryConfig?: RetryConfig);
|
|
111
|
+
/**
|
|
112
|
+
* Get cached value if available and valid for the given QoS level.
|
|
113
|
+
*
|
|
114
|
+
* @param key Cache key
|
|
115
|
+
* @param qos Quality of service level
|
|
116
|
+
* @returns Cached value or undefined
|
|
117
|
+
*/
|
|
118
|
+
getCached<T>(key: string, qos: QoSLevel): T | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Store value in cache with TTL.
|
|
121
|
+
*
|
|
122
|
+
* @param key Cache key
|
|
123
|
+
* @param value Value to cache
|
|
124
|
+
* @param ttl Time-to-live in milliseconds (uses default if omitted)
|
|
125
|
+
*/
|
|
126
|
+
setCached<T>(key: string, value: T, ttl?: number): void;
|
|
127
|
+
/**
|
|
128
|
+
* Invalidate cached entry.
|
|
129
|
+
*
|
|
130
|
+
* @param key Cache key
|
|
131
|
+
*/
|
|
132
|
+
invalidate(key: string): void;
|
|
133
|
+
/**
|
|
134
|
+
* Clear all cached entries.
|
|
135
|
+
*/
|
|
136
|
+
clearCache(): void;
|
|
137
|
+
/**
|
|
138
|
+
* Get cache statistics.
|
|
139
|
+
*/
|
|
140
|
+
getCacheStats(): {
|
|
141
|
+
size: number;
|
|
142
|
+
maxSize: number;
|
|
143
|
+
entries: Array<{
|
|
144
|
+
key: string;
|
|
145
|
+
age: number;
|
|
146
|
+
usageCount: number;
|
|
147
|
+
}>;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Record successful operation for a service.
|
|
151
|
+
*
|
|
152
|
+
* @param serviceName Service identifier (e.g., 'registry', 'gateway', 'transport')
|
|
153
|
+
*/
|
|
154
|
+
recordSuccess(serviceName: string): void;
|
|
155
|
+
/**
|
|
156
|
+
* Record failed operation for a service.
|
|
157
|
+
*
|
|
158
|
+
* @param serviceName Service identifier
|
|
159
|
+
* @param error Error message
|
|
160
|
+
*/
|
|
161
|
+
recordFailure(serviceName: string, error: string): void;
|
|
162
|
+
/**
|
|
163
|
+
* Get service health status.
|
|
164
|
+
*
|
|
165
|
+
* @param serviceName Service identifier
|
|
166
|
+
* @returns Service health status
|
|
167
|
+
*/
|
|
168
|
+
getServiceHealth(serviceName: string): ServiceHealth;
|
|
169
|
+
/**
|
|
170
|
+
* Get detailed service status.
|
|
171
|
+
*
|
|
172
|
+
* @param serviceName Service identifier
|
|
173
|
+
* @returns Service status or undefined if not tracked
|
|
174
|
+
*/
|
|
175
|
+
getServiceStatus(serviceName: string): ServiceStatus | undefined;
|
|
176
|
+
/**
|
|
177
|
+
* Reset service health tracking.
|
|
178
|
+
*
|
|
179
|
+
* @param serviceName Service identifier (all services if omitted)
|
|
180
|
+
*/
|
|
181
|
+
resetServiceHealth(serviceName?: string): void;
|
|
182
|
+
/**
|
|
183
|
+
* Execute operation with retry logic based on QoS level.
|
|
184
|
+
*
|
|
185
|
+
* @param operation Async operation to execute
|
|
186
|
+
* @param context Operation context with QoS level
|
|
187
|
+
* @returns Result of operation
|
|
188
|
+
*/
|
|
189
|
+
withRetry<T, E>(operation: () => Promise<Result<T, E>>, context: OperationContext): Promise<Result<T, E>>;
|
|
190
|
+
/**
|
|
191
|
+
* Get maximum retry attempts for QoS level.
|
|
192
|
+
*/
|
|
193
|
+
private getMaxAttempts;
|
|
194
|
+
/**
|
|
195
|
+
* Calculate exponential backoff delay with optional jitter.
|
|
196
|
+
*/
|
|
197
|
+
private calculateBackoff;
|
|
198
|
+
/**
|
|
199
|
+
* Sleep for specified milliseconds.
|
|
200
|
+
*/
|
|
201
|
+
private sleep;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Registry lookup with graceful degradation.
|
|
205
|
+
*
|
|
206
|
+
* Provides intelligent fallback for DID resolution:
|
|
207
|
+
* 1. Try primary registry lookup
|
|
208
|
+
* 2. On failure, check cache based on QoS level
|
|
209
|
+
* 3. Return user-friendly error if all fallbacks exhausted
|
|
210
|
+
*
|
|
211
|
+
* @param registry Trust registry instance
|
|
212
|
+
* @param did DID to resolve
|
|
213
|
+
* @param context Operation context
|
|
214
|
+
* @param manager Degradation manager
|
|
215
|
+
* @returns Registry lookup result
|
|
216
|
+
*/
|
|
217
|
+
export declare function registryLookupWithFallback(registry: TrustRegistry, did: string, context: OperationContext, manager: GracefulDegradationManager): Promise<Result<{
|
|
218
|
+
publicKey: string;
|
|
219
|
+
endpoint?: string;
|
|
220
|
+
}, ACIErrorDetail>>;
|
|
221
|
+
/**
|
|
222
|
+
* Send envelope with transport fallback.
|
|
223
|
+
*
|
|
224
|
+
* Tries multiple transports in order until one succeeds:
|
|
225
|
+
* 1. Primary transport with retry
|
|
226
|
+
* 2. Fallback transports (if configured)
|
|
227
|
+
* 3. Return detailed error if all transports fail
|
|
228
|
+
*
|
|
229
|
+
* @param transports Array of transport adapters
|
|
230
|
+
* @param envelope Envelope to send
|
|
231
|
+
* @param recipientDid Recipient DID
|
|
232
|
+
* @param context Operation context
|
|
233
|
+
* @param manager Degradation manager
|
|
234
|
+
* @returns Send result
|
|
235
|
+
*/
|
|
236
|
+
export declare function sendWithTransportFallback(transports: XailTransportAdapter[], envelope: unknown, recipientDid: string, context: OperationContext, manager: GracefulDegradationManager): Promise<Result<void, ACIErrorDetail>>;
|
|
237
|
+
/**
|
|
238
|
+
* Enhance error with QoS-appropriate messaging.
|
|
239
|
+
*
|
|
240
|
+
* @param error Original error
|
|
241
|
+
* @param context Operation context
|
|
242
|
+
* @param manager Degradation manager
|
|
243
|
+
* @returns Enhanced error with actionable hints
|
|
244
|
+
*/
|
|
245
|
+
export declare function enhanceError(error: ACIErrorDetail, context: OperationContext, manager: GracefulDegradationManager): ACIErrorDetail;
|
|
246
|
+
export type { CacheEntry, ServiceStatus };
|
|
@@ -0,0 +1 @@
|
|
|
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,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
|
-
}
|
|
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)}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module health-check
|
|
3
|
+
* Health check endpoint for operational monitoring and load balancer probes.
|
|
4
|
+
*
|
|
5
|
+
* Provides three probe types following Kubernetes health check patterns:
|
|
6
|
+
* - **Startup probe**: Verify initialization complete (one-time check)
|
|
7
|
+
* - **Readiness probe**: Verify dependencies ready (registry, transport)
|
|
8
|
+
* - **Liveness probe**: Verify service responsive (self-check)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { createHealthChecker } from '@private.me/xbind';
|
|
13
|
+
*
|
|
14
|
+
* const checker = createHealthChecker({
|
|
15
|
+
* registry: myTrustRegistry,
|
|
16
|
+
* transport: myTransport
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Load balancer endpoint
|
|
20
|
+
* app.get('/health', async (req, res) => {
|
|
21
|
+
* const status = await checker.check();
|
|
22
|
+
* res.status(status.healthy ? 200 : 503).json(status);
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import type { TrustRegistry } from './trust-registry.js';
|
|
27
|
+
import type { XailTransportAdapter } from './transport.js';
|
|
28
|
+
/**
|
|
29
|
+
* Health check probe type.
|
|
30
|
+
*/
|
|
31
|
+
export type ProbeType = 'startup' | 'readiness' | 'liveness';
|
|
32
|
+
/**
|
|
33
|
+
* Health status for individual component.
|
|
34
|
+
*/
|
|
35
|
+
export interface ComponentHealth {
|
|
36
|
+
/** Component name (e.g., 'registry', 'transport') */
|
|
37
|
+
readonly name: string;
|
|
38
|
+
/** Component status: 'healthy', 'degraded', 'unhealthy' */
|
|
39
|
+
readonly status: 'healthy' | 'degraded' | 'unhealthy';
|
|
40
|
+
/** Human-readable status message */
|
|
41
|
+
readonly message?: string;
|
|
42
|
+
/** Response time in milliseconds */
|
|
43
|
+
readonly latencyMs?: number;
|
|
44
|
+
/** Last check timestamp (ISO 8601) */
|
|
45
|
+
readonly lastCheck: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Overall health check result.
|
|
49
|
+
*/
|
|
50
|
+
export interface HealthStatus {
|
|
51
|
+
/** Overall health: true if all components healthy, false otherwise */
|
|
52
|
+
readonly healthy: boolean;
|
|
53
|
+
/** Timestamp of health check (ISO 8601) */
|
|
54
|
+
readonly timestamp: string;
|
|
55
|
+
/** Uptime in seconds since checker created */
|
|
56
|
+
readonly uptimeSeconds: number;
|
|
57
|
+
/** Individual component health statuses */
|
|
58
|
+
readonly components: ComponentHealth[];
|
|
59
|
+
/** Additional metadata */
|
|
60
|
+
readonly metadata?: {
|
|
61
|
+
/** Service name */
|
|
62
|
+
readonly service?: string;
|
|
63
|
+
/** Service version */
|
|
64
|
+
readonly version?: string;
|
|
65
|
+
/** Environment (development, staging, production) */
|
|
66
|
+
readonly environment?: string;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Configuration for health checker.
|
|
71
|
+
*/
|
|
72
|
+
export interface HealthCheckerOptions {
|
|
73
|
+
/** Trust registry to check (optional) */
|
|
74
|
+
readonly registry?: TrustRegistry;
|
|
75
|
+
/** Transport adapter to check (optional) */
|
|
76
|
+
readonly transport?: XailTransportAdapter;
|
|
77
|
+
/** Service name for metadata */
|
|
78
|
+
readonly serviceName?: string;
|
|
79
|
+
/** Service version for metadata */
|
|
80
|
+
readonly version?: string;
|
|
81
|
+
/** Environment name for metadata */
|
|
82
|
+
readonly environment?: string;
|
|
83
|
+
/** Timeout for component checks in milliseconds (default: 5000) */
|
|
84
|
+
readonly timeoutMs?: number;
|
|
85
|
+
/** Enable startup probe tracking (default: true) */
|
|
86
|
+
readonly enableStartupProbe?: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Health checker instance.
|
|
90
|
+
*/
|
|
91
|
+
export interface HealthChecker {
|
|
92
|
+
/**
|
|
93
|
+
* Perform health check on all configured components.
|
|
94
|
+
* @param probe - Probe type (defaults to 'readiness')
|
|
95
|
+
*/
|
|
96
|
+
check(probe?: ProbeType): Promise<HealthStatus>;
|
|
97
|
+
/**
|
|
98
|
+
* Mark startup probe as complete.
|
|
99
|
+
* Call after initialization finishes (e.g., after agent.register()).
|
|
100
|
+
*/
|
|
101
|
+
markStartupComplete(): void;
|
|
102
|
+
/**
|
|
103
|
+
* Check if startup probe is complete.
|
|
104
|
+
*/
|
|
105
|
+
isStartupComplete(): boolean;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Create a health checker instance.
|
|
109
|
+
*
|
|
110
|
+
* @param options - Configuration options
|
|
111
|
+
* @returns Health checker instance
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const checker = createHealthChecker({
|
|
116
|
+
* registry: agent.registry,
|
|
117
|
+
* transport: agent.transport,
|
|
118
|
+
* serviceName: 'my-service',
|
|
119
|
+
* version: '1.0.0'
|
|
120
|
+
* });
|
|
121
|
+
*
|
|
122
|
+
* // Mark startup complete after initialization
|
|
123
|
+
* await agent.register();
|
|
124
|
+
* checker.markStartupComplete();
|
|
125
|
+
*
|
|
126
|
+
* // Check health
|
|
127
|
+
* const status = await checker.check('readiness');
|
|
128
|
+
* console.log(status.healthy ? 'Ready' : 'Not ready');
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export declare function createHealthChecker(options?: HealthCheckerOptions): HealthChecker;
|
|
132
|
+
/**
|
|
133
|
+
* Express/Fastify middleware helper for health endpoint.
|
|
134
|
+
*
|
|
135
|
+
* @param checker - Health checker instance
|
|
136
|
+
* @param probe - Probe type (default: 'readiness')
|
|
137
|
+
* @returns Request handler
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```typescript
|
|
141
|
+
* const checker = createHealthChecker({ registry, transport });
|
|
142
|
+
*
|
|
143
|
+
* // Express
|
|
144
|
+
* app.get('/health', healthEndpoint(checker));
|
|
145
|
+
* app.get('/health/startup', healthEndpoint(checker, 'startup'));
|
|
146
|
+
* app.get('/health/liveness', healthEndpoint(checker, 'liveness'));
|
|
147
|
+
* app.get('/health/readiness', healthEndpoint(checker, 'readiness'));
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export declare function healthEndpoint(checker: HealthChecker, probe?: ProbeType): (req: any, res: any) => Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class HealthCheckerImpl{startTime;registry;transport;serviceName;version;environment;timeoutMs;enableStartupProbe;startupComplete=!1;constructor(t={}){this.startTime=Date.now(),this.registry=t.registry,this.transport=t.transport,this.serviceName=t.serviceName,this.version=t.version,this.environment=t.environment,this.timeoutMs=t.timeoutMs??5e3,this.enableStartupProbe=t.enableStartupProbe??!0}markStartupComplete(){this.startupComplete=!0}isStartupComplete(){return this.startupComplete}async check(t="readiness"){const e=(new Date).toISOString(),s=Math.floor((Date.now()-this.startTime)/1e3);if("startup"===t&&this.enableStartupProbe){const t=[{name:"startup",status:this.startupComplete?"healthy":"unhealthy",message:this.startupComplete?"Initialization complete":"Waiting for startup completion (call markStartupComplete())",lastCheck:e}];return{healthy:this.startupComplete,timestamp:e,uptimeSeconds:s,components:t,metadata:this.buildMetadata()}}if("liveness"===t){return{healthy:!0,timestamp:e,uptimeSeconds:s,components:[{name:"self",status:"healthy",message:"Service responsive",lastCheck:e}],metadata:this.buildMetadata()}}const a=[];if(this.registry){const t=await this.checkRegistry();a.push(t)}if(this.transport){const t=await this.checkTransport();a.push(t)}0===a.length&&a.push({name:"self",status:"healthy",message:"No dependencies configured",lastCheck:e});return{healthy:a.every(t=>"healthy"===t.status),timestamp:e,uptimeSeconds:s,components:a,metadata:this.buildMetadata()}}buildMetadata(){const t={};return this.serviceName&&(t.service=this.serviceName),this.version&&(t.version=this.version),this.environment&&(t.environment=this.environment),Object.keys(t).length>0?t:void 0}async checkRegistry(){const t=Date.now(),e=(new Date).toISOString();try{if(!this.registry)return{name:"registry",status:"unhealthy",message:"Registry not configured",lastCheck:e};const s=await this.withTimeout(this.registry.resolve("did:xail:health_check_probe"),this.timeoutMs),a=Date.now()-t;if(s&&"object"==typeof s&&"ok"in s){if(s.ok)return{name:"registry",status:"healthy",message:"Registry responding",latencyMs:a,lastCheck:e};{const t="string"==typeof s.error?s.error:"UNKNOWN";return t.includes("NOT_FOUND")?{name:"registry",status:"healthy",message:"Registry responding",latencyMs:a,lastCheck:e}:{name:"registry",status:"unhealthy",message:`Registry error: ${t}`,latencyMs:a,lastCheck:e}}}return{name:"registry",status:"healthy",message:"Registry responding",latencyMs:a,lastCheck:e}}catch(s){const a=Date.now()-t,r=s instanceof Error?s.message:"Unknown error";return r.toLowerCase().includes("timeout")?{name:"registry",status:"degraded",message:`Registry slow (>${this.timeoutMs}ms)`,latencyMs:a,lastCheck:e}:{name:"registry",status:"unhealthy",message:`Registry error: ${r}`,latencyMs:a,lastCheck:e}}}async checkTransport(){const t=Date.now(),e=(new Date).toISOString();try{if(!this.transport)return{name:"transport",status:"unhealthy",message:"Transport not configured",lastCheck:e};return{name:"transport",status:"healthy",message:"Transport adapter available",latencyMs:Date.now()-t,lastCheck:e}}catch(s){const a=Date.now()-t;return{name:"transport",status:"unhealthy",message:`Transport error: ${s instanceof Error?s.message:"Unknown error"}`,latencyMs:a,lastCheck:e}}}async withTimeout(t,e){return Promise.race([t,new Promise((t,s)=>setTimeout(()=>s(new Error("Health check timeout")),e))])}}export function createHealthChecker(t){return new HealthCheckerImpl(t)}export function healthEndpoint(t,e="readiness"){return async(s,a)=>{try{const s=await t.check(e),r=s.healthy?200:503;a.status(r).json(s)}catch(t){const e=t instanceof Error?t.message:"Unknown error";a.status(503).json({healthy:!1,timestamp:(new Date).toISOString(),uptimeSeconds:0,components:[{name:"health-check",status:"unhealthy",message:`Health check failed: ${e}`,lastCheck:(new Date).toISOString()}]})}}}
|