@vurb/core 3.7.13 → 3.8.0

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 (114) hide show
  1. package/README.md +63 -0
  2. package/dist/cli/commands/deploy.js +1 -1
  3. package/dist/cli/commands/deploy.js.map +1 -1
  4. package/dist/cli/commands/dev.js +1 -1
  5. package/dist/cli/commands/dev.js.map +1 -1
  6. package/dist/client/VurbClient.js +2 -2
  7. package/dist/client/VurbClient.js.map +1 -1
  8. package/dist/core/StandardSchema.js +1 -1
  9. package/dist/core/StandardSchema.js.map +1 -1
  10. package/dist/core/builder/BuildPipeline.js +3 -3
  11. package/dist/core/builder/BuildPipeline.js.map +1 -1
  12. package/dist/core/builder/FluentToolBuilder.d.ts +1 -1
  13. package/dist/core/builder/FluentToolBuilder.js +3 -3
  14. package/dist/core/builder/FluentToolBuilder.js.map +1 -1
  15. package/dist/core/builder/GroupedToolBuilder.js +2 -2
  16. package/dist/core/builder/GroupedToolBuilder.js.map +1 -1
  17. package/dist/core/execution/ExecutionPipeline.js +3 -3
  18. package/dist/core/execution/ExecutionPipeline.js.map +1 -1
  19. package/dist/core/index.d.ts +2 -2
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +1 -1
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/core/initVurb.js +2 -2
  24. package/dist/core/initVurb.js.map +1 -1
  25. package/dist/core/middleware/AuditTrail.js +1 -1
  26. package/dist/core/middleware/AuditTrail.js.map +1 -1
  27. package/dist/core/middleware/ContextDerivation.js +1 -1
  28. package/dist/core/middleware/ContextDerivation.js.map +1 -1
  29. package/dist/core/middleware/InputFirewall.js +1 -1
  30. package/dist/core/middleware/InputFirewall.js.map +1 -1
  31. package/dist/core/middleware/RateLimiter.js +1 -1
  32. package/dist/core/middleware/RateLimiter.js.map +1 -1
  33. package/dist/core/registry/ToolRegistry.js +1 -1
  34. package/dist/core/registry/ToolRegistry.js.map +1 -1
  35. package/dist/core/response.d.ts +53 -2
  36. package/dist/core/response.d.ts.map +1 -1
  37. package/dist/core/response.js +42 -1
  38. package/dist/core/response.js.map +1 -1
  39. package/dist/domain/Group.js +2 -2
  40. package/dist/domain/Group.js.map +1 -1
  41. package/dist/exposition/ExpositionCompiler.d.ts +1 -1
  42. package/dist/exposition/ExpositionCompiler.js +2 -2
  43. package/dist/exposition/ExpositionCompiler.js.map +1 -1
  44. package/dist/fsm/StateMachineGate.js +3 -3
  45. package/dist/fsm/StateMachineGate.js.map +1 -1
  46. package/dist/handoff/DelegationToken.d.ts +58 -0
  47. package/dist/handoff/DelegationToken.d.ts.map +1 -0
  48. package/dist/handoff/DelegationToken.js +186 -0
  49. package/dist/handoff/DelegationToken.js.map +1 -0
  50. package/dist/handoff/HandoffStateStore.d.ts +52 -0
  51. package/dist/handoff/HandoffStateStore.d.ts.map +1 -0
  52. package/dist/handoff/HandoffStateStore.js +73 -0
  53. package/dist/handoff/HandoffStateStore.js.map +1 -0
  54. package/dist/handoff/index.d.ts +32 -0
  55. package/dist/handoff/index.d.ts.map +1 -0
  56. package/dist/handoff/index.js +19 -0
  57. package/dist/handoff/index.js.map +1 -0
  58. package/dist/handoff/middleware.d.ts +36 -0
  59. package/dist/handoff/middleware.d.ts.map +1 -0
  60. package/dist/handoff/middleware.js +118 -0
  61. package/dist/handoff/middleware.js.map +1 -0
  62. package/dist/handoff/types.d.ts +51 -0
  63. package/dist/handoff/types.d.ts.map +1 -0
  64. package/dist/handoff/types.js +9 -0
  65. package/dist/handoff/types.js.map +1 -0
  66. package/dist/index.d.ts +5 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +3 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/introspection/CryptoAttestation.js +1 -1
  71. package/dist/introspection/CryptoAttestation.js.map +1 -1
  72. package/dist/introspection/GovernanceObserver.js +1 -1
  73. package/dist/introspection/GovernanceObserver.js.map +1 -1
  74. package/dist/introspection/TokenEconomics.js +1 -1
  75. package/dist/introspection/TokenEconomics.js.map +1 -1
  76. package/dist/introspection/ToolContract.js +2 -2
  77. package/dist/introspection/ToolContract.js.map +1 -1
  78. package/dist/observability/TelemetryBus.js +3 -3
  79. package/dist/observability/TelemetryBus.js.map +1 -1
  80. package/dist/presenter/JudgeChain.js +2 -2
  81. package/dist/presenter/JudgeChain.js.map +1 -1
  82. package/dist/presenter/PromptFirewall.js +2 -2
  83. package/dist/presenter/PromptFirewall.js.map +1 -1
  84. package/dist/presenter/RedactEngine.js +3 -3
  85. package/dist/presenter/RedactEngine.js.map +1 -1
  86. package/dist/prompt/FluentPromptBuilder.js +1 -1
  87. package/dist/prompt/FluentPromptBuilder.js.map +1 -1
  88. package/dist/resource/ResourceRegistry.d.ts +1 -1
  89. package/dist/resource/ResourceRegistry.js +1 -1
  90. package/dist/sandbox/SandboxEngine.d.ts +1 -1
  91. package/dist/sandbox/SandboxEngine.js +6 -6
  92. package/dist/sandbox/SandboxEngine.js.map +1 -1
  93. package/dist/sandbox/SandboxGuard.js +5 -5
  94. package/dist/sandbox/SandboxGuard.js.map +1 -1
  95. package/dist/server/DevServer.js +2 -2
  96. package/dist/server/DevServer.js.map +1 -1
  97. package/dist/server/ServerAttachment.d.ts +39 -1
  98. package/dist/server/ServerAttachment.d.ts.map +1 -1
  99. package/dist/server/ServerAttachment.js +78 -26
  100. package/dist/server/ServerAttachment.js.map +1 -1
  101. package/dist/server/ServerResolver.d.ts.map +1 -1
  102. package/dist/server/ServerResolver.js +15 -2
  103. package/dist/server/ServerResolver.js.map +1 -1
  104. package/dist/server/autoDiscover.js +2 -2
  105. package/dist/server/autoDiscover.js.map +1 -1
  106. package/dist/server/index.d.ts +1 -1
  107. package/dist/server/index.d.ts.map +1 -1
  108. package/dist/server/index.js.map +1 -1
  109. package/dist/server/startServer.js +2 -2
  110. package/dist/server/startServer.js.map +1 -1
  111. package/dist/state-sync/StateSyncBuilder.d.ts +1 -1
  112. package/dist/state-sync/StateSyncBuilder.js +2 -2
  113. package/dist/state-sync/StateSyncBuilder.js.map +1 -1
  114. package/package.json +151 -151
