@caracalai/sdk 0.1.6-rc.3 → 0.2.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,13 @@
1
- export * from './envelope.js';
2
- export * from './context.js';
3
- export * from './coordinator.js';
4
- export * from './primitives.js';
5
- export * from './http.js';
1
+ export { HeaderAuthorization, HeaderTraceparent, HeaderTracestate, HeaderBaggage, BaggageAgentSession, BaggageDelegationEdge, BaggageParentEdge, BaggageSession, BaggageHop, MaxHop, formatTraceparent, parseTraceparent, encodeBaggage, parseBaggage, fromHeaders, decodeEnvelope, encodeEnvelope, toHeaders, } from './envelope.js';
2
+ export type { Envelope, HeaderGetter, HeaderSetter } from './envelope.js';
3
+ export { current, captureContext, bind, withOverrides, toEnvelope, fromEnvelope, fromVerifiedEnvelope, describeAuthority, } from './context.js';
4
+ export type { CaracalContext, AuthoritySummary, VerifiedClaims } from './context.js';
5
+ export { CoordinatorError, Lifecycle, startCoordinatorSession, terminateSession, acquireSessionLease, heartbeatSession, createDelegation, revokeDelegation, getInboundDelegation, listInboundDelegations, } from './coordinator.js';
6
+ export type { CoordinatorCallEvent, CoordinatorClient, SessionStatus, DelegationConstraints, StartSessionRequest, StartSessionResponse, DelegationRequest, DelegationResponse, HeartbeatResponse, InboundDelegation, } from './coordinator.js';
7
+ export { Authority, session, delegate, acceptDelegation, startSession, attachSession } from './primitives.js';
8
+ export type { AuthorityMode, SessionInput, DelegateInput, Delegation, StartSessionInput, SessionHandle, AttachSessionInput, } from './primitives.js';
9
+ export { caracalContextMiddleware, caracalFastifyHook } from './http.js';
10
+ export type { IncomingLike, FastifyRequestLike, ServerResponseLike, FastifyReplyLike, ConnectMiddleware } from './http.js';
6
11
  export { Caracal } from './client.js';
7
- export type { CaracalConfig, SpawnOptions, DelegateOptions, LifecycleHook, RootOptions, TokenSource, ClientSecretOptions, } from './client.js';
12
+ export type { CaracalConfig, SessionOptions, DelegateOptions, LifecycleHook, CallOptions, TokenSource, ClientCredentials, CredentialsResolver, ClientSecretOptions, } from './client.js';
8
13
  //# sourceMappingURL=advanced.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAUA,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACrC,YAAY,EACV,aAAa,EACb,YAAY,EACZ,eAAe,EACf,aAAa,EACb,WAAW,EACX,WAAW,EACX,mBAAmB,GACpB,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,cAAc,EACd,cAAc,EACd,SAAS,GACV,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACzE,OAAO,EACL,OAAO,EACP,cAAc,EACd,IAAI,EACJ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACpF,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,oBAAoB,EACpB,iBAAiB,EACjB,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC7G,YAAY,EACV,aAAa,EACb,YAAY,EACZ,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACxE,YAAY,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC1H,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACrC,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,EACf,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,aAAa,CAAA"}
package/dist/advanced.js CHANGED
@@ -7,10 +7,10 @@
7
7
  * "@caracalai/sdk" entrypoint; reach for these when building a transport
8
8
  * adapter or framework integration.
9
9
  */
10
- export * from './envelope.js';
11
- export * from './context.js';
12
- export * from './coordinator.js';
13
- export * from './primitives.js';
14
- export * from './http.js';
10
+ export { HeaderAuthorization, HeaderTraceparent, HeaderTracestate, HeaderBaggage, BaggageAgentSession, BaggageDelegationEdge, BaggageParentEdge, BaggageSession, BaggageHop, MaxHop, formatTraceparent, parseTraceparent, encodeBaggage, parseBaggage, fromHeaders, decodeEnvelope, encodeEnvelope, toHeaders, } from './envelope.js';
11
+ export { current, captureContext, bind, withOverrides, toEnvelope, fromEnvelope, fromVerifiedEnvelope, describeAuthority, } from './context.js';
12
+ export { CoordinatorError, Lifecycle, startCoordinatorSession, terminateSession, acquireSessionLease, heartbeatSession, createDelegation, revokeDelegation, getInboundDelegation, listInboundDelegations, } from './coordinator.js';
13
+ export { Authority, session, delegate, acceptDelegation, startSession, attachSession } from './primitives.js';
14
+ export { caracalContextMiddleware, caracalFastifyHook } from './http.js';
15
15
  export { Caracal } from './client.js';
