@fulmenhq/tsfulmen 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
- import { a as BehaviorInfo, m as SignalInfo, k as SignalCatalog, S as SignalManager, l as SignalHandler, H as HandlerOptions, F as FallbackLogger, T as TelemetryEmitter } from '../manager-CH3fX7zO.js';
1
+ import { a as BehaviorInfo, m as SignalInfo, k as SignalCatalog, S as SignalManager, F as FallbackLogger, T as TelemetryEmitter, l as SignalHandler, H as HandlerOptions } from '../manager-CH3fX7zO.js';
2
2
  export { B as Behavior, b as BehaviorPhase, E as ExitCodes, L as LogLevel, O as OsMappings, P as PlatformOverrides, e as PlatformSupport, f as PlatformSupportLevel, i as Signal, j as SignalBehavior, n as SignalManagerOptions, o as TimeoutBehavior, W as WindowsFallback, p as WindowsFallbackBehavior, q as WindowsFallbackOptions, s as WindowsFallbackResult, c as createSignalManager, g as getFallbackMetadata, d as getHttpFallbackGuidance, h as handleWindowsFallback, r as requiresFallback } from '../manager-CH3fX7zO.js';
3
+ import { I as Identity } from '../types-Dv5TERCM.js';
3
4
 
4
5
  /**
5
6
  * Signal Capability Detection
@@ -116,560 +117,647 @@ declare function getBehavior(id: string): Promise<BehaviorInfo | null>;
116
117
  declare function getSignalCatalog(): Promise<SignalCatalog>;
117
118
 
118
119
  /**
119
- * Signal Handler Convenience Wrappers
120
+ * HTTP Signal Endpoint Helper
120
121
  *
121
- * Common signal handling patterns for shutdown, reload, and custom behaviors.
122
+ * Framework-agnostic scaffold for POST /admin/signal endpoint.
123
+ * Applications provide auth/rate-limiting; helper handles validation and execution.
122
124
  */
123
125
 
124
126
  /**
125
- * Register a graceful shutdown handler
126
- *
127
- * Convenience wrapper for SIGTERM and SIGINT handlers.
128
- * Automatically registers both signals to the same handler.
127
+ * Signal request payload
128
+ */
129
+ interface SignalRequest {
130
+ signal: string;
131
+ reason?: string;
132
+ correlation_id?: string;
133
+ }
134
+ /**
135
+ * Signal response (success)
136
+ */
137
+ interface SignalResponse {
138
+ status: "accepted";
139
+ signal: string;
140
+ correlation_id: string;
141
+ message: string;
142
+ }
143
+ /**
144
+ * Signal error response
145
+ */
146
+ interface SignalErrorResponse {
147
+ status: "error";
148
+ error: string;
149
+ message: string;
150
+ valid_signals?: string[];
151
+ }
152
+ /**
153
+ * Authentication result
154
+ */
155
+ interface AuthResult {
156
+ authenticated: boolean;
157
+ identity?: string;
158
+ reason?: string;
159
+ }
160
+ /**
161
+ * Rate limit result
162
+ */
163
+ interface RateLimitResult {
164
+ allowed: boolean;
165
+ remaining?: number;
166
+ reset_at?: number;
167
+ }
168
+ /**
169
+ * Authentication hook function
129
170
  *
130
- * @param manager - Signal manager instance
131
- * @param handler - Shutdown handler function
132
- * @param options - Handler options
171
+ * Applications must provide this to validate requests.
172
+ * Returns authentication result with optional identity.
173
+ */
174
+ type AuthHook = (req: unknown) => Promise<AuthResult> | AuthResult;
175
+ /**
176
+ * Rate limiting hook function
133
177
  *
134
- * @example
135
- * ```typescript
136
- * await onShutdown(manager, async () => {
137
- * await closeDatabase();
138
- * await flushLogs();
139
- * });
140
- * ```
178
+ * Applications may provide this to enforce rate limits.
179
+ * Returns whether request is allowed and quota info.
141
180
  */