@@ -0,0 +1,58 @@
1
+ import type { HandoffStateStore } from './index.js';
2
+ /**
3
+ * JWT-inspired claims embedded in the delegation token.
4
+ *
5
+ * @property iss - Issuer URL (usually the gateway origin)
6
+ * @property sub - Scope identifier (e.g. `'finance_scope'`)
7
+ * @property iat - Issued-at Unix timestamp (seconds)
8
+ * @property exp - Expiry Unix timestamp (seconds)
9
+ * @property tid - Unique transaction ID for distributed tracing
10
+ * @property state - Inline carry-over state (< 2 KB)
11
+ * @property state_id - Claim-Check reference when state was externalized
12
+ * @property traceparent - W3C Trace Context header for distributed tracing
13
+ */
14
+ export interface DelegationClaims {
15
+ iss: string;
16
+ sub: string;
17
+ iat: number;
18
+ exp: number;
19
+ tid: string;
20
+ state?: Record<string, unknown>;
21
+ state_id?: string;
22
+ traceparent?: string;
23
+ }
24
+ /** Thrown by {@link verifyDelegationToken} on invalid or expired tokens. */
25
+ export declare class HandoffAuthError extends Error {
26
+ readonly code: 'MISSING_DELEGATION_TOKEN' | 'INVALID_DELEGATION_TOKEN' | 'EXPIRED_DELEGATION_TOKEN' | 'INVALID_SIGNATURE';
27
+ constructor(code: 'MISSING_DELEGATION_TOKEN' | 'INVALID_DELEGATION_TOKEN' | 'EXPIRED_DELEGATION_TOKEN' | 'INVALID_SIGNATURE', message: string);
28
+ }
29
+ /**
30
+ * Mint a signed HMAC-SHA256 delegation token.
31
+ *
32
+ * If `carryOverState` exceeds 2 KB, the state is persisted in `store`
33
+ * and only the resulting `state_id` UUID is embedded in the token.
34
+ *
35
+ * @param scope - Scope identifier (e.g. `'finance'`)
36
+ * @param ttlSeconds - Token lifetime in seconds
37
+ * @param secret - HMAC signing secret (minimum 32 chars recommended)
38
+ * @param issuer - Issuer identifier (gateway URL or name)
39
+ * @param carryOverState - Optional semantic context to carry to the upstream
40
+ * @param store - Required when `carryOverState` may exceed 2 KB
41
+ * @param traceparent - W3C traceparent for distributed tracing
42
+ * @returns Signed token string: `base64url(claims).base64url(sig)`
43
+ */
44
+ export declare function mintDelegationToken(scope: string, ttlSeconds: number, secret: string, issuer?: string, carryOverState?: Record<string, unknown>, store?: HandoffStateStore, traceparent?: string): Promise<string>;
45
+ /**
46
+ * Verify a delegation token and return its claims.
47
+ *
48
+ * - Validates HMAC signature (constant-time)
49
+ * - Validates expiry (`exp`)
50
+ * - Hydrates `carryOverState` from store when `state_id` is present (Claim-Check retrieval)
51
+ *
52
+ * @param raw - Raw token produced by {@link mintDelegationToken}
53
+ * @param secret - HMAC signing secret (must match the mint secret)
54
+ * @param store - Required if the token may contain a `state_id`
55
+ * @throws {@link HandoffAuthError} on any validation failure
56
+ */
57
+ export declare function verifyDelegationToken(raw: string, secret: string, store?: HandoffStateStore): Promise<DelegationClaims>;
58
+ //# sourceMappingURL=DelegationToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegationToken.d.ts","sourceRoot":"","sources":["../../src/handoff/DelegationToken.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAgBpD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD,4EAA4E;AAC5E,qBAAa,gBAAiB,SAAQ,KAAK;aAEnB,IAAI,EACd,0BAA0B,GAC1B,0BAA0B,GAC1B,0BAA0B,GAC1B,mBAAmB;gBAJT,IAAI,EACd,0BAA0B,GAC1B,0BAA0B,GAC1B,0BAA0B,GAC1B,mBAAmB,EACzB,OAAO,EAAE,MAAM;CAKtB;AA4CD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACrC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,SAAiB,EACvB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxC,KAAK,CAAC,EAAE,iBAAiB,EACzB,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,MAAM,CAAC,CAiCjB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACvC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CA0E3B"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Federated Handoff Protocol — Delegation Token
3
+ *
4
+ * HMAC-SHA256 token for zero-trust delegation between gateway and
5
+ * upstream micro-servers. Uses Node.js native `crypto` — no external deps.
6
+ *
7
+ * Claim-Check Pattern: if `carryOverState` exceeds 2 KB, the state is
8
+ * persisted in a {@link HandoffStateStore} and only the `state_id` UUID
9
+ * is embedded in the token, keeping HTTP headers within safe limits.
10
+ *
11
+ * @module
12
+ */
13
+ import { createHmac, randomUUID, timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto';
14
+ // ============================================================================
15
+ // Constants
16
+ // ============================================================================
17
+ /** Maximum bytes allowed inline in the token before Claim-Check kicks in. */
18
+ const CLAIM_CHECK_THRESHOLD_BYTES = 2048;
19
+ /** Separator between the base64url token body and the HMAC signature. */
20
+ const TOKEN_SEP = '.';
21
+ // ============================================================================
22
+ // Errors
23
+ // ============================================================================
24
+ /** Thrown by {@link verifyDelegationToken} on invalid or expired tokens. */
25
+ export class HandoffAuthError extends Error {
26
+ code;
27
+ constructor(code, message) {
28
+ super(message);
29
+ this.code = code;
30
+ this.name = 'HandoffAuthError';
31
+ }
32
+ }
33
+ // ============================================================================
34
+ // Internal helpers
35
+ // ============================================================================
36
+ function encodePayload(claims) {
37
+ return Buffer.from(JSON.stringify(claims), 'utf8').toString('base64url');
38
+ }
39
+ function decodePayload(encoded) {
40
+ return JSON.parse(Buffer.from(encoded, 'base64url').toString('utf8'));
41
+ }
42
+ function sign(payload, secret) {
43
+ return createHmac('sha256', secret).update(payload).digest('base64url');
44
+ }
45
+ function byteLength(obj) {
46
+ return Buffer.byteLength(JSON.stringify(obj), 'utf8');
47
+ }
48
+ /**
49
+ * Constant-time string comparison to prevent timing side-channel attacks.
50
+ *
51
+ * Uses Node.js native `crypto.timingSafeEqual` instead of a manual loop.
52
+ * V8's JIT can optimise a manual loop and leak the signature length via timing
53
+ * when the two strings have different lengths.
54
+ *
55
+ * `crypto.timingSafeEqual` requires equal-length Buffers, so we check lengths
56
+ * first (leaking that the lengths differ, but NOT the expected length).
57
+ */
58
+ function timingSafeEqual(a, b) {
59
+ const bufA = Buffer.from(a, 'utf8');
60
+ const bufB = Buffer.from(b, 'utf8');
61
+ // Length mismatch exits early — this only leaks "they differ", not which is longer.
62
+ if (bufA.length !== bufB.length)
63
+ return false;
64
+ return cryptoTimingSafeEqual(bufA, bufB);
65
+ }
66
+ // ============================================================================
67
+ // Public API
68
+ // ============================================================================
69
+ /**
70
+ * Mint a signed HMAC-SHA256 delegation token.
71
+ *
72
+ * If `carryOverState` exceeds 2 KB, the state is persisted in `store`
73
+ * and only the resulting `state_id` UUID is embedded in the token.
74
+ *
75
+ * @param scope - Scope identifier (e.g. `'finance'`)
76
+ * @param ttlSeconds - Token lifetime in seconds
77
+ * @param secret - HMAC signing secret (minimum 32 chars recommended)
78
+ * @param issuer - Issuer identifier (gateway URL or name)
79
+ * @param carryOverState - Optional semantic context to carry to the upstream
80
+ * @param store - Required when `carryOverState` may exceed 2 KB
81
+ * @param traceparent - W3C traceparent for distributed tracing
82
+ * @returns Signed token string: `base64url(claims).base64url(sig)`
83
+ */
84
+ export async function mintDelegationToken(scope, ttlSeconds, secret, issuer = 'vurb-gateway', carryOverState, store, traceparent) {
85
+ const now = Math.floor(Date.now() / 1000);
86
+ const claims = {
87
+ iss: issuer,
88
+ sub: scope,
89
+ iat: now,
90
+ exp: now + ttlSeconds,
91
+ tid: randomUUID(),
92
+ };
93
+ if (traceparent) {
94
+ claims.traceparent = traceparent;
95
+ }
96
+ if (carryOverState !== undefined) {
97
+ if (byteLength(carryOverState) > CLAIM_CHECK_THRESHOLD_BYTES) {
98
+ if (!store) {
99
+ throw new Error('[vurb/core] carryOverState exceeds 2 KB but no HandoffStateStore was provided. ' +
100
+ 'Pass a store to SwarmGatewayConfig.stateStore.');
101
+ }
102
+ const stateId = randomUUID();
103
+ await store.set(stateId, carryOverState, ttlSeconds + 60);
104
+ claims.state_id = stateId;
105
+ }
106
+ else {
107
+ claims.state = carryOverState;
108
+ }
109
+ }
110
+ const payload = encodePayload(claims);
111
+ const sig = sign(payload, secret);
112
+ return `${payload}${TOKEN_SEP}${sig}`;
113
+ }
114
+ /**
115
+ * Verify a delegation token and return its claims.
116
+ *
117
+ * - Validates HMAC signature (constant-time)
118
+ * - Validates expiry (`exp`)
119
+ * - Hydrates `carryOverState` from store when `state_id` is present (Claim-Check retrieval)
120
+ *
121
+ * @param raw - Raw token produced by {@link mintDelegationToken}
122
+ * @param secret - HMAC signing secret (must match the mint secret)
123
+ * @param store - Required if the token may contain a `state_id`
124
+ * @throws {@link HandoffAuthError} on any validation failure
125
+ */
126
+ export async function verifyDelegationToken(raw, secret, store) {
127
+ const sep = raw.lastIndexOf(TOKEN_SEP);
128
+ if (sep === -1) {
129
+ throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', 'Malformed token: missing signature separator.');
130
+ }
131
+ const payload = raw.slice(0, sep);
132
+ const providedSig = raw.slice(sep + 1);
133
+ const expectedSig = sign(payload, secret);
134
+ if (!timingSafeEqual(providedSig, expectedSig)) {
135
+ throw new HandoffAuthError('INVALID_SIGNATURE', 'Delegation token signature is invalid.');
136
+ }
137
+ let claims;
138
+ try {
139
+ claims = decodePayload(payload);
140
+ }
141
+ catch {
142
+ throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', 'Delegation token payload could not be decoded.');
143
+ }
144
+ const now = Math.floor(Date.now() / 1000);
145
+ if (claims.exp < now) {
146
+ throw new HandoffAuthError('EXPIRED_DELEGATION_TOKEN', `Delegation token expired at ${new Date(claims.exp * 1000).toISOString()}.`);
147
+ }
148
+ // Claim-Check: hydrate state from store
149
+ if (claims.state_id) {
150
+ if (!store) {
151
+ throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', 'Token contains state_id but no HandoffStateStore was provided.');
152
+ }
153
+ // Use the two-phase get+delete pattern only when both optional methods exist
154
+ // on the store. Fall back to the atomic `getAndDelete` for stores that implement
155
+ // the minimum interface (only `set` + `getAndDelete`).
156
+ // Without this check, calling `store.get()` on a minimal store would throw
157
+ // "store.get is not a function" at runtime — a silent crash invisible in TS.
158
+ let state;
159
+ if (typeof store.get === 'function' && typeof store.delete === 'function') {
160
+ // Two-phase: read first, delete only after data is safely in claims.state
161
+ state = await store.get(claims.state_id);
162
+ if (state !== undefined) {
163
+ claims.state = state;
164
+ await store.delete(claims.state_id);
165
+ }
166
+ }
167
+ else {
168
+ // Atomic fallback: getAndDelete cannot lose data between read and delete
169
+ state = await store.getAndDelete(claims.state_id);
170
+ if (state !== undefined) {
171
+ claims.state = state;
172
+ }
173
+ }
174
+ // If state_id was present but the store returned nothing, the state has
175
+ // either expired (TTL elapsed) or was already consumed (replay attempt).
176
+ // Silently continuing would give the upstream an empty context — the correct
177
+ // behaviour is to reject the token so the caller gets a clear signal.
178
+ if (state === undefined) {
179
+ throw new HandoffAuthError('EXPIRED_DELEGATION_TOKEN', 'Delegation token carry-over state has expired or was already consumed. ' +
180
+ 'Initiate a new handoff.');
181
+ }
182
+ delete claims.state_id;
183
+ }
184
+ return claims;
185
+ }
186
+ //# sourceMappingURL=DelegationToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegationToken.js","sourceRoot":"","sources":["../../src/handoff/DelegationToken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAG/F,+EAA+E;AAC/E,YAAY;AACZ,+EAA+E;AAE/E,6EAA6E;AAC7E,MAAM,2BAA2B,GAAG,IAAI,CAAC;AAEzC,yEAAyE;AACzE,MAAM,SAAS,GAAG,GAAG,CAAC;AA6BtB,+EAA+E;AAC/E,SAAS;AACT,+EAA+E;AAE/E,4EAA4E;AAC5E,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAEnB;IADpB,YACoB,IAIS,EACzB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAPC,SAAI,GAAJ,IAAI,CAIK;QAIzB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACnC,CAAC;CACJ;AAED,+EAA+E;AAC/E,mBAAmB;AACnB,+EAA+E;AAE/E,SAAS,aAAa,CAAC,MAAwB;IAC3C,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAqB,CAAC;AAC9F,CAAC;AAED,SAAS,IAAI,CAAC,OAAe,EAAE,MAAc;IACzC,OAAO,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,UAAU,CAAC,GAAY;IAC5B,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,eAAe,CAAC,CAAS,EAAE,CAAS;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,oFAAoF;IACpF,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,+EAA+E;AAC/E,aAAa;AACb,+EAA+E;AAE/E;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACrC,KAAa,EACb,UAAkB,EAClB,MAAc,EACd,MAAM,GAAG,cAAc,EACvB,cAAwC,EACxC,KAAyB,EACzB,WAAoB;IAEpB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAqB;QAC7B,GAAG,EAAE,MAAM;QACX,GAAG,EAAE,KAAK;QACV,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,GAAG,GAAG,UAAU;QACrB,GAAG,EAAE,UAAU,EAAE;KACpB,CAAC;IAEF,IAAI,WAAW,EAAE,CAAC;QACd,MAAM,CAAC,WAAW,GAAG,WAAW,CAAC;IACrC,CAAC;IAED,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,UAAU,CAAC,cAAc,CAAC,GAAG,2BAA2B,EAAE,CAAC;YAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CACX,iFAAiF;oBACjF,gDAAgD,CACnD,CAAC;YACN,CAAC;YACD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;YAC7B,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,UAAU,GAAG,EAAE,CAAC,CAAC;YAC1D,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC;QAC9B,CAAC;aAAM,CAAC;YACJ,MAAM,CAAC,KAAK,GAAG,cAAc,CAAC;QAClC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAClC,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACvC,GAAW,EACX,MAAc,EACd,KAAyB;IAEzB,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IACvC,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;QACb,MAAM,IAAI,gBAAgB,CAAC,0BAA0B,EAAE,+CAA+C,CAAC,CAAC;IAC5G,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClC,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAE1C,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,gBAAgB,CAAC,mBAAmB,EAAE,wCAAwC,CAAC,CAAC;IAC9F,CAAC;IAED,IAAI,MAAwB,CAAC;IAC7B,IAAI,CAAC;QACD,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACL,MAAM,IAAI,gBAAgB,CAAC,0BAA0B,EAAE,gDAAgD,CAAC,CAAC;IAC7G,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,GAAG,GAAG,GAAG,EAAE,CAAC;QACnB,MAAM,IAAI,gBAAgB,CACtB,0BAA0B,EAC1B,+BAA+B,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,GAAG,CAC9E,CAAC;IACN,CAAC;IAED,wCAAwC;IACxC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAClB,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,MAAM,IAAI,gBAAgB,CACtB,0BAA0B,EAC1B,gEAAgE,CACnE,CAAC;QACN,CAAC;QACD,6EAA6E;QAC7E,iFAAiF;QACjF,uDAAuD;QACvD,2EAA2E;QAC3E,6EAA6E;QAC7E,IAAI,KAA0C,CAAC;QAC/C,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,UAAU,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YACxE,0EAA0E;YAC1E,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACzC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACtB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;gBACrB,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACxC,CAAC;QACL,CAAC;aAAM,CAAC;YACJ,yEAAyE;YACzE,KAAK,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAClD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACtB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;YACzB,CAAC;QACL,CAAC;QAED,wEAAwE;QACxE,yEAAyE;QACzE,6EAA6E;QAC7E,sEAAsE;QACtE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,gBAAgB,CACtB,0BAA0B,EAC1B,yEAAyE;gBACzE,yBAAyB,CAC5B,CAAC;QACN,CAAC;QAED,OAAO,MAAM,CAAC,QAAQ,CAAC;IAC3B,CAAC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Federated Handoff Protocol (FHP) — HandoffStateStore
3
+ *
4
+ * Interface e implementação padrão para o padrão Claim-Check.
5
+ * Extender com adaptadores de plataforma (ex: `@vurb/cloudflare`).
6
+ *
7
+ * @module
8
+ */
9
+ import type { HandoffStateStore } from './index.js';
10
+ /**
11
+ * In-memory implementation of {@link HandoffStateStore}.
12
+ *
13
+ * **Default for Node.js stateful processes.**
14
+ * Not suitable for edge/serverless where each request runs in a fresh isolate.
15
+ * For those environments, implement `HandoffStateStore` using your platform's
16
+ * KV store (e.g. Cloudflare KV via `@vurb/cloudflare`).
17
+ *
18
+ * TTL is enforced lazily on `getAndDelete` — no background timers or
19
+ * memory leaks from expired entries that are never retrieved.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import { SwarmGateway } from '@vurb/swarm';
24
+ * import { InMemoryHandoffStateStore } from '@vurb/core';
25
+ *
26
+ * const gateway = new SwarmGateway({
27
+ * registry: { finance: 'http://finance-agent:8081' },
28
+ * delegationSecret: process.env.VURB_DELEGATION_SECRET!,
29
+ * stateStore: new InMemoryHandoffStateStore(),
30
+ * });
31
+ * ```
32
+ */
33
+ export declare class InMemoryHandoffStateStore implements HandoffStateStore {
34
+ private readonly _store;
35
+ set(stateId: string, state: Record<string, unknown>, ttlSeconds: number): Promise<void>;
36
+ getAndDelete(stateId: string): Promise<Record<string, unknown> | undefined>;
37
+ /**
38
+ * Read without deleting (non-atomic, use for two-phase pattern).
39
+ * Falls back gracefully if the stateId has expired.
40
+ *
41
+ * Expired entries are pruned on access (lazy TTL cleanup),
42
+ * consistent with `getAndDelete`. Without this, expired entries would accumulate
43
+ * in the Map indefinitely if `get()` was called but the data was already expired
44
+ * (e.g. a slow upstream that delayed token verification past the TTL window).
45
+ */
46
+ get(stateId: string): Promise<Record<string, unknown> | undefined>;
47
+ /** delete without reading. No-op if the key does not exist. */
48
+ delete(stateId: string): Promise<void>;
49
+ /** Current number of entries in the store (useful for testing). */
50
+ get size(): number;
51
+ }
52
+ //# sourceMappingURL=HandoffStateStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HandoffStateStore.d.ts","sourceRoot":"","sources":["../../src/handoff/HandoffStateStore.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAOpD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;IAC/D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IAElD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvF,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAejF;;;;;;;;OAQG;IACG,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAUxE,+DAA+D;IACzD,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5C,mEAAmE;IACnE,IAAI,IAAI,IAAI,MAAM,CAEjB;CACJ"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * In-memory implementation of {@link HandoffStateStore}.
3
+ *
4
+ * **Default for Node.js stateful processes.**
5
+ * Not suitable for edge/serverless where each request runs in a fresh isolate.
6
+ * For those environments, implement `HandoffStateStore` using your platform's
7
+ * KV store (e.g. Cloudflare KV via `@vurb/cloudflare`).
8
+ *
9
+ * TTL is enforced lazily on `getAndDelete` — no background timers or
10
+ * memory leaks from expired entries that are never retrieved.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { SwarmGateway } from '@vurb/swarm';
15
+ * import { InMemoryHandoffStateStore } from '@vurb/core';
16
+ *
17
+ * const gateway = new SwarmGateway({
18
+ * registry: { finance: 'http://finance-agent:8081' },
19
+ * delegationSecret: process.env.VURB_DELEGATION_SECRET!,
20
+ * stateStore: new InMemoryHandoffStateStore(),
21
+ * });
22
+ * ```
23
+ */
24
+ export class InMemoryHandoffStateStore {
25
+ _store = new Map();
26
+ async set(stateId, state, ttlSeconds) {
27
+ this._store.set(stateId, {
28
+ state,
29
+ expiresAt: Date.now() + ttlSeconds * 1000,
30
+ });
31
+ }
32
+ async getAndDelete(stateId) {
33
+ const entry = this._store.get(stateId);
34
+ if (!entry)
35
+ return undefined;
36
+ // Check TTL BEFORE deleting: if an entry is expired it was never valid for use,
37
+ // so we clean it up and return undefined. Lazy cleanup — no background timer needed.
38
+ if (Date.now() > entry.expiresAt) {
39
+ this._store.delete(stateId); // lazy TTL cleanup — no background timer
40
+ return undefined;
41
+ }
42
+ this._store.delete(stateId);
43
+ return entry.state;
44
+ }
45
+ /**
46
+ * Read without deleting (non-atomic, use for two-phase pattern).
47
+ * Falls back gracefully if the stateId has expired.
48
+ *
49
+ * Expired entries are pruned on access (lazy TTL cleanup),
50
+ * consistent with `getAndDelete`. Without this, expired entries would accumulate
51
+ * in the Map indefinitely if `get()` was called but the data was already expired
52
+ * (e.g. a slow upstream that delayed token verification past the TTL window).
53
+ */
54
+ async get(stateId) {
55
+ const entry = this._store.get(stateId);
56
+ if (!entry)
57
+ return undefined;
58
+ if (Date.now() > entry.expiresAt) {
59
+ this._store.delete(stateId); // lazy TTL cleanup — consistent with getAndDelete
60
+ return undefined;
61
+ }
62
+ return entry.state;
63
+ }
64
+ /** delete without reading. No-op if the key does not exist. */
65
+ async delete(stateId) {
66
+ this._store.delete(stateId);
67
+ }
68
+ /** Current number of entries in the store (useful for testing). */
69
+ get size() {
70
+ return this._store.size;
71
+ }
72
+ }
73
+ //# sourceMappingURL=HandoffStateStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HandoffStateStore.js","sourceRoot":"","sources":["../../src/handoff/HandoffStateStore.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,OAAO,yBAAyB;IACjB,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IAExD,KAAK,CAAC,GAAG,CAAC,OAAe,EAAE,KAA8B,EAAE,UAAkB;QACzE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE;YACrB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI;SAC5C,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,OAAe;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,gFAAgF;QAChF,qFAAqF;QACrF,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,yCAAyC;YACtE,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC,KAAK,CAAC;IACvB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,GAAG,CAAC,OAAe;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC;YAC/B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,kDAAkD;YAC/E,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACvB,CAAC;IAED,+DAA+D;IAC/D,KAAK,CAAC,MAAM,CAAC,OAAe;QACxB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,mEAAmE;IACnE,IAAI,IAAI;QACJ,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC5B,CAAC;CACJ"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Federated Handoff Protocol (FHP) — Core Barrel
3
+ *
4
+ * Exporta todas as primitivas FHP que pertencem ao `@vurb/core`:
5
+ *
6
+ * - **Tipos de dados**: `HandoffPayload`, `HandoffResponse`, `HandoffStateStore`
7
+ * - **Guards e factories**: `isHandoffResponse`, `handoff`
8
+ * - **Crypto**: `mintDelegationToken`, `verifyDelegationToken`, `DelegationClaims`, `HandoffAuthError`
9
+ * - **Store**: `InMemoryHandoffStateStore`
10
+ * - **Middleware zero-trust**: `requireGatewayClearance`, `GatewayClearanceContext`
11
+ */
12
+ export type { HandoffPayload, HandoffResponse } from '../core/response.js';
13
+ export { isHandoffResponse, handoff } from '../core/response.js';
14
+ /** @see {@link HandoffStateStore} */
15
+ export type {
16
+ /**
17
+ * Pluggable persistence interface for the Claim-Check pattern.
18
+ *
19
+ * When `carryOverState` exceeds 2 KB, the SwarmGateway persists
20
+ * it under a UUID key and embeds only the key inside the HMAC token,
21
+ * keeping HTTP headers within safe limits (< 8 KB nginx/ALB limit).
22
+ *
23
+ * Default implementation: {@link InMemoryHandoffStateStore}.
24
+ * Platform adapters: implement this interface (e.g. `@vurb/cloudflare`).
25
+ */
26
+ HandoffStateStore, } from './types.js';
27
+ export { InMemoryHandoffStateStore } from './HandoffStateStore.js';
28
+ export { mintDelegationToken, verifyDelegationToken, HandoffAuthError, } from './DelegationToken.js';
29
+ export type { DelegationClaims } from './DelegationToken.js';
30
+ export { requireGatewayClearance } from './middleware.js';
31
+ export type { GatewayClearanceContext } from './middleware.js';
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handoff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGjE,qCAAqC;AACrC,YAAY;AACR;;;;;;;;;GASG;AACH,iBAAiB,GACpB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,EACH,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GACnB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Federated Handoff Protocol (FHP) — Core Barrel
3
+ *
4
+ * Exporta todas as primitivas FHP que pertencem ao `@vurb/core`:
5
+ *
6
+ * - **Tipos de dados**: `HandoffPayload`, `HandoffResponse`, `HandoffStateStore`
7
+ * - **Guards e factories**: `isHandoffResponse`, `handoff`
8
+ * - **Crypto**: `mintDelegationToken`, `verifyDelegationToken`, `DelegationClaims`, `HandoffAuthError`
9
+ * - **Store**: `InMemoryHandoffStateStore`
10
+ * - **Middleware zero-trust**: `requireGatewayClearance`, `GatewayClearanceContext`
11
+ */
12
+ export { isHandoffResponse, handoff } from '../core/response.js';
13
+ // ── Store padrão ─────────────────────────────────────────────────────────────
14
+ export { InMemoryHandoffStateStore } from './HandoffStateStore.js';
15
+ // ── Delegation Token (mint/verify, HMAC-SHA256 puro) ─────────────────────────
16
+ export { mintDelegationToken, verifyDelegationToken, HandoffAuthError, } from './DelegationToken.js';
17
+ // ── Middleware zero-trust (para micro-servidores upstream) ───────────────────
18
+ export { requireGatewayClearance } from './middleware.js';
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handoff/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAkBjE,gFAAgF;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,gFAAgF;AAChF,OAAO,EACH,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,GACnB,MAAM,sBAAsB,CAAC;AAG9B,gFAAgF;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { HandoffStateStore } from './index.js';
2
+ /**
3
+ * Properties injected into the handler context by `requireGatewayClearance`.
4
+ *
5
+ * Merge with your own context type via intersection:
6
+ * ```typescript
7
+ * type AppContext = BaseContext & GatewayClearanceContext;
8
+ * ```
9
+ */
10
+ export interface GatewayClearanceContext {
11
+ /** Carry-over state from the gateway (inline or hydrated via Claim-Check). */
12
+ handoffState: Record<string, unknown>;
13
+ /** Delegation scope — the `sub` claim of the token (e.g. `'finance'`). */
14
+ handoffScope: string;
15
+ /** Transaction ID for distributed tracing (correlates with gateway spans). */
16
+ handoffTid: string;
17
+ /** W3C traceparent header, if present in the token. */
18
+ handoffTraceparent?: string;
19
+ }
20
+ /**
21
+ * Zero-trust middleware que valida o token de delegação HMAC.
22
+ *
23
+ * Lê o header `x-vurb-delegation` do `extra` do pedido MCP,
24
+ * verifica a assinatura HMAC-SHA256 e o TTL, e injeta
25
+ * {@link GatewayClearanceContext} no contexto do handler.
26
+ *
27
+ * @param secret - Segredo HMAC partilhado entre o gateway e o micro-servidor.
28
+ * Deve corresponder a `SwarmGatewayConfig.delegationSecret`.
29
+ * @param store - Store opcional para hidratação do Claim-Check (necessário quando
30
+ * `carryOverState` pode exceder 2 KB).
31
+ *
32
+ * @throws {@link HandoffAuthError} — traduzido para erro `FORBIDDEN` pelo framework
33
+ * se não for capturado pelo handler.
34
+ */
35
+ export declare function requireGatewayClearance(secret: string, store?: HandoffStateStore): (ctx: unknown) => Promise<GatewayClearanceContext>;
36
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/handoff/middleware.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAMpD;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACpC,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,iBAAiB,IAGX,KAAK,OAAO,KAAG,OAAO,CAAC,uBAAuB,CAAC,CA6BhE"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Federated Handoff Protocol — Zero-Trust Middleware
3
+ *
4
+ * Middleware para micro-servidores upstream.
5
+ * Rejeita qualquer pedido sem um token de delegação HMAC válido
6
+ * emitido por um SwarmGateway autorizado.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { requireGatewayClearance } from '@vurb/core';
11
+ *
12
+ * export const refund = f.tool('finance.refund')
13
+ * .use(requireGatewayClearance(process.env.VURB_DELEGATION_SECRET!))
14
+ * .withString('invoiceId', 'Invoice ID')
15
+ * .handle(async (input, ctx) => {
16
+ * // ctx.handoffState — carry-over state from the gateway
17
+ * // ctx.handoffScope — delegation scope (e.g. 'finance')
18
+ * // ctx.handoffTid — transaction ID for tracing
19
+ * // ctx.handoffTraceparent — W3C traceparent, if present
20
+ * return success(await stripe.refund(input.invoiceId));
21
+ * });
22
+ * ```
23
+ *
24
+ * @module
25
+ */
26
+ import { verifyDelegationToken, HandoffAuthError } from './DelegationToken.js';
27
+ // ============================================================================
28
+ // Middleware factory
29
+ // ============================================================================
30
+ /**
31
+ * Zero-trust middleware que valida o token de delegação HMAC.
32
+ *
33
+ * Lê o header `x-vurb-delegation` do `extra` do pedido MCP,
34
+ * verifica a assinatura HMAC-SHA256 e o TTL, e injeta
35
+ * {@link GatewayClearanceContext} no contexto do handler.
36
+ *
37
+ * @param secret - Segredo HMAC partilhado entre o gateway e o micro-servidor.
38
+ * Deve corresponder a `SwarmGatewayConfig.delegationSecret`.
39
+ * @param store - Store opcional para hidratação do Claim-Check (necessário quando
40
+ * `carryOverState` pode exceder 2 KB).
41
+ *
42
+ * @throws {@link HandoffAuthError} — traduzido para erro `FORBIDDEN` pelo framework
43
+ * se não for capturado pelo handler.
44
+ */
45
+ export function requireGatewayClearance(secret, store) {
46
+ return async (ctx) => {
47
+ const raw = extractDelegationHeader(ctx);
48
+ if (!raw) {
49
+ throw new HandoffAuthError('MISSING_DELEGATION_TOKEN', 'This tool requires a delegation token from the SwarmGateway. ' +
50
+ 'Direct access without a gateway is not permitted.');
51
+ }
52
+ let claims;
53
+ try {
54
+ claims = await verifyDelegationToken(raw, secret, store);
55
+ }
56
+ catch (err) {
57
+ if (err instanceof HandoffAuthError)
58
+ throw err;
59
+ throw new HandoffAuthError('INVALID_DELEGATION_TOKEN', `Invalid token: ${err.message}`);
60
+ }
61
+ return {
62
+ handoffState: claims.state ?? {},
63
+ handoffScope: claims.sub,
64
+ handoffTid: claims.tid,
65
+ ...(claims.traceparent ? { handoffTraceparent: claims.traceparent } : {}),
66
+ };
67
+ };
68
+ }
69
+ // ============================================================================
70
+ // Header extraction — duck-typed, transport-agnostic
71
+ // ============================================================================
72
+ /**
73
+ * Extract the delegation header value from the MCP handler context.
74
+ * Header lookup is case-insensitive (HTTP/1.1 RFC 7230 §3.2).
75
+ *
76
+ * HTTP proxies (nginx, Cloudflare, AWS ALB) may normalize headers to
77
+ * Title-Case (`X-Vurb-Delegation`) or strip them. A case-insensitive scan
78
+ * with `toLowerCase()` ensures the token is found regardless of normalization.
79
+ */
80
+ function extractDelegationHeader(ctx) {
81
+ if (!ctx || typeof ctx !== 'object')
82
+ return undefined;
83
+ const c = ctx;
84
+ // MCP SDK extra: extra.requestInfo.headers['x-vurb-delegation']
85
+ const requestInfo = c['requestInfo'];
86
+ if (requestInfo && typeof requestInfo === 'object') {
87
+ const h = requestInfo['headers'];
88
+ const val = findHeaderCaseInsensitive(h, 'x-vurb-delegation');
89
+ if (val !== undefined)
90
+ return val;
91
+ }
92
+ // Fallback: ctx.headers (plain HTTP / custom transports)
93
+ const val = findHeaderCaseInsensitive(c['headers'], 'x-vurb-delegation');
94
+ if (val !== undefined)
95
+ return val;
96
+ return undefined;
97
+ }
98
+ /**
99
+ * Case-insensitive lookup of `headerName` in a headers map.
100
+ * Returns the value as a string if found, or `undefined`.
101
+ */
102
+ function findHeaderCaseInsensitive(headers, headerName) {
103
+ if (!headers || typeof headers !== 'object')
104
+ return undefined;
105
+ const h = headers;
106
+ const lower = headerName.toLowerCase();
107
+ // Fast path: exact key (most common — MCP SDK always uses lowercase)
108
+ if (typeof h[lower] === 'string')
109
+ return h[lower];
110
+ // Slow path: scan all keys case-insensitively (proxy-normalized headers)
111
+ for (const key of Object.keys(h)) {
112
+ if (key.toLowerCase() === lower && typeof h[key] === 'string') {
113
+ return h[key];
114
+ }
115
+ }
116
+ return undefined;
117
+ }
118
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/handoff/middleware.ts"],"names":[],"mappings":"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"}