16
16
  //# sourceMappingURL=advanced.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"advanced.js","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"advanced.js","sourceRoot":"","sources":["../src/advanced.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,MAAM,EACN,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,WAAW,EACX,cAAc,EACd,cAAc,EACd,SAAS,GACV,MAAM,eAAe,CAAA;AAEtB,OAAO,EACL,OAAO,EACP,cAAc,EACd,IAAI,EACJ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,uBAAuB,EACvB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,kBAAkB,CAAA;AAazB,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAU7G,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAExE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA"}
package/dist/client.d.ts CHANGED
@@ -1,64 +1,249 @@
1
- import { type CaracalContext } from './context.js';
1
+ import { type CaracalContext, type VerifiedClaims } from './context.js';
2
2
  import { type HeaderGetter } from './envelope.js';
3
- import { type CoordinatorClient } from './coordinator.js';
4
- import { type Grant, type ServiceAgent } from './primitives.js';
5
- import { type DelegationConstraints } from './coordinator.js';
3
+ import { type Authority, type Delegation, type SessionHandle } from './primitives.js';
4
+ import { type CoordinatorCallEvent, type CoordinatorClient, type DelegationConstraints } from './coordinator.js';
6
5
  import type { JsonObject } from './json.js';
6
+ import { type ApprovalState, type OAuthEvent } from '@caracalai/oauth';
7
7
  export interface ResourceBinding {
8
8
  resourceId: string;
9
9
  upstreamPrefix: string;
10
10
  }
11
11
  export type TokenSource = () => string | Promise<string>;
