@stackwright-services/runtime 0.0.1

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.
@@ -0,0 +1,985 @@
1
+ import { CapabilityContext } from '@stackwright-services/capabilities';
2
+ import { HttpTrigger, EventTrigger, ScheduleTrigger, QueueTrigger } from '@stackwright-services/types';
3
+ import { Hono } from 'hono';
4
+ import { z } from 'zod';
5
+ import { SNSClient } from '@aws-sdk/client-sns';
6
+ import { SQSClient } from '@aws-sdk/client-sqs';
7
+ export { CapabilityDescriptor, DataSourceDescriptorEntry, PrebuildRegistration, TriggerTypeDescriptor, registerServices } from './prebuild/index.mjs';
8
+
9
+ /**
10
+ * Options for creating an HTTP trigger runtime.
11
+ */
12
+ interface HttpTriggerOptions {
13
+ /** The HTTP trigger configuration from the flow */
14
+ trigger: HttpTrigger;
15
+ /** Flow handler function */
16
+ handler: (input: unknown, context: CapabilityContext) => Promise<unknown>;
17
+ /** Optional input validation schema */
18
+ inputSchema?: z.ZodType;
19
+ /** Optional output validation schema */
20
+ outputSchema?: z.ZodType;
21
+ }
22
+ /**
23
+ * Typed error response for consistent error handling.
24
+ */
25
+ interface ServiceError {
26
+ error: string;
27
+ code: string;
28
+ details?: unknown;
29
+ }
30
+ /**
31
+ * Create a Hono app for an HTTP trigger.
32
+ *
33
+ * This is the runtime entry point for HTTP-triggered flows.
34
+ * The same code works on:
35
+ * - Node.js (via @hono/node-server)
36
+ * - AWS Lambda (via Lambda Web Adapter)
37
+ * - Edge runtimes (Cloudflare Workers, Deno Deploy)
38
+ */
39
+ declare function createHttpTrigger(options: HttpTriggerOptions): Hono;
40
+
41
+ /**
42
+ * Message payload carried through the bus.
43
+ * The payload is typed at the flow level; here it is `unknown` because
44
+ * the provider is transport-agnostic.
45
+ */
46
+ interface BusMessage {
47
+ /** Message payload (typed at the flow level via Zod schemas) */
48
+ payload: unknown;
49
+ /** W3C Trace Context headers for distributed trace propagation */
50
+ traceContext?: Record<string, string>;
51
+ /** Message metadata */
52
+ metadata?: MessageMetadata;
53
+ }
54
+ interface MessageMetadata {
55
+ /** ISO 8601 timestamp of when the message was produced */
56
+ timestamp?: string;
57
+ /** Logical source identifier (e.g. the flow that published) */
58
+ source?: string;
59
+ /** Correlation ID for request-scoped grouping */
60
+ correlationId?: string;
61
+ /** Unique message ID (assigned by the provider if not supplied) */
62
+ messageId?: string;
63
+ }
64
+ interface PublishResult {
65
+ /** Provider-assigned message ID */
66
+ messageId: string;
67
+ /** Topic the message was published to */
68
+ topic: string;
69
+ /** Whether publishing succeeded */
70
+ success: boolean;
71
+ }
72
+ /**
73
+ * Handler function invoked when a message arrives on a subscribed topic.
74
+ */
75
+ type MessageHandler = (message: BusMessage) => Promise<void>;
76
+ /**
77
+ * A live subscription to a topic. Call cancel() to stop receiving messages.
78
+ */
79
+ interface Subscription {
80
+ /** Unique subscription ID */
81
+ id: string;
82
+ /** The topic this subscription is listening on */
83
+ topic: string;
84
+ /** Cancel this subscription — stops message delivery */
85
+ cancel(): Promise<void>;
86
+ }
87
+ /**
88
+ * MessageBusProvider adapter interface.
89
+ *
90
+ * Same adapter pattern as ObservabilityProvider:
91
+ * - Interface defined here
92
+ * - InMemoryMessageBusProvider for testing (in-memory delivery)
93
+ * - Concrete adapters (SQS/SNS, Kafka, etc.) bind at the deployment target
94
+ * - Factory selects provider based on environment configuration
95
+ *
96
+ * Topic format: the YAML writes `bus:<topic-name>` (e.g. `bus:equipment-status`).
97
+ * The provider receives just the topic name (e.g. `equipment-status`) after the
98
+ * `bus:` prefix is stripped by the trigger adapter or effect that calls it.
99
+ *
100
+ * Concrete bus implementations (SQS, Kafka, NATS) are hidden behind this interface.
101
+ * A citizen developer's YAML references `bus:equipment-status`; it never knows
102
+ * whether the underlying transport is SQS in the demo or Kafka in the cluster.
103
+ */
104
+ interface MessageBusProvider {
105
+ /** Publish a message to a topic */
106
+ publish(topic: string, message: BusMessage): Promise<PublishResult>;
107
+ /** Subscribe to a topic with a handler. Returns a Subscription handle. */
108
+ subscribe(topic: string, handler: MessageHandler): Promise<Subscription>;
109
+ /** Unsubscribe — equivalent to calling subscription.cancel() */
110
+ unsubscribe(subscription: Subscription): Promise<void>;
111
+ }
112
+
113
+ /**
114
+ * Trigger adapter interface -- the seam between trigger types and the runtime.
115
+ * Each trigger type (http, schedule, queue, event) has its own adapter.
116
+ */
117
+ interface TriggerAdapter {
118
+ /** Trigger type this adapter handles */
119
+ readonly type: string;
120
+ /** Start listening for this trigger */
121
+ start(): Promise<void>;
122
+ /** Stop listening */
123
+ stop(): Promise<void>;
124
+ }
125
+
126
+ /**
127
+ * Options for creating an event trigger adapter.
128
+ */
129
+ interface EventTriggerOptions {
130
+ /** The event trigger configuration from the flow */
131
+ trigger: EventTrigger;
132
+ /** Message bus provider to subscribe through */
133
+ messageBus: MessageBusProvider;
134
+ /** Flow handler function invoked for each valid message */
135
+ handler: (input: unknown, context: CapabilityContext) => Promise<unknown>;
136
+ /** Optional Zod schema for input validation — invalid messages are logged and skipped */
137
+ inputSchema?: z.ZodType;
138
+ }
139
+ /**
140
+ * EventTriggerAdapter — subscribes to a MessageBusProvider topic and routes
141
+ * incoming messages through the compiled flow handler.
142
+ *
143
+ * Bus consumers must never crash: handler errors are logged, not rethrown.
144
+ * Validation failures are logged as warnings and the message is skipped.
145
+ */
146
+ declare class EventTriggerAdapter implements TriggerAdapter {
147
+ readonly type = "event";
148
+ readonly topic: string;
149
+ private readonly messageBus;
150
+ private readonly handler;
151
+ private readonly inputSchema;
152
+ private subscription;
153
+ constructor(options: EventTriggerOptions);
154
+ start(): Promise<void>;
155
+ stop(): Promise<void>;
156
+ private handleMessage;
157
+ }
158
+
159
+ /**
160
+ * Options for creating a schedule trigger adapter.
161
+ */
162
+ interface ScheduleTriggerOptions {
163
+ /** The schedule trigger configuration from the flow */
164
+ trigger: ScheduleTrigger;
165
+ /** Flow handler function invoked on each tick */
166
+ handler: (input: unknown, context: CapabilityContext) => Promise<unknown>;
167
+ /** Optional Zod schema for input validation */
168
+ inputSchema?: z.ZodType;
169
+ /**
170
+ * Override interval in milliseconds for local dev / testing.
171
+ * When not provided, defaults to 60_000ms and logs a warning that
172
+ * cron parsing is not built-in. In production, scheduling is handled
173
+ * by infrastructure (EventBridge, CronJob on K8s, etc.).
174
+ */
175
+ intervalMs?: number;
176
+ }
177
+ /**
178
+ * ScheduleTriggerAdapter — local-dev / test scheduler that fires the flow
179
+ * handler on a fixed interval.
180
+ *
181
+ * NOT a production cron implementation. In production the cron expression
182
+ * is consumed by infrastructure (EventBridge rules, K8s CronJobs, etc.)
183
+ * and this adapter is not used.
184
+ *
185
+ * Handler errors are caught and logged — the scheduler never crashes.
186
+ */
187
+ declare class ScheduleTriggerAdapter implements TriggerAdapter {
188
+ readonly type = "schedule";
189
+ readonly cron: string;
190
+ private readonly handler;
191
+ private readonly inputSchema;
192
+ private readonly intervalMs;
193
+ private timer;
194
+ constructor(options: ScheduleTriggerOptions);
195
+ start(): Promise<void>;
196
+ stop(): Promise<void>;
197
+ private tick;
198
+ }
199
+
200
+ /**
201
+ * Options for creating a queue trigger adapter.
202
+ */
203
+ interface QueueTriggerOptions {
204
+ /** The queue trigger configuration from the flow */
205
+ trigger: QueueTrigger;
206
+ /** Message bus provider to subscribe through */
207
+ messageBus: MessageBusProvider;
208
+ /** Flow handler function invoked for each valid message */
209
+ handler: (input: unknown, context: CapabilityContext) => Promise<unknown>;
210
+ /** Optional Zod schema for input validation — invalid messages are logged and skipped */
211
+ inputSchema?: z.ZodType;
212
+ }
213
+ /**
214
+ * QueueTriggerAdapter — subscribes to a MessageBusProvider topic and routes
215
+ * incoming messages through the compiled flow handler.
216
+ *
217
+ * Semantically represents point-to-point delivery (single consumer), unlike
218
+ * the fan-out semantics of EventTriggerAdapter. At the runtime adapter level
219
+ * both use messageBus.subscribe() — the distinction lives in the concrete
220
+ * bus implementation (SQS vs SNS+SQS, competing consumers vs broadcast).
221
+ *
222
+ * Bus consumers must never crash: handler errors are logged, not rethrown.
223
+ * Validation failures are logged as warnings and the message is skipped.
224
+ */
225
+ declare class QueueTriggerAdapter implements TriggerAdapter {
226
+ readonly type = "queue";
227
+ readonly queueName: string;
228
+ private readonly messageBus;
229
+ private readonly handler;
230
+ private readonly inputSchema;
231
+ private subscription;
232
+ constructor(options: QueueTriggerOptions);
233
+ start(): Promise<void>;
234
+ stop(): Promise<void>;
235
+ private handleMessage;
236
+ }
237
+
238
+ /**
239
+ * ObservabilityProvider interface -- what the compiled flow code calls.
240
+ * The compiler generates code that calls these methods.
241
+ * The runtime injects either OTel or no-op based on configuration.
242
+ */
243
+ interface ObservabilityProvider {
244
+ /** Start a span for a capability invocation */
245
+ startSpan(name: string, attributes?: Record<string, string | number | boolean>): SpanHandle;
246
+ /** Record a metric (counter, histogram, etc.) */
247
+ recordMetric(name: string, value: number, tags?: Record<string, string>): void;
248
+ }
249
+ interface SpanHandle {
250
+ /** Set an attribute on the span */
251
+ setAttribute(key: string, value: string | number | boolean): void;
252
+ /** Mark the span as errored */
253
+ setError(error: Error): void;
254
+ /** End the span (record duration) */
255
+ end(): void;
256
+ }
257
+
258
+ /**
259
+ * No-op observability provider -- zero runtime cost.
260
+ * Used when OTel is not configured (the default).
261
+ * All methods are no-ops that return immediately.
262
+ */
263
+ declare class NoopObservabilityProvider implements ObservabilityProvider {
264
+ startSpan(name: string, attributes?: Record<string, string | number | boolean>): SpanHandle;
265
+ recordMetric(name: string, value: number, tags?: Record<string, string>): void;
266
+ }
267
+ /** Singleton no-op provider */
268
+ declare const noopProvider: NoopObservabilityProvider;
269
+
270
+ /**
271
+ * OpenTelemetry-compatible observability provider.
272
+ *
273
+ * In Phase 1, this uses structured console logging with span semantics.
274
+ * In Phase 2+, this wraps @opentelemetry/api (trace + metrics).
275
+ *
276
+ * The interface is stable -- upgrading to real OTel is a drop-in replacement.
277
+ */
278
+ declare class OtelObservabilityProvider implements ObservabilityProvider {
279
+ private readonly serviceName;
280
+ constructor(serviceName?: string);
281
+ startSpan(name: string, attributes?: Record<string, string | number | boolean>): SpanHandle;
282
+ recordMetric(name: string, value: number, tags?: Record<string, string>): void;
283
+ }
284
+
285
+ /**
286
+ * Create an observability provider based on environment configuration.
287
+ *
288
+ * - OTEL_ENABLED=true (or "1") -> OtelObservabilityProvider
289
+ * - Otherwise -> NoopObservabilityProvider (zero cost)
290
+ *
291
+ * The provider is injected into compiled flow code, not imported.
292
+ * This means zero runtime overhead when observability is disabled.
293
+ */
294
+ declare function createObservabilityProvider(options?: {
295
+ serviceName?: string;
296
+ }): ObservabilityProvider;
297
+
298
+ /**
299
+ * AuditProvider interface -- records structured audit events for capability invocations.
300
+ *
301
+ * Audit events capture WHAT happened (capability, inputs, outputs, status)
302
+ * while ObservabilityProvider captures HOW it performed (latency, spans, metrics).
303
+ *
304
+ * These are two views of one stream -- same invocation, different concerns.
305
+ */
306
+ /** Structured audit event -- one per capability invocation */
307
+ interface AuditEvent {
308
+ /** Event type discriminator */
309
+ type: 'capability_invocation' | 'workflow_transition' | 'flow_start' | 'flow_end';
310
+ /** W3C trace ID (propagated from request context) */
311
+ traceId: string;
312
+ /** ISO 8601 timestamp */
313
+ timestamp: string;
314
+ /** Flow or workflow ID */
315
+ flowId: string;
316
+ /** Step name within the flow */
317
+ stepName: string;
318
+ /** Capability that was invoked (e.g. 'collection.filter') */
319
+ capabilityName: string;
320
+ /** Truncated SHA-256 of serialized input (first 16 hex chars) */
321
+ inputHash: string;
322
+ /** Truncated SHA-256 of serialized output (first 16 hex chars) */
323
+ outputHash: string;
324
+ /** Duration in milliseconds */
325
+ durationMs: number;
326
+ /** Success or failure */
327
+ status: 'success' | 'error';
328
+ /** Error message if status is 'error' */
329
+ error?: string;
330
+ }
331
+ /** Provider interface -- implementations decide WHERE events go */
332
+ interface AuditProvider {
333
+ /** Record a single audit event */
334
+ record(event: AuditEvent): void;
335
+ /** Flush any buffered events (for graceful shutdown) */
336
+ flush(): Promise<void>;
337
+ }
338
+
339
+ /** No-op audit provider -- zero runtime cost when auditing is disabled */
340
+ declare class NoopAuditProvider implements AuditProvider {
341
+ record(_event: AuditEvent): void;
342
+ flush(): Promise<void>;
343
+ }
344
+ /** Singleton no-op audit provider */
345
+ declare const noopAuditProvider: NoopAuditProvider;
346
+
347
+ /**
348
+ * Console audit provider -- writes events to stderr as JSON Lines.
349
+ *
350
+ * Works in Lambda (stderr -> CloudWatch), containers (stderr -> log collector),
351
+ * and local dev (stderr -> terminal).
352
+ *
353
+ * Uses stderr to avoid mixing with application stdout.
354
+ */
355
+ declare class ConsoleAuditProvider implements AuditProvider {
356
+ record(event: AuditEvent): void;
357
+ flush(): Promise<void>;
358
+ }
359
+
360
+ /**
361
+ * Create an audit provider based on environment configuration.
362
+ *
363
+ * - STACKWRIGHT_AUDIT=true (or "1") -> ConsoleAuditProvider (stderr JSON Lines)
364
+ * - Otherwise -> NoopAuditProvider (zero cost)
365
+ *
366
+ * Defaults to OFF in production, can be enabled with env var.
367
+ */
368
+ declare function createAuditProvider(): AuditProvider;
369
+
370
+ /**
371
+ * Compute a truncated SHA-256 hash of a value.
372
+ *
373
+ * Used for audit events -- captures enough to detect "did the input/output change?"
374
+ * without storing full payloads (which may be large or contain PII).
375
+ *
376
+ * Returns first 16 hex characters of SHA-256.
377
+ * Deterministic: same input always produces same hash.
378
+ */
379
+ declare function auditHash(value: unknown): string;
380
+
381
+ /**
382
+ * In-memory MessageBusProvider — delivers messages locally within the process.
383
+ *
384
+ * Used for:
385
+ * - Unit and integration testing (no infrastructure required)
386
+ * - Local development (stackwright-services dev mode)
387
+ * - Default when no external bus is configured
388
+ *
389
+ * Messages published to a topic are delivered synchronously to all
390
+ * subscribed handlers for that topic.
391
+ */
392
+ declare class InMemoryMessageBusProvider implements MessageBusProvider {
393
+ private subscriptions;
394
+ private nextId;
395
+ publish(topic: string, message: BusMessage): Promise<PublishResult>;
396
+ subscribe(topic: string, handler: MessageHandler): Promise<Subscription>;
397
+ unsubscribe(subscription: Subscription): Promise<void>;
398
+ /** Helper for testing: get count of active subscriptions for a topic */
399
+ getSubscriptionCount(topic: string): number;
400
+ /** Helper for testing: clear all subscriptions */
401
+ clear(): void;
402
+ }
403
+ /** Singleton in-memory provider for convenience */
404
+ declare const inMemoryBusProvider: InMemoryMessageBusProvider;
405
+
406
+ /**
407
+ * SQS/SNS MessageBusProvider — the first concrete adapter.
408
+ *
409
+ * Architecture:
410
+ * - PUBLISH: Uses SNS Publish to deliver messages to a topic ARN.
411
+ * - SUBSCRIBE: Uses SQS long-polling (ReceiveMessage) to consume from a queue URL.
412
+ *
413
+ * Topic/queue resolution:
414
+ * - Logical topic name → SNS Topic ARN via config map or env var:
415
+ * BUS_TOPIC_{UPPER_SNAKE_NAME}_ARN (e.g. BUS_TOPIC_EQUIPMENT_STATUS_ARN)
416
+ * - Logical queue name → SQS Queue URL via config map or env var:
417
+ * BUS_QUEUE_{UPPER_SNAKE_NAME}_URL (e.g. BUS_QUEUE_EQUIPMENT_STATUS_URL)
418
+ *
419
+ * Trace propagation:
420
+ * - traceContext serialized as SNS/SQS MessageAttributes (StringValue)
421
+ * - metadata fields (timestamp, source, correlationId) also as MessageAttributes
422
+ *
423
+ * Graceful shutdown:
424
+ * - Each subscription has a polling loop with an `active` flag
425
+ * - stop()/unsubscribe() sets active=false, polling loop exits after current
426
+ * ReceiveMessage returns
427
+ *
428
+ * AWS credentials:
429
+ * - Uses standard AWS SDK v3 credential chain (env vars, instance profile, etc.)
430
+ * - NO credential handling in this adapter
431
+ *
432
+ * Design note: This is a thin wrapper. No retry logic beyond SDK defaults,
433
+ * no DLQ support, no batch processing. Production hardening is a separate bead.
434
+ */
435
+
436
+ interface SqsSnsConfig {
437
+ /** AWS region (defaults to AWS_REGION env var or 'us-east-1') */
438
+ region?: string;
439
+ /** SNS Topic ARN mapping: logical name → ARN. Overrides env vars. */
440
+ topicArns?: Record<string, string>;
441
+ /** SQS Queue URL mapping: logical name → URL. Overrides env vars. */
442
+ queueUrls?: Record<string, string>;
443
+ /** SQS long-poll wait time in seconds (default: 20, max for long-polling) */
444
+ waitTimeSeconds?: number;
445
+ /** Max messages per ReceiveMessage call (default: 10) */
446
+ maxMessages?: number;
447
+ /**
448
+ * Minimum delay between poll cycles in ms (default: 100).
449
+ * Prevents tight-spinning when waitTimeSeconds is low or the queue is
450
+ * empty and the SDK resolves instantly. In production with
451
+ * waitTimeSeconds=20 the 100ms gap is negligible.
452
+ */
453
+ pollIntervalMs?: number;
454
+ /** Optional SNS client (for testing/DI) */
455
+ snsClient?: SNSClient;
456
+ /** Optional SQS client (for testing/DI) */
457
+ sqsClient?: SQSClient;
458
+ }
459
+ declare class SqsSnsMessageBusProvider implements MessageBusProvider {
460
+ private readonly snsClient;
461
+ private readonly sqsClient;
462
+ private readonly topicArns;
463
+ private readonly queueUrls;
464
+ private readonly waitTimeSeconds;
465
+ private readonly maxMessages;
466
+ private readonly pollIntervalMs;
467
+ private readonly pollingStates;
468
+ constructor(config?: SqsSnsConfig);
469
+ publish(topic: string, message: BusMessage): Promise<PublishResult>;
470
+ subscribe(topic: string, handler: MessageHandler): Promise<Subscription>;
471
+ unsubscribe(subscription: Subscription): Promise<void>;
472
+ private resolveTopicArn;
473
+ private resolveQueueUrl;
474
+ private buildSnsAttributes;
475
+ private poll;
476
+ private parseSqsMessage;
477
+ private extractTraceContext;
478
+ private extractMetadata;
479
+ }
480
+
481
+ /**
482
+ * Create a MessageBusProvider based on environment configuration.
483
+ *
484
+ * - MESSAGE_BUS_PROVIDER=memory (or unset) -> InMemoryMessageBusProvider
485
+ * - MESSAGE_BUS_PROVIDER=sqs -> SQS/SNS adapter
486
+ *
487
+ * Same pattern as createObservabilityProvider().
488
+ */
489
+ declare function createMessageBusProvider(options?: {
490
+ /** Override the provider type (defaults to env var) */
491
+ provider?: string;
492
+ /** Provider-specific config (e.g. SqsSnsConfig for 'sqs') */
493
+ config?: Record<string, unknown>;
494
+ }): MessageBusProvider;
495
+
496
+ /**
497
+ * Supported notification channels.
498
+ * The set is closed — adding a new channel is a deliberate, audited act.
499
+ */
500
+ type NotificationChannel = 'email' | 'sms' | 'push' | 'in-app';
501
+ /**
502
+ * Notification payload carried to the provider.
503
+ * The payload is typed at the flow level; here it is `unknown` because
504
+ * the provider is transport-agnostic.
505
+ */
506
+ interface NotificationMessage {
507
+ /** Recipient user identifier — the provider resolves this to a delivery address */
508
+ recipient: string;
509
+ /** Delivery channel */
510
+ channel: NotificationChannel;
511
+ /** Template name for the notification content */
512
+ template: string;
513
+ /** Typed payload for template interpolation */
514
+ payload: unknown;
515
+ /** Optional subject line (for email, push) */
516
+ subject?: string;
517
+ /** W3C Trace Context headers for distributed trace propagation */
518
+ traceContext?: Record<string, string>;
519
+ /** Notification metadata */
520
+ metadata?: NotificationMetadata;
521
+ }
522
+ interface NotificationMetadata {
523
+ /** ISO 8601 timestamp of when the notification was produced */
524
+ timestamp?: string;
525
+ /** Logical source identifier (e.g. the flow that triggered the notification) */
526
+ source?: string;
527
+ /** Correlation ID for request-scoped grouping */
528
+ correlationId?: string;
529
+ }
530
+ interface SendResult {
531
+ /** Provider-assigned notification ID */
532
+ notificationId: string;
533
+ /** Channel the notification was sent through */
534
+ channel: string;
535
+ /** Whether sending succeeded */
536
+ success: boolean;
537
+ }
538
+ /**
539
+ * NotificationProvider adapter interface.
540
+ *
541
+ * Same adapter pattern as MessageBusProvider and ObservabilityProvider:
542
+ * - Interface defined here
543
+ * - InMemoryNotificationProvider for testing (records notifications in-memory)
544
+ * - Concrete adapters (SendGrid, Twilio, FCM, etc.) bind at the deployment target
545
+ * - Factory selects provider based on environment configuration
546
+ *
547
+ * Multi-channel (email, SMS, push, in-app) — the provider resolves the
548
+ * recipient identifier to an actual delivery address for each channel.
549
+ *
550
+ * Template-based — the provider looks up the template name and interpolates
551
+ * the typed payload. Template management is the provider's concern, not the
552
+ * capability's.
553
+ */
554
+ interface NotificationProvider {
555
+ /** Send a notification to a user */
556
+ send(notification: NotificationMessage): Promise<SendResult>;
557
+ }
558
+
559
+ /**
560
+ * In-memory NotificationProvider — records notifications locally for inspection.
561
+ *
562
+ * Used for:
563
+ * - Unit and integration testing (no infrastructure required)
564
+ * - Local development (stackwright-services dev mode)
565
+ * - Default when no external notification service is configured
566
+ *
567
+ * Notifications are stored in-memory and can be inspected via helper methods.
568
+ */
569
+ declare class InMemoryNotificationProvider implements NotificationProvider {
570
+ private notifications;
571
+ private nextId;
572
+ send(notification: NotificationMessage): Promise<SendResult>;
573
+ /** Helper for testing: get all sent notifications */
574
+ getSentNotifications(): ReadonlyArray<NotificationMessage & {
575
+ notificationId: string;
576
+ }>;
577
+ /** Helper for testing: get notifications filtered by channel */
578
+ getByChannel(channel: string): ReadonlyArray<NotificationMessage & {
579
+ notificationId: string;
580
+ }>;
581
+ /** Helper for testing: get notifications filtered by recipient */
582
+ getByRecipient(recipient: string): ReadonlyArray<NotificationMessage & {
583
+ notificationId: string;
584
+ }>;
585
+ /** Helper for testing: get count of sent notifications */
586
+ get sentCount(): number;
587
+ /** Helper for testing: clear all recorded notifications */
588
+ clear(): void;
589
+ }
590
+ /** Singleton in-memory provider for convenience */
591
+ declare const inMemoryNotificationProvider: InMemoryNotificationProvider;
592
+
593
+ /**
594
+ * Create a NotificationProvider based on environment configuration.
595
+ *
596
+ * - NOTIFICATION_PROVIDER=memory (or unset) -> InMemoryNotificationProvider
597
+ * - NOTIFICATION_PROVIDER=sendgrid -> (future: SendGrid adapter)
598
+ * - NOTIFICATION_PROVIDER=twilio -> (future: Twilio adapter)
599
+ *
600
+ * Same pattern as createMessageBusProvider() and createObservabilityProvider().
601
+ */
602
+ declare function createNotificationProvider(options?: {
603
+ /** Override the provider type (defaults to env var) */
604
+ provider?: string;
605
+ }): NotificationProvider;
606
+
607
+ /**
608
+ * Claims extracted from an authentication token.
609
+ */
610
+ interface AuthClaims {
611
+ /** Subject identifier (user ID) */
612
+ sub: string;
613
+ /** Roles assigned to the user */
614
+ roles: string[];
615
+ /** Token expiration timestamp (Unix seconds) */
616
+ exp?: number;
617
+ /** Additional claims — extensible */
618
+ [key: string]: unknown;
619
+ }
620
+ /**
621
+ * Token validation result.
622
+ */
623
+ interface TokenValidationResult {
624
+ valid: boolean;
625
+ claims?: AuthClaims;
626
+ error?: string;
627
+ }
628
+ /**
629
+ * AuthProvider adapter interface.
630
+ *
631
+ * Same adapter pattern as MessageBusProvider and ObservabilityProvider.
632
+ * Concrete implementations (OIDC, CAC/PKI) are discovered via IoC
633
+ * when @stackwright-pro/auth is installed.
634
+ *
635
+ * Used by:
636
+ * - HTTP trigger to validate requests
637
+ * - Workflow guard_role to gate transitions
638
+ */
639
+ interface AuthProvider {
640
+ /** Provider name for logging/diagnostics */
641
+ readonly name: string;
642
+ /** Validate a token and return claims if valid */
643
+ validateToken(token: string): Promise<TokenValidationResult>;
644
+ /** Extract claims from an already-validated token (fast path) */
645
+ extractClaims(token: string): Promise<AuthClaims>;
646
+ /** Check if claims include the required role */
647
+ checkRole(claims: AuthClaims, role: string): boolean;
648
+ }
649
+
650
+ /**
651
+ * No-op AuthProvider — fail-closed default.
652
+ *
653
+ * Used when no auth provider is configured:
654
+ * - validateToken() always returns { valid: false } (reject all tokens)
655
+ * - extractClaims() throws (no claims without a real provider)
656
+ * - checkRole() always returns false (deny all role checks)
657
+ *
658
+ * This is intentionally fail-closed: if you forget to install a real
659
+ * auth provider, every auth check fails rather than silently passing.
660
+ */
661
+ declare class NoopAuthProvider implements AuthProvider {
662
+ readonly name = "noop";
663
+ validateToken(token: string): Promise<TokenValidationResult>;
664
+ extractClaims(token: string): Promise<AuthClaims>;
665
+ checkRole(claims: AuthClaims, role: string): boolean;
666
+ }
667
+ /** Singleton no-op provider */
668
+ declare const noopAuthProvider: NoopAuthProvider;
669
+
670
+ type AuthProviderType = 'noop' | 'oidc' | 'cac';
671
+ /**
672
+ * Create an AuthProvider based on the requested type.
673
+ *
674
+ * - 'noop' (or unset) -> NoopAuthProvider (fail-closed default)
675
+ * - 'oidc' -> Future: lazy require('@stackwright-pro/auth-oidc')
676
+ * - 'cac' -> Future: lazy require('@stackwright-pro/auth-cac')
677
+ *
678
+ * Same pattern as createMessageBusProvider() and createObservabilityProvider().
679
+ */
680
+ declare function createAuthProvider(type?: AuthProviderType): AuthProvider;
681
+
682
+ /**
683
+ * Descriptor for a data source exposed by an installed provider.
684
+ */
685
+ interface DataSourceDescriptor {
686
+ /**
687
+ * Unique source ID. Matches the `source:` YAML reference without the transport prefix.
688
+ * e.g. "equipment-api" (from `openapi:equipment-api`).
689
+ */
690
+ id: string;
691
+ /** Transport protocol this source is backed by */
692
+ transport: 'openapi' | 'pulse';
693
+ /** List of named operations this source supports */
694
+ operations: string[];
695
+ /** Human-readable description (optional, from adapter metadata) */
696
+ description?: string;
697
+ }
698
+ /**
699
+ * Result returned by a DataSourceProvider execute() call.
700
+ */
701
+ interface DataSourceResult {
702
+ /** The raw data returned by the operation */
703
+ data: unknown;
704
+ /** Optional provider-level metadata (pagination cursors, rate-limit headers, etc.) */
705
+ metadata?: Record<string, unknown>;
706
+ }
707
+ /**
708
+ * DataSourceProvider adapter interface.
709
+ *
710
+ * Same adapter pattern as MessageBusProvider and ObservabilityProvider:
711
+ * - Interface defined here
712
+ * - NoopDataSourceProvider for environments where no provider is installed
713
+ * - Concrete adapters (OpenAPI, Pulse) discovered via IoC at factory time
714
+ * - Factory selects provider based on installed packages + environment config
715
+ *
716
+ * Source reference format: `<transport>:<id>` (e.g. `openapi:equipment-api`, `pulse:fleet-db`).
717
+ * The provider receives the id and operation after the prefix is stripped by the
718
+ * capability or trigger that calls it.
719
+ *
720
+ * This is the typed, permission-scoped replacement for the raw `service.call` effect.
721
+ * Citizen YAML references a named source + operation; the provider resolves it to a
722
+ * typed, Zod-validated client call and the compiler derives least-privilege permissions
723
+ * from the declared source references.
724
+ */
725
+ interface DataSourceProvider {
726
+ /** Provider name for logging/diagnostics */
727
+ readonly name: string;
728
+ /**
729
+ * Enumerate all data sources this provider knows about.
730
+ * Called at prebuild time to expand the valid `source:` schema surface.
731
+ */
732
+ listSources(): DataSourceDescriptor[];
733
+ /**
734
+ * Execute a named operation against a source.
735
+ *
736
+ * @param source - Source ID (without transport prefix, e.g. "equipment-api")
737
+ * @param operation - Operation name (e.g. "listUnits", "getUnit")
738
+ * @param params - Operation parameters (typed at the source level)
739
+ * @returns Typed result wrapped in DataSourceResult
740
+ * @throws If the source or operation is not known, or if the call fails
741
+ */
742
+ execute(source: string, operation: string, params: unknown): Promise<DataSourceResult>;
743
+ }
744
+
745
+ /**
746
+ * NoopDataSourceProvider — used when no data source provider is installed.
747
+ *
748
+ * listSources() returns an empty array (no sources available to the schema expander).
749
+ * execute() throws a descriptive error pointing to the correct package to install.
750
+ *
751
+ * This is the fail-closed default, consistent with NoopAuthProvider.
752
+ */
753
+ declare class NoopDataSourceProvider implements DataSourceProvider {
754
+ readonly name = "noop";
755
+ listSources(): DataSourceDescriptor[];
756
+ execute(_source: string, _operation: string, _params: unknown): Promise<DataSourceResult>;
757
+ }
758
+ declare const noopDataSourceProvider: NoopDataSourceProvider;
759
+
760
+ /**
761
+ * Create a DataSourceProvider based on installed packages and environment configuration.
762
+ *
763
+ * Resolution order (first match wins):
764
+ * 1. `options.provider` — explicit code-level override
765
+ * 2. `DATA_SOURCE_PROVIDER` env var — environment-level override
766
+ * 3. IoC auto-discovery — scans for installed adapter packages via require.resolve()
767
+ * a. `@stackwright-pro/openapi/datasource-adapter` (checked first)
768
+ * b. `@stackwright-pro/pulse/datasource-adapter`
769
+ * 4. NoopDataSourceProvider — fail-closed default, throws on execute()
770
+ *
771
+ * Same pattern as createMessageBusProvider() and createAuthProvider().
772
+ *
773
+ * @example Default (IoC — install the package, nothing else needed)
774
+ * ```typescript
775
+ * // With @stackwright-pro/pulse in node_modules:
776
+ * const provider = createDataSourceProvider();
777
+ * // → PulseDataSourceProvider (auto-discovered)
778
+ *
779
+ * // With nothing installed:
780
+ * const provider = createDataSourceProvider();
781
+ * // → NoopDataSourceProvider (execute() will throw)
782
+ * ```
783
+ *
784
+ * @example Explicit override with config (useful in tests and custom setups)
785
+ * ```typescript
786
+ * const provider = createDataSourceProvider({
787
+ * provider: 'pulse',
788
+ * config: {
789
+ * sources: [{
790
+ * id: 'fleet-db',
791
+ * operations: { queryGeneratorStatus: mockHandler },
792
+ * }],
793
+ * },
794
+ * });
795
+ * ```
796
+ *
797
+ * @example Environment-level override (force noop in isolated tests)
798
+ * ```bash
799
+ * DATA_SOURCE_PROVIDER=noop node my-service.js
800
+ * DATA_SOURCE_PROVIDER=pulse node my-service.js # force pulse even if openapi is installed
801
+ * ```
802
+ */
803
+ declare function createDataSourceProvider(options?: {
804
+ /** Override the provider type ('openapi' | 'pulse' | 'noop') */
805
+ provider?: string;
806
+ /** Provider-specific config passed through to the adapter */
807
+ config?: Record<string, unknown>;
808
+ }): DataSourceProvider;
809
+
810
+ /**
811
+ * Result of a prebuild-time data source scan.
812
+ */
813
+ interface DataSourceScanResult {
814
+ /** All sources discovered across all installed providers */
815
+ sources: DataSourceDescriptor[];
816
+ /** Which provider packages were found (for logging/diagnostics) */
817
+ detectedProviders: string[];
818
+ }
819
+ /**
820
+ * Scan installed packages at prebuild time to discover available data sources.
821
+ *
822
+ * This is called by the compiler and prebuild hook (yo8) to expand the valid
823
+ * `source:` schema surface. If @stackwright-pro/openapi or @stackwright-pro/pulse
824
+ * are installed and expose an adapter registry, their sources are enumerated here.
825
+ *
826
+ * This is a pure filesystem/module-resolution operation — no network, no side effects.
827
+ * It is safe to call at build time (no running server required).
828
+ *
829
+ * Returns an empty sources array if no providers are installed, which causes the
830
+ * compiler to emit a warning (not an error) when a `source:` reference is encountered.
831
+ */
832
+ declare function scanDataSources(): DataSourceScanResult;
833
+
834
+ /**
835
+ * A typed operation handler for a single OpenAPI operation.
836
+ * Receives the caller-supplied params and returns the operation result.
837
+ * Implementations are typically generated typed client methods.
838
+ */
839
+ type OpenApiOperationHandler = (params: unknown) => Promise<unknown>;
840
+ /**
841
+ * Registration entry for a single OpenAPI-backed data source.
842
+ */
843
+ interface OpenApiSourceRegistration {
844
+ /** Source ID — matches the id in `source: openapi:<id>` YAML references */
845
+ id: string;
846
+ /** Human-readable description (forwarded to DataSourceDescriptor) */
847
+ description?: string;
848
+ /** Map of operation name → typed handler function */
849
+ operations: Record<string, OpenApiOperationHandler>;
850
+ }
851
+ /**
852
+ * Configuration for OpenApiDataSourceProvider.
853
+ */
854
+ interface OpenApiAdapterConfig {
855
+ /** Data sources to register. Each source has a set of named operation handlers. */
856
+ sources: OpenApiSourceRegistration[];
857
+ }
858
+ /**
859
+ * OpenApiDataSourceProvider — wraps registered OpenAPI operation handlers as a DataSourceProvider.
860
+ *
861
+ * Architecture:
862
+ * - Callers register named sources at construction time, each with a map of operation handlers.
863
+ * - `listSources()` returns the full descriptor table for all registered sources.
864
+ * - `execute(source, operation, params)` dispatches to the registered handler.
865
+ * - Handlers are typically generated typed OpenAPI client methods (from @stackwright-pro/openapi),
866
+ * but any function with the right signature works — useful for testing with mocks.
867
+ *
868
+ * Design rationale:
869
+ * - Same registry pattern as SqsSnsMessageBusProvider (wraps external clients, injectable for testing).
870
+ * - `@stackwright-pro/openapi` uses this adapter when registering its generated clients; the adapter
871
+ * is transport-agnostic and does not hard-import that package.
872
+ * - The `DataSourceResult.metadata` field carries the raw handler response shape for callers
873
+ * that need the full structure beyond `data`.
874
+ *
875
+ * Error semantics:
876
+ * - Unknown source → rejects with a descriptive Error (not throws sync).
877
+ * - Unknown operation → rejects with a descriptive Error.
878
+ * - Handler errors propagate as-is (the handler is responsible for shaping them).
879
+ */
880
+ declare class OpenApiDataSourceProvider implements DataSourceProvider {
881
+ private readonly config;
882
+ readonly name = "openapi";
883
+ private readonly sourcesMap;
884
+ constructor(config: OpenApiAdapterConfig);
885
+ listSources(): DataSourceDescriptor[];
886
+ execute(source: string, operation: string, params: unknown): Promise<DataSourceResult>;
887
+ }
888
+ /**
889
+ * Factory function — creates an OpenApiDataSourceProvider from config.
890
+ * This is the entry point called by createDataSourceProvider() and
891
+ * by @stackwright-pro/openapi's datasource-adapter when installed.
892
+ */
893
+ declare function createOpenApiDataSourceProvider(config: OpenApiAdapterConfig): DataSourceProvider;
894
+ /**
895
+ * Prebuild-time source listing — returns an empty array because registered
896
+ * sources are known only at runtime when handlers are wired.
897
+ *
898
+ * @stackwright-pro/openapi's datasource-adapter overrides this with a static
899
+ * manifest of all generated clients, enabling the compiler to validate
900
+ * `source: openapi:*` references at build time.
901
+ */
902
+ declare function listDataSources$1(): DataSourceDescriptor[];
903
+
904
+ /**
905
+ * A typed operation handler for a single Pulse operation.
906
+ * Receives the caller-supplied params and returns the operation result.
907
+ * Implementations are typically generated typed client methods.
908
+ */
909
+ type PulseOperationHandler = (params: unknown) => Promise<unknown>;
910
+ /**
911
+ * Registration entry for a single Pulse-backed data source.
912
+ */
913
+ interface PulseSourceRegistration {
914
+ /** Source ID — matches the id in `source: pulse:<id>` YAML references */
915
+ id: string;
916
+ /** Human-readable description (forwarded to DataSourceDescriptor) */
917
+ description?: string;
918
+ /** Map of operation name → typed handler function */
919
+ operations: Record<string, PulseOperationHandler>;
920
+ }
921
+ /**
922
+ * Configuration for PulseDataSourceProvider.
923
+ */
924
+ interface PulseAdapterConfig {
925
+ /** Data sources to register. Each source has a set of named operation handlers. */
926
+ sources: PulseSourceRegistration[];
927
+ }
928
+ /**
929
+ * PulseDataSourceProvider — wraps registered Pulse operation handlers as a DataSourceProvider.
930
+ *
931
+ * Architecture:
932
+ * - Callers register named sources at construction time, each with a map of operation handlers.
933
+ * - `listSources()` returns the full descriptor table for all registered sources.
934
+ * - `execute(source, operation, params)` dispatches to the registered handler.
935
+ * - Handlers are typically generated typed Pulse client methods (from @stackwright-pro/pulse),
936
+ * but any function with the right signature works — useful for testing with mocks.
937
+ *
938
+ * Design rationale:
939
+ * - Same registry pattern as SqsSnsMessageBusProvider (wraps external clients, injectable for testing).
940
+ * - `@stackwright-pro/pulse` uses this adapter when registering its generated clients; the adapter
941
+ * is transport-agnostic and does not hard-import that package.
942
+ * - The `DataSourceResult.metadata` field carries the raw handler response shape for callers
943
+ * that need the full structure beyond `data`.
944
+ *
945
+ * Usage example:
946
+ * ```typescript
947
+ * const provider = createPulseDataSourceProvider({
948
+ * sources: [{
949
+ * id: 'fleet-db',
950
+ * operations: { queryMetrics: pulseClient.queryMetrics },
951
+ * }],
952
+ * });
953
+ * // Matches `source: pulse:fleet-db` YAML references
954
+ * ```
955
+ *
956
+ * Error semantics:
957
+ * - Unknown source → rejects with a descriptive Error (not throws sync).
958
+ * - Unknown operation → rejects with a descriptive Error.
959
+ * - Handler errors propagate as-is (the handler is responsible for shaping them).
960
+ */
961
+ declare class PulseDataSourceProvider implements DataSourceProvider {
962
+ private readonly config;
963
+ readonly name = "pulse";
964
+ private readonly sourcesMap;
965
+ constructor(config: PulseAdapterConfig);
966
+ listSources(): DataSourceDescriptor[];
967
+ execute(source: string, operation: string, params: unknown): Promise<DataSourceResult>;
968
+ }
969
+ /**
970
+ * Factory function — creates a PulseDataSourceProvider from config.
971
+ * This is the entry point called by createDataSourceProvider() and
972
+ * by @stackwright-pro/pulse's datasource-adapter when installed.
973
+ */
974
+ declare function createPulseDataSourceProvider(config: PulseAdapterConfig): DataSourceProvider;
975
+ /**
976
+ * Prebuild-time source listing — returns an empty array because registered
977
+ * sources are known only at runtime when handlers are wired.
978
+ *
979
+ * @stackwright-pro/pulse's datasource-adapter overrides this with a static
980
+ * manifest of all generated clients, enabling the compiler to validate
981
+ * `source: pulse:*` references at build time.
982
+ */
983
+ declare function listDataSources(): DataSourceDescriptor[];
984
+
985
+ export { type AuditEvent, type AuditProvider, type AuthClaims, type AuthProvider, type AuthProviderType, type BusMessage, ConsoleAuditProvider, type DataSourceDescriptor, type DataSourceProvider, type DataSourceResult, type DataSourceScanResult, EventTriggerAdapter, type EventTriggerOptions, type HttpTriggerOptions, InMemoryMessageBusProvider, InMemoryNotificationProvider, type MessageBusProvider, type MessageHandler, type MessageMetadata, NoopAuditProvider, NoopAuthProvider, NoopDataSourceProvider, NoopObservabilityProvider, type NotificationChannel, type NotificationMessage, type NotificationMetadata, type NotificationProvider, type ObservabilityProvider, type OpenApiAdapterConfig, OpenApiDataSourceProvider, type OpenApiOperationHandler, type OpenApiSourceRegistration, OtelObservabilityProvider, type PublishResult, type PulseAdapterConfig, PulseDataSourceProvider, type PulseOperationHandler, type PulseSourceRegistration, QueueTriggerAdapter, type QueueTriggerOptions, ScheduleTriggerAdapter, type ScheduleTriggerOptions, type SendResult, type ServiceError, type SpanHandle, type SqsSnsConfig, SqsSnsMessageBusProvider, type Subscription, type TokenValidationResult, type TriggerAdapter, auditHash, createAuditProvider, createAuthProvider, createDataSourceProvider, createHttpTrigger, createMessageBusProvider, createNotificationProvider, createObservabilityProvider, createOpenApiDataSourceProvider, createPulseDataSourceProvider, inMemoryBusProvider, inMemoryNotificationProvider, listDataSources$1 as listOpenApiDataSources, listDataSources as listPulseDataSources, noopAuditProvider, noopAuthProvider, noopDataSourceProvider, noopProvider, scanDataSources };