142
- declare function onShutdown(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
181
+ type RateLimitHook = (identity: string, signal: string) => Promise<RateLimitResult> | RateLimitResult;
143
182
  /**
144
- * Register a config reload handler
183
+ * Signal endpoint options
184
+ */
185
+ interface SignalEndpointOptions {
186
+ /**
187
+ * Signal manager instance
188
+ */
189
+ manager: SignalManager;
190
+ /**
191
+ * Authentication hook (required)
192
+ */
193
+ auth: AuthHook;
194
+ /**
195
+ * Rate limiting hook (optional)
196
+ */
197
+ rateLimit?: RateLimitHook;
198
+ /**
199
+ * Logger for endpoint events
200
+ */
201
+ logger?: FallbackLogger;
202
+ /**
203
+ * Telemetry emitter
204
+ */
205
+ telemetry?: TelemetryEmitter;
206
+ /**
207
+ * Allowed signals (default: all catalog signals)
208
+ */
209
+ allowedSignals?: string[];
210
+ }
211
+ /**
212
+ * Create a framework-agnostic signal endpoint handler
145
213
  *
146
- * Convenience wrapper for SIGHUP handler.
147
- * Only registers on POSIX platforms (SIGHUP not supported on Windows).
214
+ * Returns an async function that processes signal requests.
215
+ * Applications wire this to their HTTP framework (Express, Fastify, etc.)
148
216
  *
149
- * @param manager - Signal manager instance
150
- * @param handler - Reload handler function
151
- * @param options - Handler options
217
+ * @param options - Endpoint configuration
152
218
  *
153
- * @example
219
+ * @example Express
154
220
  * ```typescript
155
- * await onReload(manager, async () => {
156
- * const newConfig = await loadConfig();
157
- * await validateConfig(newConfig);
158
- * process.exit(129); // Exit for restart
221
+ * const handler = createSignalEndpoint({
222
+ * manager,
223
+ * auth: async (req) => {
224
+ * const token = req.headers.authorization?.split(' ')[1];
225
+ * return { authenticated: token === process.env.ADMIN_TOKEN };
226
+ * },
159
227
  * });
160
- * ```
161
- */
162
- declare function onReload(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
163
- /**
164
- * Register a custom handler for SIGUSR1
165
228
  *
166
- * Common use cases: toggle debug logging, reopen log files, dump statistics.
167
- *
168
- * @param manager - Signal manager instance
169
- * @param handler - Custom handler function
170
- * @param options - Handler options
229
+ * app.post('/admin/signal', async (req, res) => {
230
+ * const result = await handler(req.body, req);
231
+ * res.status(result.status === 'accepted' ? 202 : result.statusCode || 400)
232
+ * .json(result);
233
+ * });
234
+ * ```
171
235
  *
172
- * @example
236
+ * @example Fastify
173
237
  * ```typescript
174
- * await onUSR1(manager, async () => {
175
- * logger.info('SIGUSR1 received - reopening log files');
176
- * await reopenLogFiles();
238
+ * const handler = createSignalEndpoint({ manager, auth });
239
+ *
240
+ * fastify.post('/admin/signal', async (request, reply) => {
241
+ * const result = await handler(request.body, request);
242
+ * reply.status(result.status === 'accepted' ? 202 : 400).send(result);
177
243
  * });
178
244
  * ```
179
245
  */
180
- declare function onUSR1(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
246
+ declare function createSignalEndpoint(options: SignalEndpointOptions): (payload: SignalRequest, req: unknown) => Promise<(SignalResponse | SignalErrorResponse) & {
247
+ statusCode?: number;
248
+ }>;
181
249
  /**
182
- * Register a custom handler for SIGUSR2
250
+ * Create a simple bearer token auth hook
183
251
  *
184
- * Common use cases: trigger profiling, rotate credentials, toggle verbose mode.
252
+ * Validates requests against a static token.
253
+ * For production, use mTLS or more robust auth.
185
254
  *
186
- * @param manager - Signal manager instance
187
- * @param handler - Custom handler function
188
- * @param options - Handler options
255
+ * @param expectedToken - Expected bearer token
189
256
  *
190
257
  * @example
191
258
  * ```typescript
192
- * await onUSR2(manager, async () => {
193
- * logger.info('SIGUSR2 received - toggling debug mode');
194
- * toggleDebugMode();
195
- * });
259
+ * const auth = createBearerTokenAuth(process.env.ADMIN_TOKEN);
260
+ * const handler = createSignalEndpoint({ manager, auth });
196
261
  * ```
197
262
  */
198
- declare function onUSR2(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
263
+ declare function createBearerTokenAuth(expectedToken: string): AuthHook;
199
264
  /**
200
- * Register an emergency quit handler
265
+ * Create a simple in-memory rate limiter
201
266
  *
202
- * Convenience wrapper for SIGQUIT (immediate exit, no cleanup).
267
+ * Tracks requests per identity with sliding window.
268
+ * For production, use Redis or distributed rate limiting.
203
269
  *
204
- * @param manager - Signal manager instance
205
- * @param handler - Emergency quit handler
206
- * @param options - Handler options
270
+ * @param requestsPerMinute - Max requests per minute per identity
207
271
  *
208
272
  * @example
209
273
  * ```typescript
210
- * await onEmergencyQuit(manager, async () => {
211
- * logger.error('SIGQUIT received - emergency exit');
212
- * process.exit(131);
213
- * });
274
+ * const rateLimit = createSimpleRateLimiter(10); // 10 req/min
275
+ * const handler = createSignalEndpoint({ manager, auth, rateLimit });
214
276
  * ```
215
277
  */
216
- declare function onEmergencyQuit(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
278
+ declare function createSimpleRateLimiter(requestsPerMinute: number): RateLimitHook;
279
+
217
280
  /**
218
- * Register handlers for all common shutdown signals
219
- *
220
- * Registers the same handler for SIGTERM, SIGINT, and SIGQUIT.
221
- * Useful for applications that want consistent shutdown behavior.
281
+ * Configuration Reload Helpers
222
282
  *
223
- * @param manager - Signal manager instance
224
- * @param handler - Shutdown handler function
225
- * @param options - Handler options
283
+ * Implements restart-based config reload pattern with mandatory schema validation.
284
+ * Per Crucible standard: validate before restart, reject invalid configs without disruption.
226
285
  */
227
- declare function onAnyShutdown(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
228
286
 
229
287
  /**
230
- * Double-Tap Signal Handling
288
+ * Configuration validator function type
231
289
  *
232
- * Implements Ctrl+C double-tap pattern for graceful shutdown with force-quit option.
233
- * Per Crucible standard: 2-second window, immediate exit on second signal.
290
+ * Applications provide this function to validate new config against schema.
291
+ * Should return validation result with errors if invalid.
234
292
  */
235
-
293
+ type ConfigValidator<T = unknown> = (config: T) => Promise<ConfigValidationResult> | ConfigValidationResult;
236
294
  /**
237
- * Double-tap configuration
295
+ * Configuration validation result
238
296
  */
239
- interface DoubleTapConfig {
297
+ interface ConfigValidationResult {
298
+ valid: boolean;
299
+ errors?: Array<{
300
+ path: string;
301
+ message: string;
302
+ }>;
303
+ }
304
+ /**
305
+ * Configuration loader function type
306
+ *
307
+ * Applications provide this function to load new config from disk/environment.
308
+ */
309
+ type ConfigLoader<T = unknown> = () => Promise<T> | T;
310
+ /**
311
+ * Config reload options
312
+ */
313
+ interface ConfigReloadOptions<T = unknown> {
240
314
  /**
241
- * Debounce window in milliseconds (default: 2000ms per Crucible standard)
315
+ * Config loader function
242
316
  */
243
- windowMs?: number;
317
+ loader: ConfigLoader<T>;
244
318
  /**
245
- * Exit code for forced double-tap exit (default: 130 for SIGINT)
319
+ * Schema validator function
246
320
  */
247
- exitCode?: number;
321
+ validator: ConfigValidator<T>;
248
322
  /**
249
- * Message to display on first signal (default: from catalog)
323
+ * Callback invoked after successful validation, before exit
324
+ * Use for cleanup, logging, etc.
250
325
  */
251
- hintMessage?: string;
326
+ onValidated?: (config: T) => void | Promise<void>;
252
327
  /**
253
- * Logger for double-tap events
328
+ * Exit code for successful reload (default: 129 for SIGHUP)
329
+ */
330
+ exitCode?: number;
331
+ /**
332
+ * Logger for reload events
254
333
  */
255
334
  logger?: FallbackLogger;
256
335
  /**
257
- * Enable test mode (prevents process.exit calls)
336
+ * Telemetry emitter
337
+ */
338
+ telemetry?: TelemetryEmitter;
339
+ /**
340
+ * Test mode (prevents process.exit)
258
341
  */
259
342
  testMode?: boolean;
260
343
  }
261
344
  /**
262
- * Double-tap state tracker
345
+ * Config reload result (for testing)
263
346
  */
264
- interface DoubleTapState {
265
- firstTapTime: number | null;
266
- windowMs: number;
267
- exitCode: number;
268
- hintMessage: string;
269
- logger?: FallbackLogger;
270
- testMode: boolean;
271
- }
272
- /**
273
- * Create double-tap state tracker for a signal
274
- *
275
- * @param signalName - Signal name (typically "SIGINT")
276
- * @param config - Double-tap configuration
277
- */
278
- declare function createDoubleTapTracker(signalName: string, config?: DoubleTapConfig): Promise<DoubleTapState>;
279
- /**
280
- * Handle double-tap signal logic
281
- *
282
- * Returns true if this is the second tap (force-quit), false if first tap.
283
- * Updates state to track timing between taps.
284
- *
285
- * @param state - Double-tap state tracker
286
- * @returns true if force-quit should proceed, false if graceful shutdown
287
- */
288
- declare function handleDoubleTap(state: DoubleTapState): boolean;
289
- /**
290
- * Reset double-tap state
291
- *
292
- * Called when graceful shutdown completes before second tap.
293
- */
294
- declare function resetDoubleTap(state: DoubleTapState): void;
295
- /**
296
- * Check if currently within double-tap window
297
- *
298
- * Useful for testing and debugging.
299
- */
300
- declare function isWithinWindow(state: DoubleTapState): boolean;
301
- /**
302
- * Get time remaining in double-tap window (milliseconds)
303
- *
304
- * Returns null if not in a window, otherwise milliseconds remaining.
305
- */
306
- declare function getWindowTimeRemaining(state: DoubleTapState): number | null;
307
-
308
- /**
309
- * Signal Support Guards
310
- *
311
- * Validation functions that throw actionable errors when signals are unsupported.
312
- * Used to fail-fast with clear operational guidance.
313
- */
314
- /**
315
- * Guard options
316
- */
317
- interface GuardOptions {
318
- /**
319
- * Include platform-specific operational guidance in error message
320
- */
321
- includeGuidance?: boolean;
347
+ interface ConfigReloadResult {
348
+ reloaded: boolean;
349
+ validationErrors?: Array<{
350
+ path: string;
351
+ message: string;
352
+ }>;
353
+ error?: Error;
322
354
  }
323
355
  /**
324
- * Ensure a signal is supported on the current platform
356
+ * Create a config reload handler with schema validation
325
357
  *
326
- * Throws an error with actionable guidance if the signal is not supported.
327
- * Use this as a guard at the start of signal registration functions.
358
+ * Returns a signal handler function that implements restart-based reload:
359
+ * 1. Load new config
360
+ * 2. Validate against schema (mandatory)
361
+ * 3. If invalid: log errors, continue with current config
362
+ * 4. If valid: invoke callback, exit for restart
328
363
  *
329
- * @param signalName - Signal name (e.g., "SIGTERM") or id (e.g., "term")
330
- * @param options - Guard configuration
331
- * @throws {FoundryCatalogError} If signal is not found or not supported
364
+ * @param options - Reload configuration
332
365
  *
333
366
  * @example
334
367
  * ```typescript
335
- * await ensureSupported("SIGHUP");
336
- * // On Windows: throws with HTTP fallback guidance
337
- * // On POSIX: passes through
338
- * ```
339
- */
340
- declare function ensureSupported(signalName: string, options?: GuardOptions): Promise<void>;
341
- /**
342
- * Ensure platform supports signal-based exit codes
343
- *
344
- * Throws an error if the platform doesn't support the POSIX 128+N exit code pattern.
345
- * Use this when exit code semantics are critical to application logic.
346
- *
347
- * @throws {FoundryCatalogError} If platform doesn't support signal exit codes
368
+ * const reloadHandler = createConfigReloadHandler({
369
+ * loader: () => loadConfig('./config.yaml'),
370
+ * validator: (config) => validateConfigSchema(config),
371
+ * onValidated: async (config) => {
372
+ * logger.info('Config validated, restarting...');
373
+ * },
374
+ * logger: myLogger,
375
+ * });
348
376
  *
349
- * @example
350
- * ```typescript
351
- * ensureSignalExitCodesSupported();
352
- * // On Windows: throws with guidance
353
- * // On POSIX: passes through
377
+ * await manager.register('SIGHUP', reloadHandler);
354
378
  * ```
355
379
  */
356
- declare function ensureSignalExitCodesSupported(): void;
357
- /**
358
- * Ensure platform is POSIX
359
- *
360
- * Throws an error if the platform is not POSIX-compliant.
361
- * Use this for functionality that strictly requires POSIX signal semantics.
362
- *
363
- * @throws {FoundryCatalogError} If platform is not POSIX
364
- */
365
- declare function ensurePOSIX(): void;
380
+ declare function createConfigReloadHandler<T = unknown>(options: ConfigReloadOptions<T>): () => Promise<void>;
366
381
  /**
367
- * Ensure platform is Windows
368
- *
369
- * Throws an error if the platform is not Windows.
370
- * Use this for Windows-specific fallback testing or functionality.
382
+ * Three-strikes failure tracker
371
383
  *
372
- * @throws {FoundryCatalogError} If platform is not Windows
384
+ * Tracks consecutive config reload failures and triggers alerts.
385
+ * Useful for detecting persistent config source issues.
373
386
  */
374
- declare function ensureWindows(): void;
387
+ declare class ConfigReloadTracker {
388
+ private failures;
389
+ private lastFailureTime;
390
+ private readonly maxFailures;
391
+ private readonly logger?;
392
+ private readonly telemetry?;
393
+ constructor(options: {
394
+ maxFailures?: number;
395
+ logger?: FallbackLogger;
396
+ telemetry?: TelemetryEmitter;
397
+ });
398
+ /**
399
+ * Record a reload failure
400
+ *
401
+ * @returns true if threshold exceeded, false otherwise
402
+ */
403
+ recordFailure(): boolean;
404
+ /**
405
+ * Record a successful reload (resets counter)
406
+ */
407
+ recordSuccess(): void;
408
+ /**
409
+ * Get current failure count
410
+ */
411
+ getFailureCount(): number;
412
+ /**
413
+ * Get last failure timestamp
414
+ */
415
+ getLastFailureTime(): number | null;
416
+ }
375
417
 
376
418
  /**
377
- * HTTP Signal Endpoint Helper
419
+ * HTTP Config Reload Endpoint Helper
378
420
  *
379
- * Framework-agnostic scaffold for POST /admin/signal endpoint.
380
- * Applications provide auth/rate-limiting; helper handles validation and execution.
421
+ * Framework-agnostic scaffold for POST /admin/config/reload.
422
+ *
423
+ * Unlike the signal-based reload handler, this helper is synchronous from the
424
+ * perspective of the caller (it returns a response object). Applications can
425
+ * choose whether to apply config live or initiate a restart.
381
426
  */
382
427
 
383
- /**
384
- * Signal request payload
385
- */
386
- interface SignalRequest {
387
- signal: string;
428
+ interface ConfigReloadRequest {
388
429
  reason?: string;
389
430
  correlation_id?: string;
390
431
  }
391
- /**
392
- * Signal response (success)
393
- */
394
- interface SignalResponse {
395
- status: "accepted";
396
- signal: string;
432
+ interface ConfigReloadResponse {
433
+ status: "reloaded";
397
434
  correlation_id: string;
398
435
  message: string;
399
436
  }
400
- /**
401
- * Signal error response
402
- */
403
- interface SignalErrorResponse {
437
+ interface ConfigReloadErrorResponse {
404
438
  status: "error";
405
439
  error: string;
406
440
  message: string;
407
- valid_signals?: string[];
441
+ validation_errors?: Array<{
442
+ path: string;
443
+ message: string;
444
+ }>;
408
445
  }
409
- /**
410
- * Authentication result
411
- */
412
- interface AuthResult {
413
- authenticated: boolean;
414
- identity?: string;
415
- reason?: string;
446
+ type ConfigReloadRateLimitHook = (identity: string) => Promise<RateLimitResult> | RateLimitResult;
447
+ interface ConfigReloadEndpointOptions<T = unknown> {
448
+ loader: ConfigLoader<T>;
449
+ validator?: ConfigValidator<T>;
450
+ onReload?: (config: T) => Promise<void> | void;
451
+ auth: AuthHook;
452
+ rateLimit?: ConfigReloadRateLimitHook;
453
+ logger?: FallbackLogger;
454
+ telemetry?: TelemetryEmitter;
416
455
  }
456
+ declare function createConfigReloadEndpoint<T = unknown>(options: ConfigReloadEndpointOptions<T>): (payload: ConfigReloadRequest, req: unknown) => Promise<(ConfigReloadResponse | ConfigReloadErrorResponse) & {
457
+ statusCode?: number;
458
+ }>;
459
+
417
460
  /**
418
- * Rate limit result
461
+ * HTTP Control Discovery Endpoint Helper
462
+ *
463
+ * Framework-agnostic scaffold for a control-plane discovery endpoint.
419
464
  */
420
- interface RateLimitResult {
421
- allowed: boolean;
422
- remaining?: number;
423
- reset_at?: number;
465
+
466
+ interface ControlEndpointDescriptor {
467
+ method: string;
468
+ path: string;
469
+ summary?: string;
470
+ }
471
+ interface ControlDiscoveryResponse {
472
+ status: "ok";
473
+ service: {
474
+ name: string;
475
+ vendor: string;
476
+ version: string;
477
+ };
478
+ runtime: {
479
+ name: string;
480
+ version?: string;
481
+ platform: string;
482
+ arch: string;
483
+ };
484
+ auth_summary?: string;
485
+ endpoints: ControlEndpointDescriptor[];
424
486
  }
487
+ interface ControlDiscoveryErrorResponse {
488
+ status: "error";
489
+ error: string;
490
+ message: string;
491
+ }
492
+ interface ControlDiscoveryEndpointOptions {
493
+ identity: Identity;
494
+ version: string;
495
+ endpoints: ControlEndpointDescriptor[];
496
+ auth?: AuthHook;
497
+ authSummary?: string;
498
+ logger?: FallbackLogger;
499
+ telemetry?: TelemetryEmitter;
500
+ }
501
+ declare function createControlDiscoveryEndpoint(options: ControlDiscoveryEndpointOptions): (req: unknown) => Promise<(ControlDiscoveryResponse | ControlDiscoveryErrorResponse) & {
502
+ statusCode?: number;
503
+ }>;
504
+
425
505
  /**
426
- * Authentication hook function
506
+ * Signal Handler Convenience Wrappers
427
507
  *
428
- * Applications must provide this to validate requests.
429
- * Returns authentication result with optional identity.
508
+ * Common signal handling patterns for shutdown, reload, and custom behaviors.
430
509
  */
431
- type AuthHook = (req: unknown) => Promise<AuthResult> | AuthResult;
510
+
432
511
  /**
433
- * Rate limiting hook function
512
+ * Register a graceful shutdown handler
434
513
  *
435
- * Applications may provide this to enforce rate limits.
436
- * Returns whether request is allowed and quota info.
437
- */
438
- type RateLimitHook = (identity: string, signal: string) => Promise<RateLimitResult> | RateLimitResult;
439
- /**
440
- * Signal endpoint options
514
+ * Convenience wrapper for SIGTERM and SIGINT handlers.
515
+ * Automatically registers both signals to the same handler.
516
+ *
517
+ * @param manager - Signal manager instance
518
+ * @param handler - Shutdown handler function
519
+ * @param options - Handler options
520
+ *
521
+ * @example
522
+ * ```typescript
523
+ * await onShutdown(manager, async () => {
524
+ * await closeDatabase();
525
+ * await flushLogs();
526
+ * });
527
+ * ```
441
528
  */
442
- interface SignalEndpointOptions {
443
- /**
444
- * Signal manager instance
445
- */
446
- manager: SignalManager;
447
- /**
448
- * Authentication hook (required)
449
- */
450
- auth: AuthHook;
451
- /**
452
- * Rate limiting hook (optional)
453
- */
454
- rateLimit?: RateLimitHook;
455
- /**
456
- * Logger for endpoint events
457
- */
458
- logger?: FallbackLogger;
459
- /**
460
- * Telemetry emitter
461
- */
462
- telemetry?: TelemetryEmitter;
463
- /**
464
- * Allowed signals (default: all catalog signals)
465
- */
466
- allowedSignals?: string[];
467
- }
529
+ declare function onShutdown(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
468
530
  /**
469
- * Create a framework-agnostic signal endpoint handler
531
+ * Register a config reload handler
470
532
  *
471
- * Returns an async function that processes signal requests.
472
- * Applications wire this to their HTTP framework (Express, Fastify, etc.)
533
+ * Convenience wrapper for SIGHUP handler.
534
+ * Only registers on POSIX platforms (SIGHUP not supported on Windows).
473
535
  *
474
- * @param options - Endpoint configuration
536
+ * @param manager - Signal manager instance
537
+ * @param handler - Reload handler function
538
+ * @param options - Handler options
475
539
  *
476
- * @example Express
540
+ * @example
477
541
  * ```typescript
478
- * const handler = createSignalEndpoint({
479
- * manager,
480
- * auth: async (req) => {
481
- * const token = req.headers.authorization?.split(' ')[1];
482
- * return { authenticated: token === process.env.ADMIN_TOKEN };
483
- * },
484
- * });
485
- *
486
- * app.post('/admin/signal', async (req, res) => {
487
- * const result = await handler(req.body, req);
488
- * res.status(result.status === 'accepted' ? 202 : result.statusCode || 400)
489
- * .json(result);
542
+ * await onReload(manager, async () => {
543
+ * const newConfig = await loadConfig();
544
+ * await validateConfig(newConfig);
545
+ * process.exit(129); // Exit for restart
490
546
  * });
491
547
  * ```
548
+ */
549
+ declare function onReload(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
550
+ /**
551
+ * Register a custom handler for SIGUSR1
492
552
  *
493
- * @example Fastify
494
- * ```typescript
495
- * const handler = createSignalEndpoint({ manager, auth });
553
+ * Common use cases: toggle debug logging, reopen log files, dump statistics.
496
554
  *
497
- * fastify.post('/admin/signal', async (request, reply) => {
498
- * const result = await handler(request.body, request);
499
- * reply.status(result.status === 'accepted' ? 202 : 400).send(result);
555
+ * @param manager - Signal manager instance
556
+ * @param handler - Custom handler function
557
+ * @param options - Handler options
558
+ *
559
+ * @example
560
+ * ```typescript
561
+ * await onUSR1(manager, async () => {
562
+ * logger.info('SIGUSR1 received - reopening log files');
563
+ * await reopenLogFiles();
500
564
  * });
501
565
  * ```
502
566
  */
503
- declare function createSignalEndpoint(options: SignalEndpointOptions): (payload: SignalRequest, req: unknown) => Promise<(SignalResponse | SignalErrorResponse) & {
504
- statusCode?: number;
505
- }>;
567
+ declare function onUSR1(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
506
568
  /**
507
- * Create a simple bearer token auth hook
569
+ * Register a custom handler for SIGUSR2
508
570
  *
509
- * Validates requests against a static token.
510
- * For production, use mTLS or more robust auth.
571
+ * Common use cases: trigger profiling, rotate credentials, toggle verbose mode.
511
572
  *
512
- * @param expectedToken - Expected bearer token
573
+ * @param manager - Signal manager instance
574
+ * @param handler - Custom handler function
575
+ * @param options - Handler options
513
576
  *
514
577
  * @example
515
578
  * ```typescript
516
- * const auth = createBearerTokenAuth(process.env.ADMIN_TOKEN);
517
- * const handler = createSignalEndpoint({ manager, auth });
579
+ * await onUSR2(manager, async () => {
580
+ * logger.info('SIGUSR2 received - toggling debug mode');
581
+ * toggleDebugMode();
582
+ * });
518
583
  * ```
519
584
  */
520
- declare function createBearerTokenAuth(expectedToken: string): AuthHook;
585
+ declare function onUSR2(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
521
586
  /**
522
- * Create a simple in-memory rate limiter
587
+ * Register an emergency quit handler
523
588
  *
524
- * Tracks requests per identity with sliding window.
525
- * For production, use Redis or distributed rate limiting.
589
+ * Convenience wrapper for SIGQUIT (immediate exit, no cleanup).
526
590
  *
527
- * @param requestsPerMinute - Max requests per minute per identity
591
+ * @param manager - Signal manager instance
592
+ * @param handler - Emergency quit handler
593
+ * @param options - Handler options
528
594
  *
529
595
  * @example
530
596
  * ```typescript
531
- * const rateLimit = createSimpleRateLimiter(10); // 10 req/min
532
- * const handler = createSignalEndpoint({ manager, auth, rateLimit });
597
+ * await onEmergencyQuit(manager, async () => {
598
+ * logger.error('SIGQUIT received - emergency exit');
599
+ * process.exit(131);
600
+ * });
533
601
  * ```
534
602
  */
535
- declare function createSimpleRateLimiter(requestsPerMinute: number): RateLimitHook;
536
-
603
+ declare function onEmergencyQuit(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
537
604
  /**
538
- * Configuration Reload Helpers
605
+ * Register handlers for all common shutdown signals
539
606
  *
540
- * Implements restart-based config reload pattern with mandatory schema validation.
541
- * Per Crucible standard: validate before restart, reject invalid configs without disruption.
542
- */
543
-
544
- /**
545
- * Configuration validator function type
607
+ * Registers the same handler for SIGTERM, SIGINT, and SIGQUIT.
608
+ * Useful for applications that want consistent shutdown behavior.
546
609
  *
547
- * Applications provide this function to validate new config against schema.
548
- * Should return validation result with errors if invalid.
549
- */
550
- type ConfigValidator<T = unknown> = (config: T) => Promise<ConfigValidationResult> | ConfigValidationResult;
551
- /**
552
- * Configuration validation result
610
+ * @param manager - Signal manager instance
611
+ * @param handler - Shutdown handler function
612
+ * @param options - Handler options
553
613
  */
554
- interface ConfigValidationResult {
555
- valid: boolean;
556
- errors?: Array<{
557
- path: string;
558
- message: string;
559
- }>;
560
- }
614
+ declare function onAnyShutdown(manager: SignalManager, handler: SignalHandler, options?: HandlerOptions): Promise<void>;
615
+
561
616
  /**
562
- * Configuration loader function type
617
+ * Double-Tap Signal Handling
563
618
  *
564
- * Applications provide this function to load new config from disk/environment.
619
+ * Implements Ctrl+C double-tap pattern for graceful shutdown with force-quit option.
620
+ * Per Crucible standard: 2-second window, immediate exit on second signal.
565
621
  */
566
- type ConfigLoader<T = unknown> = () => Promise<T> | T;
622
+
567
623
  /**
568
- * Config reload options
624
+ * Double-tap configuration
569
625
  */
570
- interface ConfigReloadOptions<T = unknown> {
571
- /**
572
- * Config loader function
573
- */
574
- loader: ConfigLoader<T>;
575
- /**
576
- * Schema validator function
577
- */
578
- validator: ConfigValidator<T>;
626
+ interface DoubleTapConfig {
579
627
  /**
580
- * Callback invoked after successful validation, before exit
581
- * Use for cleanup, logging, etc.
628
+ * Debounce window in milliseconds (default: 2000ms per Crucible standard)
582
629
  */
583
- onValidated?: (config: T) => void | Promise<void>;
630
+ windowMs?: number;
584
631
  /**
585
- * Exit code for successful reload (default: 129 for SIGHUP)
632
+ * Exit code for forced double-tap exit (default: 130 for SIGINT)
586
633
  */
587
634
  exitCode?: number;
588
635
  /**
589
- * Logger for reload events
636
+ * Message to display on first signal (default: from catalog)
590
637
  */
591
- logger?: FallbackLogger;
638
+ hintMessage?: string;
592
639
  /**
593
- * Telemetry emitter
640
+ * Logger for double-tap events
594
641
  */
595
- telemetry?: TelemetryEmitter;
642
+ logger?: FallbackLogger;
596
643
  /**
597
- * Test mode (prevents process.exit)
644
+ * Enable test mode (prevents process.exit calls)
598
645
  */
599
646
  testMode?: boolean;
600
647
  }
601
648
  /**
602
- * Config reload result (for testing)
649
+ * Double-tap state tracker
603
650
  */
604
- interface ConfigReloadResult {
605
- reloaded: boolean;
606
- validationErrors?: Array<{
607
- path: string;
608
- message: string;
609
- }>;
610
- error?: Error;
651
+ interface DoubleTapState {
652
+ firstTapTime: number | null;
653
+ windowMs: number;
654
+ exitCode: number;
655
+ hintMessage: string;
656
+ logger?: FallbackLogger;
657
+ testMode: boolean;
611
658
  }
612
659
  /**
613
- * Create a config reload handler with schema validation
660
+ * Create double-tap state tracker for a signal
614
661
  *
615
- * Returns a signal handler function that implements restart-based reload:
616
- * 1. Load new config
617
- * 2. Validate against schema (mandatory)
618
- * 3. If invalid: log errors, continue with current config
619
- * 4. If valid: invoke callback, exit for restart
662
+ * @param signalName - Signal name (typically "SIGINT")
663
+ * @param config - Double-tap configuration
664
+ */
665
+ declare function createDoubleTapTracker(signalName: string, config?: DoubleTapConfig): Promise<DoubleTapState>;
666
+ /**
667
+ * Handle double-tap signal logic
620
668
  *
621
- * @param options - Reload configuration
669
+ * Returns true if this is the second tap (force-quit), false if first tap.
670
+ * Updates state to track timing between taps.
622
671
  *
623
- * @example
624
- * ```typescript
625
- * const reloadHandler = createConfigReloadHandler({
626
- * loader: () => loadConfig('./config.yaml'),
627
- * validator: (config) => validateConfigSchema(config),
628
- * onValidated: async (config) => {
629
- * logger.info('Config validated, restarting...');
630
- * },
631
- * logger: myLogger,
632
- * });
672
+ * @param state - Double-tap state tracker
673
+ * @returns true if force-quit should proceed, false if graceful shutdown
674
+ */
675
+ declare function handleDoubleTap(state: DoubleTapState): boolean;
676
+ /**
677
+ * Reset double-tap state
633
678
  *
634
- * await manager.register('SIGHUP', reloadHandler);
635
- * ```
679
+ * Called when graceful shutdown completes before second tap.
636
680
  */
637
- declare function createConfigReloadHandler<T = unknown>(options: ConfigReloadOptions<T>): () => Promise<void>;
681
+ declare function resetDoubleTap(state: DoubleTapState): void;
638
682
  /**
639
- * Three-strikes failure tracker
683
+ * Check if currently within double-tap window
640
684
  *
641
- * Tracks consecutive config reload failures and triggers alerts.
642
- * Useful for detecting persistent config source issues.
685
+ * Useful for testing and debugging.
643
686
  */
644
- declare class ConfigReloadTracker {
645
- private failures;
646
- private lastFailureTime;
647
- private readonly maxFailures;
648
- private readonly logger?;
649
- private readonly telemetry?;
650
- constructor(options: {
651
- maxFailures?: number;
652
- logger?: FallbackLogger;
653
- telemetry?: TelemetryEmitter;
654
- });
655
- /**
656
- * Record a reload failure
657
- *
658
- * @returns true if threshold exceeded, false otherwise
659
- */
660
- recordFailure(): boolean;
661
- /**
662
- * Record a successful reload (resets counter)
663
- */
664
- recordSuccess(): void;
665
- /**
666
- * Get current failure count
667
- */
668
- getFailureCount(): number;
687
+ declare function isWithinWindow(state: DoubleTapState): boolean;
688
+ /**
689
+ * Get time remaining in double-tap window (milliseconds)
690
+ *
691
+ * Returns null if not in a window, otherwise milliseconds remaining.
692
+ */
693
+ declare function getWindowTimeRemaining(state: DoubleTapState): number | null;
694
+
695
+ /**
696
+ * Signal Support Guards
697
+ *
698
+ * Validation functions that throw actionable errors when signals are unsupported.
699
+ * Used to fail-fast with clear operational guidance.
700
+ */
701
+ /**
702
+ * Guard options
703
+ */
704
+ interface GuardOptions {
669
705
  /**
670
- * Get last failure timestamp
706
+ * Include platform-specific operational guidance in error message
671
707
  */
672
- getLastFailureTime(): number | null;
708
+ includeGuidance?: boolean;
673
709
  }
710
+ /**
711
+ * Ensure a signal is supported on the current platform
712
+ *
713
+ * Throws an error with actionable guidance if the signal is not supported.
714
+ * Use this as a guard at the start of signal registration functions.
715
+ *
716
+ * @param signalName - Signal name (e.g., "SIGTERM") or id (e.g., "term")
717
+ * @param options - Guard configuration
718
+ * @throws {FoundryCatalogError} If signal is not found or not supported
719
+ *
720
+ * @example
721
+ * ```typescript
722
+ * await ensureSupported("SIGHUP");
723
+ * // On Windows: throws with HTTP fallback guidance
724
+ * // On POSIX: passes through
725
+ * ```
726
+ */
727
+ declare function ensureSupported(signalName: string, options?: GuardOptions): Promise<void>;
728
+ /**
729
+ * Ensure platform supports signal-based exit codes
730
+ *
731
+ * Throws an error if the platform doesn't support the POSIX 128+N exit code pattern.
732
+ * Use this when exit code semantics are critical to application logic.
733
+ *
734
+ * @throws {FoundryCatalogError} If platform doesn't support signal exit codes
735
+ *
736
+ * @example
737
+ * ```typescript
738
+ * ensureSignalExitCodesSupported();
739
+ * // On Windows: throws with guidance
740
+ * // On POSIX: passes through
741
+ * ```
742
+ */
743
+ declare function ensureSignalExitCodesSupported(): void;
744
+ /**
745
+ * Ensure platform is POSIX
746
+ *
747
+ * Throws an error if the platform is not POSIX-compliant.
748
+ * Use this for functionality that strictly requires POSIX signal semantics.
749
+ *
750
+ * @throws {FoundryCatalogError} If platform is not POSIX
751
+ */
752
+ declare function ensurePOSIX(): void;
753
+ /**
754
+ * Ensure platform is Windows
755
+ *
756
+ * Throws an error if the platform is not Windows.
757
+ * Use this for Windows-specific fallback testing or functionality.
758
+ *
759
+ * @throws {FoundryCatalogError} If platform is not Windows
760
+ */
761
+ declare function ensureWindows(): void;
674
762
 
675
- export { type AuthHook, type AuthResult, BehaviorInfo, type ConfigLoader, type ConfigReloadOptions, type ConfigReloadResult, ConfigReloadTracker, type ConfigValidationResult, type ConfigValidator, type DoubleTapConfig, type DoubleTapState, FallbackLogger, type GuardOptions, HandlerOptions, type Platform, type PlatformCapabilities, type RateLimitHook, type RateLimitResult, SignalCatalog, type SignalEndpointOptions, type SignalErrorResponse, SignalHandler, SignalInfo, SignalManager, type SignalRequest, type SignalResponse, TelemetryEmitter, createBearerTokenAuth, createConfigReloadHandler, createDoubleTapTracker, createSignalEndpoint, createSimpleRateLimiter, ensurePOSIX, ensureSignalExitCodesSupported, ensureSupported, ensureWindows, getBehavior, getPlatform, getPlatformCapabilities, getSignal, getSignalCatalog, getSignalNumber, getSignalsVersion, getWindowTimeRemaining, getWindowsEvent, handleDoubleTap, isPOSIX, isWindows, isWithinWindow, listBehaviors, listSignals, onAnyShutdown, onEmergencyQuit, onReload, onShutdown, onUSR1, onUSR2, resetDoubleTap, supportsSignal, supportsSignalExitCodes };
763
+ export { type AuthHook, type AuthResult, BehaviorInfo, type ConfigLoader, type ConfigReloadEndpointOptions, type ConfigReloadErrorResponse, type ConfigReloadOptions, type ConfigReloadRateLimitHook, type ConfigReloadRequest, type ConfigReloadResponse, type ConfigReloadResult, ConfigReloadTracker, type ConfigValidationResult, type ConfigValidator, type ControlDiscoveryEndpointOptions, type ControlDiscoveryErrorResponse, type ControlDiscoveryResponse, type ControlEndpointDescriptor, type DoubleTapConfig, type DoubleTapState, FallbackLogger, type GuardOptions, HandlerOptions, type Platform, type PlatformCapabilities, type RateLimitHook, type RateLimitResult, SignalCatalog, type SignalEndpointOptions, type SignalErrorResponse, SignalHandler, SignalInfo, SignalManager, type SignalRequest, type SignalResponse, TelemetryEmitter, createBearerTokenAuth, createConfigReloadEndpoint, createConfigReloadHandler, createControlDiscoveryEndpoint, createDoubleTapTracker, createSignalEndpoint, createSimpleRateLimiter, ensurePOSIX, ensureSignalExitCodesSupported, ensureSupported, ensureWindows, getBehavior, getPlatform, getPlatformCapabilities, getSignal, getSignalCatalog, getSignalNumber, getSignalsVersion, getWindowTimeRemaining, getWindowsEvent, handleDoubleTap, isPOSIX, isWindows, isWithinWindow, listBehaviors, listSignals, onAnyShutdown, onEmergencyQuit, onReload, onShutdown, onUSR1, onUSR2, resetDoubleTap, supportsSignal, supportsSignalExitCodes };