12
- export interface CaracalConfig {
13
- coordinator: CoordinatorClient;
12
+ /** The credential triple a client-secret client acts as. */
13
+ export interface ClientCredentials {
14
14
  zoneId: string;
15
15
  applicationId: string;
16
+ clientSecret: string;
17
+ }
18
+ /**
19
+ * Resolves the current client credentials at each control-plane operation, so
20
+ * a secret rotated by a secrets manager or a credential provisioned after the
21
+ * process started is simply presented on the next exchange - the client is
22
+ * never rebuilt. Returning null or undefined means no usable credential exists
23
+ * yet; the operation fails closed with CredentialsUnavailableError.
24
+ */
25
+ export type CredentialsResolver = () => ClientCredentials | null | undefined | Promise<ClientCredentials | null | undefined>;
26
+ /** A control-plane operation ran while the credentials resolver had no usable credential; the operation fails closed. */
27
+ export declare class CredentialsUnavailableError extends Error {
28
+ constructor();
29
+ }
30
+ /** A minted resource mandate and the lifetime the STS granted it. */
31
+ export interface MintedMandate {
32
+ token: string;
33
+ expiresInSeconds: number;
34
+ }
35
+ /** A federated Subject and the mandate proving it. */
36
+ export interface FederatedSubject {
37
+ /** Anchors Coordinator attribution when passed as subjectAuthorityRecordId; it does not alone propagate the user sub to later mints. */
38
+ subjectAuthorityRecordId: string;
39
+ /** The Subject mandate used for user-facing flows; it carries no resource authority. */
40
+ token: string;
41
+ expiresInSeconds: number;
42
+ }
43
+ /**
44
+ * Client-secret credential surface behind a configured Caracal client:
45
+ * resolves the acting identity, invalidates the cached lifecycle token after
46
+ * a server-side rejection, and mints scoped resource mandates for gateway
47
+ * calls. Integrators never construct one: `fromClientSecret` (and profile or
48
+ * environment detection) wires it into the client; the interface exists so
49
+ * the credential surface can be observed and faked in tests.
50
+ */
51
+ export interface ClientSecretExchanger {
52
+ invalidate(): void;
53
+ /** The zone and application the credentials currently resolve to; fails closed when unresolved. */
54
+ identity(): Promise<{
55
+ zoneId: string;
56
+ applicationId: string;
57
+ credentialGeneration?: string;
58
+ }>;
59
+ mintMandate(resourceId: string, scopes: string[], opts?: {
60
+ sessionId?: string;
61
+ delegationId?: string;
62
+ ttlSeconds?: number;
63
+ approvalId?: string;
64
+ signal?: AbortSignal;
65
+ cache?: boolean;
66
+ }): Promise<MintedMandate>;
67
+ /** Exchanges an end user's external identity token for a Subject mandate; never cached. */
68
+ federateSubject(idToken: string, opts?: {
69
+ ttlSeconds?: number;
70
+ timeoutMs?: number;
71
+ signal?: AbortSignal;
72
+ }): Promise<MintedMandate>;
73
+ waitForApproval(approvalId: string, opts?: {
74
+ timeoutMs?: number;
75
+ signal?: AbortSignal;
76
+ }): Promise<ApprovalState>;
77
+ /** Attaches the observability sink token-exchange events report to. */
78
+ onEvent(cb: (event: OAuthEvent) => void): void;
79
+ }
80
+ export interface CaracalConfig {
81
+ coordinator: CoordinatorClient;
82
+ /** Static identity; omitted for a credentials-resolver client, which resolves it through the exchanger per operation. */
83
+ zoneId?: string;
84
+ applicationId?: string;
16
85
  subjectToken?: string;
17
86
  tokenSource?: TokenSource;
87
+ exchanger?: ClientSecretExchanger;
18
88
  gatewayUrl?: string;
19
89
  resources?: ResourceBinding[];
90
+ /** Default TTL for sessions run with session(); a session started with startSession lives by its heartbeat lease instead. */
20
91
  defaultTtlSeconds?: number;
92
+ /** Receives SDK operational warnings (lease loss, cleanup failures, boundary misconfiguration); defaults to console.warn. */
93
+ logger?: (message: string, err?: unknown) => void;
21
94
  }
22
- export interface SpawnOptions {
23
- grant?: Grant;
95
+ export interface SessionOptions {
96
+ authority?: Authority;
24
97
  ttlSeconds?: number;
25
- subjectSessionId?: string;
26
- parentId?: string;
98
+ /** Authority record attached to the Session for Coordinator attribution; it does not alone propagate the user sub to later mints. */
99
+ subjectAuthorityRecordId?: string;
100
+ /** Federated Subject mandate proving control of subjectAuthorityRecordId. */
101
+ subjectAuthorityRecordToken?: string;
102
+ /** Session to parent under; defaults to the session bound on the calling context. */
103
+ parentSessionId?: string;
104
+ /** What this session is for, in operator terms; recorded as metadata.task and shown wherever the session is inspected. */
105
+ task?: string;
27
106
  metadata?: JsonObject;
107
+ /** Role labels the zone's grant policy matches (`input.principal.labels`); descriptive for policy and audit, never grants. */
28
108
  labels?: string[];
109
+ /** W3C trace id (32 lowercase hex characters) to correlate the session under; generated when absent. */
29
110
  traceId?: string;
111
+ /**
112
+ * Optional stable operation identifier from a queue, webhook, workflow, or scheduler.
113
+ * Reusing it with the same inputs replays the Coordinator's session-creation result;
114
+ * changed inputs fail with a conflict. It does not suppress this callback or make
115
+ * downstream side effects exactly once. Ordinary code should omit it.
116
+ */
117
+ idempotencyKey?: string;
118
+ signal?: AbortSignal;
30
119
  }
31
- export interface ServiceOptions {
32
- grant?: Grant;
120
+ export interface StartSessionOptions {
121
+ authority?: Authority;
33
122
  ttlSeconds?: number;
34
- subjectSessionId?: string;
35
- parentId?: string;
123
+ /** Authority record attached to the Session for Coordinator attribution; it does not alone propagate the user sub to later mints. */
124
+ subjectAuthorityRecordId?: string;
125
+ /** Federated Subject mandate proving control of subjectAuthorityRecordId. */
126
+ subjectAuthorityRecordToken?: string;
127
+ /** Session to parent under; defaults to the session bound on the calling context. */
128
+ parentSessionId?: string;
129
+ /** What this session is for, in operator terms; recorded as metadata.task and shown wherever the session is inspected. */
130
+ task?: string;
36
131
  metadata?: JsonObject;
132
+ /** Role labels the zone's grant policy matches (`input.principal.labels`); descriptive for policy and audit, never grants. */
37
133
  labels?: string[];
134
+ /** W3C trace id (32 lowercase hex characters) to correlate the session under; generated when absent. */
38
135
  traceId?: string;
136
+ /** Optional stable operation identifier; see SessionOptions.idempotencyKey for its creation-only guarantee. */
137
+ idempotencyKey?: string;
138
+ /**
139
+ * Auto-heartbeat cadence. Leave unset to derive it from the server lease;
140
+ * a positive value fixes the interval; zero or a negative value disables
141
+ * the background timer, leaving the lease to manual heartbeat calls.
142
+ */
39
143
  heartbeatIntervalMs?: number;
144
+ /** Called once if the coordinator reports the session permanently gone. */
145
+ onLeaseLost?: (err: unknown) => void;
146
+ /** Called when the coordinator reports a different service-session state. */
147
+ onStateChange?: (status: string) => void;
148
+ signal?: AbortSignal;
40
149
  }
41
150
  export interface DelegateOptions {
42
- to: string;
43
- toApplicationId: string;
151
+ toSessionId: string;
152
+ /** Receiving application; defaults to the caller's own application for same-app delegation. */
153
+ toApplicationId?: string;
44
154
  resourceId?: string;
45
155
  scopes: string[];
46
156
  constraints?: DelegationConstraints;
47
- ttlSeconds?: number;
157
+ ttlSeconds: number;
158
+ signal?: AbortSignal;
48
159
  }
49
160
  export type LifecycleHook = (ctx: CaracalContext) => void | Promise<void>;
50
- export interface RootOptions {
51
- allowRoot?: boolean;
161
+ /**
162
+ * One delegation presentation by this client. Emitted whenever
163
+ * acceptDelegation binds a delegation - and with `ok: false` when an
164
+ * up-front validation rejects it - so a forensic consumer can correlate
165
+ * which workload presented which delegation on which session.
166
+ */
167
+ export interface DelegationAcceptEvent {
168
+ type: 'delegation.accept';
169
+ delegationId: string;
170
+ sessionId?: string;
171
+ validated: boolean;
172
+ ok: boolean;
173
+ durationMs: number;
174
+ }
175
+ /** Control-plane operation reported to onEvent subscribers: a token exchange, an approval wait, a coordinator call, or a delegation acceptance. */
176
+ export type CaracalEvent = OAuthEvent | CoordinatorCallEvent | DelegationAcceptEvent;
177
+ export type EventHook = (event: CaracalEvent) => void;
178
+ /** Identity selection for calls made outside a bound session context. */
179
+ export interface CallOptions {
180
+ /** Run the call as the application's own identity instead of a bound session; explicit opt-in. */
181
+ asApplication?: boolean;
182
+ }
183
+ /**
184
+ * Transport behavior options. `scopes` switches gateway-routed requests from
185
+ * the raw subject token to a scoped resource mandate minted for the routed
186
+ * resource and the bound session identity; requires a client-secret
187
+ * configuration. `approvalId` retries an approval-gated mint so the request
188
+ * proceeds once an authenticated approver has satisfied the hold. `timeoutMs`
189
+ * bounds every request the transport sends when the caller supplies no signal
190
+ * of its own (combined when both are present). `propagation` controls where
191
+ * the context envelope (traceparent, baggage) is written: 'always' (the
192
+ * default) propagates to every host so downstream Caracal-aware services can
193
+ * rebind context; 'gateway-only' keeps the caracal.* correlation ids off
194
+ * third-party hosts.
195
+ */
196
+ export interface TransportOptions extends CallOptions {
197
+ scopes?: string[];
198
+ approvalId?: string;
199
+ timeoutMs?: number;
200
+ propagation?: 'always' | 'gateway-only';
201
+ }
202
+ /** Optional mint inputs: a TTL override, the approval id for retrying an approval-gated mint, an explicit context, and an abort signal. */
203
+ export interface MandateOptions {
204
+ ttlSeconds?: number;
205
+ approvalId?: string;
206
+ ctx?: CaracalContext;
207
+ signal?: AbortSignal;
208
+ }
209
+ /**
210
+ * Application transport behavior. `scopes` is the authority every request
211
+ * presents; `labels` mark the sessions each mint cycle starts and are the
212
+ * role labels the zone's grant policy matches - unset, they default to the
213
+ * application id, the same default role `ensureGrants` authors, so a grant
214
+ * and its transport align without either naming a role; `mandateTtlSeconds`
215
+ * bounds each minted mandate (the backing sessions outlive it by a small
216
+ * buffer).
217
+ */
218
+ export interface ApplicationTransportOptions {
219
+ scopes: string[];
220
+ labels?: string[];
221
+ mandateTtlSeconds?: number;
222
+ approvalId?: string;
223
+ timeoutMs?: number;
224
+ }
225
+ export interface BindOptions extends CallOptions {
226
+ verify?: (token: string) => void | VerifiedClaims | Promise<void | VerifiedClaims>;
52
227
  }
53
228
  export interface ClientSecretOptions {
54
229
  coordinatorUrl: string;
55
230
  stsUrl: string;
56
- zoneId: string;
57
- applicationId: string;
58
- clientSecret: string;
59
- resources: Array<string | ResourceBinding>;
231
+ /** Static credential triple; pass `credentials` instead when the credential is rotated or provisioned at runtime. */
232
+ zoneId?: string;
233
+ applicationId?: string;
234
+ clientSecret?: string;
235
+ /** Dynamic credential source, resolved per operation; exactly one of this or the static triple is required. */
236
+ credentials?: CredentialsResolver;
237
+ /**
238
+ * Resources this client mints lifecycle tokens for and routes through the
239
+ * Gateway. Optional for a client that only runs application transports,
240
+ * which mint per resource; session and lifecycle paths require at least one.
241
+ */
242
+ resources?: Array<string | ResourceBinding>;
60
243
  gatewayUrl?: string;
61
244
  scope?: string;
245
+ /** Seeds CaracalConfig.defaultTtlSeconds for sessions run with session(). */
246
+ defaultTtlSeconds?: number;
62
247
  fetchImpl?: typeof fetch;
63
248
  }
64
249
  export interface CaracalOptions {
@@ -66,14 +251,21 @@ export interface CaracalOptions {
66
251
  configPath?: string;
67
252
  clientSecret?: Partial<ClientSecretOptions>;
68
253
  }
69
- export interface GatewayRequest {
254
+ export interface GatewayTarget {
70
255
  url: string;
71
256
  headers: Record<string, string>;
72
257
  }
73
258
  export declare class Caracal {
74
259
  readonly config: CaracalConfig;
75
- private agentStartHooks;
76
- private agentEndHooks;
260
+ private sessionStartHooks;
261
+ private sessionEndHooks;
262
+ private eventHooks;
263
+ private appAuthorities;
264
+ private appInflight;
265
+ private appGeneration;
266
+ private appProvisionTail;
267
+ private closed;
268
+ private unverifiedBoundaryWarned;
77
269
  /**
78
270
  * Creates a Caracal client. With no arguments, credentials are
79
271
  * auto-detected from `CARACAL_CONFIG`, the default generated profile, or
@@ -84,41 +276,261 @@ export declare class Caracal {
84
276
  static fromEnv(env?: NodeJS.ProcessEnv): Caracal;
85
277
  static fromClientSecret(opts: ClientSecretOptions): Caracal;
86
278
  static fromConfig(path?: string, env?: NodeJS.ProcessEnv): Caracal;
279
+ /**
280
+ * Releases client-held state: cached application mandates and their
281
+ * in-flight mint cycles are dropped, the credential exchanger's cached
282
+ * lifecycle token is invalidated, and the sessions backing released
283
+ * application transports are terminated best-effort - any that termination
284
+ * misses retire on their own TTL. The client stays usable; the next call
285
+ * simply mints fresh state.
286
+ */
87
287
  close(): Promise<void>;
88
- spawn<T>(fn: () => Promise<T>, opts?: SpawnOptions): Promise<T>;
89
- spawnService(opts?: ServiceOptions): Promise<ServiceAgent>;
90
- delegate<T>(opts: DelegateOptions, fn: () => Promise<T>): Promise<T>;
288
+ private warnLog;
289
+ private ensureOpen;
290
+ /**
291
+ * The zone and application this client acts as: the static configuration
292
+ * when present, otherwise resolved through the credentials source, which
293
+ * fails closed while no usable credential exists. Useful for logging and
294
+ * metric labels.
295
+ */
296
+ identity(): Promise<{
297
+ zoneId: string;
298
+ applicationId: string;
299
+ }>;
300
+ /**
301
+ * Run fn inside a governed session: a bounded identity Caracal establishes
302
+ * around whatever fn executes - an AI agent step, a job, a tool call, any
303
+ * code. The session binds delegated authority, records audit attribution,
304
+ * and is retired when fn returns. Pass `authority: Authority.narrow([...])`
305
+ * to bound the session to a subset of the caller's scopes.
306
+ *
307
+ * @example
308
+ * ```ts
309
+ * const summary = await caracal.session(async (ctx) => {
310
+ * log.info({ sessionId: ctx.sessionId }, 'triage run started')
311
+ * const res = await caracal.fetch('resource://pipernet', '/tickets')
312
+ * return res.json()
313
+ * }, { labels: ['ticket-triage'] })
314
+ * ```
315
+ *
316
+ * @example Narrowed child session
317
+ * ```ts
318
+ * await caracal.session(work, {
319
+ * authority: Authority.narrow(['tickets:read'], { resourceId: 'resource://pipernet' }),
320
+ * })
321
+ * ```
322
+ */
323
+ session<T>(fn: (ctx: CaracalContext) => Promise<T>, opts?: SessionOptions): Promise<T>;
324
+ /**
325
+ * Start a governed session that outlives a block: the returned handle keeps
326
+ * the session's lease renewed and the holder retires it with close.
327
+ */
328
+ startSession(opts?: StartSessionOptions): Promise<SessionHandle>;
329
+ /**
330
+ * Delegate a slice of the current session's authority to an existing peer
331
+ * session and return the created delegation. The receiver defaults to the
332
+ * caller's own application; name toApplicationId only for cross-application
333
+ * delegation. The receiving session accepts it with acceptDelegation.
334
+ */
335
+ delegate(opts: DelegateOptions): Promise<Delegation>;
336
+ /**
337
+ * Run fn under a delegation received from a peer: the current session
338
+ * context is rebound so token exchanges inside fn present the delegation.
339
+ * Acceptance is local; the STS re-validates the delegation on every mint,
340
+ * so a wrong or revoked id fails at first use. Pass `{ validate: true }`
341
+ * to check the delegation up front instead: the coordinator's inbound
342
+ * listing for this session must hold it live, so a mistyped or already
343
+ * revoked id fails here with a targeted error.
344
+ */
345
+ acceptDelegation<T>(delegationId: string, fn: () => Promise<T>, opts?: {
346
+ validate?: boolean;
347
+ }): Promise<T>;
348
+ /**
349
+ * Re-attach to a service session that already exists - typically after a
350
+ * process restart, using a session id the previous holder persisted (for
351
+ * example from SessionHandle.sessionId). The session is validated with an
352
+ * immediate lease renewal, and the returned handle renews and retires it
353
+ * exactly like one from startSession.
354
+ */
355
+ attachSession(sessionId: string, opts?: {
356
+ heartbeatIntervalMs?: number;
357
+ onLeaseLost?: (err: unknown) => void;
358
+ }): Promise<SessionHandle>;
91
359
  bind<T>(ctx: CaracalContext, fn: () => Promise<T>): Promise<T>;
92
- onAgentStart(cb: LifecycleHook): void;
93
- onAgentEnd(cb: LifecycleHook): void;
360
+ onSessionStart(cb: LifecycleHook): void;
361
+ onSessionEnd(cb: LifecycleHook): void;
362
+ /**
363
+ * Subscribes to control-plane operation events: token exchanges (with cache
364
+ * outcome), approval waits, and coordinator calls, each carrying outcome and
365
+ * duration. Bridge them to any metrics or tracing system; a hook that throws
366
+ * is ignored and never disturbs the operation that emitted the event.
367
+ * Returns a disposer that unsubscribes the hook.
368
+ */
369
+ onEvent(cb: EventHook): () => void;
370
+ private emitEvent;
94
371
  private fire;
95
372
  current(): CaracalContext | undefined;
96
- headers(opts?: RootOptions): Record<string, string>;
97
- headersAsync(opts?: RootOptions): Promise<Record<string, string>>;
98
- bindFromHeaders<T>(headers: Record<string, string | string[] | undefined> | HeaderGetter, fn: () => Promise<T>, opts?: RootOptions): Promise<T>;
99
373
  /**
100
- * Returns a fetch-shaped function that injects the Caracal envelope (traceparent
101
- * + baggage) onto outbound requests and, for gateway-routed calls, replaces the
102
- * `Authorization` header with the current subject token. Pass to any provider
103
- * SDK that accepts a custom fetch.
374
+ * Envelope headers plus the bearer credential for the current context.
375
+ * The context token is returned as bound; long-lived holders needing a
376
+ * refreshed own-credential token use headersAsync. Calling as the
377
+ * application's own identity requires `{ asApplication: true }`.
378
+ */
379
+ headers(opts?: CallOptions): Record<string, string>;
380
+ /**
381
+ * Async variant of headers. For contexts this process established from its
382
+ * own credentials, the bearer is resolved fresh through the token source so
383
+ * long-lived holders never present an expired token; inbound contexts stay
384
+ * pinned to the caller's token.
385
+ */
386
+ headersAsync(opts?: CallOptions): Promise<Record<string, string>>;
387
+ bindFromHeaders<T>(headers: Headers | Record<string, string | string[] | undefined> | HeaderGetter, fn: () => Promise<T>, opts?: BindOptions): Promise<T>;
388
+ /**
389
+ * Returns a fetch-shaped function that injects the Caracal context envelope
390
+ * (traceparent, tracestate, baggage) onto outbound requests, merging with any
391
+ * headers the caller or an OpenTelemetry SDK already set. The bearer is
392
+ * attached only to gateway-routed calls, where the Gateway terminates it at
393
+ * the trust boundary: a scoped mandate when `scopes` is set, otherwise the
394
+ * context's subject token. No default timeout is applied unless `timeoutMs`
395
+ * is set; a caller-supplied `init.signal` still applies (combined when both
396
+ * are present). Pass to any provider SDK that accepts a custom fetch.
397
+ *
398
+ * @example Hand the transport to a provider SDK
399
+ * ```ts
400
+ * const openai = new OpenAI({
401
+ * baseURL: 'https://api.pipernet.example/v1',
402
+ * apiKey: 'unused-gateway-injects-credentials',
403
+ * fetch: caracal.transport({ scopes: ['chat:complete'] }),
404
+ * })
405
+ * ```
406
+ */
407
+ transport(opts?: TransportOptions): typeof fetch;
408
+ /**
409
+ * Resolves the bearer for a gateway-bound request: a scoped mandate when
410
+ * scopes are set and the routed resource is known, a fresh token from the
411
+ * token source for contexts this process established from its own
412
+ * credentials, the pinned context token for inbound-bound contexts, or the
413
+ * application token when running as the application itself.
414
+ */
415
+ private gatewayToken;
416
+ /**
417
+ * Mints a resource mandate for the current session: a short-lived token
418
+ * audienced to `resourceId` and narrowed to `scopes`, carrying the session
419
+ * and delegation of the bound context. The STS evaluates policy against
420
+ * that session's authority, so a narrowed child can mint only what its
421
+ * delegation allows. Results are cached per resource, scope set, and
422
+ * session identity, and refreshed before expiry.
423
+ *
424
+ * When a scope is approval-gated this throws ApprovalRequiredError; retry
425
+ * with `approvalId` set to the returned approval id once an authenticated
426
+ * approver has satisfied it. Requires a client-secret configuration.
104
427
  */
105
- transport(opts?: RootOptions): typeof fetch;
106
- gatewayRequest(resourceId: string, path?: string): GatewayRequest;
428
+ mintMandate(resourceId: string, scopes: string[], opts?: MandateOptions): Promise<MintedMandate>;
429
+ /**
430
+ * Exchange an end user's identity token from a zone-trusted external issuer
431
+ * for the Subject's Caracal Authority record. The returned subjectAuthorityRecordId
432
+ * anchors governed work to that user (`session(fn, { subjectAuthorityRecordId })`),
433
+ * and the returned token is the user's own mandate for user-facing flows
434
+ * such as approval decisions. Never cached: each federation is an explicit
435
+ * identity event, recorded in the audit stream. Requires a client-secret
436
+ * configuration and a subject issuer registered on the zone.
437
+ */
438
+ federateSubject(idToken: string, opts?: {
439
+ ttlSeconds?: number;
440
+ timeoutMs?: number;
441
+ signal?: AbortSignal;
442
+ }): Promise<FederatedSubject>;
443
+ /**
444
+ * Long-polls an approval until an approver decides it, it
445
+ * expires, or the timeout elapses. Returns the final lifecycle state:
446
+ * 'approved' means retrying the mint with `approvalId` set will succeed;
447
+ * 'rejected', 'expired', and 'consumed' are terminal; 'pending' means the
448
+ * timeout elapsed with no decision and waiting again is safe. Pass `signal`
449
+ * to abort the wait early.
450
+ */
451
+ waitForApproval(approvalId: string, opts?: {
452
+ timeoutMs?: number;
453
+ signal?: AbortSignal;
454
+ }): Promise<ApprovalState>;
455
+ /**
456
+ * Runs an approval-gated operation end to end. fn is invoked once; when it
457
+ * throws ApprovalRequiredError the client long-polls the approval and, on
458
+ * approval, invokes fn again with the approval id so the retried mint
459
+ * consumes the decision. Any other outcome (rejected, expired, consumed, or
460
+ * the wait timing out) rethrows the original ApprovalRequiredError, whose
461
+ * approvalId lets the caller resume the wait later.
462
+ *
463
+ * @example
464
+ * ```ts
465
+ * const mandate = await caracal.withApproval((approvalId) =>
466
+ * caracal.mintMandate('resource://pipernet', ['funds:transfer'], { approvalId }),
467
+ * )
468
+ * ```
469
+ */
470
+ withApproval<T>(fn: (approvalId?: string) => Promise<T>, opts?: {
471
+ timeoutMs?: number;
472
+ signal?: AbortSignal;
473
+ }): Promise<T>;
474
+ gatewayRequest(resourceId: string, path?: string): GatewayTarget;
107
475
  /**
108
476
  * One-call happy path: sends `init` to `path` on the given resource through the
109
- * Gateway with Caracal context and authority injected. Equivalent to building a
110
- * `gatewayRequest` and calling it with `transport`. The resource header always
111
- * wins over any caller-supplied `X-Caracal-Resource`.
477
+ * Gateway with Caracal context and authority injected. Accepts the transport
478
+ * options inline: pass `scopes` to authorize with a scoped resource mandate,
479
+ * `approvalId` to retry an approval-gated call once its hold is satisfied, and
480
+ * `asApplication` to call as the application's own identity. The resource header
481
+ * always wins over any caller-supplied `X-Caracal-Resource`. No default timeout
482
+ * is applied; pass `timeoutMs` or `signal` (e.g. AbortSignal.timeout) to bound a call.
483
+ */
484
+ fetch(resourceId: string, path?: string, init?: RequestInit & TransportOptions): Promise<Response>;
485
+ /**
486
+ * A fetch pinned to one resource, running as the application's own
487
+ * identity rather than a bound session context: each mint cycle bootstraps
488
+ * a lifecycle token, starts a source/target session pair, narrows the
489
+ * target to `scopes` on the resource over a delegation, and mints a fresh
490
+ * single-use mandate for every request. Authority cycles are cached per
491
+ * resolved identity, resource, scope set, effective labels, and mandate
492
+ * TTL, then re-provisioned on a fresh session pair before expiry; sessions from
493
+ * a failed cycle are terminated best-effort. Requests already addressed to
494
+ * the Gateway pass through unchanged; other absolute URLs are rewritten
495
+ * onto it. When a scope is approval-gated the request rejects with
496
+ * ApprovalRequiredError. Requires a client-secret configuration.
497
+ *
498
+ * Provisioning costs four control-plane calls (bootstrap mint, two session
499
+ * spawns, one delegation) roughly once per TTL. Every Gateway request then
500
+ * performs one uncached final mint so its replay-protected JTI is unique.
501
+ *
502
+ * @example Application-identity LLM transport
503
+ * ```ts
504
+ * const llm = new OpenAI({
505
+ * baseURL: 'https://api.pipernet.example/v1',
506
+ * apiKey: 'unused-gateway-injects-credentials',
507
+ * fetch: caracal.applicationTransport('resource://pipernet', { scopes: ['chat:complete'] }),
508
+ * })
509
+ * ```
112
510
  */
113
- fetch(resourceId: string, path?: string, init?: RequestInit, opts?: RootOptions): Promise<Response>;
511
+ applicationTransport(resourceId: string, opts: ApplicationTransportOptions): typeof fetch;
512
+ private appAuthority;
513
+ private withAppProvisioning;
514
+ private appAuthorityCycle;
515
+ private retireAppAuthorities;
114
516
  private routeThroughGateway;
517
+ /** Reports whether the request is already addressed to the Gateway origin, where the subject token terminates. */
518
+ private targetsGateway;
115
519
  /**
116
- * Binds Caracal context after a verifier boundary. This does not verify JWT
117
- * signatures, audience, scopes, token use, or revocation.
520
+ * Binds Caracal context at the inbound request boundary. Pass `verify` to
521
+ * enforce the bearer token before binding; without it this middleware only
522
+ * propagates context and must sit behind a verifier boundary. Boundary
523
+ * failures (missing token, verify rejection) answer 401 directly; errors
524
+ * thrown by downstream handlers still flow to the framework error path.
118
525
  */
119
- contextMiddleware(opts?: RootOptions): (req: {
526
+ contextMiddleware(opts?: BindOptions): (req: {
120
527
  headers: Record<string, string | string[] | undefined>;
121
- }, _res: unknown, next: (err?: unknown) => void) => void;
528
+ }, res: {
529
+ statusCode: number;
530
+ setHeader(name: string, value: string): void;
531
+ end(body?: string): void;
532
+ }, next: (err?: unknown) => void) => void;
533
+ private invalidate;
122
534
  private rootTokenSync;
123
535
  private rootToken;
124
536
  }