@sylphx/sdk 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/health/index.d.ts +35 -1
- package/dist/health/index.mjs +31 -0
- package/dist/health/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/health/index.d.ts
CHANGED
|
@@ -455,8 +455,34 @@ interface HealthScore {
|
|
|
455
455
|
* `health.signal.factor`.
|
|
456
456
|
*/
|
|
457
457
|
readonly signalFactors?: Record<string, number>;
|
|
458
|
+
/**
|
|
459
|
+
* Non-secret release/deployment identity for live-build verification.
|
|
460
|
+
*
|
|
461
|
+
* Additive to the ADR-111 wire shape: probe sidecars ignore it, while
|
|
462
|
+
* operators and deployment monitors can prove which immutable build is
|
|
463
|
+
* answering `/healthz` without needing access to the deploy control plane.
|
|
464
|
+
*/
|
|
465
|
+
readonly release?: HealthRelease;
|
|
458
466
|
readonly lastTickAt: string;
|
|
459
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Public, non-secret release identity attached to a health response.
|
|
470
|
+
*
|
|
471
|
+
* These fields intentionally mirror platform-injected runtime identity
|
|
472
|
+
* variables (`SYLPHX_*`) instead of arbitrary metadata. Health endpoints are
|
|
473
|
+
* commonly public, so this surface must stay provenance-focused and must not
|
|
474
|
+
* become a general key/value bag that can accidentally expose secrets.
|
|
475
|
+
*/
|
|
476
|
+
interface HealthRelease {
|
|
477
|
+
readonly version?: string | null;
|
|
478
|
+
readonly commitSha?: string | null;
|
|
479
|
+
readonly imageDigest?: string | null;
|
|
480
|
+
readonly imageTag?: string | null;
|
|
481
|
+
readonly deploymentId?: string | null;
|
|
482
|
+
readonly buildId?: string | null;
|
|
483
|
+
readonly replicaId?: string | null;
|
|
484
|
+
}
|
|
485
|
+
type HealthReleaseInput = HealthRelease | (() => HealthRelease | null | undefined);
|
|
460
486
|
/**
|
|
461
487
|
* Wire format the sidecar's `app-poll.ts::parseAppHealthBody` consumes.
|
|
462
488
|
*
|
|
@@ -543,6 +569,14 @@ interface SylphxHealthOptions {
|
|
|
543
569
|
* adapter; the interface is public and stable so any backend works.
|
|
544
570
|
*/
|
|
545
571
|
readonly history?: HistoryOption;
|
|
572
|
+
/**
|
|
573
|
+
* Optional non-secret release identity to include in every HealthSnapshot.
|
|
574
|
+
*
|
|
575
|
+
* Accepts a static object or a synchronous resolver. Resolver failures are
|
|
576
|
+
* swallowed: release metadata must never block the load-bearing health
|
|
577
|
+
* response.
|
|
578
|
+
*/
|
|
579
|
+
readonly release?: HealthReleaseInput;
|
|
546
580
|
}
|
|
547
581
|
|
|
548
582
|
/**
|
|
@@ -1354,4 +1388,4 @@ interface SylphxHealth extends HealthEvaluator {
|
|
|
1354
1388
|
}
|
|
1355
1389
|
declare function sylphxHealth(opts?: SylphxHealthOptions): SylphxHealth;
|
|
1356
1390
|
|
|
1357
|
-
export { type AsyncSignal, BAGGAGE_KEY_CAUSE, BAGGAGE_KEY_CHAIN, type CausalityHandle, type CausalityOption, type CausalityOptions, DEFAULT_CAUSE_THRESHOLD, DEFAULT_HISTORY_CAPACITY, type DependencyPingOptions, type ErrorRateOptions, type ErrorRateSignalHandle, type EventLoopLagOptions, type ExpressHealthMiddleware, type ExpressNextLike, type ExpressRequestLike, type ExpressResponseLike, type FastifyHealthPlugin, type FastifyInstanceLike, type FastifyReplyLike, type FetchHealthHandler, GENESIS_PREV_HASH, HealthError, type HealthEvaluator, type HealthScore, type HealthSnapshot, type HistoryEntry, type HistoryOption, type HistoryQuery, type HistoryRecorder, type HistoryRecorderOptions, type HistoryStore, type HonoContextLike, type HonoHealthMiddleware, type HonoNextLike, type InMemoryHistoryStoreOptions, MAX_CHAIN_LENGTH, type MemoryPressureOptions, EVENT_NAMES as OTEL_EVENT_NAMES, METRIC_NAMES as OTEL_METRIC_NAMES, SPAN_ATTRIBUTES as OTEL_SPAN_ATTRIBUTES, type OtelEmitter, type OtelEmitterOptions, type OtelOption, type OtelStaticAttributes, type PingSignalOptions, type QueueDepthOptions, type ScoringStrategy, type Signal, type SignalBase, type SignalReading, type SylphxHealth, type SylphxHealthOptions, type SyncSignal, type UnixSocketServerHandle, type UnixSocketServerOptions, type UpstreamCause, type VerificationResult, type WithHealthOptions, canonicalizeSignals, createCausalityHandle, createHistoryRecorder, createInMemoryHistoryStore, createNodeHandler, createOtelEmitter, createWebHandler, databaseSignal, defaultScoringStrategy, errorRateSignal, eventLoopLagSignal, memoryPressureSignal, pingSignal, queueDepthSignal, redisSignal, sylphxHealth, verifyChain, weightedProduct, withHealth };
|
|
1391
|
+
export { type AsyncSignal, BAGGAGE_KEY_CAUSE, BAGGAGE_KEY_CHAIN, type CausalityHandle, type CausalityOption, type CausalityOptions, DEFAULT_CAUSE_THRESHOLD, DEFAULT_HISTORY_CAPACITY, type DependencyPingOptions, type ErrorRateOptions, type ErrorRateSignalHandle, type EventLoopLagOptions, type ExpressHealthMiddleware, type ExpressNextLike, type ExpressRequestLike, type ExpressResponseLike, type FastifyHealthPlugin, type FastifyInstanceLike, type FastifyReplyLike, type FetchHealthHandler, GENESIS_PREV_HASH, HealthError, type HealthEvaluator, type HealthRelease, type HealthReleaseInput, type HealthScore, type HealthSnapshot, type HistoryEntry, type HistoryOption, type HistoryQuery, type HistoryRecorder, type HistoryRecorderOptions, type HistoryStore, type HonoContextLike, type HonoHealthMiddleware, type HonoNextLike, type InMemoryHistoryStoreOptions, MAX_CHAIN_LENGTH, type MemoryPressureOptions, EVENT_NAMES as OTEL_EVENT_NAMES, METRIC_NAMES as OTEL_METRIC_NAMES, SPAN_ATTRIBUTES as OTEL_SPAN_ATTRIBUTES, type OtelEmitter, type OtelEmitterOptions, type OtelOption, type OtelStaticAttributes, type PingSignalOptions, type QueueDepthOptions, type ScoringStrategy, type Signal, type SignalBase, type SignalReading, type SylphxHealth, type SylphxHealthOptions, type SyncSignal, type UnixSocketServerHandle, type UnixSocketServerOptions, type UpstreamCause, type VerificationResult, type WithHealthOptions, canonicalizeSignals, createCausalityHandle, createHistoryRecorder, createInMemoryHistoryStore, createNodeHandler, createOtelEmitter, createWebHandler, databaseSignal, defaultScoringStrategy, errorRateSignal, eventLoopLagSignal, memoryPressureSignal, pingSignal, queueDepthSignal, redisSignal, sylphxHealth, verifyChain, weightedProduct, withHealth };
|
package/dist/health/index.mjs
CHANGED
|
@@ -1207,6 +1207,35 @@ function clampHealthFactor(value) {
|
|
|
1207
1207
|
if (value > 1) return 1;
|
|
1208
1208
|
return value;
|
|
1209
1209
|
}
|
|
1210
|
+
var HEALTH_RELEASE_KEYS = [
|
|
1211
|
+
"version",
|
|
1212
|
+
"commitSha",
|
|
1213
|
+
"imageDigest",
|
|
1214
|
+
"imageTag",
|
|
1215
|
+
"deploymentId",
|
|
1216
|
+
"buildId",
|
|
1217
|
+
"replicaId"
|
|
1218
|
+
];
|
|
1219
|
+
function resolveHealthRelease(input) {
|
|
1220
|
+
if (!input) return void 0;
|
|
1221
|
+
try {
|
|
1222
|
+
const value = typeof input === "function" ? input() : input;
|
|
1223
|
+
if (!value) return void 0;
|
|
1224
|
+
const release = {};
|
|
1225
|
+
for (const key of HEALTH_RELEASE_KEYS) {
|
|
1226
|
+
const raw = value[key];
|
|
1227
|
+
if (raw === null) {
|
|
1228
|
+
release[key] = null;
|
|
1229
|
+
} else if (typeof raw === "string") {
|
|
1230
|
+
const trimmed = raw.trim();
|
|
1231
|
+
if (trimmed.length > 0) release[key] = trimmed;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return Object.keys(release).length > 0 ? release : void 0;
|
|
1235
|
+
} catch {
|
|
1236
|
+
return void 0;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1210
1239
|
function sylphxHealth(opts = {}) {
|
|
1211
1240
|
const signals = opts.signals && opts.signals.length > 0 ? [...opts.signals] : [eventLoopLagSignal({ degradedMs: 5e3, deadMs: 3e4, weight: 1 })];
|
|
1212
1241
|
const scoringStrategy = opts.scoringStrategy ?? defaultScoringStrategy();
|
|
@@ -1234,10 +1263,12 @@ function sylphxHealth(opts = {}) {
|
|
|
1234
1263
|
signalsMap[signal.name] = reading.value;
|
|
1235
1264
|
signalFactors[signal.name] = reading.unknown === true ? 1 : clampHealthFactor(reading.healthFactor);
|
|
1236
1265
|
}
|
|
1266
|
+
const release = resolveHealthRelease(opts.release);
|
|
1237
1267
|
const result = {
|
|
1238
1268
|
score,
|
|
1239
1269
|
signals: signalsMap,
|
|
1240
1270
|
signalFactors,
|
|
1271
|
+
...release ? { release } : {},
|
|
1241
1272
|
lastTickAt: now().toISOString()
|
|
1242
1273
|
};
|
|
1243
1274
|
lastScore = result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/health/causality.ts","../../src/health/effects.ts","../../src/health/types.ts","../../src/health/handler.ts","../../src/health/history.ts","../../src/health/otel.ts","../../src/health/scoring.ts","../../src/health/signals/event-loop-lag.ts","../../src/health/unix-socket-server.ts","../../src/health/middleware.ts","../../src/health/signals/error-rate.ts","../../src/health/signals/memory-pressure.ts","../../src/health/signals/ping.ts","../../src/health/signals/queue-depth.ts","../../src/health/index.ts"],"sourcesContent":["/**\n * Causality-aware health propagation for `@sylphx/sdk/health` (ADR-143).\n *\n * Reads + writes the OpenTelemetry Baggage entry `sylphx.health.cause`\n * with value `<signal>@<service>[,<signal>@<service>...]` per ADR-143 §3.1.\n * Append-only `sylphx.health.cause_chain` carries the hop chain so\n * operators can reconstruct multi-hop incident paths.\n *\n * # Read side (downstream)\n *\n * On every `evaluate()`, the SDK reads `propagation.getActiveBaggage()`\n * before computing the score. When the baggage carries an\n * `sylphx.health.cause` entry, the resulting `HealthScore.upstreamCause`\n * surfaces the parsed causes + chain. The OTel emitter then sets\n * `service.health.cause_root` / `cause_signal` / `cause_chain` span\n * attributes and increments the `health.downstream_cause` Counter.\n *\n * # Write side (upstream — the service whose signal is failing)\n *\n * `runWithCause(fn, score)` wraps an async function in a new OTel\n * context where the cause baggage is set. Used by `withHealth.*`\n * middleware to inject baggage on every non-/healthz request so that\n * downstream calls within that request inherit the cause via standard\n * OTel context propagation (W3C Baggage header on HTTP, native\n * baggage on gRPC, queue metadata on Kafka/Cloud Tasks).\n *\n * # Graceful fallback\n *\n * `@opentelemetry/api` is an optional peer dependency (see ADR-142\n * §2.3). When the dynamic import fails — or when the runtime exposes\n * only a partial OTel API — the handle transitions to `'unavailable'`,\n * every method becomes a no-op, and `runWithCause` runs the inner\n * function with no context modification. The `/healthz` contract is\n * unchanged in either case (ADR-143 §2.5 cardinal rule).\n *\n * # Cardinal rules (carried from ADR-141 / ADR-142)\n *\n * - Propagation never throws. Errors in `readCause`, `encodeCause`,\n * `runWithCause` are caught and downgraded to no-op behaviour.\n * - The HTTP response shape is unchanged. Baggage is augmentation;\n * probe surface is the load-bearing contract.\n */\n\n// Compile-time-only types — erased at emit time so consumers without\n// @opentelemetry/api installed do not see a missing-module error.\nimport type { Baggage, BaggageEntry, Context, ContextAPI, PropagationAPI } from '@opentelemetry/api'\nimport type { HealthScore } from './types'\n\n// ─── Public types ────────────────────────────────────────────────────────────\n\nexport const BAGGAGE_KEY_CAUSE = 'sylphx.health.cause'\nexport const BAGGAGE_KEY_CHAIN = 'sylphx.health.cause_chain'\n\n/** Default threshold below which a signal triggers cause propagation. */\nexport const DEFAULT_CAUSE_THRESHOLD = 0.5\n\n/** Maximum number of hops persisted in the chain baggage (ADR-143 §5). */\nexport const MAX_CHAIN_LENGTH = 5\n\n/**\n * The flag value passed by callers via `sylphxHealth({ causality })` /\n * `withHealth.hono({ causality })`. `true` (default) enables propagation\n * with sensible defaults; `false` disables it; an object configures it.\n */\nexport type CausalityOption = boolean | CausalityOptions\n\nexport interface CausalityOptions {\n\t/**\n\t * Below this signal factor, the SDK considers the signal \"causing\n\t * degradation\" and emits a cause entry. Default: `0.5` — matches the\n\t * ADR-111 kill threshold. Range: `(0, 1]`.\n\t */\n\treadonly threshold?: number\n\t/**\n\t * Override the service name used in cause entries. Falls back to\n\t * `process.env.OTEL_SERVICE_NAME` then `process.env.SERVICE_NAME`\n\t * then `\"unknown\"`. Operators of multi-tenant runtimes typically\n\t * override this per evaluator.\n\t */\n\treadonly serviceName?: string\n\t/**\n\t * When true (default), the SDK appends the local service name to\n\t * `sylphx.health.cause_chain` baggage so downstream operators can\n\t * reconstruct multi-hop incident paths.\n\t */\n\treadonly propagateChain?: boolean\n}\n\n/** Parsed view of an upstream cause read from baggage. */\nexport interface UpstreamCause {\n\t/**\n\t * The list of `<signal>@<service>` pairs carried in the baggage entry.\n\t * Operators read `causes[0]` for the most-significant root; the full\n\t * list preserves multi-cause concurrent failures.\n\t */\n\treadonly causes: ReadonlyArray<{ readonly signal: string; readonly service: string }>\n\t/**\n\t * The hop chain — services that observed this cause and forwarded it.\n\t * Most recent service is at the tail. `chain.length === 0` means the\n\t * baggage entry was just minted (no downstream services have observed\n\t * it yet — typically only at the moment-of-incident origin).\n\t */\n\treadonly chain: ReadonlyArray<string>\n}\n\n/** Operational handle the SDK uses to read + write cause baggage. */\nexport interface CausalityHandle {\n\t/** Async loader state — exposed for tests + diagnostics. */\n\treadonly state: 'loading' | 'ready' | 'unavailable' | 'disposed'\n\t/** Read upstream cause from active OTel baggage. Returns null when absent. */\n\treadCause(): UpstreamCause | null\n\t/**\n\t * Encode the local cause string for baggage. Returns null when none of\n\t * the signals' factors are below the configured threshold.\n\t */\n\tencodeCause(score: HealthScore): string | null\n\t/**\n\t * Run `fn` within a new OTel context that has our cause + chain\n\t * baggage entries set. Used by the middleware to inject baggage on\n\t * every non-/healthz request. When OTel API is unavailable, this\n\t * degrades to `fn()` directly.\n\t */\n\trunWithCause<T>(fn: () => T | Promise<T>, score: HealthScore): Promise<T>\n\t/** Tear down internal references. Idempotent. */\n\tdispose(): void\n}\n\n// ─── Loader (mirrors OTel emitter pattern) ───────────────────────────────────\n\ninterface CausalityApi {\n\treadonly context: ContextAPI\n\treadonly propagation: PropagationAPI\n}\n\nlet cachedApi: CausalityApi | null | undefined\n\n/**\n * Lazy load the OTel `context` + `propagation` namespaces. Cached across\n * causality handles so we only attempt the dynamic import once per\n * process. Test-only reset hook exposed for unit tests.\n */\nexport async function tryLoadCausalityApi(): Promise<CausalityApi | null> {\n\tif (cachedApi !== undefined) return cachedApi\n\ttry {\n\t\tconst api = await import('@opentelemetry/api')\n\t\tif (!api?.context || !api?.propagation) {\n\t\t\tcachedApi = null\n\t\t\treturn cachedApi\n\t\t}\n\t\tcachedApi = { context: api.context, propagation: api.propagation }\n\t} catch {\n\t\tcachedApi = null\n\t}\n\treturn cachedApi\n}\n\n/** Test-only escape hatch. */\nexport function __resetCausalityApiCacheForTests(): void {\n\tcachedApi = undefined\n}\n\n// ─── Encoding / parsing helpers ──────────────────────────────────────────────\n\nfunction defaultServiceName(): string {\n\tif (typeof process !== 'undefined' && process.env) {\n\t\tif (process.env.OTEL_SERVICE_NAME !== undefined) return process.env.OTEL_SERVICE_NAME\n\t\tif (process.env.SERVICE_NAME !== undefined) return process.env.SERVICE_NAME\n\t}\n\treturn 'unknown'\n}\n\nfunction isSafeIdentifier(part: string): boolean {\n\t// Conservative: no commas (separator), no `@` (encoder), no whitespace.\n\t// Anything else is fair game for service / signal identifiers.\n\treturn part.length > 0 && !/[,@\\s]/.test(part)\n}\n\n/** Serialise the local cause for the baggage entry. */\nfunction encodeCauseFromScore(\n\tscore: HealthScore,\n\tthreshold: number,\n\tserviceName: string,\n): string | null {\n\tconst factors = score.signalFactors\n\tif (!factors) return null\n\tconst entries: string[] = []\n\tfor (const [signalName, factor] of Object.entries(factors)) {\n\t\tif (typeof factor !== 'number' || !Number.isFinite(factor)) continue\n\t\tif (factor >= threshold) continue\n\t\tif (!isSafeIdentifier(signalName) || !isSafeIdentifier(serviceName)) continue\n\t\tentries.push(`${signalName}@${serviceName}`)\n\t}\n\tif (entries.length === 0) return null\n\treturn entries.join(',')\n}\n\n/**\n * Parse the cause baggage value. Malformed entries are silently dropped\n * (we never throw on a corrupt baggage entry — ADR-143 §2.5).\n */\nfunction parseCauseValue(value: string): UpstreamCause['causes'] {\n\tconst out: Array<{ signal: string; service: string }> = []\n\tfor (const raw of value.split(',')) {\n\t\tconst piece = raw.trim()\n\t\tif (piece.length === 0) continue\n\t\tconst atIdx = piece.indexOf('@')\n\t\tif (atIdx <= 0 || atIdx === piece.length - 1) continue\n\t\tconst signal = piece.slice(0, atIdx)\n\t\tconst service = piece.slice(atIdx + 1)\n\t\tif (!isSafeIdentifier(signal) || !isSafeIdentifier(service)) continue\n\t\tout.push({ signal, service })\n\t}\n\treturn out\n}\n\nfunction parseChainValue(value: string): ReadonlyArray<string> {\n\treturn value\n\t\t.split(',')\n\t\t.map((s) => s.trim())\n\t\t.filter((s) => s.length > 0 && isSafeIdentifier(s))\n}\n\nfunction appendChain(\n\tprev: ReadonlyArray<string>,\n\tserviceName: string,\n\tmaxLen: number,\n): ReadonlyArray<string> {\n\tif (!isSafeIdentifier(serviceName)) return prev\n\t// Don't double-append when we're the most recent hop.\n\tif (prev[prev.length - 1] === serviceName) return prev\n\tconst next = [...prev, serviceName]\n\tif (next.length <= maxLen) return next\n\treturn next.slice(next.length - maxLen)\n}\n\n// ─── Implementation ──────────────────────────────────────────────────────────\n\nclass CausalityHandleImpl implements CausalityHandle {\n\tprivate _state: CausalityHandle['state'] = 'loading'\n\tprivate apiRef: CausalityApi | null = null\n\tprivate readonly threshold: number\n\tprivate readonly serviceName: string\n\tprivate readonly propagateChain: boolean\n\n\tconstructor(opts: CausalityOptions) {\n\t\tconst threshold = opts.threshold ?? DEFAULT_CAUSE_THRESHOLD\n\t\tif (!Number.isFinite(threshold) || threshold <= 0 || threshold > 1) {\n\t\t\tthrow new Error(`createCausalityHandle: threshold must be in (0, 1], got ${threshold}`)\n\t\t}\n\t\tthis.threshold = threshold\n\t\tthis.serviceName = opts.serviceName ?? defaultServiceName()\n\t\tthis.propagateChain = opts.propagateChain !== false\n\t\tvoid this.initialize()\n\t}\n\n\tget state(): CausalityHandle['state'] {\n\t\treturn this._state\n\t}\n\n\tprivate async initialize(): Promise<void> {\n\t\tconst api = await tryLoadCausalityApi()\n\t\tif (this._state === 'disposed') return\n\t\tif (api === null) {\n\t\t\tthis._state = 'unavailable'\n\t\t\treturn\n\t\t}\n\t\tthis.apiRef = api\n\t\tthis._state = 'ready'\n\t}\n\n\treadCause(): UpstreamCause | null {\n\t\tif (this._state !== 'ready' || !this.apiRef) return null\n\t\ttry {\n\t\t\tconst baggage = this.apiRef.propagation.getActiveBaggage()\n\t\t\tif (!baggage) return null\n\t\t\tconst causeEntry = baggage.getEntry(BAGGAGE_KEY_CAUSE)\n\t\t\tif (!causeEntry || typeof causeEntry.value !== 'string') return null\n\t\t\tconst causes = parseCauseValue(causeEntry.value)\n\t\t\tif (causes.length === 0) return null\n\t\t\tconst chainEntry = baggage.getEntry(BAGGAGE_KEY_CHAIN)\n\t\t\tconst chain =\n\t\t\t\tchainEntry && typeof chainEntry.value === 'string' ? parseChainValue(chainEntry.value) : []\n\t\t\treturn { causes, chain }\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\tencodeCause(score: HealthScore): string | null {\n\t\ttry {\n\t\t\treturn encodeCauseFromScore(score, this.threshold, this.serviceName)\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync runWithCause<T>(fn: () => T | Promise<T>, score: HealthScore): Promise<T> {\n\t\tif (this._state !== 'ready' || !this.apiRef) {\n\t\t\treturn await fn()\n\t\t}\n\t\ttry {\n\t\t\tconst localCause = this.encodeCause(score)\n\t\t\tconst existing = this.apiRef.propagation.getActiveBaggage()\n\t\t\tconst existingChain = existing?.getEntry(BAGGAGE_KEY_CHAIN)?.value\n\t\t\t\t? parseChainValue(existing.getEntry(BAGGAGE_KEY_CHAIN)!.value)\n\t\t\t\t: []\n\t\t\t// Nothing to do — no local cause AND no upstream baggage to forward.\n\t\t\tif (!localCause && !existing) {\n\t\t\t\treturn await fn()\n\t\t\t}\n\n\t\t\tconst baseBaggage = existing ?? this.apiRef.propagation.createBaggage()\n\t\t\tlet nextBaggage: Baggage = baseBaggage\n\n\t\t\tif (localCause) {\n\t\t\t\tconst newEntry: BaggageEntry = { value: localCause }\n\t\t\t\tconst upstream = existing?.getEntry(BAGGAGE_KEY_CAUSE)?.value\n\t\t\t\tconst mergedValue = upstream ? mergeCauseValues(upstream, localCause) : localCause\n\t\t\t\tnextBaggage = nextBaggage.setEntry(BAGGAGE_KEY_CAUSE, {\n\t\t\t\t\t...newEntry,\n\t\t\t\t\tvalue: mergedValue,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif (this.propagateChain && (localCause || existingChain.length > 0)) {\n\t\t\t\tconst nextChain = localCause\n\t\t\t\t\t? appendChain(existingChain, this.serviceName, MAX_CHAIN_LENGTH)\n\t\t\t\t\t: existingChain\n\t\t\t\tif (nextChain.length > 0) {\n\t\t\t\t\tnextBaggage = nextBaggage.setEntry(BAGGAGE_KEY_CHAIN, {\n\t\t\t\t\t\tvalue: nextChain.join(','),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst ctxWithBaggage: Context = this.apiRef.propagation.setBaggage(\n\t\t\t\tthis.apiRef.context.active(),\n\t\t\t\tnextBaggage,\n\t\t\t)\n\t\t\treturn await this.apiRef.context.with(ctxWithBaggage, fn)\n\t\t} catch {\n\t\t\t// Cardinal rule — propagation never throws.\n\t\t\treturn await fn()\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis._state = 'disposed'\n\t\tthis.apiRef = null\n\t}\n}\n\n/**\n * Merge two cause values (`signal@service,signal@service`) into one,\n * deduplicating identical `signal@service` pairs while preserving order\n * (existing first, local appended). Bounded by the baggage W3C size limit.\n */\nfunction mergeCauseValues(existing: string, local: string): string {\n\tconst seen = new Set<string>()\n\tconst out: string[] = []\n\tfor (const piece of [...existing.split(','), ...local.split(',')]) {\n\t\tconst trimmed = piece.trim()\n\t\tif (trimmed.length === 0 || seen.has(trimmed)) continue\n\t\tseen.add(trimmed)\n\t\tout.push(trimmed)\n\t}\n\treturn out.join(',')\n}\n\n/**\n * Build a causality handle. Returns a handle whose state starts at\n * `'loading'`; emit / read operations are no-ops during loading. Once\n * the dynamic import resolves, state transitions to `'ready'` (or\n * `'unavailable'` when OTel API is absent).\n */\nexport function createCausalityHandle(opts: CausalityOptions = {}): CausalityHandle {\n\treturn new CausalityHandleImpl(opts)\n}\n","/**\n * Effect TS integration for `@sylphx/sdk/health` (Rule 21 / ADR-058 Amendment).\n *\n * Effect-native services should NEVER call `Effect.runPromise` inside\n * business logic — this module exposes `evaluateEffect` so they can fold\n * the health computation into their own fiber graph.\n *\n * `effect` is an OPTIONAL peer dependency — apps that don't import this\n * module never pull it in (sideEffects: false + tree-shaking guarantees\n * this; verified in tests). The `Effect` type is imported from the\n * peer-dep at runtime; consumers either provide it or never reach this\n * code path.\n */\n\nimport { Effect } from 'effect'\nimport type { HealthError, HealthScore } from './types'\nimport { HealthError as HealthErrorClass } from './types'\n\n/**\n * Lift a `() => Promise<HealthScore>` evaluator into an Effect-native value.\n *\n * Errors thrown by the evaluator are tagged `HealthError` so callers can\n * use `Effect.catchTag('HealthError', …)` for typed recovery.\n *\n * @example\n * ```ts\n * import { Effect } from 'effect'\n * import { sylphxHealth } from '@sylphx/sdk/health'\n *\n * const health = sylphxHealth({ signals: [...] })\n *\n * const program = Effect.gen(function* () {\n * const { score, signals } = yield* health.evaluateEffect\n * yield* Effect.log(`health=${score.toFixed(2)} signals=${JSON.stringify(signals)}`)\n * return score\n * })\n *\n * // Only the entry point runs the Effect (Rule 21).\n * const finalScore = await Effect.runPromise(program)\n * ```\n */\nexport function evaluateEffect(\n\tevaluator: () => Promise<HealthScore>,\n): Effect.Effect<HealthScore, HealthError, never> {\n\treturn Effect.tryPromise({\n\t\ttry: () => evaluator(),\n\t\tcatch: (err) => new HealthErrorClass('health evaluation failed', err),\n\t})\n}\n","/**\n * Type SSOT for `@sylphx/sdk/health` (ADR-111 §4).\n *\n * Pure runtime types — framework-free, no schema library required at the\n * SDK boundary. Apps that want Standard Schema validation (per ADR-084)\n * can wrap the wire-format `HealthSnapshot` in their own schema.\n *\n * The wire shape (`HealthSnapshot`) is the **stable contract** the\n * `sylphx-health-agent` sidecar parses (see ADR-111 §3.2.4 +\n * `apps/health-agent/src/app-poll.ts::parseAppHealthBody`). Do not break it.\n */\n\n// ─── Signal — what an app registers ─────────────────────────────────────────\n\n/**\n * One reading produced by a `Signal.read()`. The aggregator turns each\n * reading into a `factor` in `[0, 1]` then folds them into the score via\n * the configured `ScoringStrategy`.\n *\n * `value` is the raw observation (ms, ratio, count, …) — surfaced verbatim\n * in the wire snapshot so operators can debug from JSON without re-running\n * the signal logic.\n *\n * `healthFactor` is the normalised health in `[0, 1]`:\n * - `1` = fully healthy\n * - `0` = dead (or \"we don't know\" if `unknown=true` and the policy says so)\n *\n * `unknown=true` means the signal **could not be measured this tick** (e.g.\n * cgroup file unreadable). Scoring strategies treat unknown signals as\n * `factor=1` (don't penalise an app for our own missing data).\n */\nexport interface SignalReading {\n\treadonly value: number | string | boolean\n\treadonly healthFactor: number\n\treadonly unknown?: boolean\n}\n\n/**\n * A health signal — one named, weighted measurement the score is built from.\n *\n * Two variants:\n * - `SyncSignal` — `read(): SignalReading` (e.g. event-loop lag)\n * - `AsyncSignal` — `read(): Promise<SignalReading>` (e.g. queue depth\n * fetched over IPC)\n *\n * The discriminated `Signal` union accepts both. The aggregator awaits\n * each reading uniformly via `Promise.resolve(signal.read())`.\n *\n * Implementations are pure — no internal mutation outside the closure-\n * captured monitor state (e.g. `monitorEventLoopDelay()`). Stop /\n * cleanup are exposed via `dispose()` for tests and graceful shutdown.\n */\nexport interface SignalBase {\n\t/** Unique stable identifier; appears in the wire `signals.<name>` map. */\n\treadonly name: string\n\t/**\n\t * Weight in the weighted-product score. Strictly `> 0` for active\n\t * signals; `0` is a no-op signal (kept for compatibility with\n\t * conditional registration but skipped in scoring).\n\t */\n\treadonly weight: number\n\t/**\n\t * Tear down any background work (timers, monitors, file watchers).\n\t * Called during graceful shutdown and from test cleanup.\n\t */\n\tdispose?(): void\n}\nexport interface SyncSignal extends SignalBase {\n\tread(): SignalReading\n}\nexport interface AsyncSignal extends SignalBase {\n\tread(): Promise<SignalReading>\n}\nexport type Signal = SyncSignal | AsyncSignal\n\n// ─── Score — what the sidecar reads ─────────────────────────────────────────\n\n/**\n * The complete health score + signal breakdown produced by `health.evaluate()`.\n *\n * `score` is the normalised aggregate in `[0, 1]`. Signal payload is\n * verbatim values from each `SignalReading.value` so operators can\n * cross-reference Grafana dashboards with the JSON.\n */\nexport interface HealthScore {\n\treadonly score: number\n\treadonly signals: Record<string, number | string | boolean>\n\t/**\n\t * Normalised per-signal health factors in [0, 1].\n\t *\n\t * Additive to the ADR-111 wire shape: existing readers can ignore it,\n\t * while ADR-142 OTel emission uses it as the SSOT for\n\t * `health.signal.factor`.\n\t */\n\treadonly signalFactors?: Record<string, number>\n\treadonly lastTickAt: string\n}\n\n/**\n * Wire format the sidecar's `app-poll.ts::parseAppHealthBody` consumes.\n *\n * Pinned by ADR-111 §3.2.4 — keep stable. The sidecar tolerates extra\n * keys; do NOT remove existing ones without sequencing the sidecar update\n * first.\n */\nexport type HealthSnapshot = HealthScore\n\n// ─── Errors ─────────────────────────────────────────────────────────────────\n\n/**\n * Tagged error type for the Effect API. Promise consumers see the same\n * `message` via `Error.message` — the tag is for `Effect.catchTag`.\n */\nexport class HealthError extends Error {\n\treadonly _tag = 'HealthError' as const\n\treadonly cause?: unknown\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(message)\n\t\tthis.name = 'HealthError'\n\t\tthis.cause = cause\n\t}\n}\n\n// ─── Scoring strategy ───────────────────────────────────────────────────────\n\n/**\n * `ScoringStrategy` collapses N readings → one `score` in `[0, 1]`.\n *\n * Default is `weightedProduct` (see `scoring.ts`). Custom strategies can\n * be plugged in via `sylphxHealth({ scoringStrategy: myStrategy })`.\n */\nexport type ScoringStrategy = (\n\treadings: ReadonlyArray<{ signal: Signal; reading: SignalReading }>,\n) => number\n\n// ─── Public option types ────────────────────────────────────────────────────\n\nexport interface SylphxHealthOptions {\n\t/**\n\t * Signals to register. If omitted, defaults to a single\n\t * `eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 })` per\n\t * ADR-111 §4.6 (\"sane out-of-box\").\n\t */\n\treadonly signals?: ReadonlyArray<Signal>\n\t/**\n\t * Strategy used to fold readings into a score. Defaults to\n\t * `weightedProduct` from `scoring.ts`.\n\t */\n\treadonly scoringStrategy?: ScoringStrategy\n\t/**\n\t * Optional injected clock — used by tests for deterministic\n\t * `lastTickAt` timestamps.\n\t */\n\treadonly now?: () => Date\n\t/**\n\t * OpenTelemetry emission (ADR-142). When `true` (default), every\n\t * `evaluate()` records `health.score` / `health.signal.*` metrics and\n\t * sets `service.health.score` on the active span. `false` disables\n\t * emission entirely. An object configures advanced behaviour (custom\n\t * meter, span events, static attributes).\n\t *\n\t * Graceful fallback: if `@opentelemetry/api` is not installed in the\n\t * host project, the emitter no-ops cleanly — no warning, no exception.\n\t */\n\treadonly otel?: import('./otel').OtelOption\n\t/**\n\t * Causality-aware health propagation (ADR-143). When `true` (default),\n\t * every `evaluate()` reads upstream cause from active OTel baggage\n\t * and surfaces it on the metric stream (`health.downstream_cause`\n\t * counter + `service.health.cause_root` span attribute). The\n\t * `withHealth.*` middleware additionally writes the local cause into\n\t * the request context baggage on every non-`/healthz` request so\n\t * downstream services inherit causality via standard OTel\n\t * propagation. `false` disables both read and write. An object\n\t * tunes the threshold + service-name + chain behaviour.\n\t *\n\t * Graceful fallback: if `@opentelemetry/api` is not installed, the\n\t * handle becomes `'unavailable'` and propagation is a no-op.\n\t */\n\treadonly causality?: import('./causality').CausalityOption\n\t/**\n\t * Audit-grade health history (ADR-144). When `true` (default), every\n\t * `evaluate()` records a tamper-evident `HistoryEntry` into the\n\t * configured `HistoryStore` (default: in-memory ring buffer with\n\t * capacity 1000). Entries form a SHA-256 prev-hash chain — operators\n\t * (and compliance auditors) can replay the chain to detect post-hoc\n\t * tampering.\n\t *\n\t * Set `false` to disable recording. Pass an object to plug a\n\t * persistent store (Postgres / S3 / OpenSearch) or override capacity.\n\t *\n\t * The in-memory store is suitable for testing + low-stakes single-pod\n\t * deployments. SOC 2 / GDPR / HIPAA compliance requires a persistent\n\t * adapter; the interface is public and stable so any backend works.\n\t */\n\treadonly history?: import('./history').HistoryOption\n}\n","/**\n * Universal HTTP handler for `@sylphx/sdk/health` (ADR-111 §4.2).\n *\n * Framework-agnostic: returns either a `Web Fetch API` `Response`\n * (Hono / Bun.serve / itty-router / Next.js routes) or, via thin adapters,\n * a Node `(req, res) => void` (Express / Fastify-as-classic-handler).\n *\n * The handler returns HTTP 200 for scores at or above the direct-probe\n * liveness floor and HTTP 503 below it. The response body is always the\n * complete HealthSnapshot, so the ADR-111 sidecar can still read the score\n * even when direct kubelet probes need a status-coded failure.\n */\n\nimport type { HealthScore } from './types'\n\nconst DIRECT_PROBE_LIVENESS_FLOOR = 0.5\n\nfunction statusForScore(score: number): 200 | 503 {\n\treturn Number.isFinite(score) && score >= DIRECT_PROBE_LIVENESS_FLOOR ? 200 : 503\n}\n\n/**\n * Minimal contract a `health` instance must expose for the handler to\n * call. The full `health` object satisfies this implicitly via\n * `evaluate()` from `./index.ts`.\n */\nexport interface HealthEvaluator {\n\tevaluate(): Promise<HealthScore>\n}\n\n/**\n * Build a Web-Fetch-style handler. Suitable for:\n * - Hono: `app.get('/healthz', health.handler())`\n * - Bun.serve: `fetch: health.handler()`\n * - Next.js route.ts: `export const GET = health.handler()`\n * - itty-router / Hattip / standard `(Request) => Response` runtimes\n */\nexport function createWebHandler(source: HealthEvaluator): (req?: Request) => Promise<Response> {\n\treturn async (_req?: Request): Promise<Response> => {\n\t\ttry {\n\t\t\tconst score = await source.evaluate()\n\t\t\treturn new Response(JSON.stringify(score), {\n\t\t\t\tstatus: statusForScore(score.score),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t// Cache-Control: never cache. Even 1-second cache on a\n\t\t\t\t\t// CDN edge would mask a fast-moving health collapse.\n\t\t\t\t\t'Cache-Control': 'no-store, no-cache, must-revalidate',\n\t\t\t\t},\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err)\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\terror: 'health_evaluator_failed',\n\t\t\t\t\tmessage,\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'Cache-Control': 'no-store, no-cache, must-revalidate',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * Build a Node-style `(req, res)` handler — for Express / classic Fastify.\n *\n * Adapter that delegates to `createWebHandler()` so logic stays in one\n * place. Imported lazily by callers that need it; doesn't drag any node\n * types into Web-only code paths.\n */\nexport interface NodeIncoming {\n\tmethod?: string\n\turl?: string\n}\nexport interface NodeOutgoing {\n\tstatusCode: number\n\tsetHeader(name: string, value: string): void\n\tend(body?: string): void\n}\nexport function createNodeHandler(\n\tsource: HealthEvaluator,\n): (req: NodeIncoming, res: NodeOutgoing) => Promise<void> {\n\treturn async (_req, res) => {\n\t\ttry {\n\t\t\tconst score = await source.evaluate()\n\t\t\tres.statusCode = statusForScore(score.score)\n\t\t\tres.setHeader('Content-Type', 'application/json')\n\t\t\tres.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')\n\t\t\tres.end(JSON.stringify(score))\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err)\n\t\t\tres.statusCode = 500\n\t\t\tres.setHeader('Content-Type', 'application/json')\n\t\t\tres.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')\n\t\t\tres.end(JSON.stringify({ error: 'health_evaluator_failed', message }))\n\t\t}\n\t}\n}\n","/**\n * Audit-grade health history for `@sylphx/sdk/health` (ADR-144).\n *\n * Every `evaluate()` produces a tamper-evident `HistoryEntry` linked to\n * the previous entry via a SHA-256 prev-hash chain (the same primitive\n * AWS CloudTrail uses for digest-based log file integrity validation).\n * Operators run `sylphx health verify` to walk the chain offline and\n * detect any post-hoc tampering.\n *\n * # Storage is pluggable\n *\n * The SDK ships an `InMemoryHistoryStore` (ring buffer, FIFO eviction)\n * as the reference implementation. Customers wanting compliance-grade\n * durability (SOC 2 Type II, GDPR Article 32, HIPAA 164.312(b)) plug\n * their own persistent `HistoryStore` — a thin interface that the SDK\n * calls with each new entry. Adapter packages for Postgres / S3 / cold\n * storage ship separately.\n *\n * # Cardinal rule (carried from ADR-141 / 142 / 143)\n *\n * Recording failures never throw upstream. The `/healthz` response is\n * the load-bearing contract; the history record is observability\n * augmentation. A store that throws during `append()` produces a logged\n * warning (in dev) and is otherwise a no-op for that evaluation.\n *\n * # Wire format normative\n *\n * Per ADR-144 §3 — sorted-key canonical signal JSON, base16 SHA-256\n * hashes, six-decimal-place score precision, `|` separator for the\n * entry-hash preimage. Stable from day one.\n */\n\nimport type { HealthScore } from './types'\n\n// ─── Public types ───────────────────────────────────────────────────────────\n\n/**\n * One tamper-evident history entry. The `prevHash` links to the previous\n * entry's `entryHash`; entry 0 (`sequence === 0`) uses the genesis\n * placeholder `\"0\".repeat(64)`. Stable wire shape — additive evolution\n * only.\n */\nexport interface HistoryEntry {\n\t/** Monotonic, 0-indexed per service. */\n\treadonly sequence: number\n\t/** ISO-8601 timestamp matching `HealthScore.lastTickAt`. */\n\treadonly evaluatedAt: string\n\t/** Aggregate score in `[0, 1]`. */\n\treadonly score: number\n\t/** Base16 SHA-256 of canonicalized signal map. */\n\treadonly signalsDigest: string\n\t/** Base16 SHA-256 of previous entry's `entryHash` (`\"0\"*64` at sequence 0). */\n\treadonly prevHash: string\n\t/** Base16 SHA-256 of `(sequence|evaluatedAt|score|signalsDigest|prevHash)`. */\n\treadonly entryHash: string\n}\n\n/** Query for `HistoryStore.range`. */\nexport interface HistoryQuery {\n\t/** Inclusive lower sequence. Omit for \"from the beginning available\". */\n\treadonly fromSequence?: number\n\t/** Inclusive upper sequence. Omit for \"to the most recent\". */\n\treadonly toSequence?: number\n\t/** Inclusive lower timestamp (ISO-8601). */\n\treadonly fromTimestamp?: string\n\t/** Inclusive upper timestamp (ISO-8601). */\n\treadonly toTimestamp?: string\n\t/** Maximum entries returned. Implementations may cap further. */\n\treadonly limit?: number\n}\n\n/** Pluggable persistence adapter. Implementations may be sync or async. */\nexport interface HistoryStore {\n\t/** Append one entry. MUST persist before resolving for compliance use. */\n\tappend(entry: HistoryEntry): Promise<void> | void\n\t/** Retrieve entries matching `query` in ascending sequence order. */\n\trange(query: HistoryQuery): Promise<readonly HistoryEntry[]> | readonly HistoryEntry[]\n\t/** Most recent entry, or null when empty. */\n\tlatest(): Promise<HistoryEntry | null> | HistoryEntry | null\n\t/** Optional capacity hint for ring-buffer stores. */\n\treadonly capacity?: number\n}\n\n/** Outcome of chain verification — `verified: false` always carries a reason. */\nexport interface VerificationResult {\n\treadonly verified: boolean\n\treadonly entriesChecked: number\n\treadonly firstBreakAtSequence: number | null\n\treadonly reason: string | null\n}\n\n/** Options for the in-memory reference store. */\nexport interface InMemoryHistoryStoreOptions {\n\t/** Ring-buffer capacity. Default 1000. */\n\treadonly capacity?: number\n}\n\n/** Options for the recorder bound to a `sylphxHealth` instance. */\nexport interface HistoryRecorderOptions {\n\t/** Underlying store. Defaults to `InMemoryHistoryStore`. */\n\treadonly store?: HistoryStore\n\t/** Capacity passed to the default in-memory store. Default 1000. */\n\treadonly capacity?: number\n}\n\n/** The flag value passed via `sylphxHealth({ history })`. */\nexport type HistoryOption = boolean | HistoryRecorderOptions\n\n/** Genesis prev-hash for the very first entry per service. */\nexport const GENESIS_PREV_HASH = '0'.repeat(64)\n\n/** Default ring-buffer size when callers don't override. */\nexport const DEFAULT_HISTORY_CAPACITY = 1000\n\nconst HASH_TEXT_ENCODER = new TextEncoder()\nconst HEX_BYTE = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))\n\n// ─── Canonicalization + hashing ─────────────────────────────────────────────\n\n/**\n * Sorted-key JSON canonicalization. Same signals in different insertion\n * order produce the same string — guarantees `signalsDigest` stability\n * across V8 / Bun / browser JSON.stringify implementations.\n */\nexport function canonicalizeSignals(signals: Record<string, number | string | boolean>): string {\n\tconst keys = Object.keys(signals).sort()\n\tconst pairs: Array<[string, number | string | boolean]> = keys.map((k) => {\n\t\t// Defensive: signals[k] could be undefined if caller passed a sparse\n\t\t// map; fall back to empty string. JSON.stringify would otherwise\n\t\t// drop the key silently.\n\t\tconst value = signals[k]\n\t\treturn [k, value as number | string | boolean]\n\t})\n\treturn JSON.stringify(Object.fromEntries(pairs))\n}\n\n/**\n * Six-decimal-place float encoding for the score. Picked because:\n * - JavaScript Number → fixed-precision string is identical across V8,\n * Bun, and SpiderMonkey at 6 decimals.\n * - 6 decimals == 1 part in 10^6, well below the noise floor of any\n * real health signal.\n */\nfunction encodeScore(score: number): string {\n\treturn score.toFixed(6)\n}\n\n/**\n * `crypto.subtle.digest` is the cross-runtime SHA-256 primitive. Available\n * in Node ≥19, Bun, browsers, Cloudflare Workers, Vercel Edge. We do not\n * require a peer dep on `node:crypto` — when `crypto.subtle` is missing\n * (rare; very old Node), the recorder gracefully degrades to entries with\n * empty hashes and the verifier reports \"no hash chain available.\"\n */\nasync function sha256Hex(input: string): Promise<string> {\n\tconst subtle = (globalThis as { crypto?: { subtle?: SubtleCrypto } }).crypto?.subtle\n\tif (!subtle || typeof subtle.digest !== 'function') {\n\t\treturn ''\n\t}\n\tconst buf = HASH_TEXT_ENCODER.encode(input)\n\tconst digest = await subtle.digest('SHA-256', buf)\n\tconst bytes = new Uint8Array(digest)\n\tlet out = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tout += HEX_BYTE[bytes[i] as number]\n\t}\n\treturn out\n}\n\n/**\n * Compute the canonical entry-hash from the entry's prehash fields.\n * Separator is `|`; none of the input fields contain it (sequence digits,\n * ISO-8601 timestamp, fixed-precision float, hex hashes).\n */\nasync function computeEntryHash(prehash: Omit<HistoryEntry, 'entryHash'>): Promise<string> {\n\tconst canonical = [\n\t\tprehash.sequence.toString(10),\n\t\tprehash.evaluatedAt,\n\t\tencodeScore(prehash.score),\n\t\tprehash.signalsDigest,\n\t\tprehash.prevHash,\n\t].join('|')\n\treturn await sha256Hex(canonical)\n}\n\n// ─── InMemoryHistoryStore — ring buffer reference impl ─────────────────────\n\nclass InMemoryHistoryStoreImpl implements HistoryStore {\n\treadonly capacity: number\n\tprivate buf: HistoryEntry[] = []\n\n\tconstructor(opts: InMemoryHistoryStoreOptions = {}) {\n\t\tconst cap = opts.capacity ?? DEFAULT_HISTORY_CAPACITY\n\t\tif (!Number.isFinite(cap) || cap <= 0 || cap > 10_000_000) {\n\t\t\tthrow new Error(`InMemoryHistoryStore: capacity must be in (0, 10000000], got ${cap}`)\n\t\t}\n\t\tthis.capacity = cap\n\t}\n\n\tappend(entry: HistoryEntry): void {\n\t\tthis.buf.push(entry)\n\t\tif (this.buf.length > this.capacity) {\n\t\t\tthis.buf.shift()\n\t\t}\n\t}\n\n\trange(query: HistoryQuery): readonly HistoryEntry[] {\n\t\tconst out: HistoryEntry[] = []\n\t\tfor (const e of this.buf) {\n\t\t\tif (query.fromSequence !== undefined && e.sequence < query.fromSequence) continue\n\t\t\tif (query.toSequence !== undefined && e.sequence > query.toSequence) continue\n\t\t\tif (query.fromTimestamp !== undefined && e.evaluatedAt < query.fromTimestamp) continue\n\t\t\tif (query.toTimestamp !== undefined && e.evaluatedAt > query.toTimestamp) continue\n\t\t\tout.push(e)\n\t\t\tif (query.limit !== undefined && out.length >= query.limit) break\n\t\t}\n\t\treturn out\n\t}\n\n\tlatest(): HistoryEntry | null {\n\t\treturn this.buf[this.buf.length - 1] ?? null\n\t}\n}\n\n/** Build an in-memory ring-buffer history store. */\nexport function createInMemoryHistoryStore(opts: InMemoryHistoryStoreOptions = {}): HistoryStore {\n\treturn new InMemoryHistoryStoreImpl(opts)\n}\n\n// ─── HistoryRecorder — bound to one sylphxHealth instance ──────────────────\n\n/**\n * Records every `HealthScore` as a hash-chained entry. Owns a single\n * `nextSequence` counter + the `prevHash` value carried forward across\n * recordings. The recorder is per-instance (per-service); the chain is\n * never shared across services.\n */\nexport interface HistoryRecorder {\n\t/** Backing store. Exposed for advanced callers (e.g. tests + verification). */\n\treadonly store: HistoryStore\n\t/** Record one score. Resolves to the persisted entry (or null on failure). */\n\trecord(score: HealthScore): Promise<HistoryEntry | null>\n\t/** Convenience: store.range. */\n\trange(query: HistoryQuery): Promise<readonly HistoryEntry[]>\n\t/** Convenience: store.latest. */\n\tlatest(): Promise<HistoryEntry | null>\n\t/**\n\t * Verify the chain over a query window. Walks entries in sequence\n\t * order, recomputing each `entryHash` and confirming it matches the\n\t * next entry's `prevHash`. Returns the first break with a reason.\n\t */\n\tverify(query: HistoryQuery): Promise<VerificationResult>\n\t/** Tear down internal references. Idempotent. */\n\tdispose(): void\n}\n\nclass HistoryRecorderImpl implements HistoryRecorder {\n\treadonly store: HistoryStore\n\tprivate nextSequence = 0\n\tprivate prevHash: string = GENESIS_PREV_HASH\n\tprivate disposed = false\n\tprivate initPromise: Promise<void>\n\t/**\n\t * Serializes record() calls so concurrent `evaluate()` invocations\n\t * don't race on `nextSequence` / `prevHash`. Each new call chains off\n\t * the previous; the chain itself never propagates errors (catch in the\n\t * tail so one failed record doesn't poison subsequent ones).\n\t */\n\tprivate inFlight: Promise<unknown> = Promise.resolve()\n\n\tconstructor(opts: HistoryRecorderOptions) {\n\t\tthis.store = opts.store ?? createInMemoryHistoryStore({ capacity: opts.capacity })\n\t\t// Best-effort warm start: if the store already has entries (e.g. a\n\t\t// persistent adapter), continue from the latest. Failures fall back\n\t\t// to \"fresh chain\" — a defensible default since the cardinal rule\n\t\t// forbids throwing.\n\t\tthis.initPromise = this.warmStart()\n\t}\n\n\tprivate async warmStart(): Promise<void> {\n\t\ttry {\n\t\t\tconst latest = await this.store.latest()\n\t\t\tif (latest && Number.isFinite(latest.sequence)) {\n\t\t\t\tthis.nextSequence = latest.sequence + 1\n\t\t\t\tthis.prevHash = latest.entryHash || GENESIS_PREV_HASH\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cardinal rule.\n\t\t}\n\t}\n\n\tprivate async recordInternal(score: HealthScore): Promise<HistoryEntry | null> {\n\t\tif (this.disposed) return null\n\t\tawait this.initPromise\n\t\ttry {\n\t\t\tconst signalsDigest = await sha256Hex(canonicalizeSignals(score.signals))\n\t\t\tconst prehash: Omit<HistoryEntry, 'entryHash'> = {\n\t\t\t\tsequence: this.nextSequence,\n\t\t\t\tevaluatedAt: score.lastTickAt,\n\t\t\t\tscore: score.score,\n\t\t\t\tsignalsDigest,\n\t\t\t\tprevHash: this.prevHash,\n\t\t\t}\n\t\t\tconst entryHash = await computeEntryHash(prehash)\n\t\t\tconst entry: HistoryEntry = { ...prehash, entryHash }\n\t\t\tawait this.store.append(entry)\n\t\t\tthis.nextSequence += 1\n\t\t\tthis.prevHash = entryHash || GENESIS_PREV_HASH\n\t\t\treturn entry\n\t\t} catch {\n\t\t\t// Cardinal rule.\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync record(score: HealthScore): Promise<HistoryEntry | null> {\n\t\t// Chain off the previous in-flight record so concurrent\n\t\t// `evaluate()` calls (e.g. multiple kubelet probes within one\n\t\t// async tick) produce a deterministic, gap-free sequence.\n\t\tconst job = this.inFlight.then(() => this.recordInternal(score))\n\t\t// Swallow errors in the queue so one failure doesn't poison the\n\t\t// rest. recordInternal itself already returns null on failure;\n\t\t// catch here is defensive for any unexpected synchronous throw.\n\t\tthis.inFlight = job.catch(() => undefined)\n\t\treturn await job\n\t}\n\n\tprivate async drainPendingRecords(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.inFlight\n\t\t} catch {\n\t\t\t// Defensive only: recordInternal is non-throwing and the queue\n\t\t\t// tail already swallows failures.\n\t\t}\n\t}\n\n\tasync range(query: HistoryQuery): Promise<readonly HistoryEntry[]> {\n\t\tawait this.drainPendingRecords()\n\t\ttry {\n\t\t\treturn await this.store.range(query)\n\t\t} catch {\n\t\t\treturn []\n\t\t}\n\t}\n\n\tasync latest(): Promise<HistoryEntry | null> {\n\t\tawait this.drainPendingRecords()\n\t\ttry {\n\t\t\treturn await this.store.latest()\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync verify(query: HistoryQuery): Promise<VerificationResult> {\n\t\tconst entries = await this.range(query)\n\t\treturn await verifyChain(entries)\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true\n\t}\n}\n\n/** Build a recorder bound to a (defaulted) in-memory store. */\nexport function createHistoryRecorder(opts: HistoryRecorderOptions = {}): HistoryRecorder {\n\treturn new HistoryRecorderImpl(opts)\n}\n\n// ─── Verifier — walks a chain and reports tampering ────────────────────────\n\n/**\n * Walk an ordered (by sequence) chain of entries, recomputing each\n * `entryHash` and confirming it matches the next entry's `prevHash`.\n * Returns the first break encountered.\n *\n * Edge cases:\n * - Empty input → `verified: true, entriesChecked: 0`. Vacuous truth.\n * - First entry has `prevHash !== GENESIS_PREV_HASH` AND `sequence === 0`\n * → break (\"genesis prev-hash mismatch\").\n * - First entry's `sequence > 0` but `prevHash === GENESIS_PREV_HASH`\n * → this is a sub-window query; we accept and chain forward from\n * `entries[0]`.\n */\nexport async function verifyChain(\n\tentries: ReadonlyArray<HistoryEntry>,\n): Promise<VerificationResult> {\n\tif (entries.length === 0) {\n\t\treturn { verified: true, entriesChecked: 0, firstBreakAtSequence: null, reason: null }\n\t}\n\tlet expectedPrev: string | null = null\n\tlet checked = 0\n\tfor (let i = 0; i < entries.length; i++) {\n\t\tconst entry = entries[i] as HistoryEntry\n\t\t// 1. First-entry structural check: genesis sequence MUST carry\n\t\t// the canonical genesis prev-hash. Runs BEFORE the entryHash\n\t\t// recompute so callers can build a hand-crafted entry and see\n\t\t// a clear \"genesis entry...\" failure rather than the more\n\t\t// cryptic entryHash-mismatch message.\n\t\tif (i === 0 && entry.sequence === 0 && entry.prevHash !== GENESIS_PREV_HASH) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: checked,\n\t\t\t\tfirstBreakAtSequence: entry.sequence,\n\t\t\t\treason: `genesis entry must have prevHash=\"${GENESIS_PREV_HASH}\", got ${entry.prevHash}`,\n\t\t\t}\n\t\t}\n\t\t// 2. Inter-entry prev-hash chain check: each entry except the\n\t\t// window's first MUST link to the previous entry's entryHash.\n\t\tif (i > 0 && expectedPrev !== null && entry.prevHash !== expectedPrev) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: checked,\n\t\t\t\tfirstBreakAtSequence: entry.sequence,\n\t\t\t\treason: `prevHash mismatch at sequence ${entry.sequence}: expected ${expectedPrev}, got ${entry.prevHash}`,\n\t\t\t}\n\t\t}\n\t\t// 3. Per-entry hash integrity: recompute and compare.\n\t\tconst recomputedHash = await computeEntryHash({\n\t\t\tsequence: entry.sequence,\n\t\t\tevaluatedAt: entry.evaluatedAt,\n\t\t\tscore: entry.score,\n\t\t\tsignalsDigest: entry.signalsDigest,\n\t\t\tprevHash: entry.prevHash,\n\t\t})\n\t\tif (entry.entryHash && recomputedHash && recomputedHash !== entry.entryHash) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: checked,\n\t\t\t\tfirstBreakAtSequence: entry.sequence,\n\t\t\t\treason: `entryHash mismatch at sequence ${entry.sequence}: stored=${entry.entryHash} recomputed=${recomputedHash}`,\n\t\t\t}\n\t\t}\n\t\texpectedPrev = entry.entryHash || null\n\t\tchecked += 1\n\t}\n\treturn {\n\t\tverified: true,\n\t\tentriesChecked: checked,\n\t\tfirstBreakAtSequence: null,\n\t\treason: null,\n\t}\n}\n","/**\n * OpenTelemetry emission for `@sylphx/sdk/health` (ADR-142).\n *\n * Every `evaluate()` produces a `HealthScore`. This module turns that score\n * into three OpenTelemetry signals:\n *\n * 1. `health.score` — synchronous Gauge, range [0, 1]\n * 2. `health.signal.factor` — per-signal Gauge, range [0, 1]\n * 3. `health.signal.value` — per-signal numeric Gauge (when value is a\n * finite number)\n * 3b. `health.signal.value_state` — per-signal Counter (when value is a\n * string label like \"ok\" / \"timeout\")\n *\n * If a span is active in OTel context when `emit()` runs, the score is also\n * set as a span attribute (`service.health.score`) and — when `events: true`\n * — a `health.evaluated` span event is recorded.\n *\n * # Graceful fallback\n *\n * `@opentelemetry/api` is an **optional** peer dependency. The module loads\n * it via dynamic `import()` inside a try/catch. When the import fails (the\n * consumer's project did not install OTel), every emit() call is a no-op —\n * no warnings, no exceptions, no peer-dep requirement.\n *\n * # Lifecycle\n *\n * The emitter starts in `loading` state. It kicks off the async OTel API\n * load in the background. Until the load resolves (or fails), `emit()` is a\n * no-op. For probe paths that run every 10s, the first probe may miss\n * emission but the second probe is guaranteed to emit if OTel is available.\n *\n * # Performance budget\n *\n * `emit()` p99 < 500µs when ready, < 50ns when in loading / unavailable\n * state. Measured by the OTel benchmark in `__tests__/otel-emission.test.ts`.\n */\n\n// Compile-time-only types — these `import type` statements are erased at\n// emit time, so consumers without @opentelemetry/api installed do not see a\n// missing-module error at module load.\nimport type { Attributes, Counter, Gauge, Meter, TraceAPI, Tracer } from '@opentelemetry/api'\nimport type { UpstreamCause } from './causality'\nimport type { HealthScore } from './types'\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Static attributes the emitter merges onto every metric / event /\n * span-attribute it produces. Use to stamp deployment metadata\n * (`deployment.env`, `git.sha`, …) that the OTel resource detector\n * doesn't pick up automatically.\n */\nexport type OtelStaticAttributes = Readonly<Record<string, string | number | boolean>>\n\nexport interface OtelEmitterOptions {\n\t/**\n\t * Pre-built OTel Meter. When provided, the emitter uses it directly and\n\t * skips the async OTel API load. Common case: tests, sandboxes, multi-\n\t * tenant runtimes wiring a custom MeterProvider per request.\n\t */\n\treadonly meter?: Meter\n\t/**\n\t * Pre-built OTel Tracer. Same intent as `meter`. The emitter uses the\n\t * tracer only to read the active span when setting attributes /\n\t * events; it does not create new spans.\n\t */\n\treadonly tracer?: Tracer\n\t/**\n\t * When true, the emitter records a `health.evaluated` span event on\n\t * every evaluation (if a span is active). Default false — high-\n\t * frequency probe paths produce many events and most operators only\n\t * need the metric.\n\t */\n\treadonly events?: boolean\n\t/** Static attributes merged onto every emitted metric / span attribute. */\n\treadonly attributes?: OtelStaticAttributes\n}\n\n/**\n * The flag value passed by callers via `sylphxHealth({ otel })` /\n * `withHealth.hono({ otel })`. `true` (default) enables emission with\n * sensible defaults; `false` disables it; an object configures it.\n */\nexport type OtelOption = boolean | OtelEmitterOptions\n\nexport interface OtelEmitter {\n\t/**\n\t * Emit metrics / span attributes / event for one HealthScore.\n\t * `upstreamCause` (ADR-143) carries the parsed causality baggage so the\n\t * emitter can record `health.downstream_cause` counter +\n\t * `service.health.cause_*` span attributes. Non-throwing.\n\t */\n\temit(score: HealthScore, upstreamCause?: UpstreamCause | null): void\n\t/**\n\t * Tear down any cached instruments. Called from `sylphxHealth.dispose()`\n\t * during graceful shutdown. The OTel SDK owns the underlying meter\n\t * lifecycle; this method only releases the emitter's caches.\n\t */\n\tdispose(): void\n\t/** Readiness state — exposed for tests. `loading` is transient; `ready` and `unavailable` are terminal. */\n\treadonly state: 'loading' | 'ready' | 'unavailable' | 'disposed'\n}\n\nconst SDK_NAME = '@sylphx/sdk/health'\nconst SDK_VERSION = '0.11.0'\n\n/** Metric names — pinned by ADR-142 §3.1 + ADR-143 §3.3, stable from day one. */\nexport const METRIC_NAMES = {\n\tscore: 'health.score',\n\tsignalFactor: 'health.signal.factor',\n\tsignalValue: 'health.signal.value',\n\tsignalValueState: 'health.signal.value_state',\n\t/** ADR-143 §3.3 — increments per evaluation when an upstream cause is observed. */\n\tdownstreamCause: 'health.downstream_cause',\n} as const\n\n/** Span attribute names — pinned by ADR-142 §2.1.3 + ADR-143 §3.2, stable from day one. */\nexport const SPAN_ATTRIBUTES = {\n\tscore: 'service.health.score',\n\tsignalFactor: (name: string) => `service.health.signal.${name}.factor`,\n\t/** ADR-143 §3.2 — the upstream service that originated the failure chain. */\n\tcauseRoot: 'service.health.cause_root',\n\t/** ADR-143 §3.2 — the signal at the root that failed. */\n\tcauseSignal: 'service.health.cause_signal',\n\t/** ADR-143 §3.2 — comma-delimited hop chain (oldest to newest). */\n\tcauseChain: 'service.health.cause_chain',\n} as const\n\n/** Span event name — pinned by ADR-142 §2.1.4. */\nexport const EVENT_NAMES = {\n\tevaluated: 'health.evaluated',\n} as const\n\n/**\n * Build an OTel emitter.\n *\n * Returns a sealed emitter object whose `emit()` is initially a no-op\n * while the OTel API loads in the background. Once loaded, all subsequent\n * `emit()` calls produce metrics / span attributes / events per ADR-142.\n *\n * When `@opentelemetry/api` is not installed in the host project (the\n * common case for hobby projects), the emitter transitions to\n * `unavailable` and all `emit()` calls remain no-ops for the lifetime of\n * the instance. No exceptions, no warnings.\n */\nexport function createOtelEmitter(opts: OtelEmitterOptions = {}): OtelEmitter {\n\treturn new OtelEmitterImpl(opts)\n}\n\n// ─── Implementation ──────────────────────────────────────────────────────────\n\ninterface CachedInstruments {\n\treadonly scoreGauge: Gauge<Attributes>\n\treadonly factorGauge: Gauge<Attributes>\n\treadonly valueGauge: Gauge<Attributes>\n\treadonly valueStateCounter: Counter<Attributes>\n\treadonly downstreamCauseCounter: Counter<Attributes>\n}\n\nclass OtelEmitterImpl implements OtelEmitter {\n\tprivate _state: 'loading' | 'ready' | 'unavailable' | 'disposed' = 'loading'\n\tprivate instruments: CachedInstruments | null = null\n\tprivate traceApi: TraceAPI | null = null\n\tprivate readonly staticAttrs: OtelStaticAttributes\n\tprivate readonly events: boolean\n\n\tconstructor(private readonly opts: OtelEmitterOptions) {\n\t\tthis.staticAttrs = opts.attributes ?? {}\n\t\tthis.events = opts.events === true\n\t\tif (opts.meter) {\n\t\t\tthis.instruments = buildInstruments(opts.meter)\n\t\t\t// User-supplied tracer cannot give us the `getActiveSpan()` lookup\n\t\t\t// (that lives on the `trace` API namespace, not on a Tracer\n\t\t\t// instance). We still expose span correlation via the\n\t\t\t// `tracer.startActiveSpan` path when the user wires their own\n\t\t\t// tracer, falling back to no-correlation when they don't pass\n\t\t\t// the full namespace. The async loader path always picks up the\n\t\t\t// namespace from `@opentelemetry/api`.\n\t\t\tthis._state = 'ready'\n\t\t\treturn\n\t\t}\n\t\tvoid this.initialize()\n\t}\n\n\tget state(): OtelEmitter['state'] {\n\t\treturn this._state\n\t}\n\n\tprivate async initialize(): Promise<void> {\n\t\tconst api = await tryLoadOtelApi()\n\t\tif (api === null) {\n\t\t\tthis._state = 'unavailable'\n\t\t\treturn\n\t\t}\n\t\t// Late-state guard: dispose() may have flipped `_state` to 'disposed'\n\t\t// while the dynamic import was in flight. Honour the disposal.\n\t\tif (this._state === 'disposed') return\n\t\ttry {\n\t\t\tconst meter = api.metrics.getMeter(SDK_NAME, SDK_VERSION)\n\t\t\tthis.instruments = buildInstruments(meter)\n\t\t\tthis.traceApi = api.trace\n\t\t\tthis._state = 'ready'\n\t\t} catch {\n\t\t\t// OTel API loaded but instrument creation failed — host\n\t\t\t// environment surfaces a partial / broken implementation\n\t\t\t// (browser polyfill, edge runtime stub). Treat as unavailable\n\t\t\t// rather than leaving the emitter wedged in 'loading'.\n\t\t\tthis._state = 'unavailable'\n\t\t}\n\t}\n\n\temit(score: HealthScore, upstreamCause?: UpstreamCause | null): void {\n\t\tif (this._state !== 'ready' || !this.instruments) return\n\t\tconst baseAttrs: Attributes = { ...this.staticAttrs }\n\t\ttry {\n\t\t\t// 1. Top-level score gauge.\n\t\t\tthis.instruments.scoreGauge.record(score.score, baseAttrs)\n\n\t\t\t// 2. Per-signal metrics.\n\t\t\tfor (const [signalName, rawValue] of Object.entries(score.signals)) {\n\t\t\t\tconst signalAttrs: Attributes = { ...baseAttrs, 'signal.name': signalName }\n\t\t\t\tconst factor = score.signalFactors?.[signalName]\n\t\t\t\tif (typeof factor === 'number' && Number.isFinite(factor)) {\n\t\t\t\t\tthis.instruments.factorGauge.record(factor, signalAttrs)\n\t\t\t\t}\n\n\t\t\t\tif (typeof rawValue === 'number' && Number.isFinite(rawValue)) {\n\t\t\t\t\tthis.instruments.valueGauge.record(rawValue, signalAttrs)\n\t\t\t\t} else {\n\t\t\t\t\tconst stateAttrs: Attributes = {\n\t\t\t\t\t\t...signalAttrs,\n\t\t\t\t\t\tstate: String(rawValue),\n\t\t\t\t\t}\n\t\t\t\t\tthis.instruments.valueStateCounter.add(1, stateAttrs)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 3. ADR-143 — upstream cause counter + span attributes.\n\t\t\tif (upstreamCause && upstreamCause.causes.length > 0) {\n\t\t\t\tfor (const cause of upstreamCause.causes) {\n\t\t\t\t\tthis.instruments.downstreamCauseCounter.add(1, {\n\t\t\t\t\t\t...baseAttrs,\n\t\t\t\t\t\tcause_root: cause.service,\n\t\t\t\t\t\tcause_signal: cause.signal,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 4. Active-span correlation (trace + event + cause attributes).\n\t\t\tif (this.traceApi) {\n\t\t\t\tconst span = this.traceApi.getActiveSpan?.()\n\t\t\t\tmaybeAttachToSpan(span, score, this.events, upstreamCause)\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cardinal rule (ADR-141 §3.3.5): emission must never throw — the\n\t\t\t// /healthz response is the load-bearing surface, not the metric.\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis._state = 'disposed'\n\t\tthis.instruments = null\n\t\tthis.traceApi = null\n\t}\n}\n\n// ─── OTel API loader ─────────────────────────────────────────────────────────\n\nlet cachedApi: typeof import('@opentelemetry/api') | null | undefined\n\n/**\n * Lazy load `@opentelemetry/api`. Returns the module on success, null when\n * the package is not installed. Cached across emitters so we only attempt\n * the dynamic import once per process.\n *\n * Exported for tests; production code should call `createOtelEmitter()`.\n */\nexport async function tryLoadOtelApi(): Promise<typeof import('@opentelemetry/api') | null> {\n\tif (cachedApi !== undefined) return cachedApi\n\ttry {\n\t\tcachedApi = await import('@opentelemetry/api')\n\t} catch {\n\t\tcachedApi = null\n\t}\n\treturn cachedApi\n}\n\n/** Test-only escape hatch: forget the cached load so a subsequent test can\n * observe a fresh attempt (with a mocked dynamic import). */\nexport function __resetOtelApiCacheForTests(): void {\n\tcachedApi = undefined\n}\n\n// ─── Instrument construction ─────────────────────────────────────────────────\n\nfunction buildInstruments(meter: Meter): CachedInstruments {\n\treturn {\n\t\tscoreGauge: meter.createGauge(METRIC_NAMES.score, {\n\t\t\tdescription: 'Multi-signal health score per ADR-141 §4. Range [0, 1]. Higher = healthier.',\n\t\t\tunit: '1',\n\t\t}),\n\t\tfactorGauge: meter.createGauge(METRIC_NAMES.signalFactor, {\n\t\t\tdescription:\n\t\t\t\t'Per-signal health factor; folded into health.score by the SDK scoring strategy (default: weightedProduct, ADR-111 §4.3).',\n\t\t\tunit: '1',\n\t\t}),\n\t\tvalueGauge: meter.createGauge(METRIC_NAMES.signalValue, {\n\t\t\tdescription:\n\t\t\t\t'Raw observation behind a health signal (ms, count, rate). Numeric values only; string-valued signals emit health.signal.value_state instead.',\n\t\t}),\n\t\tvalueStateCounter: meter.createCounter(METRIC_NAMES.signalValueState, {\n\t\t\tdescription:\n\t\t\t\t'Per-evaluation counter for non-numeric signal readings (e.g. \"ok\" / \"timeout\"). state attribute carries the string label.',\n\t\t}),\n\t\tdownstreamCauseCounter: meter.createCounter(METRIC_NAMES.downstreamCause, {\n\t\t\tdescription:\n\t\t\t\t'Per-evaluation counter incremented when this service observes an upstream cause via OTel baggage (ADR-143). Operators read rate() to size the blast radius of an incident.',\n\t\t}),\n\t}\n}\n\n// ─── Span attribute / event attachment ───────────────────────────────────────\n\n/**\n * Active-span correlation. Sets `service.health.score` on the active span\n * if any. Records `health.evaluated` event when `events` is true.\n *\n * The span parameter is typed as `unknown` because `Tracer.getActiveSpan()`\n * is only on the modern (v1.9+) API surface; older OTel APIs lack it. We\n * structurally probe for the methods we use.\n */\nfunction maybeAttachToSpan(\n\tspan: unknown,\n\tscore: HealthScore,\n\tevents: boolean,\n\tupstreamCause: UpstreamCause | null | undefined,\n): void {\n\tif (!span || typeof span !== 'object') return\n\tconst setAttribute = (span as { setAttribute?: (k: string, v: unknown) => void }).setAttribute\n\tif (typeof setAttribute === 'function') {\n\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.score, score.score)\n\t\tfor (const [signalName, factor] of Object.entries(score.signalFactors ?? {})) {\n\t\t\tif (typeof factor === 'number' && Number.isFinite(factor)) {\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.signalFactor(signalName), factor)\n\t\t\t}\n\t\t}\n\t\t// ADR-143 — cause attribution. The primary cause is the first\n\t\t// `<signal>@<service>` in the baggage value (operator dashboards\n\t\t// pivot on the primary). The full hop chain is preserved verbatim\n\t\t// so multi-hop incident paths reconstruct without ambiguity.\n\t\tif (upstreamCause && upstreamCause.causes.length > 0) {\n\t\t\tconst primary = upstreamCause.causes[0]\n\t\t\tif (primary) {\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.causeRoot, primary.service)\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.causeSignal, primary.signal)\n\t\t\t}\n\t\t\tif (upstreamCause.chain.length > 0) {\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.causeChain, upstreamCause.chain.join(','))\n\t\t\t}\n\t\t}\n\t}\n\tif (events) {\n\t\tconst addEvent = (span as { addEvent?: (name: string, attrs?: Attributes) => void }).addEvent\n\t\tif (typeof addEvent === 'function') {\n\t\t\taddEvent.call(span, EVENT_NAMES.evaluated, {\n\t\t\t\t[SPAN_ATTRIBUTES.score]: score.score,\n\t\t\t\t'service.health.lastTickAt': score.lastTickAt,\n\t\t\t})\n\t\t}\n\t}\n}\n","/**\n * Scoring strategies (ADR-111 §4 — three-tier health gate).\n *\n * The default `weightedProduct` strategy is multiplicative — a single bad\n * signal can drag the whole score down (any factor of 0 → score 0). This is\n * the right semantic for **liveness**: one critical subsystem dead means\n * the runtime is not ready, regardless of how healthy the rest is.\n *\n * Mathematics:\n * score = ∏ factor_i ^ weight_i (over signals where weight > 0 AND\n * reading is not `unknown`)\n *\n * Where weights are normalised to sum=1 first, so absolute weight values\n * don't matter — only the ratios do. This keeps user expectations sane:\n * `[w=2, w=2]` and `[w=10, w=10]` produce identical scores.\n *\n * Edge cases (deterministic, never throw):\n * - empty input → 1 (perfect health, nothing to penalise)\n * - all weights = 0 → 1 (no active signals)\n * - all readings `unknown` → 1 (we can't see; cardinal-rule fallback\n * lives at the sidecar boundary, not\n * here — ADR-111 §3.2.5)\n * - factor < 0 / NaN → clamped to 0\n * - factor > 1 → clamped to 1\n * - weight < 0 → clamped to 0 (ignored)\n *\n * The clamps make us **safe by construction** — a misconfigured signal\n * cannot push the score outside `[0, 1]`. Tests exercise all branches.\n */\n\nimport type { ScoringStrategy, Signal, SignalReading } from './types'\n\n/** Clamp `x` to `[0, 1]`, mapping NaN to 0. */\nfunction clamp01(x: number): number {\n\tif (!Number.isFinite(x)) return 0\n\tif (x < 0) return 0\n\tif (x > 1) return 1\n\treturn x\n}\n\n/**\n * Weighted geometric mean — the default scoring strategy.\n *\n * @example\n * weightedProduct([\n * { signal: { name: 'lag', weight: 0.4 }, reading: { healthFactor: 0.4 } },\n * { signal: { name: 'q', weight: 0.6 }, reading: { healthFactor: 1.0 } },\n * ])\n * // → 0.4^0.4 × 1.0^0.6 ≈ 0.693 — ADR-111 §4.5 worked example\n */\nexport const weightedProduct: ScoringStrategy = (readings) => {\n\tif (readings.length === 0) return 1\n\n\t// 1. Filter active signals (weight > 0, reading defined, not unknown).\n\tconst active: Array<{ factor: number; weight: number }> = []\n\tfor (const { signal, reading } of readings) {\n\t\tconst w = signal.weight\n\t\tif (!Number.isFinite(w) || w <= 0) continue\n\t\tif (reading.unknown === true) continue\n\t\tactive.push({\n\t\t\tfactor: clamp01(reading.healthFactor),\n\t\t\tweight: w,\n\t\t})\n\t}\n\tif (active.length === 0) return 1\n\n\t// 2. Normalise weights to sum=1.\n\tconst totalWeight = active.reduce((sum, a) => sum + a.weight, 0)\n\tif (totalWeight <= 0 || !Number.isFinite(totalWeight)) return 1\n\n\t// 3. Geometric mean: exp(Σ w_i × ln(f_i)) — exp/log is more numerically\n\t// stable than repeated `Math.pow` × multiplication for small factors.\n\t// ln(0) → -Infinity → exp(-Infinity) → 0, which is exactly what we\n\t// want (any dead signal kills the score).\n\tlet logSum = 0\n\tfor (const { factor, weight } of active) {\n\t\tconst normalisedWeight = weight / totalWeight\n\t\tif (factor <= 0) {\n\t\t\t// Short-circuit: a single zero factor => score 0, regardless of\n\t\t\t// the others. Avoids ln(0) sentinel value plumbing.\n\t\t\treturn 0\n\t\t}\n\t\tlogSum += normalisedWeight * Math.log(factor)\n\t}\n\tconst score = Math.exp(logSum)\n\treturn clamp01(score)\n}\n\n/**\n * Convenience: build a default scoring strategy.\n *\n * Reserved for future: weighted-min, weighted-mean, etc. For now there's\n * one strategy and `weightedProduct` is the only export — this keeps the\n * surface narrow until a concrete second use-case appears.\n */\nexport function defaultScoringStrategy(): ScoringStrategy {\n\treturn weightedProduct\n}\n\n// Re-export the types used by callers writing custom strategies.\nexport type { ScoringStrategy, Signal, SignalReading }\n","/**\n * `eventLoopLagSignal` — main-thread blocking detector (ADR-111 §4.3).\n *\n * Measures Node/Bun's libuv event-loop delay using `monitorEventLoopDelay()`\n * from `node:perf_hooks`. The monitor is a histogram updated by libuv at a\n * configurable resolution (default 10 ms here — every tick measures lag\n * between expected wake and actual wake). We report the **max** observed\n * since the last `read()` then `reset()` — that's the worst-case stall\n * the app suffered in the polling window.\n *\n * Mapping observed-lag-ms → healthFactor ∈ [0, 1]:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━┓\n * │ ┃ linear interpolation\n * │ ┃\n * 0.0 ┤ ┗━━━━━━━━━━━━━\n * ┼─────────┼──────────┼──────► observed lag (ms)\n * 0 degradedMs deadMs\n *\n * Below `degradedMs` → factor 1 (healthy). Above `deadMs` → factor 0 (dead).\n * In-between → linear interpolation. ADR-111 §4.3 default thresholds:\n * degradedMs = 5000 (5 s — same order as ADR-110's 10 s probe timeout)\n * deadMs = 30000 (30 s — definitely wedged)\n *\n * Bun-compatibility: Bun ships `monitorEventLoopDelay()` with the same\n * shape as Node 16+. Verified against `bun:1.3` in this repo's CI.\n */\n\nimport { monitorEventLoopDelay } from 'node:perf_hooks'\nimport type { SignalReading, SyncSignal } from '../types'\n\nexport interface EventLoopLagOptions {\n\t/** Lag below this is fully healthy (factor=1). Default 5000 ms. */\n\treadonly degradedMs?: number\n\t/** Lag above this is fully dead (factor=0). Default 30000 ms. */\n\treadonly deadMs?: number\n\t/**\n\t * Histogram resolution (ms). Default 10 ms — the smaller, the more\n\t * accurate the max but the higher the libuv accounting overhead.\n\t * Node defaults are also 10 ms.\n\t */\n\treadonly resolutionMs?: number\n\t/** Weight in the weighted-product score. Default 0.4 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/**\n\t * Optional injected monitor (tests). Defaults to a fresh\n\t * `monitorEventLoopDelay()` instance.\n\t */\n\treadonly monitor?: ReturnType<typeof monitorEventLoopDelay>\n}\n\n/**\n * Build an `event-loop-lag` signal. The returned object owns a started\n * histogram monitor; call `dispose()` during shutdown / between tests.\n */\nexport function eventLoopLagSignal(opts: EventLoopLagOptions = {}): SyncSignal {\n\tconst degradedMs = opts.degradedMs ?? 5000\n\tconst deadMs = opts.deadMs ?? 30000\n\tconst resolutionMs = opts.resolutionMs ?? 10\n\tconst weight = opts.weight ?? 0.4\n\n\tif (!Number.isFinite(degradedMs) || degradedMs < 0) {\n\t\tthrow new Error(`eventLoopLagSignal: degradedMs must be >= 0, got ${degradedMs}`)\n\t}\n\tif (!Number.isFinite(deadMs) || deadMs <= degradedMs) {\n\t\tthrow new Error(\n\t\t\t`eventLoopLagSignal: deadMs must be > degradedMs (${degradedMs}), got ${deadMs}`,\n\t\t)\n\t}\n\tif (!Number.isFinite(resolutionMs) || resolutionMs < 1) {\n\t\tthrow new Error(`eventLoopLagSignal: resolutionMs must be >= 1, got ${resolutionMs}`)\n\t}\n\n\tconst monitor = opts.monitor ?? monitorEventLoopDelay({ resolution: resolutionMs })\n\tmonitor.enable()\n\n\treturn {\n\t\tname: 'eventLoopLagMs',\n\t\tweight,\n\t\tread(): SignalReading {\n\t\t\t// `max` is in nanoseconds.\n\t\t\tconst maxNs = monitor.max\n\t\t\t// On a brand-new monitor or after `reset()` the histogram can\n\t\t\t// report `max=0` (no samples yet) or, on some Node versions,\n\t\t\t// `Number.MAX_SAFE_INTEGER` as a sentinel. Treat both as\n\t\t\t// \"no reading yet\" → unknown.\n\t\t\tif (!Number.isFinite(maxNs) || maxNs <= 0 || maxNs >= Number.MAX_SAFE_INTEGER) {\n\t\t\t\tmonitor.reset()\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tconst observedMs = maxNs / 1_000_000\n\t\t\tmonitor.reset()\n\n\t\t\tlet factor: number\n\t\t\tif (observedMs <= degradedMs) factor = 1\n\t\t\telse if (observedMs >= deadMs) factor = 0\n\t\t\telse {\n\t\t\t\t// Linear interpolation between degraded (1) and dead (0).\n\t\t\t\tconst span = deadMs - degradedMs\n\t\t\t\tfactor = 1 - (observedMs - degradedMs) / span\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tvalue: Math.round(observedMs),\n\t\t\t\thealthFactor: factor,\n\t\t\t}\n\t\t},\n\t\tdispose(): void {\n\t\t\tmonitor.disable()\n\t\t},\n\t}\n}\n","/**\n * Unix-socket server for `@sylphx/sdk/health` (ADR-111 §3.2.4 + §4.2).\n *\n * The sidecar polls the app over a Unix socket — `/var/run/sylphx/health.sock`\n * by default. The shared volume (`emptyDir` mount, see ADR-111 §3.2.2) is\n * provisioned by the reconciler when the sidecar is injected. This server\n * binds Bun's native `Bun.serve({ unix: <path> })` and serves the same\n * JSON the HTTP handler does.\n *\n * Graceful shutdown is essential — the socket file lingers on disk if not\n * cleaned, and the sidecar's first reconnect attempt after a redeploy\n * would hit a stale inode. `shutdown()` calls `server.stop()` AND\n * unlinks the socket file. Process signal ownership stays with the app\n * entry point; SDK library code only returns an explicit shutdown handle.\n */\n\nimport { unlinkSync } from 'node:fs'\nimport { createWebHandler, type HealthEvaluator } from './handler'\n\n/** Bun-typed minimum for the server we need to control. */\ninterface BunServer {\n\tstop(force?: boolean): void\n\turl?: { href: string } | null\n}\n\nexport interface UnixSocketServerOptions {\n\t/** Absolute path to bind. Defaults to `/var/run/sylphx/health.sock`. */\n\treadonly path?: string\n\t/**\n\t * Override unlink behavior — useful for tests where the test runner\n\t * already owns the socket cleanup.\n\t */\n\treadonly unlinkOnShutdown?: boolean\n\t/**\n\t * Override the Bun runtime resolver. Used by tests to verify the\n\t * \"no-Bun\" error path without leaving a Bun-only test environment.\n\t * Production code reads `globalThis.Bun` directly.\n\t */\n\treadonly bunRuntime?: { serve: (cfg: unknown) => BunServer } | null\n}\n\nexport interface UnixSocketServerHandle {\n\treadonly path: string\n\treadonly server: BunServer\n\t/** Stop the server AND unlink the socket file (unless `unlinkOnShutdown=false`). */\n\tshutdown(): Promise<void>\n}\n\n/**\n * Start a Bun unix-socket HTTP server bound to `opts.path`. Returns the\n * handle so tests / shutdown handlers can call `shutdown()`.\n *\n * Pre-binds the socket: if a stale file from a prior crash exists at the\n * path, we `unlinkSync` it first so the bind doesn't `EADDRINUSE`. This\n * is safe because the path is operator-controlled (default lives under\n * `/var/run/sylphx/`, owned by the runtime UID 1000).\n */\nexport function startUnixSocketServer(\n\tsource: HealthEvaluator,\n\topts: UnixSocketServerOptions = {},\n): UnixSocketServerHandle {\n\t// Lazy reference to globalThis.Bun — keeps the import free of build-time\n\t// requirements on Bun for non-Bun consumers (server is no-op there).\n\t// Tests inject `opts.bunRuntime: null` to exercise the no-Bun path.\n\tconst bun =\n\t\topts.bunRuntime !== undefined\n\t\t\t? opts.bunRuntime\n\t\t\t: (globalThis as unknown as { Bun?: { serve: (cfg: unknown) => BunServer } }).Bun\n\tif (bun === null || bun === undefined || typeof bun.serve !== 'function') {\n\t\tthrow new Error(\n\t\t\t'startUnixSocketServer: Bun.serve is unavailable — Unix-socket transport requires a Bun runtime',\n\t\t)\n\t}\n\n\tconst path = opts.path ?? '/var/run/sylphx/health.sock'\n\tconst unlinkOnShutdown = opts.unlinkOnShutdown ?? true\n\n\t// Best-effort pre-cleanup of stale socket.\n\ttry {\n\t\tunlinkSync(path)\n\t} catch {\n\t\t// ENOENT is fine; anything else means the user has the wrong path\n\t\t// or perms — the bind below will surface a clear error.\n\t}\n\n\tconst handler = createWebHandler(source)\n\tconst server = bun.serve({\n\t\tunix: path,\n\t\tfetch: async (req: Request): Promise<Response> => handler(req),\n\t}) as BunServer\n\n\tconst handle: UnixSocketServerHandle = {\n\t\tpath,\n\t\tserver,\n\t\tasync shutdown(): Promise<void> {\n\t\t\ttry {\n\t\t\t\tserver.stop(true)\n\t\t\t} catch {\n\t\t\t\t// stopping a server that's already stopped is benign\n\t\t\t}\n\t\t\tif (unlinkOnShutdown) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(path)\n\t\t\t\t} catch {\n\t\t\t\t\t// ENOENT — already gone, fine\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n\n\treturn handle\n}\n","/**\n * Framework middleware helpers for `@sylphx/sdk/health` (ADR-141 Phase 0).\n *\n * Zero-Config one-liners for the most common JS server frameworks. Each\n * helper creates a `sylphxHealth()` instance internally and returns a\n * framework-idiomatic middleware / plugin / handler that serves `/healthz`.\n *\n * For advanced control (custom signals, scoring strategies, Unix-socket\n * IPC for the sidecar), import `sylphxHealth` directly — these helpers\n * are wrappers around the same factory.\n *\n * @example Hono\n * ```ts\n * import { Hono } from 'hono'\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const app = new Hono()\n * app.use('*', withHealth.hono())\n * ```\n *\n * @example Express\n * ```ts\n * import express from 'express'\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const app = express()\n * app.use(withHealth.express())\n * ```\n *\n * @example Fastify\n * ```ts\n * import Fastify from 'fastify'\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const fastify = Fastify()\n * await fastify.register(withHealth.fastify())\n * ```\n *\n * @example Bun.serve / Next.js / Deno / edge runtimes\n * ```ts\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const handleHealth = withHealth.fetch()\n * Bun.serve({\n * port: 3000,\n * fetch(req) {\n * if (new URL(req.url).pathname === '/healthz') return handleHealth(req)\n * return new Response('hello')\n * },\n * })\n * ```\n *\n * Structural types are used for the framework signatures so this module\n * has **zero peer dependencies** on Hono / Express / Fastify. The user's\n * framework supplies the actual types at the call site via TypeScript's\n * structural typing.\n */\n\nimport { type SylphxHealth, sylphxHealth } from './index'\nimport type { SylphxHealthOptions } from './types'\nimport type { UnixSocketServerHandle } from './unix-socket-server'\n\n/**\n * Common options for every framework helper. Extends `SylphxHealthOptions`\n * (signals, scoringStrategy, now) with a `path` override.\n */\nexport interface WithHealthOptions extends SylphxHealthOptions {\n\t/**\n\t * HTTP path to serve the health endpoint on. Defaults to `/healthz` —\n\t * the de-facto Kubernetes convention adopted by Spring Boot, FastAPI,\n\t * and the IETF `application/health+json` draft.\n\t */\n\treadonly path?: string\n\t/**\n\t * Pre-built `SylphxHealth` instance. When provided, options other than\n\t * `path` are ignored and the helper just wires the instance up. Use\n\t * this when you need to share one health instance across multiple\n\t * frameworks (e.g. Hono middleware + Unix-socket IPC for the sidecar).\n\t */\n\treadonly instance?: SylphxHealth\n\t/**\n\t * Bind the same health score on the sidecar IPC socket.\n\t *\n\t * Default: auto-enable when the platform injects\n\t * `SYLPHX_HEALTH_APP_SOCKET` into the app container. This keeps the\n\t * one-line helper enough for ADR-111 health-agent score polling while\n\t * remaining a no-op on local/dev/edge runtimes without the env var.\n\t *\n\t * Set `false` to opt out, `true` to bind the default socket path, or\n\t * pass `{ path, required }` for explicit control. `required` defaults\n\t * to `false` so non-Bun runtimes never fail module import just because\n\t * they share the public SDK.\n\t */\n\treadonly unixSocket?:\n\t\t| boolean\n\t\t| {\n\t\t\t\treadonly path?: string\n\t\t\t\treadonly required?: boolean\n\t\t\t\treadonly unlinkOnShutdown?: boolean\n\t\t }\n}\n\n/** Default HTTP path. See WithHealthOptions.path. */\nconst DEFAULT_PATH = '/healthz'\nconst DEFAULT_SOCKET_PATH = '/var/run/sylphx/health.sock'\nconst AUTO_SOCKET_HANDLES = new WeakMap<SylphxHealth, UnixSocketServerHandle>()\n\nfunction resolveInstance(opts: WithHealthOptions): SylphxHealth {\n\tif (opts.instance) return opts.instance\n\tconst { path: _path, instance: _instance, unixSocket: _unixSocket, ...sylphxOpts } = opts\n\treturn sylphxHealth(sylphxOpts)\n}\n\nfunction normalizePath(input: string | undefined): string {\n\tif (!input) return DEFAULT_PATH\n\treturn input.startsWith('/') ? input : `/${input}`\n}\n\nfunction getEnvSocketPath(): string | undefined {\n\tconst proc = (globalThis as unknown as { process?: { env?: Record<string, string | undefined> } })\n\t\t.process\n\tconst value = proc?.env?.SYLPHX_HEALTH_APP_SOCKET\n\treturn value && value.trim().length > 0 ? value.trim() : undefined\n}\n\nfunction resolveUnixSocketPath(inputPath: string | undefined, envPath: string | undefined): string {\n\tif (inputPath !== undefined) return inputPath\n\tif (envPath !== undefined) return envPath\n\treturn DEFAULT_SOCKET_PATH\n}\n\nfunction resolveUnixSocketOption(\n\tinput: WithHealthOptions['unixSocket'],\n): { path: string; required: boolean; unlinkOnShutdown?: boolean } | null {\n\tif (input === false) return null\n\tconst envPath = getEnvSocketPath()\n\tif (input === undefined) {\n\t\treturn envPath ? { path: envPath, required: false } : null\n\t}\n\tif (input === true) {\n\t\treturn { path: envPath ?? DEFAULT_SOCKET_PATH, required: false }\n\t}\n\treturn {\n\t\tpath: resolveUnixSocketPath(input.path, envPath),\n\t\trequired: input.required ?? false,\n\t\t...(input.unlinkOnShutdown !== undefined ? { unlinkOnShutdown: input.unlinkOnShutdown } : {}),\n\t}\n}\n\nfunction bindUnixSocketIfRequested(health: SylphxHealth, opts: WithHealthOptions): () => void {\n\tconst socket = resolveUnixSocketOption(opts.unixSocket)\n\tif (!socket) return () => undefined\n\tconst existing = AUTO_SOCKET_HANDLES.get(health)\n\tif (existing) return () => undefined\n\ttry {\n\t\tconst handle = health.serveUnixSocket({\n\t\t\tpath: socket.path,\n\t\t\t...(socket.unlinkOnShutdown !== undefined\n\t\t\t\t? { unlinkOnShutdown: socket.unlinkOnShutdown }\n\t\t\t\t: {}),\n\t\t})\n\t\tAUTO_SOCKET_HANDLES.set(health, handle)\n\t\treturn () => {\n\t\t\tif (AUTO_SOCKET_HANDLES.get(health) === handle) {\n\t\t\t\tAUTO_SOCKET_HANDLES.delete(health)\n\t\t\t}\n\t\t\tvoid handle.shutdown()\n\t\t}\n\t} catch (err) {\n\t\tif (socket.required) throw err\n\t\treturn () => undefined\n\t}\n}\n\n// ─── Hono ────────────────────────────────────────────────────────────────────\n\n/**\n * Minimal structural type for a Hono request context. We only need\n * `c.req.path` to decide whether the request hits the health endpoint.\n */\nexport interface HonoContextLike {\n\treadonly req: { readonly path: string }\n}\n/** Hono `Next` is a parameterless async callback that yields to the next middleware. */\nexport type HonoNextLike = () => Promise<void>\n\n/**\n * Hono middleware that responds to `GET /healthz` and yields otherwise.\n *\n * The returned function carries a `.health` reference (the underlying\n * `SylphxHealth` instance) and a `.dispose()` method for graceful\n * shutdown. Wire `.dispose()` into the app entry point's existing\n * graceful-shutdown coordinator.\n *\n * ```ts\n * const health = withHealth.hono()\n * app.use('*', health)\n * registerShutdown(() => health.dispose())\n * ```\n */\nexport interface HonoHealthMiddleware {\n\t(c: HonoContextLike, next: HonoNextLike): Promise<Response | undefined>\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction honoHelper(opts: WithHealthOptions = {}): HonoHealthMiddleware {\n\tconst path = normalizePath(opts.path)\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.handler()\n\tconst middleware = (async (c, next) => {\n\t\tif (c.req.path === path) return respond()\n\t\t// ADR-143 — on every non-/healthz request, run `next()` inside an\n\t\t// OTel context that carries our cause baggage (if any). Downstream\n\t\t// HTTP / gRPC / queue calls inherit the cause via standard OTel\n\t\t// propagation. No-op when causality is disabled, OTel is absent,\n\t\t// or `evaluate()` has not produced a lastScore yet.\n\t\tawait health.runWithCause(() => next())\n\t\treturn undefined\n\t}) as HonoHealthMiddleware\n\tObject.defineProperty(middleware, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(middleware, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn middleware\n}\n\n// ─── Express / Connect ───────────────────────────────────────────────────────\n\n/** Express-style request — only `url` is needed to match `/healthz`. */\nexport interface ExpressRequestLike {\n\treadonly url?: string\n\treadonly method?: string\n}\n/** Express-style response — subset required by the Node handler. */\nexport interface ExpressResponseLike {\n\tstatusCode: number\n\tsetHeader(name: string, value: string): void\n\tend(body?: string): void\n}\n/** Express-style next callback — called when the request is not for /healthz. */\nexport type ExpressNextLike = (err?: unknown) => void\n\nexport interface ExpressHealthMiddleware {\n\t(req: ExpressRequestLike, res: ExpressResponseLike, next: ExpressNextLike): void\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction pathOf(url: string | undefined): string {\n\tif (!url) return '/'\n\tconst q = url.indexOf('?')\n\treturn q === -1 ? url : url.slice(0, q)\n}\n\nfunction expressHelper(opts: WithHealthOptions = {}): ExpressHealthMiddleware {\n\tconst path = normalizePath(opts.path)\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.nodeHandler()\n\tconst middleware = ((req, res, next) => {\n\t\tif (pathOf(req.url) === path) {\n\t\t\tvoid respond(req, res)\n\t\t\treturn\n\t\t}\n\t\t// ADR-143 — same baggage propagation as the Hono variant. Express's\n\t\t// `next()` is synchronous-by-convention, so we kick it off inside\n\t\t// runWithCause and let the framework run downstream handlers in\n\t\t// that async context. Errors propagate via Express's standard\n\t\t// next(err) path; runWithCause never throws on its own.\n\t\tvoid health.runWithCause(async () => next())\n\t}) as ExpressHealthMiddleware\n\tObject.defineProperty(middleware, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(middleware, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn middleware\n}\n\n// ─── Fastify ─────────────────────────────────────────────────────────────────\n\n/**\n * Minimal structural type for a Fastify instance — only the `.get` route\n * registration is needed. The plugin signature matches Fastify's\n * encapsulated-plugin contract: `(instance, opts) => Promise<void>`.\n */\nexport interface FastifyInstanceLike {\n\tget(\n\t\tpath: string,\n\t\thandler: (req: unknown, reply: FastifyReplyLike) => Promise<unknown> | unknown,\n\t): unknown\n}\n/** Minimal Fastify reply — subset required by the health response. */\nexport interface FastifyReplyLike {\n\tcode(code: number): FastifyReplyLike\n\theader(name: string, value: string): FastifyReplyLike\n\tsend(body?: unknown): unknown\n}\n\nexport interface FastifyHealthPlugin {\n\t(instance: FastifyInstanceLike, opts?: unknown): Promise<void>\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction fastifyHelper(opts: WithHealthOptions = {}): FastifyHealthPlugin {\n\tconst path = normalizePath(opts.path)\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.handler()\n\tconst plugin = (async (instance) => {\n\t\tinstance.get(path, async (_req, reply) => {\n\t\t\tconst response = await respond()\n\t\t\tconst body = await response.text()\n\t\t\treply.code(response.status)\n\t\t\treply.header('Content-Type', 'application/json')\n\t\t\treply.header('Cache-Control', 'no-store, no-cache, must-revalidate')\n\t\t\treply.send(body)\n\t\t})\n\t}) as FastifyHealthPlugin\n\tObject.defineProperty(plugin, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(plugin, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn plugin\n}\n\n// ─── Pure Web Fetch (Bun.serve / Next.js / Deno / edge) ──────────────────────\n\nexport interface FetchHealthHandler {\n\t(req?: Request): Promise<Response>\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction fetchHelper(opts: WithHealthOptions = {}): FetchHealthHandler {\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.handler()\n\tconst handler = ((req?: Request) => respond(req)) as FetchHealthHandler\n\tObject.defineProperty(handler, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(handler, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn handler\n}\n\n// ─── Public surface ──────────────────────────────────────────────────────────\n\n/**\n * One-line health-check wiring for popular JS server frameworks.\n *\n * All helpers internally create a `sylphxHealth()` instance with sane\n * defaults (single event-loop-lag signal) and serve `/healthz` returning\n * the standard `HealthScore` JSON.\n *\n * Each returned helper exposes the underlying `SylphxHealth` instance\n * via `.health` and a `.dispose()` shortcut for graceful shutdown.\n *\n * See `sylphxHealth()` for advanced use (custom signals, scoring, IPC).\n */\nexport const withHealth = {\n\t/** Hono middleware. Mount with `app.use('*', withHealth.hono())`. */\n\thono: honoHelper,\n\t/** Express / Connect / Polka middleware. Mount with `app.use(withHealth.express())`. */\n\texpress: expressHelper,\n\t/** Fastify plugin. Register with `await fastify.register(withHealth.fastify())`. */\n\tfastify: fastifyHelper,\n\t/**\n\t * Pure Web Fetch handler. Works with Bun.serve, Next.js route\n\t * handlers, Deno, Cloudflare Workers, and any other `Request →\n\t * Response` runtime.\n\t */\n\tfetch: fetchHelper,\n} as const\n","/**\n * `errorRateSignal` — request-error-rate over a sliding window (ADR-111 §4.3).\n *\n * Phase B uses an in-memory ring buffer of {timestamp, isError} samples.\n * Phase C will replace this with an OTel collector subscription so the\n * sidecar gets the same data without per-process bookkeeping; for now the\n * in-memory path keeps the SDK self-contained.\n *\n * Mapping observed-rate → healthFactor:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━━━━━━━━━━━┓\n * │ ┃ linear interpolation\n * 0.0 ┤ ┗━━━━━━━━━━\n * ┼───────────────────┼────────► error rate (0..1)\n * 0 degradedRate deadRate\n *\n * Default thresholds match ADR-111 §4.3 row 3:\n * degradedRate = 0.05 (5 % errors → degraded)\n * deadRate = 0.50 (50 % errors → dead)\n *\n * `recordSuccess()` / `recordError()` are pushed by the app on each\n * request (or wired into Hono / Express middleware — the SDK provides\n * primitives, not framework integrations). Zero-traffic windows produce\n * `factor=1` (no requests = no errors = healthy by convention).\n */\n\nimport type { SignalReading, SyncSignal } from '../types'\n\nexport interface ErrorRateOptions {\n\t/** Sliding window length. Accepts ms (number) or '5s' / '1m' shorthand. */\n\treadonly window: number | `${number}s` | `${number}m`\n\t/** Rate above this is considered degraded. Default 0.05 (5 %). */\n\treadonly degradedRate?: number\n\t/** Rate at which the signal saturates to dead. Default 0.50 (50 %). */\n\treadonly deadRate?: number\n\t/**\n\t * Soft minimum sample count: until this many samples land, factor=1\n\t * regardless of rate (avoids \"1 error in 1 request → 100 %\" panics).\n\t * Default 10.\n\t */\n\treadonly minSamples?: number\n\t/** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/** Optional injected clock for tests. */\n\treadonly now?: () => number\n}\n\nexport interface ErrorRateSignalHandle extends SyncSignal {\n\t/** Record a successful request (call from app middleware). */\n\trecordSuccess(): void\n\t/** Record a failed request (call from app middleware). */\n\trecordError(): void\n\t/** Erase the window. Useful for tests. */\n\treset(): void\n}\n\ninterface Sample {\n\treadonly t: number\n\treadonly e: boolean\n}\n\nfunction parseWindow(w: ErrorRateOptions['window']): number {\n\tif (typeof w === 'number') {\n\t\tif (!Number.isFinite(w) || w <= 0) {\n\t\t\tthrow new Error(`errorRateSignal: window must be > 0, got ${w}`)\n\t\t}\n\t\treturn w\n\t}\n\tconst m = /^(\\d+)(s|m)$/.exec(w)\n\tif (m === null) {\n\t\tthrow new Error(`errorRateSignal: invalid window '${w}', expected '5s' / '1m' / number`)\n\t}\n\tconst n = Number.parseInt(m[1] as string, 10)\n\tconst unit = m[2]\n\tconst ms = unit === 's' ? n * 1000 : n * 60_000\n\tif (ms <= 0) {\n\t\tthrow new Error(`errorRateSignal: parsed window ${ms}ms must be > 0`)\n\t}\n\treturn ms\n}\n\nexport function errorRateSignal(opts: ErrorRateOptions): ErrorRateSignalHandle {\n\tconst windowMs = parseWindow(opts.window)\n\tconst degradedRate = opts.degradedRate ?? 0.05\n\tconst deadRate = opts.deadRate ?? 0.5\n\tconst minSamples = opts.minSamples ?? 10\n\tconst weight = opts.weight ?? 0.2\n\tconst now = opts.now ?? Date.now\n\n\tif (degradedRate < 0 || degradedRate > 1) {\n\t\tthrow new Error(`errorRateSignal: degradedRate must be in [0, 1], got ${degradedRate}`)\n\t}\n\tif (deadRate <= degradedRate || deadRate > 1) {\n\t\tthrow new Error(`errorRateSignal: deadRate must be in (degradedRate, 1], got ${deadRate}`)\n\t}\n\tif (!Number.isFinite(minSamples) || minSamples < 1) {\n\t\tthrow new Error(`errorRateSignal: minSamples must be >= 1, got ${minSamples}`)\n\t}\n\n\t// Linked-list-style ring; we shift the head when entries age out. For\n\t// fleets at 100 req/s with a 5 s window that's ~500 entries — well\n\t// within memory budget. Phase C swaps this for the OTel subscription.\n\tconst samples: Sample[] = []\n\n\tfunction pruneExpired(t: number): void {\n\t\tconst cutoff = t - windowMs\n\t\twhile (samples.length > 0 && (samples[0] as Sample).t < cutoff) {\n\t\t\tsamples.shift()\n\t\t}\n\t}\n\n\treturn {\n\t\tname: `recent${Math.round(windowMs / 1000)}sErrorRate`,\n\t\tweight,\n\t\trecordSuccess(): void {\n\t\t\tsamples.push({ t: now(), e: false })\n\t\t},\n\t\trecordError(): void {\n\t\t\tsamples.push({ t: now(), e: true })\n\t\t},\n\t\treset(): void {\n\t\t\tsamples.length = 0\n\t\t},\n\t\tread(): SignalReading {\n\t\t\tconst t = now()\n\t\t\tpruneExpired(t)\n\t\t\tif (samples.length === 0 || samples.length < minSamples) {\n\t\t\t\t// Insufficient traffic → don't penalise. Return rate=0 +\n\t\t\t\t// factor=1 (visible in JSON for ops triage).\n\t\t\t\treturn { value: 0, healthFactor: 1 }\n\t\t\t}\n\t\t\tlet errors = 0\n\t\t\tfor (const s of samples) {\n\t\t\t\tif (s.e) errors++\n\t\t\t}\n\t\t\tconst rate = errors / samples.length\n\n\t\t\tlet factor: number\n\t\t\tif (rate <= degradedRate) factor = 1\n\t\t\telse if (rate >= deadRate) factor = 0\n\t\t\telse {\n\t\t\t\tconst span = deadRate - degradedRate\n\t\t\t\tfactor = 1 - (rate - degradedRate) / span\n\t\t\t}\n\n\t\t\t// Round to 4 decimal places — wire JSON stays compact and the\n\t\t\t// extra precision is meaningless for ops use.\n\t\t\tconst valueRounded = Math.round(rate * 10_000) / 10_000\n\n\t\t\treturn {\n\t\t\t\tvalue: valueRounded,\n\t\t\t\thealthFactor: factor,\n\t\t\t}\n\t\t},\n\t}\n}\n","/**\n * `memoryPressureSignal` — RSS / cgroup memory.max ratio (ADR-111 §4.3).\n *\n * On Linux containers we read the cgroup v2 memory limit from\n * `/sys/fs/cgroup/memory.max`. Then `pressure = process.memoryUsage().rss /\n * limit`. ADR-111 §4.3 default thresholds:\n * degradedRatio = 0.85 (85 % → degraded)\n * deadRatio = 0.95 (95 % → dead)\n *\n * Mapping observed-pressure → healthFactor:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━━━━━━━━━━┓\n * │ ┃\n * 0.0 ┤ ┗━━━━━━━━━\n * ┼──────────────────┼─────────► pressure (0..1)\n * 0 degradedRatio deadRatio\n *\n * Graceful fallback (the cardinal-rule deference):\n * - cgroup file missing → unknown=true (signal ignored)\n * - cgroup file has 'max' → unlimited container, ratio undefined → unknown\n * - file unreadable / parse → unknown=true\n *\n * `unknown=true` makes the scoring strategy ignore the signal — we never\n * pretend to know memory pressure on a host where we can't measure it.\n *\n * cgroup v1 (legacy) lives at `/sys/fs/cgroup/memory/memory.limit_in_bytes`\n * — supported via `cgroupV1Path` for operators on older kernels.\n */\n\nimport { readFileSync } from 'node:fs'\nimport type { SignalReading, SyncSignal } from '../types'\n\nexport interface MemoryPressureOptions {\n\t/** Pressure below this is fully healthy (factor=1). Default 0.85. */\n\treadonly degradedRatio?: number\n\t/** Pressure above this is fully dead (factor=0). Default 0.95. */\n\treadonly deadRatio?: number\n\t/**\n\t * Custom cgroup v2 memory limit path. Default `/sys/fs/cgroup/memory.max`.\n\t */\n\treadonly cgroupV2Path?: string\n\t/**\n\t * Optional cgroup v1 fallback path. Default\n\t * `/sys/fs/cgroup/memory/memory.limit_in_bytes`. Used when v2 path is\n\t * unreadable AND `cgroupV1Path` is set or v2 doesn't exist.\n\t */\n\treadonly cgroupV1Path?: string\n\t/** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/**\n\t * Optional injected `process.memoryUsage` for tests.\n\t * Default: `process.memoryUsage`.\n\t */\n\treadonly memoryUsage?: () => { rss: number }\n\t/** Optional injected file reader for tests. */\n\treadonly readFile?: (path: string) => string\n}\n\n/**\n * cgroup v1 reports a sentinel value (`9223372036854771712` =\n * `Number.MAX_SAFE_INTEGER` neighbourhood, well > 2^53) when the limit is\n * unconstrained. cgroup v2 uses the literal string `max`. Both collapse\n * to \"unlimited → unknown\".\n */\nconst CGROUP_V1_UNLIMITED_FLOOR = 1e18\n\nfunction tryRead(reader: (p: string) => string, path: string): string | null {\n\ttry {\n\t\treturn reader(path)\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Read the container memory limit in bytes. Returns `null` if unknown\n * (file missing, unparseable, or unlimited).\n *\n * Exposed for direct tests of the cgroup parsing logic.\n */\nexport function readContainerMemoryLimit(\n\treader: (p: string) => string,\n\tv2Path: string,\n\tv1Path: string,\n): number | null {\n\t// 1. Try cgroup v2 first: it is the modern default for current Linux runtimes.\n\tconst v2 = tryRead(reader, v2Path)\n\tif (v2 !== null) {\n\t\tconst trimmed = v2.trim()\n\t\tif (trimmed === 'max' || trimmed === '') return null\n\t\tconst n = Number.parseInt(trimmed, 10)\n\t\tif (!Number.isFinite(n) || n <= 0) return null\n\t\t// Some kernels report ridiculous \"max-ish\" sentinel values via v2.\n\t\tif (n > CGROUP_V1_UNLIMITED_FLOOR) return null\n\t\treturn n\n\t}\n\t// 2. Fall back to cgroup v1 if v2 is unreadable.\n\tconst v1 = tryRead(reader, v1Path)\n\tif (v1 === null) return null\n\tconst trimmed = v1.trim()\n\tconst n = Number.parseInt(trimmed, 10)\n\tif (!Number.isFinite(n) || n <= 0) return null\n\tif (n > CGROUP_V1_UNLIMITED_FLOOR) return null\n\treturn n\n}\n\nexport function memoryPressureSignal(opts: MemoryPressureOptions = {}): SyncSignal {\n\tconst degradedRatio = opts.degradedRatio ?? 0.85\n\tconst deadRatio = opts.deadRatio ?? 0.95\n\tconst v2Path = opts.cgroupV2Path ?? '/sys/fs/cgroup/memory.max'\n\tconst v1Path = opts.cgroupV1Path ?? '/sys/fs/cgroup/memory/memory.limit_in_bytes'\n\tconst weight = opts.weight ?? 0.2\n\tconst memoryUsage =\n\t\topts.memoryUsage ??\n\t\t((): { rss: number } => {\n\t\t\tconst m = process.memoryUsage()\n\t\t\treturn { rss: m.rss }\n\t\t})\n\tconst readFile = opts.readFile ?? ((p: string) => readFileSync(p, 'utf8'))\n\n\tif (degradedRatio <= 0 || degradedRatio >= 1) {\n\t\tthrow new Error(`memoryPressureSignal: degradedRatio must be in (0, 1), got ${degradedRatio}`)\n\t}\n\tif (deadRatio <= degradedRatio || deadRatio > 1) {\n\t\tthrow new Error(\n\t\t\t`memoryPressureSignal: deadRatio must be in (degradedRatio, 1], got ${deadRatio}`,\n\t\t)\n\t}\n\n\t// Cache the cgroup limit: the kernel does not change it at runtime. One\n\t// readSync per process boot is cheap; per poll tick is wasteful.\n\tlet cachedLimit: number | null | undefined\n\tfunction getLimit(): number | null {\n\t\tif (cachedLimit === undefined) {\n\t\t\tcachedLimit = readContainerMemoryLimit(readFile, v2Path, v1Path)\n\t\t}\n\t\treturn cachedLimit\n\t}\n\n\treturn {\n\t\tname: 'memoryPressure',\n\t\tweight,\n\t\tread(): SignalReading {\n\t\t\tconst limit = getLimit()\n\t\t\tif (limit === null) {\n\t\t\t\t// No container limit known — degrade gracefully to \"unknown\"\n\t\t\t\t// rather than report a bogus ratio against host RAM.\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tlet rss: number\n\t\t\ttry {\n\t\t\t\trss = memoryUsage().rss\n\t\t\t} catch {\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tif (!Number.isFinite(rss) || rss < 0) {\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tconst ratio = rss / limit\n\n\t\t\tlet factor: number\n\t\t\tif (ratio <= degradedRatio) factor = 1\n\t\t\telse if (ratio >= deadRatio) factor = 0\n\t\t\telse {\n\t\t\t\tconst span = deadRatio - degradedRatio\n\t\t\t\tfactor = 1 - (ratio - degradedRatio) / span\n\t\t\t}\n\n\t\t\t// 3-decimal precision keeps the wire JSON small.\n\t\t\tconst valueRounded = Math.round(ratio * 1000) / 1000\n\t\t\treturn { value: valueRounded, healthFactor: factor }\n\t\t},\n\t}\n}\n","/**\n * `pingSignal` — generic binary readiness signal for any sub-system the\n * service depends on (Postgres, Redis, Kafka, S3, an upstream API, …).\n *\n * The signal calls `ping()` every poll tick and reports:\n * - `healthFactor: 1` if the ping resolves (any value) within the timeout\n * - `healthFactor: 0` if the ping throws OR times out\n *\n * Binary — no partial-credit semantics. For richer scoring use a custom\n * signal that returns a graded `healthFactor`; `pingSignal` is the\n * canonical \"subsystem reachable yes/no\" primitive that 99% of dependency\n * probes need.\n *\n * `databaseSignal` and `redisSignal` are thin convenience wrappers\n * with idiomatic defaults for those two ubiquitous dependencies.\n *\n * @example Generic\n * ```ts\n * import { pingSignal } from '@sylphx/sdk/health'\n *\n * app.use('*', withHealth.hono({\n * signals: [\n * pingSignal({\n * name: 'upstream-api',\n * ping: async () => { await fetch('https://upstream.example/_ping') },\n * }),\n * ],\n * }))\n * ```\n *\n * @example Database + Redis\n * ```ts\n * import { withHealth, databaseSignal, redisSignal } from '@sylphx/sdk/health'\n *\n * app.use('*', withHealth.hono({\n * signals: [\n * databaseSignal({ ping: () => pool.query('SELECT 1') }),\n * redisSignal({ ping: () => redis.ping() }),\n * ],\n * }))\n * ```\n */\n\nimport type { AsyncSignal, SignalReading } from '../types'\n\nexport interface PingSignalOptions {\n\t/** Stable signal name. Appears in the wire JSON `signals.<name>` map. */\n\treadonly name: string\n\t/**\n\t * Async function the SDK calls every poll tick. Resolve (any value) =\n\t * healthy. Throw / reject = unhealthy. Timeout → unhealthy.\n\t */\n\treadonly ping: () => Promise<unknown>\n\t/**\n\t * Per-tick timeout in milliseconds. Default 500ms — fast enough to\n\t * fold into a `/healthz` response without holding the event loop, slow\n\t * enough to avoid false negatives on a momentarily-slow dependency.\n\t * Tune up for dependencies that legitimately take longer (e.g. S3 list).\n\t */\n\treadonly timeoutMs?: number\n\t/** Weight in the weighted-product score. Default 1.0. */\n\treadonly weight?: number\n}\n\nconst DEFAULT_TIMEOUT_MS = 500\n\nexport function pingSignal(opts: PingSignalOptions): AsyncSignal {\n\tif (typeof opts.ping !== 'function') {\n\t\tthrow new Error(`pingSignal[${opts.name}]: ping must be a function`)\n\t}\n\tif (typeof opts.name !== 'string' || opts.name.length === 0) {\n\t\tthrow new Error('pingSignal: name must be a non-empty string')\n\t}\n\tconst timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\tthrow new Error(`pingSignal[${opts.name}]: timeoutMs must be > 0, got ${timeoutMs}`)\n\t}\n\tconst weight = opts.weight ?? 1.0\n\n\treturn {\n\t\tname: opts.name,\n\t\tweight,\n\t\tasync read(): Promise<SignalReading> {\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined\n\t\t\tconst timeoutPromise = new Promise<'timeout'>((resolve) => {\n\t\t\t\ttimer = setTimeout(() => resolve('timeout'), timeoutMs)\n\t\t\t})\n\t\t\ttry {\n\t\t\t\tconst result = await Promise.race([opts.ping().then(() => 'ok' as const), timeoutPromise])\n\t\t\t\tif (result === 'timeout') {\n\t\t\t\t\treturn { value: 'timeout', healthFactor: 0 }\n\t\t\t\t}\n\t\t\t\treturn { value: 'ok', healthFactor: 1 }\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err)\n\t\t\t\treturn { value: message.slice(0, 120), healthFactor: 0 }\n\t\t\t} finally {\n\t\t\t\tif (timer) clearTimeout(timer)\n\t\t\t}\n\t\t},\n\t}\n}\n\n// ─── canonical convenience wrappers ─────────────────────────────────────────\n\nexport interface DependencyPingOptions {\n\treadonly ping: () => Promise<unknown>\n\treadonly timeoutMs?: number\n\treadonly weight?: number\n\t/** Override the signal name. Defaults to the subsystem name. */\n\treadonly name?: string\n}\n\n/**\n * Postgres / SQL-database readiness — convenience over `pingSignal` with\n * `name: 'database'`. Wrap the caller's `pool.query('SELECT 1')` or\n * equivalent.\n */\nexport function databaseSignal(opts: DependencyPingOptions): AsyncSignal {\n\treturn pingSignal({\n\t\tname: opts.name ?? 'database',\n\t\tping: opts.ping,\n\t\t...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n\t\t...(opts.weight !== undefined ? { weight: opts.weight } : {}),\n\t})\n}\n\n/**\n * Redis / KV readiness — convenience over `pingSignal` with `name: 'redis'`.\n * Wrap the caller's `redis.ping()` or equivalent.\n */\nexport function redisSignal(opts: DependencyPingOptions): AsyncSignal {\n\treturn pingSignal({\n\t\tname: opts.name ?? 'redis',\n\t\tping: opts.ping,\n\t\t...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n\t\t...(opts.weight !== undefined ? { weight: opts.weight } : {}),\n\t})\n}\n","/**\n * `queueDepthSignal` — backpressure indicator (ADR-111 §4.3).\n *\n * Generic signal: the app provides a `getter` that returns the current\n * length of whatever queue the operator wants probed (BullMQ, RabbitMQ,\n * in-memory work pool, …). The signal does NOT own the queue — the app\n * does. We just measure.\n *\n * Mapping observed-depth → healthFactor:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━━━━━━━━━━┓\n * │ ┃ linear interpolation\n * 0.0 ┤ ┗━━━━━━━━━━━━\n * ┼──────────────────┼─────────► depth\n * 0 fullThreshold\n *\n * Below `fullThreshold` → factor 1. At `fullThreshold` → factor 0. Above\n * → factor 0. The implicit \"degraded\" zone is `[0.5 × fullThreshold,\n * fullThreshold]` — depth at half-full produces factor 0.5. Operators\n * tune `fullThreshold` to whatever their queue reasonably hits at peak\n * load; \"100% full\" means \"drain new traffic\", not \"kill the workload\" (the\n * three-tier gate at the sidecar handles the kill decision).\n *\n * `getter` errors are swallowed — a thrown getter produces `unknown=true`\n * (so the scoring strategy ignores this signal, instead of falsely\n * reporting score=0). The app's bug shouldn't masquerade as a sidecar\n * decision.\n */\n\nimport type { AsyncSignal, SignalReading } from '../types'\n\nexport interface QueueDepthOptions {\n\t/**\n\t * Sync or async getter the SDK calls every poll tick. Must return a\n\t * non-negative integer. Throws → reading marked `unknown=true`.\n\t */\n\treadonly getter: () => number | Promise<number>\n\t/**\n\t * Depth at which the queue is considered \"full\" (factor=0). Linear\n\t * interp from 0..fullThreshold. No default — operator-specific.\n\t */\n\treadonly fullThreshold: number\n\t/**\n\t * Optional below-which factor is always 1 (a \"soft floor\"). Default 0\n\t * — the linear interp starts at depth 0.\n\t */\n\treadonly healthyBelow?: number\n\t/** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/** Custom signal name. Default `queueDepth`. */\n\treadonly name?: string\n}\n\nexport function queueDepthSignal(opts: QueueDepthOptions): AsyncSignal {\n\tif (typeof opts.getter !== 'function') {\n\t\tthrow new Error('queueDepthSignal: getter must be a function')\n\t}\n\tif (!Number.isFinite(opts.fullThreshold) || opts.fullThreshold <= 0) {\n\t\tthrow new Error(`queueDepthSignal: fullThreshold must be > 0, got ${opts.fullThreshold}`)\n\t}\n\tconst healthyBelow = opts.healthyBelow ?? 0\n\tif (!Number.isFinite(healthyBelow) || healthyBelow < 0 || healthyBelow >= opts.fullThreshold) {\n\t\tthrow new Error(\n\t\t\t`queueDepthSignal: healthyBelow must be in [0, fullThreshold), got ${healthyBelow}`,\n\t\t)\n\t}\n\tconst weight = opts.weight ?? 0.2\n\n\treturn {\n\t\tname: opts.name ?? 'queueDepth',\n\t\tweight,\n\t\tasync read(): Promise<SignalReading> {\n\t\t\tlet raw: unknown\n\t\t\ttry {\n\t\t\t\traw = await opts.getter()\n\t\t\t} catch {\n\t\t\t\t// Cardinal-rule deference: getter blew up → unknown.\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tif (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) {\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tconst depth = raw\n\n\t\t\tlet factor: number\n\t\t\tif (depth <= healthyBelow) factor = 1\n\t\t\telse if (depth >= opts.fullThreshold) factor = 0\n\t\t\telse {\n\t\t\t\tconst span = opts.fullThreshold - healthyBelow\n\t\t\t\tfactor = 1 - (depth - healthyBelow) / span\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tvalue: depth,\n\t\t\t\thealthFactor: factor,\n\t\t\t}\n\t\t},\n\t}\n}\n","/**\n * `@sylphx/sdk/health` — Phase B multi-signal health score (ADR-111 §4).\n *\n * Apps register signals (event-loop lag, queue depth, error rate, memory\n * pressure, …); the SDK folds them into a continuous score in `[0, 1]`;\n * the **sidecar** maps the score to liveness / readiness / drain via the\n * three-tier gate. The SDK's responsibility ends at exposing the score.\n *\n * The wire format the sidecar parses (ADR-111 §3.2.4):\n *\n * ```json\n * {\n * \"score\": 0.92,\n * \"signals\": {\n * \"eventLoopLagMs\": 12,\n * \"queueDepth\": 3,\n * \"recent5sErrorRate\": 0.001,\n * \"memoryPressure\": 0.45\n * },\n * \"signalFactors\": {\n * \"eventLoopLagMs\": 1,\n * \"queueDepth\": 0.8,\n * \"recent5sErrorRate\": 0.99,\n * \"memoryPressure\": 0.7\n * },\n * \"lastTickAt\": \"2026-05-03T12:34:56.789Z\"\n * }\n * ```\n *\n * Default signals (if `sylphxHealth()` called with no `signals`):\n * - `eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 })` (weight 1.0)\n *\n * Three-tier gate (decided by **sidecar**, not SDK; ADR-111 §4.4):\n *\n * | Score | Liveness | Readiness | Effect |\n * | -------------- | -------- | --------- | --------------------- |\n * | > 0.8 | 200 | 200 | Normal traffic |\n * | (0.5, 0.8] | 200 | 503 | Drain new, no kill |\n * | <= 0.5 | 503 | 503 | Kill after threshold |\n *\n * Apps don't need to think about probe semantics — that's the sidecar's\n * job. The app just exposes the score. See `apps/health-agent/` for the\n * sidecar implementation.\n *\n * @example Worked example — OpenClaw under PDF-extract load (ADR-111 §4.5):\n *\n * ```text\n * eventLoopLagMs = 6000 → factor 0.4\n * queueDepth = 12 → factor 1.0\n * errorRate = 0.002 → factor 1.0\n * memoryPressure = 0.55 → factor 1.0\n * score = 0.4^0.4 × 1.0^0.6 ≈ 0.69\n *\n * → falls in [0.5, 0.8] → sidecar drains traffic, doesn't kill.\n * Pod gets to finish PDF extraction.\n * ```\n */\n\nimport {\n\ttype CausalityHandle,\n\ttype CausalityOption,\n\ttype CausalityOptions,\n\tcreateCausalityHandle,\n\ttype UpstreamCause,\n} from './causality'\nimport { evaluateEffect as buildEvaluateEffect } from './effects'\nimport { createNodeHandler, createWebHandler, type HealthEvaluator } from './handler'\nimport {\n\tcreateHistoryRecorder,\n\ttype HistoryEntry,\n\ttype HistoryOption,\n\ttype HistoryQuery,\n\ttype HistoryRecorder,\n\ttype HistoryRecorderOptions,\n\ttype VerificationResult,\n} from './history'\nimport {\n\tcreateOtelEmitter,\n\ttype OtelEmitter,\n\ttype OtelEmitterOptions,\n\ttype OtelOption,\n} from './otel'\nimport { defaultScoringStrategy } from './scoring'\nimport { eventLoopLagSignal } from './signals/event-loop-lag'\nimport type { HealthScore, Signal, SylphxHealthOptions } from './types'\nimport {\n\tstartUnixSocketServer,\n\ttype UnixSocketServerHandle,\n\ttype UnixSocketServerOptions,\n} from './unix-socket-server'\n\nexport {\n\tBAGGAGE_KEY_CAUSE,\n\tBAGGAGE_KEY_CHAIN,\n\ttype CausalityHandle,\n\ttype CausalityOption,\n\ttype CausalityOptions,\n\tcreateCausalityHandle,\n\tDEFAULT_CAUSE_THRESHOLD,\n\tMAX_CHAIN_LENGTH,\n\ttype UpstreamCause,\n} from './causality'\nexport {\n\tcreateNodeHandler,\n\tcreateWebHandler,\n\ttype HealthEvaluator,\n} from './handler'\nexport {\n\tcanonicalizeSignals,\n\tcreateHistoryRecorder,\n\tcreateInMemoryHistoryStore,\n\tDEFAULT_HISTORY_CAPACITY,\n\tGENESIS_PREV_HASH,\n\ttype HistoryEntry,\n\ttype HistoryOption,\n\ttype HistoryQuery,\n\ttype HistoryRecorder,\n\ttype HistoryRecorderOptions,\n\ttype HistoryStore,\n\ttype InMemoryHistoryStoreOptions,\n\ttype VerificationResult,\n\tverifyChain,\n} from './history'\nexport {\n\ttype ExpressHealthMiddleware,\n\ttype ExpressNextLike,\n\ttype ExpressRequestLike,\n\ttype ExpressResponseLike,\n\ttype FastifyHealthPlugin,\n\ttype FastifyInstanceLike,\n\ttype FastifyReplyLike,\n\ttype FetchHealthHandler,\n\ttype HonoContextLike,\n\ttype HonoHealthMiddleware,\n\ttype HonoNextLike,\n\ttype WithHealthOptions,\n\twithHealth,\n} from './middleware'\nexport {\n\tcreateOtelEmitter,\n\tEVENT_NAMES as OTEL_EVENT_NAMES,\n\tMETRIC_NAMES as OTEL_METRIC_NAMES,\n\ttype OtelEmitter,\n\ttype OtelEmitterOptions,\n\ttype OtelOption,\n\ttype OtelStaticAttributes,\n\tSPAN_ATTRIBUTES as OTEL_SPAN_ATTRIBUTES,\n} from './otel'\nexport { defaultScoringStrategy, weightedProduct } from './scoring'\nexport type {\n\tErrorRateOptions,\n\tErrorRateSignalHandle,\n} from './signals/error-rate'\nexport { errorRateSignal } from './signals/error-rate'\nexport type { EventLoopLagOptions } from './signals/event-loop-lag'\nexport { eventLoopLagSignal } from './signals/event-loop-lag'\nexport type { MemoryPressureOptions } from './signals/memory-pressure'\nexport { memoryPressureSignal } from './signals/memory-pressure'\nexport type { DependencyPingOptions, PingSignalOptions } from './signals/ping'\nexport { databaseSignal, pingSignal, redisSignal } from './signals/ping'\nexport type { QueueDepthOptions } from './signals/queue-depth'\nexport { queueDepthSignal } from './signals/queue-depth'\n// Re-exports — public surface.\nexport type {\n\tAsyncSignal,\n\tHealthScore,\n\tHealthSnapshot,\n\tScoringStrategy,\n\tSignal,\n\tSignalBase,\n\tSignalReading,\n\tSylphxHealthOptions,\n\tSyncSignal,\n} from './types'\nexport { HealthError } from './types'\nexport type {\n\tUnixSocketServerHandle,\n\tUnixSocketServerOptions,\n} from './unix-socket-server'\n\n/**\n * The handle returned by `sylphxHealth()`. Owns the registered signals,\n * exposes evaluation in both Promise + Effect form, and produces an HTTP\n * handler / Unix-socket server.\n *\n * Call `dispose()` during graceful shutdown to release per-signal\n * resources (e.g. the `monitorEventLoopDelay()` histogram).\n */\nexport interface SylphxHealth extends HealthEvaluator {\n\t/** All signals registered (read-only). */\n\treadonly signals: ReadonlyArray<Signal>\n\t/** Snapshot evaluation as a Promise. */\n\tevaluate(): Promise<HealthScore>\n\t/** Snapshot evaluation as an Effect (per Rule 21 / ADR-058 Amendment). */\n\treadonly evaluateEffect: ReturnType<typeof buildEvaluateEffect>\n\t/**\n\t * Web Fetch API HTTP handler — works under Hono, Bun.serve, Next.js\n\t * route.ts, itty-router, Hattip. Always returns 200 + JSON; the\n\t * sidecar applies the three-tier 200/503 gate.\n\t */\n\thandler(): (req?: Request) => Promise<Response>\n\t/** Node.js classic `(req, res)` handler — for Express / classic Fastify. */\n\tnodeHandler(): ReturnType<typeof createNodeHandler>\n\t/**\n\t * Bind a Bun Unix-domain socket and serve the same JSON. The sidecar\n\t * polls `/var/run/sylphx/health.sock` by default (ADR-111 §3.2.4).\n\t */\n\tserveUnixSocket(opts?: UnixSocketServerOptions): UnixSocketServerHandle\n\t/**\n\t * Most recent score returned by `evaluate()`. Returns null before\n\t * the first evaluation. Used by `withHealth.*` middleware to inject\n\t * cause baggage on non-probe requests (ADR-143).\n\t */\n\tgetLastScore(): HealthScore | null\n\t/**\n\t * Run `fn` within an OTel context that has our local cause baggage\n\t * set, derived from the most recent `evaluate()`. When causality is\n\t * disabled or the OTel API is unavailable, runs `fn()` directly.\n\t * Middleware consumers don't usually call this — they let\n\t * `withHealth.*` wire it automatically. Exposed for advanced users\n\t * who build custom transport wrappers.\n\t */\n\trunWithCause<T>(fn: () => T | Promise<T>): Promise<T>\n\t/**\n\t * Retrieve audit-grade history entries (ADR-144). Returns an empty\n\t * array when history is disabled. Operators use this for compliance\n\t * evidence + post-incident reconstruction.\n\t */\n\tgetHistory(query?: HistoryQuery): Promise<readonly HistoryEntry[]>\n\t/**\n\t * Verify the integrity of the recorded history chain over `query`\n\t * (defaults to all available entries). Returns `verified: true`\n\t * when the SHA-256 chain matches end-to-end; otherwise reports the\n\t * first sequence where tampering is detected.\n\t */\n\tverifyHistory(query?: HistoryQuery): Promise<VerificationResult>\n\t/**\n\t * Tear down all registered signals. Idempotent. Call during graceful\n\t * shutdown to release histograms, file watchers, etc.\n\t */\n\tdispose(): void\n}\n\n/**\n * Build a `SylphxHealth` instance.\n *\n * @example Hono integration:\n * ```ts\n * import { Hono } from 'hono'\n * import {\n * sylphxHealth,\n * eventLoopLagSignal,\n * queueDepthSignal,\n * errorRateSignal,\n * memoryPressureSignal,\n * } from '@sylphx/sdk/health'\n *\n * const errors = errorRateSignal({ window: '5s', degradedRate: 0.05 })\n *\n * const health = sylphxHealth({\n * signals: [\n * eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 }),\n * queueDepthSignal({ getter: () => queue.size, fullThreshold: 1000 }),\n * errors,\n * memoryPressureSignal({ degradedRatio: 0.85 }),\n * ],\n * })\n *\n * const app = new Hono()\n * app.get('/healthz', health.handler())\n *\n * // Track requests for the error-rate signal\n * app.use(async (c, next) => {\n * try { await next(); errors.recordSuccess() }\n * catch (err) { errors.recordError(); throw err }\n * })\n *\n * // Or — primary transport for the sidecar:\n * health.serveUnixSocket() // → /var/run/sylphx/health.sock\n * ```\n *\n * @example Worked example — OpenClaw under PDF-extract load (ADR-111 §4.5):\n *\n * ```text\n * eventLoopLagMs = 6000 → factor 0.4\n * queueDepth = 12 → factor 1.0\n * errorRate = 0.002 → factor 1.0\n * memoryPressure = 0.55 → factor 1.0\n * score = 0.4^0.4 × 1.0^0.6 ≈ 0.69\n *\n * → falls in [0.5, 0.8] → sidecar drains traffic, doesn't kill.\n * Pod gets to finish PDF extraction.\n * ```\n */\n/**\n * Resolve the `SylphxHealthOptions.otel` flag into an emitter (or null).\n * Default: emission ON with default settings. Setting `otel: false`\n * disables emission entirely. Passing an object overrides individual\n * settings.\n */\nfunction resolveOtelEmitter(otelOpt: OtelOption | undefined): OtelEmitter | null {\n\tif (otelOpt === false) return null\n\tconst emitterOpts: OtelEmitterOptions = otelOpt && typeof otelOpt === 'object' ? otelOpt : {}\n\treturn createOtelEmitter(emitterOpts)\n}\n\n/**\n * Resolve the `SylphxHealthOptions.causality` flag into a handle (or null).\n * Default: causality ON with default threshold (0.5) per ADR-143. Setting\n * `causality: false` disables both read and write entirely. Passing an\n * object overrides threshold / serviceName / chain behaviour.\n */\nfunction resolveCausalityHandle(opt: CausalityOption | undefined): CausalityHandle | null {\n\tif (opt === false) return null\n\tconst causalityOpts: CausalityOptions = opt && typeof opt === 'object' ? opt : {}\n\treturn createCausalityHandle(causalityOpts)\n}\n\n/**\n * Resolve the `SylphxHealthOptions.history` flag into a recorder (or null).\n * Default: history ON with the in-memory ring-buffer store (ADR-144).\n * Setting `history: false` disables recording entirely. Passing an object\n * supplies a persistent store + capacity override.\n */\nfunction resolveHistoryRecorder(opt: HistoryOption | undefined): HistoryRecorder | null {\n\tif (opt === false) return null\n\tconst recorderOpts: HistoryRecorderOptions = opt && typeof opt === 'object' ? opt : {}\n\treturn createHistoryRecorder(recorderOpts)\n}\n\nfunction clampHealthFactor(value: number): number {\n\tif (!Number.isFinite(value)) return 0\n\tif (value < 0) return 0\n\tif (value > 1) return 1\n\treturn value\n}\n\nexport function sylphxHealth(opts: SylphxHealthOptions = {}): SylphxHealth {\n\t// ADR-111 §4.6: zero-config default — register a single\n\t// event-loop-lag signal so apps get a meaningful score without any\n\t// boilerplate. Once the app registers richer signals it overrides this.\n\tconst signals: Signal[] =\n\t\topts.signals && opts.signals.length > 0\n\t\t\t? [...opts.signals]\n\t\t\t: [eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000, weight: 1 })]\n\n\tconst scoringStrategy = opts.scoringStrategy ?? defaultScoringStrategy()\n\tconst now = opts.now ?? ((): Date => new Date())\n\n\t// ADR-142 — OpenTelemetry emission is on by default. The emitter\n\t// internally graceful-falls-back when @opentelemetry/api is not\n\t// installed (returns noop emit()). Explicit `otel: false` opts out.\n\tconst otelEmitter: OtelEmitter | null = resolveOtelEmitter(opts.otel)\n\n\t// ADR-143 — causality propagation is on by default. Read upstream\n\t// cause from baggage on every evaluate(); writes happen at the\n\t// `withHealth.*` middleware boundary on non-/healthz requests (or\n\t// via the explicit `runWithCause()` API surface).\n\tconst causality: CausalityHandle | null = resolveCausalityHandle(opts.causality)\n\n\t// ADR-144 — audit-grade history recording. Default-on with the\n\t// in-memory ring-buffer store; persistent compliance-grade stores\n\t// plug in via the `HistoryStore` interface.\n\tconst historyRecorder: HistoryRecorder | null = resolveHistoryRecorder(opts.history)\n\n\tlet disposed = false\n\tlet lastScore: HealthScore | null = null\n\n\tconst evaluate = async (): Promise<HealthScore> => {\n\t\tif (disposed) {\n\t\t\tthrow new Error('sylphxHealth: instance disposed')\n\t\t}\n\t\t// ADR-143 §2.2 — read upstream cause BEFORE signal evaluation so\n\t\t// the cause is available to the emitter even if signal reads take\n\t\t// long. Non-throwing.\n\t\tconst upstreamCause: UpstreamCause | null = causality ? causality.readCause() : null\n\n\t\t// Read all signals in parallel — async-getter signals (queueDepth)\n\t\t// shouldn't serialise behind sync ones. `Promise.all` propagates a\n\t\t// thrown signal directly to the caller; signals are expected to\n\t\t// internally swallow errors and return `unknown=true` instead, so\n\t\t// we never hit this path in normal operation.\n\t\tconst readings = await Promise.all(\n\t\t\tsignals.map(async (signal) => ({\n\t\t\t\tsignal,\n\t\t\t\treading: await signal.read(),\n\t\t\t})),\n\t\t)\n\n\t\tconst score = scoringStrategy(readings)\n\n\t\tconst signalsMap: Record<string, number | string | boolean> = {}\n\t\tconst signalFactors: Record<string, number> = {}\n\t\tfor (const { signal, reading } of readings) {\n\t\t\tsignalsMap[signal.name] = reading.value\n\t\t\tsignalFactors[signal.name] =\n\t\t\t\treading.unknown === true ? 1 : clampHealthFactor(reading.healthFactor)\n\t\t}\n\n\t\tconst result: HealthScore = {\n\t\t\tscore,\n\t\t\tsignals: signalsMap,\n\t\t\tsignalFactors,\n\t\t\tlastTickAt: now().toISOString(),\n\t\t}\n\n\t\tlastScore = result\n\n\t\t// ADR-142 / ADR-143 — emit OTel metric / span attributes for the\n\t\t// result, including upstream cause when present. Non-throwing by\n\t\t// contract — emit() failures must not interfere with /healthz.\n\t\tif (otelEmitter) {\n\t\t\totelEmitter.emit(result, upstreamCause)\n\t\t}\n\n\t\t// ADR-144 — record an audit-grade history entry. Fire-and-forget\n\t\t// so a slow / failing store never blocks /healthz; recorder\n\t\t// catches its own errors per cardinal rule.\n\t\tif (historyRecorder) {\n\t\t\tvoid historyRecorder.record(result)\n\t\t}\n\n\t\treturn result\n\t}\n\n\tconst evaluator: HealthEvaluator = { evaluate }\n\n\tconst evaluateEffect = buildEvaluateEffect(evaluate)\n\n\tconst runWithCause = async <T>(fn: () => T | Promise<T>): Promise<T> => {\n\t\tif (!causality || !lastScore) return await fn()\n\t\treturn await causality.runWithCause(fn, lastScore)\n\t}\n\n\tconst getHistory = async (query: HistoryQuery = {}): Promise<readonly HistoryEntry[]> => {\n\t\tif (!historyRecorder) return []\n\t\treturn await historyRecorder.range(query)\n\t}\n\n\tconst verifyHistory = async (query: HistoryQuery = {}): Promise<VerificationResult> => {\n\t\tif (!historyRecorder) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: 0,\n\t\t\t\tfirstBreakAtSequence: null,\n\t\t\t\treason: 'history recording is disabled on this instance',\n\t\t\t}\n\t\t}\n\t\treturn await historyRecorder.verify(query)\n\t}\n\n\treturn {\n\t\tsignals,\n\t\tevaluate,\n\t\tevaluateEffect,\n\t\thandler(): (req?: Request) => Promise<Response> {\n\t\t\treturn createWebHandler(evaluator)\n\t\t},\n\t\tnodeHandler(): ReturnType<typeof createNodeHandler> {\n\t\t\treturn createNodeHandler(evaluator)\n\t\t},\n\t\tserveUnixSocket(unixOpts?: UnixSocketServerOptions): UnixSocketServerHandle {\n\t\t\treturn startUnixSocketServer(evaluator, unixOpts)\n\t\t},\n\t\tgetLastScore(): HealthScore | null {\n\t\t\treturn lastScore\n\t\t},\n\t\trunWithCause,\n\t\tgetHistory,\n\t\tverifyHistory,\n\t\tdispose(): void {\n\t\t\tif (disposed) return\n\t\t\tdisposed = true\n\t\t\tif (otelEmitter) otelEmitter.dispose()\n\t\t\tif (causality) causality.dispose()\n\t\t\tif (historyRecorder) historyRecorder.dispose()\n\t\t\tfor (const s of signals) {\n\t\t\t\ttry {\n\t\t\t\t\ts.dispose?.()\n\t\t\t\t} catch {\n\t\t\t\t\t// dispose must not throw — swallow to guarantee idempotency\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n}\n"],"mappings":";AAkDO,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAG1B,IAAM,0BAA0B;AAGhC,IAAM,mBAAmB;AA6EhC,IAAI;AAOJ,eAAsB,sBAAoD;AACzE,MAAI,cAAc,OAAW,QAAO;AACpC,MAAI;AACH,UAAM,MAAM,MAAM,OAAO,oBAAoB;AAC7C,QAAI,CAAC,KAAK,WAAW,CAAC,KAAK,aAAa;AACvC,kBAAY;AACZ,aAAO;AAAA,IACR;AACA,gBAAY,EAAE,SAAS,IAAI,SAAS,aAAa,IAAI,YAAY;AAAA,EAClE,QAAQ;AACP,gBAAY;AAAA,EACb;AACA,SAAO;AACR;AASA,SAAS,qBAA6B;AACrC,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AAClD,QAAI,QAAQ,IAAI,sBAAsB,OAAW,QAAO,QAAQ,IAAI;AACpE,QAAI,QAAQ,IAAI,iBAAiB,OAAW,QAAO,QAAQ,IAAI;AAAA,EAChE;AACA,SAAO;AACR;AAEA,SAAS,iBAAiB,MAAuB;AAGhD,SAAO,KAAK,SAAS,KAAK,CAAC,SAAS,KAAK,IAAI;AAC9C;AAGA,SAAS,qBACR,OACA,WACA,aACgB;AAChB,QAAM,UAAU,MAAM;AACtB,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,UAAoB,CAAC;AAC3B,aAAW,CAAC,YAAY,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC3D,QAAI,OAAO,WAAW,YAAY,CAAC,OAAO,SAAS,MAAM,EAAG;AAC5D,QAAI,UAAU,UAAW;AACzB,QAAI,CAAC,iBAAiB,UAAU,KAAK,CAAC,iBAAiB,WAAW,EAAG;AACrE,YAAQ,KAAK,GAAG,UAAU,IAAI,WAAW,EAAE;AAAA,EAC5C;AACA,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,QAAQ,KAAK,GAAG;AACxB;AAMA,SAAS,gBAAgB,OAAwC;AAChE,QAAM,MAAkD,CAAC;AACzD,aAAW,OAAO,MAAM,MAAM,GAAG,GAAG;AACnC,UAAM,QAAQ,IAAI,KAAK;AACvB,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,QAAQ,MAAM,QAAQ,GAAG;AAC/B,QAAI,SAAS,KAAK,UAAU,MAAM,SAAS,EAAG;AAC9C,UAAM,SAAS,MAAM,MAAM,GAAG,KAAK;AACnC,UAAM,UAAU,MAAM,MAAM,QAAQ,CAAC;AACrC,QAAI,CAAC,iBAAiB,MAAM,KAAK,CAAC,iBAAiB,OAAO,EAAG;AAC7D,QAAI,KAAK,EAAE,QAAQ,QAAQ,CAAC;AAAA,EAC7B;AACA,SAAO;AACR;AAEA,SAAS,gBAAgB,OAAsC;AAC9D,SAAO,MACL,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,iBAAiB,CAAC,CAAC;AACpD;AAEA,SAAS,YACR,MACA,aACA,QACwB;AACxB,MAAI,CAAC,iBAAiB,WAAW,EAAG,QAAO;AAE3C,MAAI,KAAK,KAAK,SAAS,CAAC,MAAM,YAAa,QAAO;AAClD,QAAM,OAAO,CAAC,GAAG,MAAM,WAAW;AAClC,MAAI,KAAK,UAAU,OAAQ,QAAO;AAClC,SAAO,KAAK,MAAM,KAAK,SAAS,MAAM;AACvC;AAIA,IAAM,sBAAN,MAAqD;AAAA,EAC5C,SAAmC;AAAA,EACnC,SAA8B;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,MAAwB;AACnC,UAAM,YAAY,KAAK,aAAa;AACpC,QAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,KAAK,YAAY,GAAG;AACnE,YAAM,IAAI,MAAM,2DAA2D,SAAS,EAAE;AAAA,IACvF;AACA,SAAK,YAAY;AACjB,SAAK,cAAc,KAAK,eAAe,mBAAmB;AAC1D,SAAK,iBAAiB,KAAK,mBAAmB;AAC9C,SAAK,KAAK,WAAW;AAAA,EACtB;AAAA,EAEA,IAAI,QAAkC;AACrC,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAc,aAA4B;AACzC,UAAM,MAAM,MAAM,oBAAoB;AACtC,QAAI,KAAK,WAAW,WAAY;AAChC,QAAI,QAAQ,MAAM;AACjB,WAAK,SAAS;AACd;AAAA,IACD;AACA,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,EACf;AAAA,EAEA,YAAkC;AACjC,QAAI,KAAK,WAAW,WAAW,CAAC,KAAK,OAAQ,QAAO;AACpD,QAAI;AACH,YAAM,UAAU,KAAK,OAAO,YAAY,iBAAiB;AACzD,UAAI,CAAC,QAAS,QAAO;AACrB,YAAM,aAAa,QAAQ,SAAS,iBAAiB;AACrD,UAAI,CAAC,cAAc,OAAO,WAAW,UAAU,SAAU,QAAO;AAChE,YAAM,SAAS,gBAAgB,WAAW,KAAK;AAC/C,UAAI,OAAO,WAAW,EAAG,QAAO;AAChC,YAAM,aAAa,QAAQ,SAAS,iBAAiB;AACrD,YAAM,QACL,cAAc,OAAO,WAAW,UAAU,WAAW,gBAAgB,WAAW,KAAK,IAAI,CAAC;AAC3F,aAAO,EAAE,QAAQ,MAAM;AAAA,IACxB,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,YAAY,OAAmC;AAC9C,QAAI;AACH,aAAO,qBAAqB,OAAO,KAAK,WAAW,KAAK,WAAW;AAAA,IACpE,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAM,aAAgB,IAA0B,OAAgC;AAC/E,QAAI,KAAK,WAAW,WAAW,CAAC,KAAK,QAAQ;AAC5C,aAAO,MAAM,GAAG;AAAA,IACjB;AACA,QAAI;AACH,YAAM,aAAa,KAAK,YAAY,KAAK;AACzC,YAAM,WAAW,KAAK,OAAO,YAAY,iBAAiB;AAC1D,YAAM,gBAAgB,UAAU,SAAS,iBAAiB,GAAG,QAC1D,gBAAgB,SAAS,SAAS,iBAAiB,EAAG,KAAK,IAC3D,CAAC;AAEJ,UAAI,CAAC,cAAc,CAAC,UAAU;AAC7B,eAAO,MAAM,GAAG;AAAA,MACjB;AAEA,YAAM,cAAc,YAAY,KAAK,OAAO,YAAY,cAAc;AACtE,UAAI,cAAuB;AAE3B,UAAI,YAAY;AACf,cAAM,WAAyB,EAAE,OAAO,WAAW;AACnD,cAAM,WAAW,UAAU,SAAS,iBAAiB,GAAG;AACxD,cAAM,cAAc,WAAW,iBAAiB,UAAU,UAAU,IAAI;AACxE,sBAAc,YAAY,SAAS,mBAAmB;AAAA,UACrD,GAAG;AAAA,UACH,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAEA,UAAI,KAAK,mBAAmB,cAAc,cAAc,SAAS,IAAI;AACpE,cAAM,YAAY,aACf,YAAY,eAAe,KAAK,aAAa,gBAAgB,IAC7D;AACH,YAAI,UAAU,SAAS,GAAG;AACzB,wBAAc,YAAY,SAAS,mBAAmB;AAAA,YACrD,OAAO,UAAU,KAAK,GAAG;AAAA,UAC1B,CAAC;AAAA,QACF;AAAA,MACD;AAEA,YAAM,iBAA0B,KAAK,OAAO,YAAY;AAAA,QACvD,KAAK,OAAO,QAAQ,OAAO;AAAA,QAC3B;AAAA,MACD;AACA,aAAO,MAAM,KAAK,OAAO,QAAQ,KAAK,gBAAgB,EAAE;AAAA,IACzD,QAAQ;AAEP,aAAO,MAAM,GAAG;AAAA,IACjB;AAAA,EACD;AAAA,EAEA,UAAgB;AACf,SAAK,SAAS;AACd,SAAK,SAAS;AAAA,EACf;AACD;AAOA,SAAS,iBAAiB,UAAkB,OAAuB;AAClE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,CAAC,GAAG,SAAS,MAAM,GAAG,GAAG,GAAG,MAAM,MAAM,GAAG,CAAC,GAAG;AAClE,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,QAAQ,WAAW,KAAK,KAAK,IAAI,OAAO,EAAG;AAC/C,SAAK,IAAI,OAAO;AAChB,QAAI,KAAK,OAAO;AAAA,EACjB;AACA,SAAO,IAAI,KAAK,GAAG;AACpB;AAQO,SAAS,sBAAsB,OAAyB,CAAC,GAAoB;AACnF,SAAO,IAAI,oBAAoB,IAAI;AACpC;;;AC3WA,SAAS,cAAc;;;ACmGhB,IAAM,cAAN,cAA0B,MAAM;AAAA,EAC7B,OAAO;AAAA,EACP;AAAA,EACT,YAAY,SAAiB,OAAiB;AAC7C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACd;AACD;;;ADhFO,SAAS,eACf,WACiD;AACjD,SAAO,OAAO,WAAW;AAAA,IACxB,KAAK,MAAM,UAAU;AAAA,IACrB,OAAO,CAAC,QAAQ,IAAI,YAAiB,4BAA4B,GAAG;AAAA,EACrE,CAAC;AACF;;;AEjCA,IAAM,8BAA8B;AAEpC,SAAS,eAAe,OAA0B;AACjD,SAAO,OAAO,SAAS,KAAK,KAAK,SAAS,8BAA8B,MAAM;AAC/E;AAkBO,SAAS,iBAAiB,QAA+D;AAC/F,SAAO,OAAO,SAAsC;AACnD,QAAI;AACH,YAAM,QAAQ,MAAM,OAAO,SAAS;AACpC,aAAO,IAAI,SAAS,KAAK,UAAU,KAAK,GAAG;AAAA,QAC1C,QAAQ,eAAe,MAAM,KAAK;AAAA,QAClC,SAAS;AAAA,UACR,gBAAgB;AAAA;AAAA;AAAA,UAGhB,iBAAiB;AAAA,QAClB;AAAA,MACD,CAAC;AAAA,IACF,SAAS,KAAK;AACb,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAO,IAAI;AAAA,QACV,KAAK,UAAU;AAAA,UACd,OAAO;AAAA,UACP;AAAA,QACD,CAAC;AAAA,QACD;AAAA,UACC,QAAQ;AAAA,UACR,SAAS;AAAA,YACR,gBAAgB;AAAA,YAChB,iBAAiB;AAAA,UAClB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;AAkBO,SAAS,kBACf,QAC0D;AAC1D,SAAO,OAAO,MAAM,QAAQ;AAC3B,QAAI;AACH,YAAM,QAAQ,MAAM,OAAO,SAAS;AACpC,UAAI,aAAa,eAAe,MAAM,KAAK;AAC3C,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,UAAU,iBAAiB,qCAAqC;AACpE,UAAI,IAAI,KAAK,UAAU,KAAK,CAAC;AAAA,IAC9B,SAAS,KAAK;AACb,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAI,aAAa;AACjB,UAAI,UAAU,gBAAgB,kBAAkB;AAChD,UAAI,UAAU,iBAAiB,qCAAqC;AACpE,UAAI,IAAI,KAAK,UAAU,EAAE,OAAO,2BAA2B,QAAQ,CAAC,CAAC;AAAA,IACtE;AAAA,EACD;AACD;;;ACMO,IAAM,oBAAoB,IAAI,OAAO,EAAE;AAGvC,IAAM,2BAA2B;AAExC,IAAM,oBAAoB,IAAI,YAAY;AAC1C,IAAM,WAAW,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAS/E,SAAS,oBAAoB,SAA4D;AAC/F,QAAM,OAAO,OAAO,KAAK,OAAO,EAAE,KAAK;AACvC,QAAM,QAAoD,KAAK,IAAI,CAAC,MAAM;AAIzE,UAAM,QAAQ,QAAQ,CAAC;AACvB,WAAO,CAAC,GAAG,KAAkC;AAAA,EAC9C,CAAC;AACD,SAAO,KAAK,UAAU,OAAO,YAAY,KAAK,CAAC;AAChD;AASA,SAAS,YAAY,OAAuB;AAC3C,SAAO,MAAM,QAAQ,CAAC;AACvB;AASA,eAAe,UAAU,OAAgC;AACxD,QAAM,SAAU,WAAsD,QAAQ;AAC9E,MAAI,CAAC,UAAU,OAAO,OAAO,WAAW,YAAY;AACnD,WAAO;AAAA,EACR;AACA,QAAM,MAAM,kBAAkB,OAAO,KAAK;AAC1C,QAAM,SAAS,MAAM,OAAO,OAAO,WAAW,GAAG;AACjD,QAAM,QAAQ,IAAI,WAAW,MAAM;AACnC,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,WAAO,SAAS,MAAM,CAAC,CAAW;AAAA,EACnC;AACA,SAAO;AACR;AAOA,eAAe,iBAAiB,SAA2D;AAC1F,QAAM,YAAY;AAAA,IACjB,QAAQ,SAAS,SAAS,EAAE;AAAA,IAC5B,QAAQ;AAAA,IACR,YAAY,QAAQ,KAAK;AAAA,IACzB,QAAQ;AAAA,IACR,QAAQ;AAAA,EACT,EAAE,KAAK,GAAG;AACV,SAAO,MAAM,UAAU,SAAS;AACjC;AAIA,IAAM,2BAAN,MAAuD;AAAA,EAC7C;AAAA,EACD,MAAsB,CAAC;AAAA,EAE/B,YAAY,OAAoC,CAAC,GAAG;AACnD,UAAM,MAAM,KAAK,YAAY;AAC7B,QAAI,CAAC,OAAO,SAAS,GAAG,KAAK,OAAO,KAAK,MAAM,KAAY;AAC1D,YAAM,IAAI,MAAM,gEAAgE,GAAG,EAAE;AAAA,IACtF;AACA,SAAK,WAAW;AAAA,EACjB;AAAA,EAEA,OAAO,OAA2B;AACjC,SAAK,IAAI,KAAK,KAAK;AACnB,QAAI,KAAK,IAAI,SAAS,KAAK,UAAU;AACpC,WAAK,IAAI,MAAM;AAAA,IAChB;AAAA,EACD;AAAA,EAEA,MAAM,OAA8C;AACnD,UAAM,MAAsB,CAAC;AAC7B,eAAW,KAAK,KAAK,KAAK;AACzB,UAAI,MAAM,iBAAiB,UAAa,EAAE,WAAW,MAAM,aAAc;AACzE,UAAI,MAAM,eAAe,UAAa,EAAE,WAAW,MAAM,WAAY;AACrE,UAAI,MAAM,kBAAkB,UAAa,EAAE,cAAc,MAAM,cAAe;AAC9E,UAAI,MAAM,gBAAgB,UAAa,EAAE,cAAc,MAAM,YAAa;AAC1E,UAAI,KAAK,CAAC;AACV,UAAI,MAAM,UAAU,UAAa,IAAI,UAAU,MAAM,MAAO;AAAA,IAC7D;AACA,WAAO;AAAA,EACR;AAAA,EAEA,SAA8B;AAC7B,WAAO,KAAK,IAAI,KAAK,IAAI,SAAS,CAAC,KAAK;AAAA,EACzC;AACD;AAGO,SAAS,2BAA2B,OAAoC,CAAC,GAAiB;AAChG,SAAO,IAAI,yBAAyB,IAAI;AACzC;AA6BA,IAAM,sBAAN,MAAqD;AAAA,EAC3C;AAAA,EACD,eAAe;AAAA,EACf,WAAmB;AAAA,EACnB,WAAW;AAAA,EACX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAA6B,QAAQ,QAAQ;AAAA,EAErD,YAAY,MAA8B;AACzC,SAAK,QAAQ,KAAK,SAAS,2BAA2B,EAAE,UAAU,KAAK,SAAS,CAAC;AAKjF,SAAK,cAAc,KAAK,UAAU;AAAA,EACnC;AAAA,EAEA,MAAc,YAA2B;AACxC,QAAI;AACH,YAAM,SAAS,MAAM,KAAK,MAAM,OAAO;AACvC,UAAI,UAAU,OAAO,SAAS,OAAO,QAAQ,GAAG;AAC/C,aAAK,eAAe,OAAO,WAAW;AACtC,aAAK,WAAW,OAAO,aAAa;AAAA,MACrC;AAAA,IACD,QAAQ;AAAA,IAER;AAAA,EACD;AAAA,EAEA,MAAc,eAAe,OAAkD;AAC9E,QAAI,KAAK,SAAU,QAAO;AAC1B,UAAM,KAAK;AACX,QAAI;AACH,YAAM,gBAAgB,MAAM,UAAU,oBAAoB,MAAM,OAAO,CAAC;AACxE,YAAM,UAA2C;AAAA,QAChD,UAAU,KAAK;AAAA,QACf,aAAa,MAAM;AAAA,QACnB,OAAO,MAAM;AAAA,QACb;AAAA,QACA,UAAU,KAAK;AAAA,MAChB;AACA,YAAM,YAAY,MAAM,iBAAiB,OAAO;AAChD,YAAM,QAAsB,EAAE,GAAG,SAAS,UAAU;AACpD,YAAM,KAAK,MAAM,OAAO,KAAK;AAC7B,WAAK,gBAAgB;AACrB,WAAK,WAAW,aAAa;AAC7B,aAAO;AAAA,IACR,QAAQ;AAEP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAM,OAAO,OAAkD;AAI9D,UAAM,MAAM,KAAK,SAAS,KAAK,MAAM,KAAK,eAAe,KAAK,CAAC;AAI/D,SAAK,WAAW,IAAI,MAAM,MAAM,MAAS;AACzC,WAAO,MAAM;AAAA,EACd;AAAA,EAEA,MAAc,sBAAqC;AAClD,QAAI;AACH,YAAM,KAAK;AAAA,IACZ,QAAQ;AAAA,IAGR;AAAA,EACD;AAAA,EAEA,MAAM,MAAM,OAAuD;AAClE,UAAM,KAAK,oBAAoB;AAC/B,QAAI;AACH,aAAO,MAAM,KAAK,MAAM,MAAM,KAAK;AAAA,IACpC,QAAQ;AACP,aAAO,CAAC;AAAA,IACT;AAAA,EACD;AAAA,EAEA,MAAM,SAAuC;AAC5C,UAAM,KAAK,oBAAoB;AAC/B,QAAI;AACH,aAAO,MAAM,KAAK,MAAM,OAAO;AAAA,IAChC,QAAQ;AACP,aAAO;AAAA,IACR;AAAA,EACD;AAAA,EAEA,MAAM,OAAO,OAAkD;AAC9D,UAAM,UAAU,MAAM,KAAK,MAAM,KAAK;AACtC,WAAO,MAAM,YAAY,OAAO;AAAA,EACjC;AAAA,EAEA,UAAgB;AACf,SAAK,WAAW;AAAA,EACjB;AACD;AAGO,SAAS,sBAAsB,OAA+B,CAAC,GAAoB;AACzF,SAAO,IAAI,oBAAoB,IAAI;AACpC;AAiBA,eAAsB,YACrB,SAC8B;AAC9B,MAAI,QAAQ,WAAW,GAAG;AACzB,WAAO,EAAE,UAAU,MAAM,gBAAgB,GAAG,sBAAsB,MAAM,QAAQ,KAAK;AAAA,EACtF;AACA,MAAI,eAA8B;AAClC,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACxC,UAAM,QAAQ,QAAQ,CAAC;AAMvB,QAAI,MAAM,KAAK,MAAM,aAAa,KAAK,MAAM,aAAa,mBAAmB;AAC5E,aAAO;AAAA,QACN,UAAU;AAAA,QACV,gBAAgB;AAAA,QAChB,sBAAsB,MAAM;AAAA,QAC5B,QAAQ,qCAAqC,iBAAiB,UAAU,MAAM,QAAQ;AAAA,MACvF;AAAA,IACD;AAGA,QAAI,IAAI,KAAK,iBAAiB,QAAQ,MAAM,aAAa,cAAc;AACtE,aAAO;AAAA,QACN,UAAU;AAAA,QACV,gBAAgB;AAAA,QAChB,sBAAsB,MAAM;AAAA,QAC5B,QAAQ,iCAAiC,MAAM,QAAQ,cAAc,YAAY,SAAS,MAAM,QAAQ;AAAA,MACzG;AAAA,IACD;AAEA,UAAM,iBAAiB,MAAM,iBAAiB;AAAA,MAC7C,UAAU,MAAM;AAAA,MAChB,aAAa,MAAM;AAAA,MACnB,OAAO,MAAM;AAAA,MACb,eAAe,MAAM;AAAA,MACrB,UAAU,MAAM;AAAA,IACjB,CAAC;AACD,QAAI,MAAM,aAAa,kBAAkB,mBAAmB,MAAM,WAAW;AAC5E,aAAO;AAAA,QACN,UAAU;AAAA,QACV,gBAAgB;AAAA,QAChB,sBAAsB,MAAM;AAAA,QAC5B,QAAQ,kCAAkC,MAAM,QAAQ,YAAY,MAAM,SAAS,eAAe,cAAc;AAAA,MACjH;AAAA,IACD;AACA,mBAAe,MAAM,aAAa;AAClC,eAAW;AAAA,EACZ;AACA,SAAO;AAAA,IACN,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,sBAAsB;AAAA,IACtB,QAAQ;AAAA,EACT;AACD;;;ACnVA,IAAM,WAAW;AACjB,IAAM,cAAc;AAGb,IAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,cAAc;AAAA,EACd,aAAa;AAAA,EACb,kBAAkB;AAAA;AAAA,EAElB,iBAAiB;AAClB;AAGO,IAAM,kBAAkB;AAAA,EAC9B,OAAO;AAAA,EACP,cAAc,CAAC,SAAiB,yBAAyB,IAAI;AAAA;AAAA,EAE7D,WAAW;AAAA;AAAA,EAEX,aAAa;AAAA;AAAA,EAEb,YAAY;AACb;AAGO,IAAM,cAAc;AAAA,EAC1B,WAAW;AACZ;AAcO,SAAS,kBAAkB,OAA2B,CAAC,GAAgB;AAC7E,SAAO,IAAI,gBAAgB,IAAI;AAChC;AAYA,IAAM,kBAAN,MAA6C;AAAA,EAO5C,YAA6B,MAA0B;AAA1B;AAC5B,SAAK,cAAc,KAAK,cAAc,CAAC;AACvC,SAAK,SAAS,KAAK,WAAW;AAC9B,QAAI,KAAK,OAAO;AACf,WAAK,cAAc,iBAAiB,KAAK,KAAK;AAQ9C,WAAK,SAAS;AACd;AAAA,IACD;AACA,SAAK,KAAK,WAAW;AAAA,EACtB;AAAA,EAtBQ,SAA2D;AAAA,EAC3D,cAAwC;AAAA,EACxC,WAA4B;AAAA,EACnB;AAAA,EACA;AAAA,EAoBjB,IAAI,QAA8B;AACjC,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAc,aAA4B;AACzC,UAAM,MAAM,MAAM,eAAe;AACjC,QAAI,QAAQ,MAAM;AACjB,WAAK,SAAS;AACd;AAAA,IACD;AAGA,QAAI,KAAK,WAAW,WAAY;AAChC,QAAI;AACH,YAAM,QAAQ,IAAI,QAAQ,SAAS,UAAU,WAAW;AACxD,WAAK,cAAc,iBAAiB,KAAK;AACzC,WAAK,WAAW,IAAI;AACpB,WAAK,SAAS;AAAA,IACf,QAAQ;AAKP,WAAK,SAAS;AAAA,IACf;AAAA,EACD;AAAA,EAEA,KAAK,OAAoB,eAA4C;AACpE,QAAI,KAAK,WAAW,WAAW,CAAC,KAAK,YAAa;AAClD,UAAM,YAAwB,EAAE,GAAG,KAAK,YAAY;AACpD,QAAI;AAEH,WAAK,YAAY,WAAW,OAAO,MAAM,OAAO,SAAS;AAGzD,iBAAW,CAAC,YAAY,QAAQ,KAAK,OAAO,QAAQ,MAAM,OAAO,GAAG;AACnE,cAAM,cAA0B,EAAE,GAAG,WAAW,eAAe,WAAW;AAC1E,cAAM,SAAS,MAAM,gBAAgB,UAAU;AAC/C,YAAI,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM,GAAG;AAC1D,eAAK,YAAY,YAAY,OAAO,QAAQ,WAAW;AAAA,QACxD;AAEA,YAAI,OAAO,aAAa,YAAY,OAAO,SAAS,QAAQ,GAAG;AAC9D,eAAK,YAAY,WAAW,OAAO,UAAU,WAAW;AAAA,QACzD,OAAO;AACN,gBAAM,aAAyB;AAAA,YAC9B,GAAG;AAAA,YACH,OAAO,OAAO,QAAQ;AAAA,UACvB;AACA,eAAK,YAAY,kBAAkB,IAAI,GAAG,UAAU;AAAA,QACrD;AAAA,MACD;AAGA,UAAI,iBAAiB,cAAc,OAAO,SAAS,GAAG;AACrD,mBAAW,SAAS,cAAc,QAAQ;AACzC,eAAK,YAAY,uBAAuB,IAAI,GAAG;AAAA,YAC9C,GAAG;AAAA,YACH,YAAY,MAAM;AAAA,YAClB,cAAc,MAAM;AAAA,UACrB,CAAC;AAAA,QACF;AAAA,MACD;AAGA,UAAI,KAAK,UAAU;AAClB,cAAM,OAAO,KAAK,SAAS,gBAAgB;AAC3C,0BAAkB,MAAM,OAAO,KAAK,QAAQ,aAAa;AAAA,MAC1D;AAAA,IACD,QAAQ;AAAA,IAGR;AAAA,EACD;AAAA,EAEA,UAAgB;AACf,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,WAAW;AAAA,EACjB;AACD;AAIA,IAAIA;AASJ,eAAsB,iBAAsE;AAC3F,MAAIA,eAAc,OAAW,QAAOA;AACpC,MAAI;AACH,IAAAA,aAAY,MAAM,OAAO,oBAAoB;AAAA,EAC9C,QAAQ;AACP,IAAAA,aAAY;AAAA,EACb;AACA,SAAOA;AACR;AAUA,SAAS,iBAAiB,OAAiC;AAC1D,SAAO;AAAA,IACN,YAAY,MAAM,YAAY,aAAa,OAAO;AAAA,MACjD,aAAa;AAAA,MACb,MAAM;AAAA,IACP,CAAC;AAAA,IACD,aAAa,MAAM,YAAY,aAAa,cAAc;AAAA,MACzD,aACC;AAAA,MACD,MAAM;AAAA,IACP,CAAC;AAAA,IACD,YAAY,MAAM,YAAY,aAAa,aAAa;AAAA,MACvD,aACC;AAAA,IACF,CAAC;AAAA,IACD,mBAAmB,MAAM,cAAc,aAAa,kBAAkB;AAAA,MACrE,aACC;AAAA,IACF,CAAC;AAAA,IACD,wBAAwB,MAAM,cAAc,aAAa,iBAAiB;AAAA,MACzE,aACC;AAAA,IACF,CAAC;AAAA,EACF;AACD;AAYA,SAAS,kBACR,MACA,OACA,QACA,eACO;AACP,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,QAAM,eAAgB,KAA4D;AAClF,MAAI,OAAO,iBAAiB,YAAY;AACvC,iBAAa,KAAK,MAAM,gBAAgB,OAAO,MAAM,KAAK;AAC1D,eAAW,CAAC,YAAY,MAAM,KAAK,OAAO,QAAQ,MAAM,iBAAiB,CAAC,CAAC,GAAG;AAC7E,UAAI,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM,GAAG;AAC1D,qBAAa,KAAK,MAAM,gBAAgB,aAAa,UAAU,GAAG,MAAM;AAAA,MACzE;AAAA,IACD;AAKA,QAAI,iBAAiB,cAAc,OAAO,SAAS,GAAG;AACrD,YAAM,UAAU,cAAc,OAAO,CAAC;AACtC,UAAI,SAAS;AACZ,qBAAa,KAAK,MAAM,gBAAgB,WAAW,QAAQ,OAAO;AAClE,qBAAa,KAAK,MAAM,gBAAgB,aAAa,QAAQ,MAAM;AAAA,MACpE;AACA,UAAI,cAAc,MAAM,SAAS,GAAG;AACnC,qBAAa,KAAK,MAAM,gBAAgB,YAAY,cAAc,MAAM,KAAK,GAAG,CAAC;AAAA,MAClF;AAAA,IACD;AAAA,EACD;AACA,MAAI,QAAQ;AACX,UAAM,WAAY,KAAmE;AACrF,QAAI,OAAO,aAAa,YAAY;AACnC,eAAS,KAAK,MAAM,YAAY,WAAW;AAAA,QAC1C,CAAC,gBAAgB,KAAK,GAAG,MAAM;AAAA,QAC/B,6BAA6B,MAAM;AAAA,MACpC,CAAC;AAAA,IACF;AAAA,EACD;AACD;;;ACjVA,SAAS,QAAQ,GAAmB;AACnC,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO;AAChC,MAAI,IAAI,EAAG,QAAO;AAClB,MAAI,IAAI,EAAG,QAAO;AAClB,SAAO;AACR;AAYO,IAAM,kBAAmC,CAAC,aAAa;AAC7D,MAAI,SAAS,WAAW,EAAG,QAAO;AAGlC,QAAM,SAAoD,CAAC;AAC3D,aAAW,EAAE,QAAQ,QAAQ,KAAK,UAAU;AAC3C,UAAM,IAAI,OAAO;AACjB,QAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG;AACnC,QAAI,QAAQ,YAAY,KAAM;AAC9B,WAAO,KAAK;AAAA,MACX,QAAQ,QAAQ,QAAQ,YAAY;AAAA,MACpC,QAAQ;AAAA,IACT,CAAC;AAAA,EACF;AACA,MAAI,OAAO,WAAW,EAAG,QAAO;AAGhC,QAAM,cAAc,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AAC/D,MAAI,eAAe,KAAK,CAAC,OAAO,SAAS,WAAW,EAAG,QAAO;AAM9D,MAAI,SAAS;AACb,aAAW,EAAE,QAAQ,OAAO,KAAK,QAAQ;AACxC,UAAM,mBAAmB,SAAS;AAClC,QAAI,UAAU,GAAG;AAGhB,aAAO;AAAA,IACR;AACA,cAAU,mBAAmB,KAAK,IAAI,MAAM;AAAA,EAC7C;AACA,QAAM,QAAQ,KAAK,IAAI,MAAM;AAC7B,SAAO,QAAQ,KAAK;AACrB;AASO,SAAS,yBAA0C;AACzD,SAAO;AACR;;;ACnEA,SAAS,6BAA6B;AA2B/B,SAAS,mBAAmB,OAA4B,CAAC,GAAe;AAC9E,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,SAAS,KAAK,UAAU;AAE9B,MAAI,CAAC,OAAO,SAAS,UAAU,KAAK,aAAa,GAAG;AACnD,UAAM,IAAI,MAAM,oDAAoD,UAAU,EAAE;AAAA,EACjF;AACA,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,YAAY;AACrD,UAAM,IAAI;AAAA,MACT,oDAAoD,UAAU,UAAU,MAAM;AAAA,IAC/E;AAAA,EACD;AACA,MAAI,CAAC,OAAO,SAAS,YAAY,KAAK,eAAe,GAAG;AACvD,UAAM,IAAI,MAAM,sDAAsD,YAAY,EAAE;AAAA,EACrF;AAEA,QAAM,UAAU,KAAK,WAAW,sBAAsB,EAAE,YAAY,aAAa,CAAC;AAClF,UAAQ,OAAO;AAEf,SAAO;AAAA,IACN,MAAM;AAAA,IACN;AAAA,IACA,OAAsB;AAErB,YAAM,QAAQ,QAAQ;AAKtB,UAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,KAAK,SAAS,OAAO,kBAAkB;AAC9E,gBAAQ,MAAM;AACd,eAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,KAAK;AAAA,MACnD;AACA,YAAM,aAAa,QAAQ;AAC3B,cAAQ,MAAM;AAEd,UAAI;AACJ,UAAI,cAAc,WAAY,UAAS;AAAA,eAC9B,cAAc,OAAQ,UAAS;AAAA,WACnC;AAEJ,cAAM,OAAO,SAAS;AACtB,iBAAS,KAAK,aAAa,cAAc;AAAA,MAC1C;AAEA,aAAO;AAAA,QACN,OAAO,KAAK,MAAM,UAAU;AAAA,QAC5B,cAAc;AAAA,MACf;AAAA,IACD;AAAA,IACA,UAAgB;AACf,cAAQ,QAAQ;AAAA,IACjB;AAAA,EACD;AACD;;;ACjGA,SAAS,kBAAkB;AAyCpB,SAAS,sBACf,QACA,OAAgC,CAAC,GACR;AAIzB,QAAM,MACL,KAAK,eAAe,SACjB,KAAK,aACJ,WAA2E;AAChF,MAAI,QAAQ,QAAQ,QAAQ,UAAa,OAAO,IAAI,UAAU,YAAY;AACzE,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAEA,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,mBAAmB,KAAK,oBAAoB;AAGlD,MAAI;AACH,eAAW,IAAI;AAAA,EAChB,QAAQ;AAAA,EAGR;AAEA,QAAM,UAAU,iBAAiB,MAAM;AACvC,QAAM,SAAS,IAAI,MAAM;AAAA,IACxB,MAAM;AAAA,IACN,OAAO,OAAO,QAAoC,QAAQ,GAAG;AAAA,EAC9D,CAAC;AAED,QAAM,SAAiC;AAAA,IACtC;AAAA,IACA;AAAA,IACA,MAAM,WAA0B;AAC/B,UAAI;AACH,eAAO,KAAK,IAAI;AAAA,MACjB,QAAQ;AAAA,MAER;AACA,UAAI,kBAAkB;AACrB,YAAI;AACH,qBAAW,IAAI;AAAA,QAChB,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;;;ACRA,IAAM,eAAe;AACrB,IAAM,sBAAsB;AAC5B,IAAM,sBAAsB,oBAAI,QAA8C;AAE9E,SAAS,gBAAgB,MAAuC;AAC/D,MAAI,KAAK,SAAU,QAAO,KAAK;AAC/B,QAAM,EAAE,MAAM,OAAO,UAAU,WAAW,YAAY,aAAa,GAAG,WAAW,IAAI;AACrF,SAAO,aAAa,UAAU;AAC/B;AAEA,SAAS,cAAc,OAAmC;AACzD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,WAAW,GAAG,IAAI,QAAQ,IAAI,KAAK;AACjD;AAEA,SAAS,mBAAuC;AAC/C,QAAM,OAAQ,WACZ;AACF,QAAM,QAAQ,MAAM,KAAK;AACzB,SAAO,SAAS,MAAM,KAAK,EAAE,SAAS,IAAI,MAAM,KAAK,IAAI;AAC1D;AAEA,SAAS,sBAAsB,WAA+B,SAAqC;AAClG,MAAI,cAAc,OAAW,QAAO;AACpC,MAAI,YAAY,OAAW,QAAO;AAClC,SAAO;AACR;AAEA,SAAS,wBACR,OACyE;AACzE,MAAI,UAAU,MAAO,QAAO;AAC5B,QAAM,UAAU,iBAAiB;AACjC,MAAI,UAAU,QAAW;AACxB,WAAO,UAAU,EAAE,MAAM,SAAS,UAAU,MAAM,IAAI;AAAA,EACvD;AACA,MAAI,UAAU,MAAM;AACnB,WAAO,EAAE,MAAM,WAAW,qBAAqB,UAAU,MAAM;AAAA,EAChE;AACA,SAAO;AAAA,IACN,MAAM,sBAAsB,MAAM,MAAM,OAAO;AAAA,IAC/C,UAAU,MAAM,YAAY;AAAA,IAC5B,GAAI,MAAM,qBAAqB,SAAY,EAAE,kBAAkB,MAAM,iBAAiB,IAAI,CAAC;AAAA,EAC5F;AACD;AAEA,SAAS,0BAA0B,QAAsB,MAAqC;AAC7F,QAAM,SAAS,wBAAwB,KAAK,UAAU;AACtD,MAAI,CAAC,OAAQ,QAAO,MAAM;AAC1B,QAAM,WAAW,oBAAoB,IAAI,MAAM;AAC/C,MAAI,SAAU,QAAO,MAAM;AAC3B,MAAI;AACH,UAAM,SAAS,OAAO,gBAAgB;AAAA,MACrC,MAAM,OAAO;AAAA,MACb,GAAI,OAAO,qBAAqB,SAC7B,EAAE,kBAAkB,OAAO,iBAAiB,IAC5C,CAAC;AAAA,IACL,CAAC;AACD,wBAAoB,IAAI,QAAQ,MAAM;AACtC,WAAO,MAAM;AACZ,UAAI,oBAAoB,IAAI,MAAM,MAAM,QAAQ;AAC/C,4BAAoB,OAAO,MAAM;AAAA,MAClC;AACA,WAAK,OAAO,SAAS;AAAA,IACtB;AAAA,EACD,SAAS,KAAK;AACb,QAAI,OAAO,SAAU,OAAM;AAC3B,WAAO,MAAM;AAAA,EACd;AACD;AAkCA,SAAS,WAAW,OAA0B,CAAC,GAAyB;AACvE,QAAM,OAAO,cAAc,KAAK,IAAI;AACpC,QAAM,SAAS,gBAAgB,IAAI;AACnC,QAAM,gBAAgB,0BAA0B,QAAQ,IAAI;AAC5D,QAAM,UAAU,OAAO,QAAQ;AAC/B,QAAM,cAAc,OAAO,GAAG,SAAS;AACtC,QAAI,EAAE,IAAI,SAAS,KAAM,QAAO,QAAQ;AAMxC,UAAM,OAAO,aAAa,MAAM,KAAK,CAAC;AACtC,WAAO;AAAA,EACR;AACA,SAAO,eAAe,YAAY,UAAU,EAAE,OAAO,QAAQ,YAAY,KAAK,CAAC;AAC/E,SAAO,eAAe,YAAY,WAAW;AAAA,IAC5C,OAAO,MAAM;AACZ,oBAAc;AACd,aAAO,QAAQ;AAAA,IAChB;AAAA,IACA,YAAY;AAAA,EACb,CAAC;AACD,SAAO;AACR;AAwBA,SAAS,OAAO,KAAiC;AAChD,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,IAAI,IAAI,QAAQ,GAAG;AACzB,SAAO,MAAM,KAAK,MAAM,IAAI,MAAM,GAAG,CAAC;AACvC;AAEA,SAAS,cAAc,OAA0B,CAAC,GAA4B;AAC7E,QAAM,OAAO,cAAc,KAAK,IAAI;AACpC,QAAM,SAAS,gBAAgB,IAAI;AACnC,QAAM,gBAAgB,0BAA0B,QAAQ,IAAI;AAC5D,QAAM,UAAU,OAAO,YAAY;AACnC,QAAM,cAAc,CAAC,KAAK,KAAK,SAAS;AACvC,QAAI,OAAO,IAAI,GAAG,MAAM,MAAM;AAC7B,WAAK,QAAQ,KAAK,GAAG;AACrB;AAAA,IACD;AAMA,SAAK,OAAO,aAAa,YAAY,KAAK,CAAC;AAAA,EAC5C;AACA,SAAO,eAAe,YAAY,UAAU,EAAE,OAAO,QAAQ,YAAY,KAAK,CAAC;AAC/E,SAAO,eAAe,YAAY,WAAW;AAAA,IAC5C,OAAO,MAAM;AACZ,oBAAc;AACd,aAAO,QAAQ;AAAA,IAChB;AAAA,IACA,YAAY;AAAA,EACb,CAAC;AACD,SAAO;AACR;AA4BA,SAAS,cAAc,OAA0B,CAAC,GAAwB;AACzE,QAAM,OAAO,cAAc,KAAK,IAAI;AACpC,QAAM,SAAS,gBAAgB,IAAI;AACnC,QAAM,gBAAgB,0BAA0B,QAAQ,IAAI;AAC5D,QAAM,UAAU,OAAO,QAAQ;AAC/B,QAAM,UAAU,OAAO,aAAa;AACnC,aAAS,IAAI,MAAM,OAAO,MAAM,UAAU;AACzC,YAAM,WAAW,MAAM,QAAQ;AAC/B,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,YAAM,KAAK,SAAS,MAAM;AAC1B,YAAM,OAAO,gBAAgB,kBAAkB;AAC/C,YAAM,OAAO,iBAAiB,qCAAqC;AACnE,YAAM,KAAK,IAAI;AAAA,IAChB,CAAC;AAAA,EACF;AACA,SAAO,eAAe,QAAQ,UAAU,EAAE,OAAO,QAAQ,YAAY,KAAK,CAAC;AAC3E,SAAO,eAAe,QAAQ,WAAW;AAAA,IACxC,OAAO,MAAM;AACZ,oBAAc;AACd,aAAO,QAAQ;AAAA,IAChB;AAAA,IACA,YAAY;AAAA,EACb,CAAC;AACD,SAAO;AACR;AAUA,SAAS,YAAY,OAA0B,CAAC,GAAuB;AACtE,QAAM,SAAS,gBAAgB,IAAI;AACnC,QAAM,gBAAgB,0BAA0B,QAAQ,IAAI;AAC5D,QAAM,UAAU,OAAO,QAAQ;AAC/B,QAAM,WAAW,CAAC,QAAkB,QAAQ,GAAG;AAC/C,SAAO,eAAe,SAAS,UAAU,EAAE,OAAO,QAAQ,YAAY,KAAK,CAAC;AAC5E,SAAO,eAAe,SAAS,WAAW;AAAA,IACzC,OAAO,MAAM;AACZ,oBAAc;AACd,aAAO,QAAQ;AAAA,IAChB;AAAA,IACA,YAAY;AAAA,EACb,CAAC;AACD,SAAO;AACR;AAgBO,IAAM,aAAa;AAAA;AAAA,EAEzB,MAAM;AAAA;AAAA,EAEN,SAAS;AAAA;AAAA,EAET,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT,OAAO;AACR;;;ACxUA,SAAS,YAAY,GAAuC;AAC3D,MAAI,OAAO,MAAM,UAAU;AAC1B,QAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,GAAG;AAClC,YAAM,IAAI,MAAM,4CAA4C,CAAC,EAAE;AAAA,IAChE;AACA,WAAO;AAAA,EACR;AACA,QAAM,IAAI,eAAe,KAAK,CAAC;AAC/B,MAAI,MAAM,MAAM;AACf,UAAM,IAAI,MAAM,oCAAoC,CAAC,kCAAkC;AAAA,EACxF;AACA,QAAM,IAAI,OAAO,SAAS,EAAE,CAAC,GAAa,EAAE;AAC5C,QAAM,OAAO,EAAE,CAAC;AAChB,QAAM,KAAK,SAAS,MAAM,IAAI,MAAO,IAAI;AACzC,MAAI,MAAM,GAAG;AACZ,UAAM,IAAI,MAAM,kCAAkC,EAAE,gBAAgB;AAAA,EACrE;AACA,SAAO;AACR;AAEO,SAAS,gBAAgB,MAA+C;AAC9E,QAAM,WAAW,YAAY,KAAK,MAAM;AACxC,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,MAAM,KAAK,OAAO,KAAK;AAE7B,MAAI,eAAe,KAAK,eAAe,GAAG;AACzC,UAAM,IAAI,MAAM,wDAAwD,YAAY,EAAE;AAAA,EACvF;AACA,MAAI,YAAY,gBAAgB,WAAW,GAAG;AAC7C,UAAM,IAAI,MAAM,+DAA+D,QAAQ,EAAE;AAAA,EAC1F;AACA,MAAI,CAAC,OAAO,SAAS,UAAU,KAAK,aAAa,GAAG;AACnD,UAAM,IAAI,MAAM,iDAAiD,UAAU,EAAE;AAAA,EAC9E;AAKA,QAAM,UAAoB,CAAC;AAE3B,WAAS,aAAa,GAAiB;AACtC,UAAM,SAAS,IAAI;AACnB,WAAO,QAAQ,SAAS,KAAM,QAAQ,CAAC,EAAa,IAAI,QAAQ;AAC/D,cAAQ,MAAM;AAAA,IACf;AAAA,EACD;AAEA,SAAO;AAAA,IACN,MAAM,SAAS,KAAK,MAAM,WAAW,GAAI,CAAC;AAAA,IAC1C;AAAA,IACA,gBAAsB;AACrB,cAAQ,KAAK,EAAE,GAAG,IAAI,GAAG,GAAG,MAAM,CAAC;AAAA,IACpC;AAAA,IACA,cAAoB;AACnB,cAAQ,KAAK,EAAE,GAAG,IAAI,GAAG,GAAG,KAAK,CAAC;AAAA,IACnC;AAAA,IACA,QAAc;AACb,cAAQ,SAAS;AAAA,IAClB;AAAA,IACA,OAAsB;AACrB,YAAM,IAAI,IAAI;AACd,mBAAa,CAAC;AACd,UAAI,QAAQ,WAAW,KAAK,QAAQ,SAAS,YAAY;AAGxD,eAAO,EAAE,OAAO,GAAG,cAAc,EAAE;AAAA,MACpC;AACA,UAAI,SAAS;AACb,iBAAW,KAAK,SAAS;AACxB,YAAI,EAAE,EAAG;AAAA,MACV;AACA,YAAM,OAAO,SAAS,QAAQ;AAE9B,UAAI;AACJ,UAAI,QAAQ,aAAc,UAAS;AAAA,eAC1B,QAAQ,SAAU,UAAS;AAAA,WAC/B;AACJ,cAAM,OAAO,WAAW;AACxB,iBAAS,KAAK,OAAO,gBAAgB;AAAA,MACtC;AAIA,YAAM,eAAe,KAAK,MAAM,OAAO,GAAM,IAAI;AAEjD,aAAO;AAAA,QACN,OAAO;AAAA,QACP,cAAc;AAAA,MACf;AAAA,IACD;AAAA,EACD;AACD;;;AC9HA,SAAS,oBAAoB;AAmC7B,IAAM,4BAA4B;AAElC,SAAS,QAAQ,QAA+B,MAA6B;AAC5E,MAAI;AACH,WAAO,OAAO,IAAI;AAAA,EACnB,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAQO,SAAS,yBACf,QACA,QACA,QACgB;AAEhB,QAAM,KAAK,QAAQ,QAAQ,MAAM;AACjC,MAAI,OAAO,MAAM;AAChB,UAAMC,WAAU,GAAG,KAAK;AACxB,QAAIA,aAAY,SAASA,aAAY,GAAI,QAAO;AAChD,UAAMC,KAAI,OAAO,SAASD,UAAS,EAAE;AACrC,QAAI,CAAC,OAAO,SAASC,EAAC,KAAKA,MAAK,EAAG,QAAO;AAE1C,QAAIA,KAAI,0BAA2B,QAAO;AAC1C,WAAOA;AAAA,EACR;AAEA,QAAM,KAAK,QAAQ,QAAQ,MAAM;AACjC,MAAI,OAAO,KAAM,QAAO;AACxB,QAAM,UAAU,GAAG,KAAK;AACxB,QAAM,IAAI,OAAO,SAAS,SAAS,EAAE;AACrC,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG,QAAO;AAC1C,MAAI,IAAI,0BAA2B,QAAO;AAC1C,SAAO;AACR;AAEO,SAAS,qBAAqB,OAA8B,CAAC,GAAe;AAClF,QAAM,gBAAgB,KAAK,iBAAiB;AAC5C,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAS,KAAK,gBAAgB;AACpC,QAAM,SAAS,KAAK,gBAAgB;AACpC,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,cACL,KAAK,gBACJ,MAAuB;AACvB,UAAM,IAAI,QAAQ,YAAY;AAC9B,WAAO,EAAE,KAAK,EAAE,IAAI;AAAA,EACrB;AACD,QAAM,WAAW,KAAK,aAAa,CAAC,MAAc,aAAa,GAAG,MAAM;AAExE,MAAI,iBAAiB,KAAK,iBAAiB,GAAG;AAC7C,UAAM,IAAI,MAAM,8DAA8D,aAAa,EAAE;AAAA,EAC9F;AACA,MAAI,aAAa,iBAAiB,YAAY,GAAG;AAChD,UAAM,IAAI;AAAA,MACT,sEAAsE,SAAS;AAAA,IAChF;AAAA,EACD;AAIA,MAAI;AACJ,WAAS,WAA0B;AAClC,QAAI,gBAAgB,QAAW;AAC9B,oBAAc,yBAAyB,UAAU,QAAQ,MAAM;AAAA,IAChE;AACA,WAAO;AAAA,EACR;AAEA,SAAO;AAAA,IACN,MAAM;AAAA,IACN;AAAA,IACA,OAAsB;AACrB,YAAM,QAAQ,SAAS;AACvB,UAAI,UAAU,MAAM;AAGnB,eAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,KAAK;AAAA,MACnD;AACA,UAAI;AACJ,UAAI;AACH,cAAM,YAAY,EAAE;AAAA,MACrB,QAAQ;AACP,eAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,KAAK;AAAA,MACnD;AACA,UAAI,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AACrC,eAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,KAAK;AAAA,MACnD;AACA,YAAM,QAAQ,MAAM;AAEpB,UAAI;AACJ,UAAI,SAAS,cAAe,UAAS;AAAA,eAC5B,SAAS,UAAW,UAAS;AAAA,WACjC;AACJ,cAAM,OAAO,YAAY;AACzB,iBAAS,KAAK,QAAQ,iBAAiB;AAAA,MACxC;AAGA,YAAM,eAAe,KAAK,MAAM,QAAQ,GAAI,IAAI;AAChD,aAAO,EAAE,OAAO,cAAc,cAAc,OAAO;AAAA,IACpD;AAAA,EACD;AACD;;;AC/GA,IAAM,qBAAqB;AAEpB,SAAS,WAAW,MAAsC;AAChE,MAAI,OAAO,KAAK,SAAS,YAAY;AACpC,UAAM,IAAI,MAAM,cAAc,KAAK,IAAI,4BAA4B;AAAA,EACpE;AACA,MAAI,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,WAAW,GAAG;AAC5D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC9D;AACA,QAAM,YAAY,KAAK,aAAa;AACpC,MAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,GAAG;AAClD,UAAM,IAAI,MAAM,cAAc,KAAK,IAAI,iCAAiC,SAAS,EAAE;AAAA,EACpF;AACA,QAAM,SAAS,KAAK,UAAU;AAE9B,SAAO;AAAA,IACN,MAAM,KAAK;AAAA,IACX;AAAA,IACA,MAAM,OAA+B;AACpC,UAAI;AACJ,YAAM,iBAAiB,IAAI,QAAmB,CAAC,YAAY;AAC1D,gBAAQ,WAAW,MAAM,QAAQ,SAAS,GAAG,SAAS;AAAA,MACvD,CAAC;AACD,UAAI;AACH,cAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,KAAK,KAAK,EAAE,KAAK,MAAM,IAAa,GAAG,cAAc,CAAC;AACzF,YAAI,WAAW,WAAW;AACzB,iBAAO,EAAE,OAAO,WAAW,cAAc,EAAE;AAAA,QAC5C;AACA,eAAO,EAAE,OAAO,MAAM,cAAc,EAAE;AAAA,MACvC,SAAS,KAAK;AACb,cAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,eAAO,EAAE,OAAO,QAAQ,MAAM,GAAG,GAAG,GAAG,cAAc,EAAE;AAAA,MACxD,UAAE;AACD,YAAI,MAAO,cAAa,KAAK;AAAA,MAC9B;AAAA,IACD;AAAA,EACD;AACD;AAiBO,SAAS,eAAe,MAA0C;AACxE,SAAO,WAAW;AAAA,IACjB,MAAM,KAAK,QAAQ;AAAA,IACnB,MAAM,KAAK;AAAA,IACX,GAAI,KAAK,cAAc,SAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IACpE,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,EAC5D,CAAC;AACF;AAMO,SAAS,YAAY,MAA0C;AACrE,SAAO,WAAW;AAAA,IACjB,MAAM,KAAK,QAAQ;AAAA,IACnB,MAAM,KAAK;AAAA,IACX,GAAI,KAAK,cAAc,SAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;AAAA,IACpE,GAAI,KAAK,WAAW,SAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,EAC5D,CAAC;AACF;;;ACnFO,SAAS,iBAAiB,MAAsC;AACtE,MAAI,OAAO,KAAK,WAAW,YAAY;AACtC,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC9D;AACA,MAAI,CAAC,OAAO,SAAS,KAAK,aAAa,KAAK,KAAK,iBAAiB,GAAG;AACpE,UAAM,IAAI,MAAM,oDAAoD,KAAK,aAAa,EAAE;AAAA,EACzF;AACA,QAAM,eAAe,KAAK,gBAAgB;AAC1C,MAAI,CAAC,OAAO,SAAS,YAAY,KAAK,eAAe,KAAK,gBAAgB,KAAK,eAAe;AAC7F,UAAM,IAAI;AAAA,MACT,qEAAqE,YAAY;AAAA,IAClF;AAAA,EACD;AACA,QAAM,SAAS,KAAK,UAAU;AAE9B,SAAO;AAAA,IACN,MAAM,KAAK,QAAQ;AAAA,IACnB;AAAA,IACA,MAAM,OAA+B;AACpC,UAAI;AACJ,UAAI;AACH,cAAM,MAAM,KAAK,OAAO;AAAA,MACzB,QAAQ;AAEP,eAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,KAAK;AAAA,MACnD;AACA,UAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,GAAG;AAChE,eAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,KAAK;AAAA,MACnD;AACA,YAAM,QAAQ;AAEd,UAAI;AACJ,UAAI,SAAS,aAAc,UAAS;AAAA,eAC3B,SAAS,KAAK,cAAe,UAAS;AAAA,WAC1C;AACJ,cAAM,OAAO,KAAK,gBAAgB;AAClC,iBAAS,KAAK,QAAQ,gBAAgB;AAAA,MACvC;AAEA,aAAO;AAAA,QACN,OAAO;AAAA,QACP,cAAc;AAAA,MACf;AAAA,IACD;AAAA,EACD;AACD;;;ACwMA,SAAS,mBAAmB,SAAqD;AAChF,MAAI,YAAY,MAAO,QAAO;AAC9B,QAAM,cAAkC,WAAW,OAAO,YAAY,WAAW,UAAU,CAAC;AAC5F,SAAO,kBAAkB,WAAW;AACrC;AAQA,SAAS,uBAAuB,KAA0D;AACzF,MAAI,QAAQ,MAAO,QAAO;AAC1B,QAAM,gBAAkC,OAAO,OAAO,QAAQ,WAAW,MAAM,CAAC;AAChF,SAAO,sBAAsB,aAAa;AAC3C;AAQA,SAAS,uBAAuB,KAAwD;AACvF,MAAI,QAAQ,MAAO,QAAO;AAC1B,QAAM,eAAuC,OAAO,OAAO,QAAQ,WAAW,MAAM,CAAC;AACrF,SAAO,sBAAsB,YAAY;AAC1C;AAEA,SAAS,kBAAkB,OAAuB;AACjD,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,MAAI,QAAQ,EAAG,QAAO;AACtB,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO;AACR;AAEO,SAAS,aAAa,OAA4B,CAAC,GAAiB;AAI1E,QAAM,UACL,KAAK,WAAW,KAAK,QAAQ,SAAS,IACnC,CAAC,GAAG,KAAK,OAAO,IAChB,CAAC,mBAAmB,EAAE,YAAY,KAAM,QAAQ,KAAO,QAAQ,EAAE,CAAC,CAAC;AAEvE,QAAM,kBAAkB,KAAK,mBAAmB,uBAAuB;AACvE,QAAM,MAAM,KAAK,QAAQ,MAAY,oBAAI,KAAK;AAK9C,QAAM,cAAkC,mBAAmB,KAAK,IAAI;AAMpE,QAAM,YAAoC,uBAAuB,KAAK,SAAS;AAK/E,QAAM,kBAA0C,uBAAuB,KAAK,OAAO;AAEnF,MAAI,WAAW;AACf,MAAI,YAAgC;AAEpC,QAAM,WAAW,YAAkC;AAClD,QAAI,UAAU;AACb,YAAM,IAAI,MAAM,iCAAiC;AAAA,IAClD;AAIA,UAAM,gBAAsC,YAAY,UAAU,UAAU,IAAI;AAOhF,UAAM,WAAW,MAAM,QAAQ;AAAA,MAC9B,QAAQ,IAAI,OAAO,YAAY;AAAA,QAC9B;AAAA,QACA,SAAS,MAAM,OAAO,KAAK;AAAA,MAC5B,EAAE;AAAA,IACH;AAEA,UAAM,QAAQ,gBAAgB,QAAQ;AAEtC,UAAM,aAAwD,CAAC;AAC/D,UAAM,gBAAwC,CAAC;AAC/C,eAAW,EAAE,QAAQ,QAAQ,KAAK,UAAU;AAC3C,iBAAW,OAAO,IAAI,IAAI,QAAQ;AAClC,oBAAc,OAAO,IAAI,IACxB,QAAQ,YAAY,OAAO,IAAI,kBAAkB,QAAQ,YAAY;AAAA,IACvE;AAEA,UAAM,SAAsB;AAAA,MAC3B;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA,YAAY,IAAI,EAAE,YAAY;AAAA,IAC/B;AAEA,gBAAY;AAKZ,QAAI,aAAa;AAChB,kBAAY,KAAK,QAAQ,aAAa;AAAA,IACvC;AAKA,QAAI,iBAAiB;AACpB,WAAK,gBAAgB,OAAO,MAAM;AAAA,IACnC;AAEA,WAAO;AAAA,EACR;AAEA,QAAM,YAA6B,EAAE,SAAS;AAE9C,QAAMC,kBAAiB,eAAoB,QAAQ;AAEnD,QAAM,eAAe,OAAU,OAAyC;AACvE,QAAI,CAAC,aAAa,CAAC,UAAW,QAAO,MAAM,GAAG;AAC9C,WAAO,MAAM,UAAU,aAAa,IAAI,SAAS;AAAA,EAClD;AAEA,QAAM,aAAa,OAAO,QAAsB,CAAC,MAAwC;AACxF,QAAI,CAAC,gBAAiB,QAAO,CAAC;AAC9B,WAAO,MAAM,gBAAgB,MAAM,KAAK;AAAA,EACzC;AAEA,QAAM,gBAAgB,OAAO,QAAsB,CAAC,MAAmC;AACtF,QAAI,CAAC,iBAAiB;AACrB,aAAO;AAAA,QACN,UAAU;AAAA,QACV,gBAAgB;AAAA,QAChB,sBAAsB;AAAA,QACtB,QAAQ;AAAA,MACT;AAAA,IACD;AACA,WAAO,MAAM,gBAAgB,OAAO,KAAK;AAAA,EAC1C;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,gBAAAA;AAAA,IACA,UAAgD;AAC/C,aAAO,iBAAiB,SAAS;AAAA,IAClC;AAAA,IACA,cAAoD;AACnD,aAAO,kBAAkB,SAAS;AAAA,IACnC;AAAA,IACA,gBAAgB,UAA4D;AAC3E,aAAO,sBAAsB,WAAW,QAAQ;AAAA,IACjD;AAAA,IACA,eAAmC;AAClC,aAAO;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAgB;AACf,UAAI,SAAU;AACd,iBAAW;AACX,UAAI,YAAa,aAAY,QAAQ;AACrC,UAAI,UAAW,WAAU,QAAQ;AACjC,UAAI,gBAAiB,iBAAgB,QAAQ;AAC7C,iBAAW,KAAK,SAAS;AACxB,YAAI;AACH,YAAE,UAAU;AAAA,QACb,QAAQ;AAAA,QAER;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;","names":["cachedApi","trimmed","n","evaluateEffect"]}
|
|
1
|
+
{"version":3,"sources":["../../src/health/causality.ts","../../src/health/effects.ts","../../src/health/types.ts","../../src/health/handler.ts","../../src/health/history.ts","../../src/health/otel.ts","../../src/health/scoring.ts","../../src/health/signals/event-loop-lag.ts","../../src/health/unix-socket-server.ts","../../src/health/middleware.ts","../../src/health/signals/error-rate.ts","../../src/health/signals/memory-pressure.ts","../../src/health/signals/ping.ts","../../src/health/signals/queue-depth.ts","../../src/health/index.ts"],"sourcesContent":["/**\n * Causality-aware health propagation for `@sylphx/sdk/health` (ADR-143).\n *\n * Reads + writes the OpenTelemetry Baggage entry `sylphx.health.cause`\n * with value `<signal>@<service>[,<signal>@<service>...]` per ADR-143 §3.1.\n * Append-only `sylphx.health.cause_chain` carries the hop chain so\n * operators can reconstruct multi-hop incident paths.\n *\n * # Read side (downstream)\n *\n * On every `evaluate()`, the SDK reads `propagation.getActiveBaggage()`\n * before computing the score. When the baggage carries an\n * `sylphx.health.cause` entry, the resulting `HealthScore.upstreamCause`\n * surfaces the parsed causes + chain. The OTel emitter then sets\n * `service.health.cause_root` / `cause_signal` / `cause_chain` span\n * attributes and increments the `health.downstream_cause` Counter.\n *\n * # Write side (upstream — the service whose signal is failing)\n *\n * `runWithCause(fn, score)` wraps an async function in a new OTel\n * context where the cause baggage is set. Used by `withHealth.*`\n * middleware to inject baggage on every non-/healthz request so that\n * downstream calls within that request inherit the cause via standard\n * OTel context propagation (W3C Baggage header on HTTP, native\n * baggage on gRPC, queue metadata on Kafka/Cloud Tasks).\n *\n * # Graceful fallback\n *\n * `@opentelemetry/api` is an optional peer dependency (see ADR-142\n * §2.3). When the dynamic import fails — or when the runtime exposes\n * only a partial OTel API — the handle transitions to `'unavailable'`,\n * every method becomes a no-op, and `runWithCause` runs the inner\n * function with no context modification. The `/healthz` contract is\n * unchanged in either case (ADR-143 §2.5 cardinal rule).\n *\n * # Cardinal rules (carried from ADR-141 / ADR-142)\n *\n * - Propagation never throws. Errors in `readCause`, `encodeCause`,\n * `runWithCause` are caught and downgraded to no-op behaviour.\n * - The HTTP response shape is unchanged. Baggage is augmentation;\n * probe surface is the load-bearing contract.\n */\n\n// Compile-time-only types — erased at emit time so consumers without\n// @opentelemetry/api installed do not see a missing-module error.\nimport type { Baggage, BaggageEntry, Context, ContextAPI, PropagationAPI } from '@opentelemetry/api'\nimport type { HealthScore } from './types'\n\n// ─── Public types ────────────────────────────────────────────────────────────\n\nexport const BAGGAGE_KEY_CAUSE = 'sylphx.health.cause'\nexport const BAGGAGE_KEY_CHAIN = 'sylphx.health.cause_chain'\n\n/** Default threshold below which a signal triggers cause propagation. */\nexport const DEFAULT_CAUSE_THRESHOLD = 0.5\n\n/** Maximum number of hops persisted in the chain baggage (ADR-143 §5). */\nexport const MAX_CHAIN_LENGTH = 5\n\n/**\n * The flag value passed by callers via `sylphxHealth({ causality })` /\n * `withHealth.hono({ causality })`. `true` (default) enables propagation\n * with sensible defaults; `false` disables it; an object configures it.\n */\nexport type CausalityOption = boolean | CausalityOptions\n\nexport interface CausalityOptions {\n\t/**\n\t * Below this signal factor, the SDK considers the signal \"causing\n\t * degradation\" and emits a cause entry. Default: `0.5` — matches the\n\t * ADR-111 kill threshold. Range: `(0, 1]`.\n\t */\n\treadonly threshold?: number\n\t/**\n\t * Override the service name used in cause entries. Falls back to\n\t * `process.env.OTEL_SERVICE_NAME` then `process.env.SERVICE_NAME`\n\t * then `\"unknown\"`. Operators of multi-tenant runtimes typically\n\t * override this per evaluator.\n\t */\n\treadonly serviceName?: string\n\t/**\n\t * When true (default), the SDK appends the local service name to\n\t * `sylphx.health.cause_chain` baggage so downstream operators can\n\t * reconstruct multi-hop incident paths.\n\t */\n\treadonly propagateChain?: boolean\n}\n\n/** Parsed view of an upstream cause read from baggage. */\nexport interface UpstreamCause {\n\t/**\n\t * The list of `<signal>@<service>` pairs carried in the baggage entry.\n\t * Operators read `causes[0]` for the most-significant root; the full\n\t * list preserves multi-cause concurrent failures.\n\t */\n\treadonly causes: ReadonlyArray<{ readonly signal: string; readonly service: string }>\n\t/**\n\t * The hop chain — services that observed this cause and forwarded it.\n\t * Most recent service is at the tail. `chain.length === 0` means the\n\t * baggage entry was just minted (no downstream services have observed\n\t * it yet — typically only at the moment-of-incident origin).\n\t */\n\treadonly chain: ReadonlyArray<string>\n}\n\n/** Operational handle the SDK uses to read + write cause baggage. */\nexport interface CausalityHandle {\n\t/** Async loader state — exposed for tests + diagnostics. */\n\treadonly state: 'loading' | 'ready' | 'unavailable' | 'disposed'\n\t/** Read upstream cause from active OTel baggage. Returns null when absent. */\n\treadCause(): UpstreamCause | null\n\t/**\n\t * Encode the local cause string for baggage. Returns null when none of\n\t * the signals' factors are below the configured threshold.\n\t */\n\tencodeCause(score: HealthScore): string | null\n\t/**\n\t * Run `fn` within a new OTel context that has our cause + chain\n\t * baggage entries set. Used by the middleware to inject baggage on\n\t * every non-/healthz request. When OTel API is unavailable, this\n\t * degrades to `fn()` directly.\n\t */\n\trunWithCause<T>(fn: () => T | Promise<T>, score: HealthScore): Promise<T>\n\t/** Tear down internal references. Idempotent. */\n\tdispose(): void\n}\n\n// ─── Loader (mirrors OTel emitter pattern) ───────────────────────────────────\n\ninterface CausalityApi {\n\treadonly context: ContextAPI\n\treadonly propagation: PropagationAPI\n}\n\nlet cachedApi: CausalityApi | null | undefined\n\n/**\n * Lazy load the OTel `context` + `propagation` namespaces. Cached across\n * causality handles so we only attempt the dynamic import once per\n * process. Test-only reset hook exposed for unit tests.\n */\nexport async function tryLoadCausalityApi(): Promise<CausalityApi | null> {\n\tif (cachedApi !== undefined) return cachedApi\n\ttry {\n\t\tconst api = await import('@opentelemetry/api')\n\t\tif (!api?.context || !api?.propagation) {\n\t\t\tcachedApi = null\n\t\t\treturn cachedApi\n\t\t}\n\t\tcachedApi = { context: api.context, propagation: api.propagation }\n\t} catch {\n\t\tcachedApi = null\n\t}\n\treturn cachedApi\n}\n\n/** Test-only escape hatch. */\nexport function __resetCausalityApiCacheForTests(): void {\n\tcachedApi = undefined\n}\n\n// ─── Encoding / parsing helpers ──────────────────────────────────────────────\n\nfunction defaultServiceName(): string {\n\tif (typeof process !== 'undefined' && process.env) {\n\t\tif (process.env.OTEL_SERVICE_NAME !== undefined) return process.env.OTEL_SERVICE_NAME\n\t\tif (process.env.SERVICE_NAME !== undefined) return process.env.SERVICE_NAME\n\t}\n\treturn 'unknown'\n}\n\nfunction isSafeIdentifier(part: string): boolean {\n\t// Conservative: no commas (separator), no `@` (encoder), no whitespace.\n\t// Anything else is fair game for service / signal identifiers.\n\treturn part.length > 0 && !/[,@\\s]/.test(part)\n}\n\n/** Serialise the local cause for the baggage entry. */\nfunction encodeCauseFromScore(\n\tscore: HealthScore,\n\tthreshold: number,\n\tserviceName: string,\n): string | null {\n\tconst factors = score.signalFactors\n\tif (!factors) return null\n\tconst entries: string[] = []\n\tfor (const [signalName, factor] of Object.entries(factors)) {\n\t\tif (typeof factor !== 'number' || !Number.isFinite(factor)) continue\n\t\tif (factor >= threshold) continue\n\t\tif (!isSafeIdentifier(signalName) || !isSafeIdentifier(serviceName)) continue\n\t\tentries.push(`${signalName}@${serviceName}`)\n\t}\n\tif (entries.length === 0) return null\n\treturn entries.join(',')\n}\n\n/**\n * Parse the cause baggage value. Malformed entries are silently dropped\n * (we never throw on a corrupt baggage entry — ADR-143 §2.5).\n */\nfunction parseCauseValue(value: string): UpstreamCause['causes'] {\n\tconst out: Array<{ signal: string; service: string }> = []\n\tfor (const raw of value.split(',')) {\n\t\tconst piece = raw.trim()\n\t\tif (piece.length === 0) continue\n\t\tconst atIdx = piece.indexOf('@')\n\t\tif (atIdx <= 0 || atIdx === piece.length - 1) continue\n\t\tconst signal = piece.slice(0, atIdx)\n\t\tconst service = piece.slice(atIdx + 1)\n\t\tif (!isSafeIdentifier(signal) || !isSafeIdentifier(service)) continue\n\t\tout.push({ signal, service })\n\t}\n\treturn out\n}\n\nfunction parseChainValue(value: string): ReadonlyArray<string> {\n\treturn value\n\t\t.split(',')\n\t\t.map((s) => s.trim())\n\t\t.filter((s) => s.length > 0 && isSafeIdentifier(s))\n}\n\nfunction appendChain(\n\tprev: ReadonlyArray<string>,\n\tserviceName: string,\n\tmaxLen: number,\n): ReadonlyArray<string> {\n\tif (!isSafeIdentifier(serviceName)) return prev\n\t// Don't double-append when we're the most recent hop.\n\tif (prev[prev.length - 1] === serviceName) return prev\n\tconst next = [...prev, serviceName]\n\tif (next.length <= maxLen) return next\n\treturn next.slice(next.length - maxLen)\n}\n\n// ─── Implementation ──────────────────────────────────────────────────────────\n\nclass CausalityHandleImpl implements CausalityHandle {\n\tprivate _state: CausalityHandle['state'] = 'loading'\n\tprivate apiRef: CausalityApi | null = null\n\tprivate readonly threshold: number\n\tprivate readonly serviceName: string\n\tprivate readonly propagateChain: boolean\n\n\tconstructor(opts: CausalityOptions) {\n\t\tconst threshold = opts.threshold ?? DEFAULT_CAUSE_THRESHOLD\n\t\tif (!Number.isFinite(threshold) || threshold <= 0 || threshold > 1) {\n\t\t\tthrow new Error(`createCausalityHandle: threshold must be in (0, 1], got ${threshold}`)\n\t\t}\n\t\tthis.threshold = threshold\n\t\tthis.serviceName = opts.serviceName ?? defaultServiceName()\n\t\tthis.propagateChain = opts.propagateChain !== false\n\t\tvoid this.initialize()\n\t}\n\n\tget state(): CausalityHandle['state'] {\n\t\treturn this._state\n\t}\n\n\tprivate async initialize(): Promise<void> {\n\t\tconst api = await tryLoadCausalityApi()\n\t\tif (this._state === 'disposed') return\n\t\tif (api === null) {\n\t\t\tthis._state = 'unavailable'\n\t\t\treturn\n\t\t}\n\t\tthis.apiRef = api\n\t\tthis._state = 'ready'\n\t}\n\n\treadCause(): UpstreamCause | null {\n\t\tif (this._state !== 'ready' || !this.apiRef) return null\n\t\ttry {\n\t\t\tconst baggage = this.apiRef.propagation.getActiveBaggage()\n\t\t\tif (!baggage) return null\n\t\t\tconst causeEntry = baggage.getEntry(BAGGAGE_KEY_CAUSE)\n\t\t\tif (!causeEntry || typeof causeEntry.value !== 'string') return null\n\t\t\tconst causes = parseCauseValue(causeEntry.value)\n\t\t\tif (causes.length === 0) return null\n\t\t\tconst chainEntry = baggage.getEntry(BAGGAGE_KEY_CHAIN)\n\t\t\tconst chain =\n\t\t\t\tchainEntry && typeof chainEntry.value === 'string' ? parseChainValue(chainEntry.value) : []\n\t\t\treturn { causes, chain }\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\tencodeCause(score: HealthScore): string | null {\n\t\ttry {\n\t\t\treturn encodeCauseFromScore(score, this.threshold, this.serviceName)\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync runWithCause<T>(fn: () => T | Promise<T>, score: HealthScore): Promise<T> {\n\t\tif (this._state !== 'ready' || !this.apiRef) {\n\t\t\treturn await fn()\n\t\t}\n\t\ttry {\n\t\t\tconst localCause = this.encodeCause(score)\n\t\t\tconst existing = this.apiRef.propagation.getActiveBaggage()\n\t\t\tconst existingChain = existing?.getEntry(BAGGAGE_KEY_CHAIN)?.value\n\t\t\t\t? parseChainValue(existing.getEntry(BAGGAGE_KEY_CHAIN)!.value)\n\t\t\t\t: []\n\t\t\t// Nothing to do — no local cause AND no upstream baggage to forward.\n\t\t\tif (!localCause && !existing) {\n\t\t\t\treturn await fn()\n\t\t\t}\n\n\t\t\tconst baseBaggage = existing ?? this.apiRef.propagation.createBaggage()\n\t\t\tlet nextBaggage: Baggage = baseBaggage\n\n\t\t\tif (localCause) {\n\t\t\t\tconst newEntry: BaggageEntry = { value: localCause }\n\t\t\t\tconst upstream = existing?.getEntry(BAGGAGE_KEY_CAUSE)?.value\n\t\t\t\tconst mergedValue = upstream ? mergeCauseValues(upstream, localCause) : localCause\n\t\t\t\tnextBaggage = nextBaggage.setEntry(BAGGAGE_KEY_CAUSE, {\n\t\t\t\t\t...newEntry,\n\t\t\t\t\tvalue: mergedValue,\n\t\t\t\t})\n\t\t\t}\n\n\t\t\tif (this.propagateChain && (localCause || existingChain.length > 0)) {\n\t\t\t\tconst nextChain = localCause\n\t\t\t\t\t? appendChain(existingChain, this.serviceName, MAX_CHAIN_LENGTH)\n\t\t\t\t\t: existingChain\n\t\t\t\tif (nextChain.length > 0) {\n\t\t\t\t\tnextBaggage = nextBaggage.setEntry(BAGGAGE_KEY_CHAIN, {\n\t\t\t\t\t\tvalue: nextChain.join(','),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst ctxWithBaggage: Context = this.apiRef.propagation.setBaggage(\n\t\t\t\tthis.apiRef.context.active(),\n\t\t\t\tnextBaggage,\n\t\t\t)\n\t\t\treturn await this.apiRef.context.with(ctxWithBaggage, fn)\n\t\t} catch {\n\t\t\t// Cardinal rule — propagation never throws.\n\t\t\treturn await fn()\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis._state = 'disposed'\n\t\tthis.apiRef = null\n\t}\n}\n\n/**\n * Merge two cause values (`signal@service,signal@service`) into one,\n * deduplicating identical `signal@service` pairs while preserving order\n * (existing first, local appended). Bounded by the baggage W3C size limit.\n */\nfunction mergeCauseValues(existing: string, local: string): string {\n\tconst seen = new Set<string>()\n\tconst out: string[] = []\n\tfor (const piece of [...existing.split(','), ...local.split(',')]) {\n\t\tconst trimmed = piece.trim()\n\t\tif (trimmed.length === 0 || seen.has(trimmed)) continue\n\t\tseen.add(trimmed)\n\t\tout.push(trimmed)\n\t}\n\treturn out.join(',')\n}\n\n/**\n * Build a causality handle. Returns a handle whose state starts at\n * `'loading'`; emit / read operations are no-ops during loading. Once\n * the dynamic import resolves, state transitions to `'ready'` (or\n * `'unavailable'` when OTel API is absent).\n */\nexport function createCausalityHandle(opts: CausalityOptions = {}): CausalityHandle {\n\treturn new CausalityHandleImpl(opts)\n}\n","/**\n * Effect TS integration for `@sylphx/sdk/health` (Rule 21 / ADR-058 Amendment).\n *\n * Effect-native services should NEVER call `Effect.runPromise` inside\n * business logic — this module exposes `evaluateEffect` so they can fold\n * the health computation into their own fiber graph.\n *\n * `effect` is an OPTIONAL peer dependency — apps that don't import this\n * module never pull it in (sideEffects: false + tree-shaking guarantees\n * this; verified in tests). The `Effect` type is imported from the\n * peer-dep at runtime; consumers either provide it or never reach this\n * code path.\n */\n\nimport { Effect } from 'effect'\nimport type { HealthError, HealthScore } from './types'\nimport { HealthError as HealthErrorClass } from './types'\n\n/**\n * Lift a `() => Promise<HealthScore>` evaluator into an Effect-native value.\n *\n * Errors thrown by the evaluator are tagged `HealthError` so callers can\n * use `Effect.catchTag('HealthError', …)` for typed recovery.\n *\n * @example\n * ```ts\n * import { Effect } from 'effect'\n * import { sylphxHealth } from '@sylphx/sdk/health'\n *\n * const health = sylphxHealth({ signals: [...] })\n *\n * const program = Effect.gen(function* () {\n * const { score, signals } = yield* health.evaluateEffect\n * yield* Effect.log(`health=${score.toFixed(2)} signals=${JSON.stringify(signals)}`)\n * return score\n * })\n *\n * // Only the entry point runs the Effect (Rule 21).\n * const finalScore = await Effect.runPromise(program)\n * ```\n */\nexport function evaluateEffect(\n\tevaluator: () => Promise<HealthScore>,\n): Effect.Effect<HealthScore, HealthError, never> {\n\treturn Effect.tryPromise({\n\t\ttry: () => evaluator(),\n\t\tcatch: (err) => new HealthErrorClass('health evaluation failed', err),\n\t})\n}\n","/**\n * Type SSOT for `@sylphx/sdk/health` (ADR-111 §4).\n *\n * Pure runtime types — framework-free, no schema library required at the\n * SDK boundary. Apps that want Standard Schema validation (per ADR-084)\n * can wrap the wire-format `HealthSnapshot` in their own schema.\n *\n * The wire shape (`HealthSnapshot`) is the **stable contract** the\n * `sylphx-health-agent` sidecar parses (see ADR-111 §3.2.4 +\n * `apps/health-agent/src/app-poll.ts::parseAppHealthBody`). Do not break it.\n */\n\n// ─── Signal — what an app registers ─────────────────────────────────────────\n\n/**\n * One reading produced by a `Signal.read()`. The aggregator turns each\n * reading into a `factor` in `[0, 1]` then folds them into the score via\n * the configured `ScoringStrategy`.\n *\n * `value` is the raw observation (ms, ratio, count, …) — surfaced verbatim\n * in the wire snapshot so operators can debug from JSON without re-running\n * the signal logic.\n *\n * `healthFactor` is the normalised health in `[0, 1]`:\n * - `1` = fully healthy\n * - `0` = dead (or \"we don't know\" if `unknown=true` and the policy says so)\n *\n * `unknown=true` means the signal **could not be measured this tick** (e.g.\n * cgroup file unreadable). Scoring strategies treat unknown signals as\n * `factor=1` (don't penalise an app for our own missing data).\n */\nexport interface SignalReading {\n\treadonly value: number | string | boolean\n\treadonly healthFactor: number\n\treadonly unknown?: boolean\n}\n\n/**\n * A health signal — one named, weighted measurement the score is built from.\n *\n * Two variants:\n * - `SyncSignal` — `read(): SignalReading` (e.g. event-loop lag)\n * - `AsyncSignal` — `read(): Promise<SignalReading>` (e.g. queue depth\n * fetched over IPC)\n *\n * The discriminated `Signal` union accepts both. The aggregator awaits\n * each reading uniformly via `Promise.resolve(signal.read())`.\n *\n * Implementations are pure — no internal mutation outside the closure-\n * captured monitor state (e.g. `monitorEventLoopDelay()`). Stop /\n * cleanup are exposed via `dispose()` for tests and graceful shutdown.\n */\nexport interface SignalBase {\n\t/** Unique stable identifier; appears in the wire `signals.<name>` map. */\n\treadonly name: string\n\t/**\n\t * Weight in the weighted-product score. Strictly `> 0` for active\n\t * signals; `0` is a no-op signal (kept for compatibility with\n\t * conditional registration but skipped in scoring).\n\t */\n\treadonly weight: number\n\t/**\n\t * Tear down any background work (timers, monitors, file watchers).\n\t * Called during graceful shutdown and from test cleanup.\n\t */\n\tdispose?(): void\n}\nexport interface SyncSignal extends SignalBase {\n\tread(): SignalReading\n}\nexport interface AsyncSignal extends SignalBase {\n\tread(): Promise<SignalReading>\n}\nexport type Signal = SyncSignal | AsyncSignal\n\n// ─── Score — what the sidecar reads ─────────────────────────────────────────\n\n/**\n * The complete health score + signal breakdown produced by `health.evaluate()`.\n *\n * `score` is the normalised aggregate in `[0, 1]`. Signal payload is\n * verbatim values from each `SignalReading.value` so operators can\n * cross-reference Grafana dashboards with the JSON.\n */\nexport interface HealthScore {\n\treadonly score: number\n\treadonly signals: Record<string, number | string | boolean>\n\t/**\n\t * Normalised per-signal health factors in [0, 1].\n\t *\n\t * Additive to the ADR-111 wire shape: existing readers can ignore it,\n\t * while ADR-142 OTel emission uses it as the SSOT for\n\t * `health.signal.factor`.\n\t */\n\treadonly signalFactors?: Record<string, number>\n\t/**\n\t * Non-secret release/deployment identity for live-build verification.\n\t *\n\t * Additive to the ADR-111 wire shape: probe sidecars ignore it, while\n\t * operators and deployment monitors can prove which immutable build is\n\t * answering `/healthz` without needing access to the deploy control plane.\n\t */\n\treadonly release?: HealthRelease\n\treadonly lastTickAt: string\n}\n\n/**\n * Public, non-secret release identity attached to a health response.\n *\n * These fields intentionally mirror platform-injected runtime identity\n * variables (`SYLPHX_*`) instead of arbitrary metadata. Health endpoints are\n * commonly public, so this surface must stay provenance-focused and must not\n * become a general key/value bag that can accidentally expose secrets.\n */\nexport interface HealthRelease {\n\treadonly version?: string | null\n\treadonly commitSha?: string | null\n\treadonly imageDigest?: string | null\n\treadonly imageTag?: string | null\n\treadonly deploymentId?: string | null\n\treadonly buildId?: string | null\n\treadonly replicaId?: string | null\n}\n\nexport type HealthReleaseInput = HealthRelease | (() => HealthRelease | null | undefined)\n\n/**\n * Wire format the sidecar's `app-poll.ts::parseAppHealthBody` consumes.\n *\n * Pinned by ADR-111 §3.2.4 — keep stable. The sidecar tolerates extra\n * keys; do NOT remove existing ones without sequencing the sidecar update\n * first.\n */\nexport type HealthSnapshot = HealthScore\n\n// ─── Errors ─────────────────────────────────────────────────────────────────\n\n/**\n * Tagged error type for the Effect API. Promise consumers see the same\n * `message` via `Error.message` — the tag is for `Effect.catchTag`.\n */\nexport class HealthError extends Error {\n\treadonly _tag = 'HealthError' as const\n\treadonly cause?: unknown\n\tconstructor(message: string, cause?: unknown) {\n\t\tsuper(message)\n\t\tthis.name = 'HealthError'\n\t\tthis.cause = cause\n\t}\n}\n\n// ─── Scoring strategy ───────────────────────────────────────────────────────\n\n/**\n * `ScoringStrategy` collapses N readings → one `score` in `[0, 1]`.\n *\n * Default is `weightedProduct` (see `scoring.ts`). Custom strategies can\n * be plugged in via `sylphxHealth({ scoringStrategy: myStrategy })`.\n */\nexport type ScoringStrategy = (\n\treadings: ReadonlyArray<{ signal: Signal; reading: SignalReading }>,\n) => number\n\n// ─── Public option types ────────────────────────────────────────────────────\n\nexport interface SylphxHealthOptions {\n\t/**\n\t * Signals to register. If omitted, defaults to a single\n\t * `eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 })` per\n\t * ADR-111 §4.6 (\"sane out-of-box\").\n\t */\n\treadonly signals?: ReadonlyArray<Signal>\n\t/**\n\t * Strategy used to fold readings into a score. Defaults to\n\t * `weightedProduct` from `scoring.ts`.\n\t */\n\treadonly scoringStrategy?: ScoringStrategy\n\t/**\n\t * Optional injected clock — used by tests for deterministic\n\t * `lastTickAt` timestamps.\n\t */\n\treadonly now?: () => Date\n\t/**\n\t * OpenTelemetry emission (ADR-142). When `true` (default), every\n\t * `evaluate()` records `health.score` / `health.signal.*` metrics and\n\t * sets `service.health.score` on the active span. `false` disables\n\t * emission entirely. An object configures advanced behaviour (custom\n\t * meter, span events, static attributes).\n\t *\n\t * Graceful fallback: if `@opentelemetry/api` is not installed in the\n\t * host project, the emitter no-ops cleanly — no warning, no exception.\n\t */\n\treadonly otel?: import('./otel').OtelOption\n\t/**\n\t * Causality-aware health propagation (ADR-143). When `true` (default),\n\t * every `evaluate()` reads upstream cause from active OTel baggage\n\t * and surfaces it on the metric stream (`health.downstream_cause`\n\t * counter + `service.health.cause_root` span attribute). The\n\t * `withHealth.*` middleware additionally writes the local cause into\n\t * the request context baggage on every non-`/healthz` request so\n\t * downstream services inherit causality via standard OTel\n\t * propagation. `false` disables both read and write. An object\n\t * tunes the threshold + service-name + chain behaviour.\n\t *\n\t * Graceful fallback: if `@opentelemetry/api` is not installed, the\n\t * handle becomes `'unavailable'` and propagation is a no-op.\n\t */\n\treadonly causality?: import('./causality').CausalityOption\n\t/**\n\t * Audit-grade health history (ADR-144). When `true` (default), every\n\t * `evaluate()` records a tamper-evident `HistoryEntry` into the\n\t * configured `HistoryStore` (default: in-memory ring buffer with\n\t * capacity 1000). Entries form a SHA-256 prev-hash chain — operators\n\t * (and compliance auditors) can replay the chain to detect post-hoc\n\t * tampering.\n\t *\n\t * Set `false` to disable recording. Pass an object to plug a\n\t * persistent store (Postgres / S3 / OpenSearch) or override capacity.\n\t *\n\t * The in-memory store is suitable for testing + low-stakes single-pod\n\t * deployments. SOC 2 / GDPR / HIPAA compliance requires a persistent\n\t * adapter; the interface is public and stable so any backend works.\n\t */\n\treadonly history?: import('./history').HistoryOption\n\t/**\n\t * Optional non-secret release identity to include in every HealthSnapshot.\n\t *\n\t * Accepts a static object or a synchronous resolver. Resolver failures are\n\t * swallowed: release metadata must never block the load-bearing health\n\t * response.\n\t */\n\treadonly release?: HealthReleaseInput\n}\n","/**\n * Universal HTTP handler for `@sylphx/sdk/health` (ADR-111 §4.2).\n *\n * Framework-agnostic: returns either a `Web Fetch API` `Response`\n * (Hono / Bun.serve / itty-router / Next.js routes) or, via thin adapters,\n * a Node `(req, res) => void` (Express / Fastify-as-classic-handler).\n *\n * The handler returns HTTP 200 for scores at or above the direct-probe\n * liveness floor and HTTP 503 below it. The response body is always the\n * complete HealthSnapshot, so the ADR-111 sidecar can still read the score\n * even when direct kubelet probes need a status-coded failure.\n */\n\nimport type { HealthScore } from './types'\n\nconst DIRECT_PROBE_LIVENESS_FLOOR = 0.5\n\nfunction statusForScore(score: number): 200 | 503 {\n\treturn Number.isFinite(score) && score >= DIRECT_PROBE_LIVENESS_FLOOR ? 200 : 503\n}\n\n/**\n * Minimal contract a `health` instance must expose for the handler to\n * call. The full `health` object satisfies this implicitly via\n * `evaluate()` from `./index.ts`.\n */\nexport interface HealthEvaluator {\n\tevaluate(): Promise<HealthScore>\n}\n\n/**\n * Build a Web-Fetch-style handler. Suitable for:\n * - Hono: `app.get('/healthz', health.handler())`\n * - Bun.serve: `fetch: health.handler()`\n * - Next.js route.ts: `export const GET = health.handler()`\n * - itty-router / Hattip / standard `(Request) => Response` runtimes\n */\nexport function createWebHandler(source: HealthEvaluator): (req?: Request) => Promise<Response> {\n\treturn async (_req?: Request): Promise<Response> => {\n\t\ttry {\n\t\t\tconst score = await source.evaluate()\n\t\t\treturn new Response(JSON.stringify(score), {\n\t\t\t\tstatus: statusForScore(score.score),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t// Cache-Control: never cache. Even 1-second cache on a\n\t\t\t\t\t// CDN edge would mask a fast-moving health collapse.\n\t\t\t\t\t'Cache-Control': 'no-store, no-cache, must-revalidate',\n\t\t\t\t},\n\t\t\t})\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err)\n\t\t\treturn new Response(\n\t\t\t\tJSON.stringify({\n\t\t\t\t\terror: 'health_evaluator_failed',\n\t\t\t\t\tmessage,\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\tstatus: 500,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t'Cache-Control': 'no-store, no-cache, must-revalidate',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t}\n}\n\n/**\n * Build a Node-style `(req, res)` handler — for Express / classic Fastify.\n *\n * Adapter that delegates to `createWebHandler()` so logic stays in one\n * place. Imported lazily by callers that need it; doesn't drag any node\n * types into Web-only code paths.\n */\nexport interface NodeIncoming {\n\tmethod?: string\n\turl?: string\n}\nexport interface NodeOutgoing {\n\tstatusCode: number\n\tsetHeader(name: string, value: string): void\n\tend(body?: string): void\n}\nexport function createNodeHandler(\n\tsource: HealthEvaluator,\n): (req: NodeIncoming, res: NodeOutgoing) => Promise<void> {\n\treturn async (_req, res) => {\n\t\ttry {\n\t\t\tconst score = await source.evaluate()\n\t\t\tres.statusCode = statusForScore(score.score)\n\t\t\tres.setHeader('Content-Type', 'application/json')\n\t\t\tres.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')\n\t\t\tres.end(JSON.stringify(score))\n\t\t} catch (err) {\n\t\t\tconst message = err instanceof Error ? err.message : String(err)\n\t\t\tres.statusCode = 500\n\t\t\tres.setHeader('Content-Type', 'application/json')\n\t\t\tres.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate')\n\t\t\tres.end(JSON.stringify({ error: 'health_evaluator_failed', message }))\n\t\t}\n\t}\n}\n","/**\n * Audit-grade health history for `@sylphx/sdk/health` (ADR-144).\n *\n * Every `evaluate()` produces a tamper-evident `HistoryEntry` linked to\n * the previous entry via a SHA-256 prev-hash chain (the same primitive\n * AWS CloudTrail uses for digest-based log file integrity validation).\n * Operators run `sylphx health verify` to walk the chain offline and\n * detect any post-hoc tampering.\n *\n * # Storage is pluggable\n *\n * The SDK ships an `InMemoryHistoryStore` (ring buffer, FIFO eviction)\n * as the reference implementation. Customers wanting compliance-grade\n * durability (SOC 2 Type II, GDPR Article 32, HIPAA 164.312(b)) plug\n * their own persistent `HistoryStore` — a thin interface that the SDK\n * calls with each new entry. Adapter packages for Postgres / S3 / cold\n * storage ship separately.\n *\n * # Cardinal rule (carried from ADR-141 / 142 / 143)\n *\n * Recording failures never throw upstream. The `/healthz` response is\n * the load-bearing contract; the history record is observability\n * augmentation. A store that throws during `append()` produces a logged\n * warning (in dev) and is otherwise a no-op for that evaluation.\n *\n * # Wire format normative\n *\n * Per ADR-144 §3 — sorted-key canonical signal JSON, base16 SHA-256\n * hashes, six-decimal-place score precision, `|` separator for the\n * entry-hash preimage. Stable from day one.\n */\n\nimport type { HealthScore } from './types'\n\n// ─── Public types ───────────────────────────────────────────────────────────\n\n/**\n * One tamper-evident history entry. The `prevHash` links to the previous\n * entry's `entryHash`; entry 0 (`sequence === 0`) uses the genesis\n * placeholder `\"0\".repeat(64)`. Stable wire shape — additive evolution\n * only.\n */\nexport interface HistoryEntry {\n\t/** Monotonic, 0-indexed per service. */\n\treadonly sequence: number\n\t/** ISO-8601 timestamp matching `HealthScore.lastTickAt`. */\n\treadonly evaluatedAt: string\n\t/** Aggregate score in `[0, 1]`. */\n\treadonly score: number\n\t/** Base16 SHA-256 of canonicalized signal map. */\n\treadonly signalsDigest: string\n\t/** Base16 SHA-256 of previous entry's `entryHash` (`\"0\"*64` at sequence 0). */\n\treadonly prevHash: string\n\t/** Base16 SHA-256 of `(sequence|evaluatedAt|score|signalsDigest|prevHash)`. */\n\treadonly entryHash: string\n}\n\n/** Query for `HistoryStore.range`. */\nexport interface HistoryQuery {\n\t/** Inclusive lower sequence. Omit for \"from the beginning available\". */\n\treadonly fromSequence?: number\n\t/** Inclusive upper sequence. Omit for \"to the most recent\". */\n\treadonly toSequence?: number\n\t/** Inclusive lower timestamp (ISO-8601). */\n\treadonly fromTimestamp?: string\n\t/** Inclusive upper timestamp (ISO-8601). */\n\treadonly toTimestamp?: string\n\t/** Maximum entries returned. Implementations may cap further. */\n\treadonly limit?: number\n}\n\n/** Pluggable persistence adapter. Implementations may be sync or async. */\nexport interface HistoryStore {\n\t/** Append one entry. MUST persist before resolving for compliance use. */\n\tappend(entry: HistoryEntry): Promise<void> | void\n\t/** Retrieve entries matching `query` in ascending sequence order. */\n\trange(query: HistoryQuery): Promise<readonly HistoryEntry[]> | readonly HistoryEntry[]\n\t/** Most recent entry, or null when empty. */\n\tlatest(): Promise<HistoryEntry | null> | HistoryEntry | null\n\t/** Optional capacity hint for ring-buffer stores. */\n\treadonly capacity?: number\n}\n\n/** Outcome of chain verification — `verified: false` always carries a reason. */\nexport interface VerificationResult {\n\treadonly verified: boolean\n\treadonly entriesChecked: number\n\treadonly firstBreakAtSequence: number | null\n\treadonly reason: string | null\n}\n\n/** Options for the in-memory reference store. */\nexport interface InMemoryHistoryStoreOptions {\n\t/** Ring-buffer capacity. Default 1000. */\n\treadonly capacity?: number\n}\n\n/** Options for the recorder bound to a `sylphxHealth` instance. */\nexport interface HistoryRecorderOptions {\n\t/** Underlying store. Defaults to `InMemoryHistoryStore`. */\n\treadonly store?: HistoryStore\n\t/** Capacity passed to the default in-memory store. Default 1000. */\n\treadonly capacity?: number\n}\n\n/** The flag value passed via `sylphxHealth({ history })`. */\nexport type HistoryOption = boolean | HistoryRecorderOptions\n\n/** Genesis prev-hash for the very first entry per service. */\nexport const GENESIS_PREV_HASH = '0'.repeat(64)\n\n/** Default ring-buffer size when callers don't override. */\nexport const DEFAULT_HISTORY_CAPACITY = 1000\n\nconst HASH_TEXT_ENCODER = new TextEncoder()\nconst HEX_BYTE = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))\n\n// ─── Canonicalization + hashing ─────────────────────────────────────────────\n\n/**\n * Sorted-key JSON canonicalization. Same signals in different insertion\n * order produce the same string — guarantees `signalsDigest` stability\n * across V8 / Bun / browser JSON.stringify implementations.\n */\nexport function canonicalizeSignals(signals: Record<string, number | string | boolean>): string {\n\tconst keys = Object.keys(signals).sort()\n\tconst pairs: Array<[string, number | string | boolean]> = keys.map((k) => {\n\t\t// Defensive: signals[k] could be undefined if caller passed a sparse\n\t\t// map; fall back to empty string. JSON.stringify would otherwise\n\t\t// drop the key silently.\n\t\tconst value = signals[k]\n\t\treturn [k, value as number | string | boolean]\n\t})\n\treturn JSON.stringify(Object.fromEntries(pairs))\n}\n\n/**\n * Six-decimal-place float encoding for the score. Picked because:\n * - JavaScript Number → fixed-precision string is identical across V8,\n * Bun, and SpiderMonkey at 6 decimals.\n * - 6 decimals == 1 part in 10^6, well below the noise floor of any\n * real health signal.\n */\nfunction encodeScore(score: number): string {\n\treturn score.toFixed(6)\n}\n\n/**\n * `crypto.subtle.digest` is the cross-runtime SHA-256 primitive. Available\n * in Node ≥19, Bun, browsers, Cloudflare Workers, Vercel Edge. We do not\n * require a peer dep on `node:crypto` — when `crypto.subtle` is missing\n * (rare; very old Node), the recorder gracefully degrades to entries with\n * empty hashes and the verifier reports \"no hash chain available.\"\n */\nasync function sha256Hex(input: string): Promise<string> {\n\tconst subtle = (globalThis as { crypto?: { subtle?: SubtleCrypto } }).crypto?.subtle\n\tif (!subtle || typeof subtle.digest !== 'function') {\n\t\treturn ''\n\t}\n\tconst buf = HASH_TEXT_ENCODER.encode(input)\n\tconst digest = await subtle.digest('SHA-256', buf)\n\tconst bytes = new Uint8Array(digest)\n\tlet out = ''\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tout += HEX_BYTE[bytes[i] as number]\n\t}\n\treturn out\n}\n\n/**\n * Compute the canonical entry-hash from the entry's prehash fields.\n * Separator is `|`; none of the input fields contain it (sequence digits,\n * ISO-8601 timestamp, fixed-precision float, hex hashes).\n */\nasync function computeEntryHash(prehash: Omit<HistoryEntry, 'entryHash'>): Promise<string> {\n\tconst canonical = [\n\t\tprehash.sequence.toString(10),\n\t\tprehash.evaluatedAt,\n\t\tencodeScore(prehash.score),\n\t\tprehash.signalsDigest,\n\t\tprehash.prevHash,\n\t].join('|')\n\treturn await sha256Hex(canonical)\n}\n\n// ─── InMemoryHistoryStore — ring buffer reference impl ─────────────────────\n\nclass InMemoryHistoryStoreImpl implements HistoryStore {\n\treadonly capacity: number\n\tprivate buf: HistoryEntry[] = []\n\n\tconstructor(opts: InMemoryHistoryStoreOptions = {}) {\n\t\tconst cap = opts.capacity ?? DEFAULT_HISTORY_CAPACITY\n\t\tif (!Number.isFinite(cap) || cap <= 0 || cap > 10_000_000) {\n\t\t\tthrow new Error(`InMemoryHistoryStore: capacity must be in (0, 10000000], got ${cap}`)\n\t\t}\n\t\tthis.capacity = cap\n\t}\n\n\tappend(entry: HistoryEntry): void {\n\t\tthis.buf.push(entry)\n\t\tif (this.buf.length > this.capacity) {\n\t\t\tthis.buf.shift()\n\t\t}\n\t}\n\n\trange(query: HistoryQuery): readonly HistoryEntry[] {\n\t\tconst out: HistoryEntry[] = []\n\t\tfor (const e of this.buf) {\n\t\t\tif (query.fromSequence !== undefined && e.sequence < query.fromSequence) continue\n\t\t\tif (query.toSequence !== undefined && e.sequence > query.toSequence) continue\n\t\t\tif (query.fromTimestamp !== undefined && e.evaluatedAt < query.fromTimestamp) continue\n\t\t\tif (query.toTimestamp !== undefined && e.evaluatedAt > query.toTimestamp) continue\n\t\t\tout.push(e)\n\t\t\tif (query.limit !== undefined && out.length >= query.limit) break\n\t\t}\n\t\treturn out\n\t}\n\n\tlatest(): HistoryEntry | null {\n\t\treturn this.buf[this.buf.length - 1] ?? null\n\t}\n}\n\n/** Build an in-memory ring-buffer history store. */\nexport function createInMemoryHistoryStore(opts: InMemoryHistoryStoreOptions = {}): HistoryStore {\n\treturn new InMemoryHistoryStoreImpl(opts)\n}\n\n// ─── HistoryRecorder — bound to one sylphxHealth instance ──────────────────\n\n/**\n * Records every `HealthScore` as a hash-chained entry. Owns a single\n * `nextSequence` counter + the `prevHash` value carried forward across\n * recordings. The recorder is per-instance (per-service); the chain is\n * never shared across services.\n */\nexport interface HistoryRecorder {\n\t/** Backing store. Exposed for advanced callers (e.g. tests + verification). */\n\treadonly store: HistoryStore\n\t/** Record one score. Resolves to the persisted entry (or null on failure). */\n\trecord(score: HealthScore): Promise<HistoryEntry | null>\n\t/** Convenience: store.range. */\n\trange(query: HistoryQuery): Promise<readonly HistoryEntry[]>\n\t/** Convenience: store.latest. */\n\tlatest(): Promise<HistoryEntry | null>\n\t/**\n\t * Verify the chain over a query window. Walks entries in sequence\n\t * order, recomputing each `entryHash` and confirming it matches the\n\t * next entry's `prevHash`. Returns the first break with a reason.\n\t */\n\tverify(query: HistoryQuery): Promise<VerificationResult>\n\t/** Tear down internal references. Idempotent. */\n\tdispose(): void\n}\n\nclass HistoryRecorderImpl implements HistoryRecorder {\n\treadonly store: HistoryStore\n\tprivate nextSequence = 0\n\tprivate prevHash: string = GENESIS_PREV_HASH\n\tprivate disposed = false\n\tprivate initPromise: Promise<void>\n\t/**\n\t * Serializes record() calls so concurrent `evaluate()` invocations\n\t * don't race on `nextSequence` / `prevHash`. Each new call chains off\n\t * the previous; the chain itself never propagates errors (catch in the\n\t * tail so one failed record doesn't poison subsequent ones).\n\t */\n\tprivate inFlight: Promise<unknown> = Promise.resolve()\n\n\tconstructor(opts: HistoryRecorderOptions) {\n\t\tthis.store = opts.store ?? createInMemoryHistoryStore({ capacity: opts.capacity })\n\t\t// Best-effort warm start: if the store already has entries (e.g. a\n\t\t// persistent adapter), continue from the latest. Failures fall back\n\t\t// to \"fresh chain\" — a defensible default since the cardinal rule\n\t\t// forbids throwing.\n\t\tthis.initPromise = this.warmStart()\n\t}\n\n\tprivate async warmStart(): Promise<void> {\n\t\ttry {\n\t\t\tconst latest = await this.store.latest()\n\t\t\tif (latest && Number.isFinite(latest.sequence)) {\n\t\t\t\tthis.nextSequence = latest.sequence + 1\n\t\t\t\tthis.prevHash = latest.entryHash || GENESIS_PREV_HASH\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cardinal rule.\n\t\t}\n\t}\n\n\tprivate async recordInternal(score: HealthScore): Promise<HistoryEntry | null> {\n\t\tif (this.disposed) return null\n\t\tawait this.initPromise\n\t\ttry {\n\t\t\tconst signalsDigest = await sha256Hex(canonicalizeSignals(score.signals))\n\t\t\tconst prehash: Omit<HistoryEntry, 'entryHash'> = {\n\t\t\t\tsequence: this.nextSequence,\n\t\t\t\tevaluatedAt: score.lastTickAt,\n\t\t\t\tscore: score.score,\n\t\t\t\tsignalsDigest,\n\t\t\t\tprevHash: this.prevHash,\n\t\t\t}\n\t\t\tconst entryHash = await computeEntryHash(prehash)\n\t\t\tconst entry: HistoryEntry = { ...prehash, entryHash }\n\t\t\tawait this.store.append(entry)\n\t\t\tthis.nextSequence += 1\n\t\t\tthis.prevHash = entryHash || GENESIS_PREV_HASH\n\t\t\treturn entry\n\t\t} catch {\n\t\t\t// Cardinal rule.\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync record(score: HealthScore): Promise<HistoryEntry | null> {\n\t\t// Chain off the previous in-flight record so concurrent\n\t\t// `evaluate()` calls (e.g. multiple kubelet probes within one\n\t\t// async tick) produce a deterministic, gap-free sequence.\n\t\tconst job = this.inFlight.then(() => this.recordInternal(score))\n\t\t// Swallow errors in the queue so one failure doesn't poison the\n\t\t// rest. recordInternal itself already returns null on failure;\n\t\t// catch here is defensive for any unexpected synchronous throw.\n\t\tthis.inFlight = job.catch(() => undefined)\n\t\treturn await job\n\t}\n\n\tprivate async drainPendingRecords(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.inFlight\n\t\t} catch {\n\t\t\t// Defensive only: recordInternal is non-throwing and the queue\n\t\t\t// tail already swallows failures.\n\t\t}\n\t}\n\n\tasync range(query: HistoryQuery): Promise<readonly HistoryEntry[]> {\n\t\tawait this.drainPendingRecords()\n\t\ttry {\n\t\t\treturn await this.store.range(query)\n\t\t} catch {\n\t\t\treturn []\n\t\t}\n\t}\n\n\tasync latest(): Promise<HistoryEntry | null> {\n\t\tawait this.drainPendingRecords()\n\t\ttry {\n\t\t\treturn await this.store.latest()\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\tasync verify(query: HistoryQuery): Promise<VerificationResult> {\n\t\tconst entries = await this.range(query)\n\t\treturn await verifyChain(entries)\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true\n\t}\n}\n\n/** Build a recorder bound to a (defaulted) in-memory store. */\nexport function createHistoryRecorder(opts: HistoryRecorderOptions = {}): HistoryRecorder {\n\treturn new HistoryRecorderImpl(opts)\n}\n\n// ─── Verifier — walks a chain and reports tampering ────────────────────────\n\n/**\n * Walk an ordered (by sequence) chain of entries, recomputing each\n * `entryHash` and confirming it matches the next entry's `prevHash`.\n * Returns the first break encountered.\n *\n * Edge cases:\n * - Empty input → `verified: true, entriesChecked: 0`. Vacuous truth.\n * - First entry has `prevHash !== GENESIS_PREV_HASH` AND `sequence === 0`\n * → break (\"genesis prev-hash mismatch\").\n * - First entry's `sequence > 0` but `prevHash === GENESIS_PREV_HASH`\n * → this is a sub-window query; we accept and chain forward from\n * `entries[0]`.\n */\nexport async function verifyChain(\n\tentries: ReadonlyArray<HistoryEntry>,\n): Promise<VerificationResult> {\n\tif (entries.length === 0) {\n\t\treturn { verified: true, entriesChecked: 0, firstBreakAtSequence: null, reason: null }\n\t}\n\tlet expectedPrev: string | null = null\n\tlet checked = 0\n\tfor (let i = 0; i < entries.length; i++) {\n\t\tconst entry = entries[i] as HistoryEntry\n\t\t// 1. First-entry structural check: genesis sequence MUST carry\n\t\t// the canonical genesis prev-hash. Runs BEFORE the entryHash\n\t\t// recompute so callers can build a hand-crafted entry and see\n\t\t// a clear \"genesis entry...\" failure rather than the more\n\t\t// cryptic entryHash-mismatch message.\n\t\tif (i === 0 && entry.sequence === 0 && entry.prevHash !== GENESIS_PREV_HASH) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: checked,\n\t\t\t\tfirstBreakAtSequence: entry.sequence,\n\t\t\t\treason: `genesis entry must have prevHash=\"${GENESIS_PREV_HASH}\", got ${entry.prevHash}`,\n\t\t\t}\n\t\t}\n\t\t// 2. Inter-entry prev-hash chain check: each entry except the\n\t\t// window's first MUST link to the previous entry's entryHash.\n\t\tif (i > 0 && expectedPrev !== null && entry.prevHash !== expectedPrev) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: checked,\n\t\t\t\tfirstBreakAtSequence: entry.sequence,\n\t\t\t\treason: `prevHash mismatch at sequence ${entry.sequence}: expected ${expectedPrev}, got ${entry.prevHash}`,\n\t\t\t}\n\t\t}\n\t\t// 3. Per-entry hash integrity: recompute and compare.\n\t\tconst recomputedHash = await computeEntryHash({\n\t\t\tsequence: entry.sequence,\n\t\t\tevaluatedAt: entry.evaluatedAt,\n\t\t\tscore: entry.score,\n\t\t\tsignalsDigest: entry.signalsDigest,\n\t\t\tprevHash: entry.prevHash,\n\t\t})\n\t\tif (entry.entryHash && recomputedHash && recomputedHash !== entry.entryHash) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: checked,\n\t\t\t\tfirstBreakAtSequence: entry.sequence,\n\t\t\t\treason: `entryHash mismatch at sequence ${entry.sequence}: stored=${entry.entryHash} recomputed=${recomputedHash}`,\n\t\t\t}\n\t\t}\n\t\texpectedPrev = entry.entryHash || null\n\t\tchecked += 1\n\t}\n\treturn {\n\t\tverified: true,\n\t\tentriesChecked: checked,\n\t\tfirstBreakAtSequence: null,\n\t\treason: null,\n\t}\n}\n","/**\n * OpenTelemetry emission for `@sylphx/sdk/health` (ADR-142).\n *\n * Every `evaluate()` produces a `HealthScore`. This module turns that score\n * into three OpenTelemetry signals:\n *\n * 1. `health.score` — synchronous Gauge, range [0, 1]\n * 2. `health.signal.factor` — per-signal Gauge, range [0, 1]\n * 3. `health.signal.value` — per-signal numeric Gauge (when value is a\n * finite number)\n * 3b. `health.signal.value_state` — per-signal Counter (when value is a\n * string label like \"ok\" / \"timeout\")\n *\n * If a span is active in OTel context when `emit()` runs, the score is also\n * set as a span attribute (`service.health.score`) and — when `events: true`\n * — a `health.evaluated` span event is recorded.\n *\n * # Graceful fallback\n *\n * `@opentelemetry/api` is an **optional** peer dependency. The module loads\n * it via dynamic `import()` inside a try/catch. When the import fails (the\n * consumer's project did not install OTel), every emit() call is a no-op —\n * no warnings, no exceptions, no peer-dep requirement.\n *\n * # Lifecycle\n *\n * The emitter starts in `loading` state. It kicks off the async OTel API\n * load in the background. Until the load resolves (or fails), `emit()` is a\n * no-op. For probe paths that run every 10s, the first probe may miss\n * emission but the second probe is guaranteed to emit if OTel is available.\n *\n * # Performance budget\n *\n * `emit()` p99 < 500µs when ready, < 50ns when in loading / unavailable\n * state. Measured by the OTel benchmark in `__tests__/otel-emission.test.ts`.\n */\n\n// Compile-time-only types — these `import type` statements are erased at\n// emit time, so consumers without @opentelemetry/api installed do not see a\n// missing-module error at module load.\nimport type { Attributes, Counter, Gauge, Meter, TraceAPI, Tracer } from '@opentelemetry/api'\nimport type { UpstreamCause } from './causality'\nimport type { HealthScore } from './types'\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Static attributes the emitter merges onto every metric / event /\n * span-attribute it produces. Use to stamp deployment metadata\n * (`deployment.env`, `git.sha`, …) that the OTel resource detector\n * doesn't pick up automatically.\n */\nexport type OtelStaticAttributes = Readonly<Record<string, string | number | boolean>>\n\nexport interface OtelEmitterOptions {\n\t/**\n\t * Pre-built OTel Meter. When provided, the emitter uses it directly and\n\t * skips the async OTel API load. Common case: tests, sandboxes, multi-\n\t * tenant runtimes wiring a custom MeterProvider per request.\n\t */\n\treadonly meter?: Meter\n\t/**\n\t * Pre-built OTel Tracer. Same intent as `meter`. The emitter uses the\n\t * tracer only to read the active span when setting attributes /\n\t * events; it does not create new spans.\n\t */\n\treadonly tracer?: Tracer\n\t/**\n\t * When true, the emitter records a `health.evaluated` span event on\n\t * every evaluation (if a span is active). Default false — high-\n\t * frequency probe paths produce many events and most operators only\n\t * need the metric.\n\t */\n\treadonly events?: boolean\n\t/** Static attributes merged onto every emitted metric / span attribute. */\n\treadonly attributes?: OtelStaticAttributes\n}\n\n/**\n * The flag value passed by callers via `sylphxHealth({ otel })` /\n * `withHealth.hono({ otel })`. `true` (default) enables emission with\n * sensible defaults; `false` disables it; an object configures it.\n */\nexport type OtelOption = boolean | OtelEmitterOptions\n\nexport interface OtelEmitter {\n\t/**\n\t * Emit metrics / span attributes / event for one HealthScore.\n\t * `upstreamCause` (ADR-143) carries the parsed causality baggage so the\n\t * emitter can record `health.downstream_cause` counter +\n\t * `service.health.cause_*` span attributes. Non-throwing.\n\t */\n\temit(score: HealthScore, upstreamCause?: UpstreamCause | null): void\n\t/**\n\t * Tear down any cached instruments. Called from `sylphxHealth.dispose()`\n\t * during graceful shutdown. The OTel SDK owns the underlying meter\n\t * lifecycle; this method only releases the emitter's caches.\n\t */\n\tdispose(): void\n\t/** Readiness state — exposed for tests. `loading` is transient; `ready` and `unavailable` are terminal. */\n\treadonly state: 'loading' | 'ready' | 'unavailable' | 'disposed'\n}\n\nconst SDK_NAME = '@sylphx/sdk/health'\nconst SDK_VERSION = '0.11.0'\n\n/** Metric names — pinned by ADR-142 §3.1 + ADR-143 §3.3, stable from day one. */\nexport const METRIC_NAMES = {\n\tscore: 'health.score',\n\tsignalFactor: 'health.signal.factor',\n\tsignalValue: 'health.signal.value',\n\tsignalValueState: 'health.signal.value_state',\n\t/** ADR-143 §3.3 — increments per evaluation when an upstream cause is observed. */\n\tdownstreamCause: 'health.downstream_cause',\n} as const\n\n/** Span attribute names — pinned by ADR-142 §2.1.3 + ADR-143 §3.2, stable from day one. */\nexport const SPAN_ATTRIBUTES = {\n\tscore: 'service.health.score',\n\tsignalFactor: (name: string) => `service.health.signal.${name}.factor`,\n\t/** ADR-143 §3.2 — the upstream service that originated the failure chain. */\n\tcauseRoot: 'service.health.cause_root',\n\t/** ADR-143 §3.2 — the signal at the root that failed. */\n\tcauseSignal: 'service.health.cause_signal',\n\t/** ADR-143 §3.2 — comma-delimited hop chain (oldest to newest). */\n\tcauseChain: 'service.health.cause_chain',\n} as const\n\n/** Span event name — pinned by ADR-142 §2.1.4. */\nexport const EVENT_NAMES = {\n\tevaluated: 'health.evaluated',\n} as const\n\n/**\n * Build an OTel emitter.\n *\n * Returns a sealed emitter object whose `emit()` is initially a no-op\n * while the OTel API loads in the background. Once loaded, all subsequent\n * `emit()` calls produce metrics / span attributes / events per ADR-142.\n *\n * When `@opentelemetry/api` is not installed in the host project (the\n * common case for hobby projects), the emitter transitions to\n * `unavailable` and all `emit()` calls remain no-ops for the lifetime of\n * the instance. No exceptions, no warnings.\n */\nexport function createOtelEmitter(opts: OtelEmitterOptions = {}): OtelEmitter {\n\treturn new OtelEmitterImpl(opts)\n}\n\n// ─── Implementation ──────────────────────────────────────────────────────────\n\ninterface CachedInstruments {\n\treadonly scoreGauge: Gauge<Attributes>\n\treadonly factorGauge: Gauge<Attributes>\n\treadonly valueGauge: Gauge<Attributes>\n\treadonly valueStateCounter: Counter<Attributes>\n\treadonly downstreamCauseCounter: Counter<Attributes>\n}\n\nclass OtelEmitterImpl implements OtelEmitter {\n\tprivate _state: 'loading' | 'ready' | 'unavailable' | 'disposed' = 'loading'\n\tprivate instruments: CachedInstruments | null = null\n\tprivate traceApi: TraceAPI | null = null\n\tprivate readonly staticAttrs: OtelStaticAttributes\n\tprivate readonly events: boolean\n\n\tconstructor(private readonly opts: OtelEmitterOptions) {\n\t\tthis.staticAttrs = opts.attributes ?? {}\n\t\tthis.events = opts.events === true\n\t\tif (opts.meter) {\n\t\t\tthis.instruments = buildInstruments(opts.meter)\n\t\t\t// User-supplied tracer cannot give us the `getActiveSpan()` lookup\n\t\t\t// (that lives on the `trace` API namespace, not on a Tracer\n\t\t\t// instance). We still expose span correlation via the\n\t\t\t// `tracer.startActiveSpan` path when the user wires their own\n\t\t\t// tracer, falling back to no-correlation when they don't pass\n\t\t\t// the full namespace. The async loader path always picks up the\n\t\t\t// namespace from `@opentelemetry/api`.\n\t\t\tthis._state = 'ready'\n\t\t\treturn\n\t\t}\n\t\tvoid this.initialize()\n\t}\n\n\tget state(): OtelEmitter['state'] {\n\t\treturn this._state\n\t}\n\n\tprivate async initialize(): Promise<void> {\n\t\tconst api = await tryLoadOtelApi()\n\t\tif (api === null) {\n\t\t\tthis._state = 'unavailable'\n\t\t\treturn\n\t\t}\n\t\t// Late-state guard: dispose() may have flipped `_state` to 'disposed'\n\t\t// while the dynamic import was in flight. Honour the disposal.\n\t\tif (this._state === 'disposed') return\n\t\ttry {\n\t\t\tconst meter = api.metrics.getMeter(SDK_NAME, SDK_VERSION)\n\t\t\tthis.instruments = buildInstruments(meter)\n\t\t\tthis.traceApi = api.trace\n\t\t\tthis._state = 'ready'\n\t\t} catch {\n\t\t\t// OTel API loaded but instrument creation failed — host\n\t\t\t// environment surfaces a partial / broken implementation\n\t\t\t// (browser polyfill, edge runtime stub). Treat as unavailable\n\t\t\t// rather than leaving the emitter wedged in 'loading'.\n\t\t\tthis._state = 'unavailable'\n\t\t}\n\t}\n\n\temit(score: HealthScore, upstreamCause?: UpstreamCause | null): void {\n\t\tif (this._state !== 'ready' || !this.instruments) return\n\t\tconst baseAttrs: Attributes = { ...this.staticAttrs }\n\t\ttry {\n\t\t\t// 1. Top-level score gauge.\n\t\t\tthis.instruments.scoreGauge.record(score.score, baseAttrs)\n\n\t\t\t// 2. Per-signal metrics.\n\t\t\tfor (const [signalName, rawValue] of Object.entries(score.signals)) {\n\t\t\t\tconst signalAttrs: Attributes = { ...baseAttrs, 'signal.name': signalName }\n\t\t\t\tconst factor = score.signalFactors?.[signalName]\n\t\t\t\tif (typeof factor === 'number' && Number.isFinite(factor)) {\n\t\t\t\t\tthis.instruments.factorGauge.record(factor, signalAttrs)\n\t\t\t\t}\n\n\t\t\t\tif (typeof rawValue === 'number' && Number.isFinite(rawValue)) {\n\t\t\t\t\tthis.instruments.valueGauge.record(rawValue, signalAttrs)\n\t\t\t\t} else {\n\t\t\t\t\tconst stateAttrs: Attributes = {\n\t\t\t\t\t\t...signalAttrs,\n\t\t\t\t\t\tstate: String(rawValue),\n\t\t\t\t\t}\n\t\t\t\t\tthis.instruments.valueStateCounter.add(1, stateAttrs)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 3. ADR-143 — upstream cause counter + span attributes.\n\t\t\tif (upstreamCause && upstreamCause.causes.length > 0) {\n\t\t\t\tfor (const cause of upstreamCause.causes) {\n\t\t\t\t\tthis.instruments.downstreamCauseCounter.add(1, {\n\t\t\t\t\t\t...baseAttrs,\n\t\t\t\t\t\tcause_root: cause.service,\n\t\t\t\t\t\tcause_signal: cause.signal,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// 4. Active-span correlation (trace + event + cause attributes).\n\t\t\tif (this.traceApi) {\n\t\t\t\tconst span = this.traceApi.getActiveSpan?.()\n\t\t\t\tmaybeAttachToSpan(span, score, this.events, upstreamCause)\n\t\t\t}\n\t\t} catch {\n\t\t\t// Cardinal rule (ADR-141 §3.3.5): emission must never throw — the\n\t\t\t// /healthz response is the load-bearing surface, not the metric.\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis._state = 'disposed'\n\t\tthis.instruments = null\n\t\tthis.traceApi = null\n\t}\n}\n\n// ─── OTel API loader ─────────────────────────────────────────────────────────\n\nlet cachedApi: typeof import('@opentelemetry/api') | null | undefined\n\n/**\n * Lazy load `@opentelemetry/api`. Returns the module on success, null when\n * the package is not installed. Cached across emitters so we only attempt\n * the dynamic import once per process.\n *\n * Exported for tests; production code should call `createOtelEmitter()`.\n */\nexport async function tryLoadOtelApi(): Promise<typeof import('@opentelemetry/api') | null> {\n\tif (cachedApi !== undefined) return cachedApi\n\ttry {\n\t\tcachedApi = await import('@opentelemetry/api')\n\t} catch {\n\t\tcachedApi = null\n\t}\n\treturn cachedApi\n}\n\n/** Test-only escape hatch: forget the cached load so a subsequent test can\n * observe a fresh attempt (with a mocked dynamic import). */\nexport function __resetOtelApiCacheForTests(): void {\n\tcachedApi = undefined\n}\n\n// ─── Instrument construction ─────────────────────────────────────────────────\n\nfunction buildInstruments(meter: Meter): CachedInstruments {\n\treturn {\n\t\tscoreGauge: meter.createGauge(METRIC_NAMES.score, {\n\t\t\tdescription: 'Multi-signal health score per ADR-141 §4. Range [0, 1]. Higher = healthier.',\n\t\t\tunit: '1',\n\t\t}),\n\t\tfactorGauge: meter.createGauge(METRIC_NAMES.signalFactor, {\n\t\t\tdescription:\n\t\t\t\t'Per-signal health factor; folded into health.score by the SDK scoring strategy (default: weightedProduct, ADR-111 §4.3).',\n\t\t\tunit: '1',\n\t\t}),\n\t\tvalueGauge: meter.createGauge(METRIC_NAMES.signalValue, {\n\t\t\tdescription:\n\t\t\t\t'Raw observation behind a health signal (ms, count, rate). Numeric values only; string-valued signals emit health.signal.value_state instead.',\n\t\t}),\n\t\tvalueStateCounter: meter.createCounter(METRIC_NAMES.signalValueState, {\n\t\t\tdescription:\n\t\t\t\t'Per-evaluation counter for non-numeric signal readings (e.g. \"ok\" / \"timeout\"). state attribute carries the string label.',\n\t\t}),\n\t\tdownstreamCauseCounter: meter.createCounter(METRIC_NAMES.downstreamCause, {\n\t\t\tdescription:\n\t\t\t\t'Per-evaluation counter incremented when this service observes an upstream cause via OTel baggage (ADR-143). Operators read rate() to size the blast radius of an incident.',\n\t\t}),\n\t}\n}\n\n// ─── Span attribute / event attachment ───────────────────────────────────────\n\n/**\n * Active-span correlation. Sets `service.health.score` on the active span\n * if any. Records `health.evaluated` event when `events` is true.\n *\n * The span parameter is typed as `unknown` because `Tracer.getActiveSpan()`\n * is only on the modern (v1.9+) API surface; older OTel APIs lack it. We\n * structurally probe for the methods we use.\n */\nfunction maybeAttachToSpan(\n\tspan: unknown,\n\tscore: HealthScore,\n\tevents: boolean,\n\tupstreamCause: UpstreamCause | null | undefined,\n): void {\n\tif (!span || typeof span !== 'object') return\n\tconst setAttribute = (span as { setAttribute?: (k: string, v: unknown) => void }).setAttribute\n\tif (typeof setAttribute === 'function') {\n\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.score, score.score)\n\t\tfor (const [signalName, factor] of Object.entries(score.signalFactors ?? {})) {\n\t\t\tif (typeof factor === 'number' && Number.isFinite(factor)) {\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.signalFactor(signalName), factor)\n\t\t\t}\n\t\t}\n\t\t// ADR-143 — cause attribution. The primary cause is the first\n\t\t// `<signal>@<service>` in the baggage value (operator dashboards\n\t\t// pivot on the primary). The full hop chain is preserved verbatim\n\t\t// so multi-hop incident paths reconstruct without ambiguity.\n\t\tif (upstreamCause && upstreamCause.causes.length > 0) {\n\t\t\tconst primary = upstreamCause.causes[0]\n\t\t\tif (primary) {\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.causeRoot, primary.service)\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.causeSignal, primary.signal)\n\t\t\t}\n\t\t\tif (upstreamCause.chain.length > 0) {\n\t\t\t\tsetAttribute.call(span, SPAN_ATTRIBUTES.causeChain, upstreamCause.chain.join(','))\n\t\t\t}\n\t\t}\n\t}\n\tif (events) {\n\t\tconst addEvent = (span as { addEvent?: (name: string, attrs?: Attributes) => void }).addEvent\n\t\tif (typeof addEvent === 'function') {\n\t\t\taddEvent.call(span, EVENT_NAMES.evaluated, {\n\t\t\t\t[SPAN_ATTRIBUTES.score]: score.score,\n\t\t\t\t'service.health.lastTickAt': score.lastTickAt,\n\t\t\t})\n\t\t}\n\t}\n}\n","/**\n * Scoring strategies (ADR-111 §4 — three-tier health gate).\n *\n * The default `weightedProduct` strategy is multiplicative — a single bad\n * signal can drag the whole score down (any factor of 0 → score 0). This is\n * the right semantic for **liveness**: one critical subsystem dead means\n * the runtime is not ready, regardless of how healthy the rest is.\n *\n * Mathematics:\n * score = ∏ factor_i ^ weight_i (over signals where weight > 0 AND\n * reading is not `unknown`)\n *\n * Where weights are normalised to sum=1 first, so absolute weight values\n * don't matter — only the ratios do. This keeps user expectations sane:\n * `[w=2, w=2]` and `[w=10, w=10]` produce identical scores.\n *\n * Edge cases (deterministic, never throw):\n * - empty input → 1 (perfect health, nothing to penalise)\n * - all weights = 0 → 1 (no active signals)\n * - all readings `unknown` → 1 (we can't see; cardinal-rule fallback\n * lives at the sidecar boundary, not\n * here — ADR-111 §3.2.5)\n * - factor < 0 / NaN → clamped to 0\n * - factor > 1 → clamped to 1\n * - weight < 0 → clamped to 0 (ignored)\n *\n * The clamps make us **safe by construction** — a misconfigured signal\n * cannot push the score outside `[0, 1]`. Tests exercise all branches.\n */\n\nimport type { ScoringStrategy, Signal, SignalReading } from './types'\n\n/** Clamp `x` to `[0, 1]`, mapping NaN to 0. */\nfunction clamp01(x: number): number {\n\tif (!Number.isFinite(x)) return 0\n\tif (x < 0) return 0\n\tif (x > 1) return 1\n\treturn x\n}\n\n/**\n * Weighted geometric mean — the default scoring strategy.\n *\n * @example\n * weightedProduct([\n * { signal: { name: 'lag', weight: 0.4 }, reading: { healthFactor: 0.4 } },\n * { signal: { name: 'q', weight: 0.6 }, reading: { healthFactor: 1.0 } },\n * ])\n * // → 0.4^0.4 × 1.0^0.6 ≈ 0.693 — ADR-111 §4.5 worked example\n */\nexport const weightedProduct: ScoringStrategy = (readings) => {\n\tif (readings.length === 0) return 1\n\n\t// 1. Filter active signals (weight > 0, reading defined, not unknown).\n\tconst active: Array<{ factor: number; weight: number }> = []\n\tfor (const { signal, reading } of readings) {\n\t\tconst w = signal.weight\n\t\tif (!Number.isFinite(w) || w <= 0) continue\n\t\tif (reading.unknown === true) continue\n\t\tactive.push({\n\t\t\tfactor: clamp01(reading.healthFactor),\n\t\t\tweight: w,\n\t\t})\n\t}\n\tif (active.length === 0) return 1\n\n\t// 2. Normalise weights to sum=1.\n\tconst totalWeight = active.reduce((sum, a) => sum + a.weight, 0)\n\tif (totalWeight <= 0 || !Number.isFinite(totalWeight)) return 1\n\n\t// 3. Geometric mean: exp(Σ w_i × ln(f_i)) — exp/log is more numerically\n\t// stable than repeated `Math.pow` × multiplication for small factors.\n\t// ln(0) → -Infinity → exp(-Infinity) → 0, which is exactly what we\n\t// want (any dead signal kills the score).\n\tlet logSum = 0\n\tfor (const { factor, weight } of active) {\n\t\tconst normalisedWeight = weight / totalWeight\n\t\tif (factor <= 0) {\n\t\t\t// Short-circuit: a single zero factor => score 0, regardless of\n\t\t\t// the others. Avoids ln(0) sentinel value plumbing.\n\t\t\treturn 0\n\t\t}\n\t\tlogSum += normalisedWeight * Math.log(factor)\n\t}\n\tconst score = Math.exp(logSum)\n\treturn clamp01(score)\n}\n\n/**\n * Convenience: build a default scoring strategy.\n *\n * Reserved for future: weighted-min, weighted-mean, etc. For now there's\n * one strategy and `weightedProduct` is the only export — this keeps the\n * surface narrow until a concrete second use-case appears.\n */\nexport function defaultScoringStrategy(): ScoringStrategy {\n\treturn weightedProduct\n}\n\n// Re-export the types used by callers writing custom strategies.\nexport type { ScoringStrategy, Signal, SignalReading }\n","/**\n * `eventLoopLagSignal` — main-thread blocking detector (ADR-111 §4.3).\n *\n * Measures Node/Bun's libuv event-loop delay using `monitorEventLoopDelay()`\n * from `node:perf_hooks`. The monitor is a histogram updated by libuv at a\n * configurable resolution (default 10 ms here — every tick measures lag\n * between expected wake and actual wake). We report the **max** observed\n * since the last `read()` then `reset()` — that's the worst-case stall\n * the app suffered in the polling window.\n *\n * Mapping observed-lag-ms → healthFactor ∈ [0, 1]:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━┓\n * │ ┃ linear interpolation\n * │ ┃\n * 0.0 ┤ ┗━━━━━━━━━━━━━\n * ┼─────────┼──────────┼──────► observed lag (ms)\n * 0 degradedMs deadMs\n *\n * Below `degradedMs` → factor 1 (healthy). Above `deadMs` → factor 0 (dead).\n * In-between → linear interpolation. ADR-111 §4.3 default thresholds:\n * degradedMs = 5000 (5 s — same order as ADR-110's 10 s probe timeout)\n * deadMs = 30000 (30 s — definitely wedged)\n *\n * Bun-compatibility: Bun ships `monitorEventLoopDelay()` with the same\n * shape as Node 16+. Verified against `bun:1.3` in this repo's CI.\n */\n\nimport { monitorEventLoopDelay } from 'node:perf_hooks'\nimport type { SignalReading, SyncSignal } from '../types'\n\nexport interface EventLoopLagOptions {\n\t/** Lag below this is fully healthy (factor=1). Default 5000 ms. */\n\treadonly degradedMs?: number\n\t/** Lag above this is fully dead (factor=0). Default 30000 ms. */\n\treadonly deadMs?: number\n\t/**\n\t * Histogram resolution (ms). Default 10 ms — the smaller, the more\n\t * accurate the max but the higher the libuv accounting overhead.\n\t * Node defaults are also 10 ms.\n\t */\n\treadonly resolutionMs?: number\n\t/** Weight in the weighted-product score. Default 0.4 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/**\n\t * Optional injected monitor (tests). Defaults to a fresh\n\t * `monitorEventLoopDelay()` instance.\n\t */\n\treadonly monitor?: ReturnType<typeof monitorEventLoopDelay>\n}\n\n/**\n * Build an `event-loop-lag` signal. The returned object owns a started\n * histogram monitor; call `dispose()` during shutdown / between tests.\n */\nexport function eventLoopLagSignal(opts: EventLoopLagOptions = {}): SyncSignal {\n\tconst degradedMs = opts.degradedMs ?? 5000\n\tconst deadMs = opts.deadMs ?? 30000\n\tconst resolutionMs = opts.resolutionMs ?? 10\n\tconst weight = opts.weight ?? 0.4\n\n\tif (!Number.isFinite(degradedMs) || degradedMs < 0) {\n\t\tthrow new Error(`eventLoopLagSignal: degradedMs must be >= 0, got ${degradedMs}`)\n\t}\n\tif (!Number.isFinite(deadMs) || deadMs <= degradedMs) {\n\t\tthrow new Error(\n\t\t\t`eventLoopLagSignal: deadMs must be > degradedMs (${degradedMs}), got ${deadMs}`,\n\t\t)\n\t}\n\tif (!Number.isFinite(resolutionMs) || resolutionMs < 1) {\n\t\tthrow new Error(`eventLoopLagSignal: resolutionMs must be >= 1, got ${resolutionMs}`)\n\t}\n\n\tconst monitor = opts.monitor ?? monitorEventLoopDelay({ resolution: resolutionMs })\n\tmonitor.enable()\n\n\treturn {\n\t\tname: 'eventLoopLagMs',\n\t\tweight,\n\t\tread(): SignalReading {\n\t\t\t// `max` is in nanoseconds.\n\t\t\tconst maxNs = monitor.max\n\t\t\t// On a brand-new monitor or after `reset()` the histogram can\n\t\t\t// report `max=0` (no samples yet) or, on some Node versions,\n\t\t\t// `Number.MAX_SAFE_INTEGER` as a sentinel. Treat both as\n\t\t\t// \"no reading yet\" → unknown.\n\t\t\tif (!Number.isFinite(maxNs) || maxNs <= 0 || maxNs >= Number.MAX_SAFE_INTEGER) {\n\t\t\t\tmonitor.reset()\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tconst observedMs = maxNs / 1_000_000\n\t\t\tmonitor.reset()\n\n\t\t\tlet factor: number\n\t\t\tif (observedMs <= degradedMs) factor = 1\n\t\t\telse if (observedMs >= deadMs) factor = 0\n\t\t\telse {\n\t\t\t\t// Linear interpolation between degraded (1) and dead (0).\n\t\t\t\tconst span = deadMs - degradedMs\n\t\t\t\tfactor = 1 - (observedMs - degradedMs) / span\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tvalue: Math.round(observedMs),\n\t\t\t\thealthFactor: factor,\n\t\t\t}\n\t\t},\n\t\tdispose(): void {\n\t\t\tmonitor.disable()\n\t\t},\n\t}\n}\n","/**\n * Unix-socket server for `@sylphx/sdk/health` (ADR-111 §3.2.4 + §4.2).\n *\n * The sidecar polls the app over a Unix socket — `/var/run/sylphx/health.sock`\n * by default. The shared volume (`emptyDir` mount, see ADR-111 §3.2.2) is\n * provisioned by the reconciler when the sidecar is injected. This server\n * binds Bun's native `Bun.serve({ unix: <path> })` and serves the same\n * JSON the HTTP handler does.\n *\n * Graceful shutdown is essential — the socket file lingers on disk if not\n * cleaned, and the sidecar's first reconnect attempt after a redeploy\n * would hit a stale inode. `shutdown()` calls `server.stop()` AND\n * unlinks the socket file. Process signal ownership stays with the app\n * entry point; SDK library code only returns an explicit shutdown handle.\n */\n\nimport { unlinkSync } from 'node:fs'\nimport { createWebHandler, type HealthEvaluator } from './handler'\n\n/** Bun-typed minimum for the server we need to control. */\ninterface BunServer {\n\tstop(force?: boolean): void\n\turl?: { href: string } | null\n}\n\nexport interface UnixSocketServerOptions {\n\t/** Absolute path to bind. Defaults to `/var/run/sylphx/health.sock`. */\n\treadonly path?: string\n\t/**\n\t * Override unlink behavior — useful for tests where the test runner\n\t * already owns the socket cleanup.\n\t */\n\treadonly unlinkOnShutdown?: boolean\n\t/**\n\t * Override the Bun runtime resolver. Used by tests to verify the\n\t * \"no-Bun\" error path without leaving a Bun-only test environment.\n\t * Production code reads `globalThis.Bun` directly.\n\t */\n\treadonly bunRuntime?: { serve: (cfg: unknown) => BunServer } | null\n}\n\nexport interface UnixSocketServerHandle {\n\treadonly path: string\n\treadonly server: BunServer\n\t/** Stop the server AND unlink the socket file (unless `unlinkOnShutdown=false`). */\n\tshutdown(): Promise<void>\n}\n\n/**\n * Start a Bun unix-socket HTTP server bound to `opts.path`. Returns the\n * handle so tests / shutdown handlers can call `shutdown()`.\n *\n * Pre-binds the socket: if a stale file from a prior crash exists at the\n * path, we `unlinkSync` it first so the bind doesn't `EADDRINUSE`. This\n * is safe because the path is operator-controlled (default lives under\n * `/var/run/sylphx/`, owned by the runtime UID 1000).\n */\nexport function startUnixSocketServer(\n\tsource: HealthEvaluator,\n\topts: UnixSocketServerOptions = {},\n): UnixSocketServerHandle {\n\t// Lazy reference to globalThis.Bun — keeps the import free of build-time\n\t// requirements on Bun for non-Bun consumers (server is no-op there).\n\t// Tests inject `opts.bunRuntime: null` to exercise the no-Bun path.\n\tconst bun =\n\t\topts.bunRuntime !== undefined\n\t\t\t? opts.bunRuntime\n\t\t\t: (globalThis as unknown as { Bun?: { serve: (cfg: unknown) => BunServer } }).Bun\n\tif (bun === null || bun === undefined || typeof bun.serve !== 'function') {\n\t\tthrow new Error(\n\t\t\t'startUnixSocketServer: Bun.serve is unavailable — Unix-socket transport requires a Bun runtime',\n\t\t)\n\t}\n\n\tconst path = opts.path ?? '/var/run/sylphx/health.sock'\n\tconst unlinkOnShutdown = opts.unlinkOnShutdown ?? true\n\n\t// Best-effort pre-cleanup of stale socket.\n\ttry {\n\t\tunlinkSync(path)\n\t} catch {\n\t\t// ENOENT is fine; anything else means the user has the wrong path\n\t\t// or perms — the bind below will surface a clear error.\n\t}\n\n\tconst handler = createWebHandler(source)\n\tconst server = bun.serve({\n\t\tunix: path,\n\t\tfetch: async (req: Request): Promise<Response> => handler(req),\n\t}) as BunServer\n\n\tconst handle: UnixSocketServerHandle = {\n\t\tpath,\n\t\tserver,\n\t\tasync shutdown(): Promise<void> {\n\t\t\ttry {\n\t\t\t\tserver.stop(true)\n\t\t\t} catch {\n\t\t\t\t// stopping a server that's already stopped is benign\n\t\t\t}\n\t\t\tif (unlinkOnShutdown) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(path)\n\t\t\t\t} catch {\n\t\t\t\t\t// ENOENT — already gone, fine\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n\n\treturn handle\n}\n","/**\n * Framework middleware helpers for `@sylphx/sdk/health` (ADR-141 Phase 0).\n *\n * Zero-Config one-liners for the most common JS server frameworks. Each\n * helper creates a `sylphxHealth()` instance internally and returns a\n * framework-idiomatic middleware / plugin / handler that serves `/healthz`.\n *\n * For advanced control (custom signals, scoring strategies, Unix-socket\n * IPC for the sidecar), import `sylphxHealth` directly — these helpers\n * are wrappers around the same factory.\n *\n * @example Hono\n * ```ts\n * import { Hono } from 'hono'\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const app = new Hono()\n * app.use('*', withHealth.hono())\n * ```\n *\n * @example Express\n * ```ts\n * import express from 'express'\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const app = express()\n * app.use(withHealth.express())\n * ```\n *\n * @example Fastify\n * ```ts\n * import Fastify from 'fastify'\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const fastify = Fastify()\n * await fastify.register(withHealth.fastify())\n * ```\n *\n * @example Bun.serve / Next.js / Deno / edge runtimes\n * ```ts\n * import { withHealth } from '@sylphx/sdk/health'\n *\n * const handleHealth = withHealth.fetch()\n * Bun.serve({\n * port: 3000,\n * fetch(req) {\n * if (new URL(req.url).pathname === '/healthz') return handleHealth(req)\n * return new Response('hello')\n * },\n * })\n * ```\n *\n * Structural types are used for the framework signatures so this module\n * has **zero peer dependencies** on Hono / Express / Fastify. The user's\n * framework supplies the actual types at the call site via TypeScript's\n * structural typing.\n */\n\nimport { type SylphxHealth, sylphxHealth } from './index'\nimport type { SylphxHealthOptions } from './types'\nimport type { UnixSocketServerHandle } from './unix-socket-server'\n\n/**\n * Common options for every framework helper. Extends `SylphxHealthOptions`\n * (signals, scoringStrategy, now) with a `path` override.\n */\nexport interface WithHealthOptions extends SylphxHealthOptions {\n\t/**\n\t * HTTP path to serve the health endpoint on. Defaults to `/healthz` —\n\t * the de-facto Kubernetes convention adopted by Spring Boot, FastAPI,\n\t * and the IETF `application/health+json` draft.\n\t */\n\treadonly path?: string\n\t/**\n\t * Pre-built `SylphxHealth` instance. When provided, options other than\n\t * `path` are ignored and the helper just wires the instance up. Use\n\t * this when you need to share one health instance across multiple\n\t * frameworks (e.g. Hono middleware + Unix-socket IPC for the sidecar).\n\t */\n\treadonly instance?: SylphxHealth\n\t/**\n\t * Bind the same health score on the sidecar IPC socket.\n\t *\n\t * Default: auto-enable when the platform injects\n\t * `SYLPHX_HEALTH_APP_SOCKET` into the app container. This keeps the\n\t * one-line helper enough for ADR-111 health-agent score polling while\n\t * remaining a no-op on local/dev/edge runtimes without the env var.\n\t *\n\t * Set `false` to opt out, `true` to bind the default socket path, or\n\t * pass `{ path, required }` for explicit control. `required` defaults\n\t * to `false` so non-Bun runtimes never fail module import just because\n\t * they share the public SDK.\n\t */\n\treadonly unixSocket?:\n\t\t| boolean\n\t\t| {\n\t\t\t\treadonly path?: string\n\t\t\t\treadonly required?: boolean\n\t\t\t\treadonly unlinkOnShutdown?: boolean\n\t\t }\n}\n\n/** Default HTTP path. See WithHealthOptions.path. */\nconst DEFAULT_PATH = '/healthz'\nconst DEFAULT_SOCKET_PATH = '/var/run/sylphx/health.sock'\nconst AUTO_SOCKET_HANDLES = new WeakMap<SylphxHealth, UnixSocketServerHandle>()\n\nfunction resolveInstance(opts: WithHealthOptions): SylphxHealth {\n\tif (opts.instance) return opts.instance\n\tconst { path: _path, instance: _instance, unixSocket: _unixSocket, ...sylphxOpts } = opts\n\treturn sylphxHealth(sylphxOpts)\n}\n\nfunction normalizePath(input: string | undefined): string {\n\tif (!input) return DEFAULT_PATH\n\treturn input.startsWith('/') ? input : `/${input}`\n}\n\nfunction getEnvSocketPath(): string | undefined {\n\tconst proc = (globalThis as unknown as { process?: { env?: Record<string, string | undefined> } })\n\t\t.process\n\tconst value = proc?.env?.SYLPHX_HEALTH_APP_SOCKET\n\treturn value && value.trim().length > 0 ? value.trim() : undefined\n}\n\nfunction resolveUnixSocketPath(inputPath: string | undefined, envPath: string | undefined): string {\n\tif (inputPath !== undefined) return inputPath\n\tif (envPath !== undefined) return envPath\n\treturn DEFAULT_SOCKET_PATH\n}\n\nfunction resolveUnixSocketOption(\n\tinput: WithHealthOptions['unixSocket'],\n): { path: string; required: boolean; unlinkOnShutdown?: boolean } | null {\n\tif (input === false) return null\n\tconst envPath = getEnvSocketPath()\n\tif (input === undefined) {\n\t\treturn envPath ? { path: envPath, required: false } : null\n\t}\n\tif (input === true) {\n\t\treturn { path: envPath ?? DEFAULT_SOCKET_PATH, required: false }\n\t}\n\treturn {\n\t\tpath: resolveUnixSocketPath(input.path, envPath),\n\t\trequired: input.required ?? false,\n\t\t...(input.unlinkOnShutdown !== undefined ? { unlinkOnShutdown: input.unlinkOnShutdown } : {}),\n\t}\n}\n\nfunction bindUnixSocketIfRequested(health: SylphxHealth, opts: WithHealthOptions): () => void {\n\tconst socket = resolveUnixSocketOption(opts.unixSocket)\n\tif (!socket) return () => undefined\n\tconst existing = AUTO_SOCKET_HANDLES.get(health)\n\tif (existing) return () => undefined\n\ttry {\n\t\tconst handle = health.serveUnixSocket({\n\t\t\tpath: socket.path,\n\t\t\t...(socket.unlinkOnShutdown !== undefined\n\t\t\t\t? { unlinkOnShutdown: socket.unlinkOnShutdown }\n\t\t\t\t: {}),\n\t\t})\n\t\tAUTO_SOCKET_HANDLES.set(health, handle)\n\t\treturn () => {\n\t\t\tif (AUTO_SOCKET_HANDLES.get(health) === handle) {\n\t\t\t\tAUTO_SOCKET_HANDLES.delete(health)\n\t\t\t}\n\t\t\tvoid handle.shutdown()\n\t\t}\n\t} catch (err) {\n\t\tif (socket.required) throw err\n\t\treturn () => undefined\n\t}\n}\n\n// ─── Hono ────────────────────────────────────────────────────────────────────\n\n/**\n * Minimal structural type for a Hono request context. We only need\n * `c.req.path` to decide whether the request hits the health endpoint.\n */\nexport interface HonoContextLike {\n\treadonly req: { readonly path: string }\n}\n/** Hono `Next` is a parameterless async callback that yields to the next middleware. */\nexport type HonoNextLike = () => Promise<void>\n\n/**\n * Hono middleware that responds to `GET /healthz` and yields otherwise.\n *\n * The returned function carries a `.health` reference (the underlying\n * `SylphxHealth` instance) and a `.dispose()` method for graceful\n * shutdown. Wire `.dispose()` into the app entry point's existing\n * graceful-shutdown coordinator.\n *\n * ```ts\n * const health = withHealth.hono()\n * app.use('*', health)\n * registerShutdown(() => health.dispose())\n * ```\n */\nexport interface HonoHealthMiddleware {\n\t(c: HonoContextLike, next: HonoNextLike): Promise<Response | undefined>\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction honoHelper(opts: WithHealthOptions = {}): HonoHealthMiddleware {\n\tconst path = normalizePath(opts.path)\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.handler()\n\tconst middleware = (async (c, next) => {\n\t\tif (c.req.path === path) return respond()\n\t\t// ADR-143 — on every non-/healthz request, run `next()` inside an\n\t\t// OTel context that carries our cause baggage (if any). Downstream\n\t\t// HTTP / gRPC / queue calls inherit the cause via standard OTel\n\t\t// propagation. No-op when causality is disabled, OTel is absent,\n\t\t// or `evaluate()` has not produced a lastScore yet.\n\t\tawait health.runWithCause(() => next())\n\t\treturn undefined\n\t}) as HonoHealthMiddleware\n\tObject.defineProperty(middleware, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(middleware, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn middleware\n}\n\n// ─── Express / Connect ───────────────────────────────────────────────────────\n\n/** Express-style request — only `url` is needed to match `/healthz`. */\nexport interface ExpressRequestLike {\n\treadonly url?: string\n\treadonly method?: string\n}\n/** Express-style response — subset required by the Node handler. */\nexport interface ExpressResponseLike {\n\tstatusCode: number\n\tsetHeader(name: string, value: string): void\n\tend(body?: string): void\n}\n/** Express-style next callback — called when the request is not for /healthz. */\nexport type ExpressNextLike = (err?: unknown) => void\n\nexport interface ExpressHealthMiddleware {\n\t(req: ExpressRequestLike, res: ExpressResponseLike, next: ExpressNextLike): void\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction pathOf(url: string | undefined): string {\n\tif (!url) return '/'\n\tconst q = url.indexOf('?')\n\treturn q === -1 ? url : url.slice(0, q)\n}\n\nfunction expressHelper(opts: WithHealthOptions = {}): ExpressHealthMiddleware {\n\tconst path = normalizePath(opts.path)\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.nodeHandler()\n\tconst middleware = ((req, res, next) => {\n\t\tif (pathOf(req.url) === path) {\n\t\t\tvoid respond(req, res)\n\t\t\treturn\n\t\t}\n\t\t// ADR-143 — same baggage propagation as the Hono variant. Express's\n\t\t// `next()` is synchronous-by-convention, so we kick it off inside\n\t\t// runWithCause and let the framework run downstream handlers in\n\t\t// that async context. Errors propagate via Express's standard\n\t\t// next(err) path; runWithCause never throws on its own.\n\t\tvoid health.runWithCause(async () => next())\n\t}) as ExpressHealthMiddleware\n\tObject.defineProperty(middleware, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(middleware, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn middleware\n}\n\n// ─── Fastify ─────────────────────────────────────────────────────────────────\n\n/**\n * Minimal structural type for a Fastify instance — only the `.get` route\n * registration is needed. The plugin signature matches Fastify's\n * encapsulated-plugin contract: `(instance, opts) => Promise<void>`.\n */\nexport interface FastifyInstanceLike {\n\tget(\n\t\tpath: string,\n\t\thandler: (req: unknown, reply: FastifyReplyLike) => Promise<unknown> | unknown,\n\t): unknown\n}\n/** Minimal Fastify reply — subset required by the health response. */\nexport interface FastifyReplyLike {\n\tcode(code: number): FastifyReplyLike\n\theader(name: string, value: string): FastifyReplyLike\n\tsend(body?: unknown): unknown\n}\n\nexport interface FastifyHealthPlugin {\n\t(instance: FastifyInstanceLike, opts?: unknown): Promise<void>\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction fastifyHelper(opts: WithHealthOptions = {}): FastifyHealthPlugin {\n\tconst path = normalizePath(opts.path)\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.handler()\n\tconst plugin = (async (instance) => {\n\t\tinstance.get(path, async (_req, reply) => {\n\t\t\tconst response = await respond()\n\t\t\tconst body = await response.text()\n\t\t\treply.code(response.status)\n\t\t\treply.header('Content-Type', 'application/json')\n\t\t\treply.header('Cache-Control', 'no-store, no-cache, must-revalidate')\n\t\t\treply.send(body)\n\t\t})\n\t}) as FastifyHealthPlugin\n\tObject.defineProperty(plugin, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(plugin, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn plugin\n}\n\n// ─── Pure Web Fetch (Bun.serve / Next.js / Deno / edge) ──────────────────────\n\nexport interface FetchHealthHandler {\n\t(req?: Request): Promise<Response>\n\treadonly health: SylphxHealth\n\tdispose(): void\n}\n\nfunction fetchHelper(opts: WithHealthOptions = {}): FetchHealthHandler {\n\tconst health = resolveInstance(opts)\n\tconst disposeSocket = bindUnixSocketIfRequested(health, opts)\n\tconst respond = health.handler()\n\tconst handler = ((req?: Request) => respond(req)) as FetchHealthHandler\n\tObject.defineProperty(handler, 'health', { value: health, enumerable: true })\n\tObject.defineProperty(handler, 'dispose', {\n\t\tvalue: () => {\n\t\t\tdisposeSocket()\n\t\t\thealth.dispose()\n\t\t},\n\t\tenumerable: false,\n\t})\n\treturn handler\n}\n\n// ─── Public surface ──────────────────────────────────────────────────────────\n\n/**\n * One-line health-check wiring for popular JS server frameworks.\n *\n * All helpers internally create a `sylphxHealth()` instance with sane\n * defaults (single event-loop-lag signal) and serve `/healthz` returning\n * the standard `HealthScore` JSON.\n *\n * Each returned helper exposes the underlying `SylphxHealth` instance\n * via `.health` and a `.dispose()` shortcut for graceful shutdown.\n *\n * See `sylphxHealth()` for advanced use (custom signals, scoring, IPC).\n */\nexport const withHealth = {\n\t/** Hono middleware. Mount with `app.use('*', withHealth.hono())`. */\n\thono: honoHelper,\n\t/** Express / Connect / Polka middleware. Mount with `app.use(withHealth.express())`. */\n\texpress: expressHelper,\n\t/** Fastify plugin. Register with `await fastify.register(withHealth.fastify())`. */\n\tfastify: fastifyHelper,\n\t/**\n\t * Pure Web Fetch handler. Works with Bun.serve, Next.js route\n\t * handlers, Deno, Cloudflare Workers, and any other `Request →\n\t * Response` runtime.\n\t */\n\tfetch: fetchHelper,\n} as const\n","/**\n * `errorRateSignal` — request-error-rate over a sliding window (ADR-111 §4.3).\n *\n * Phase B uses an in-memory ring buffer of {timestamp, isError} samples.\n * Phase C will replace this with an OTel collector subscription so the\n * sidecar gets the same data without per-process bookkeeping; for now the\n * in-memory path keeps the SDK self-contained.\n *\n * Mapping observed-rate → healthFactor:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━━━━━━━━━━━┓\n * │ ┃ linear interpolation\n * 0.0 ┤ ┗━━━━━━━━━━\n * ┼───────────────────┼────────► error rate (0..1)\n * 0 degradedRate deadRate\n *\n * Default thresholds match ADR-111 §4.3 row 3:\n * degradedRate = 0.05 (5 % errors → degraded)\n * deadRate = 0.50 (50 % errors → dead)\n *\n * `recordSuccess()` / `recordError()` are pushed by the app on each\n * request (or wired into Hono / Express middleware — the SDK provides\n * primitives, not framework integrations). Zero-traffic windows produce\n * `factor=1` (no requests = no errors = healthy by convention).\n */\n\nimport type { SignalReading, SyncSignal } from '../types'\n\nexport interface ErrorRateOptions {\n\t/** Sliding window length. Accepts ms (number) or '5s' / '1m' shorthand. */\n\treadonly window: number | `${number}s` | `${number}m`\n\t/** Rate above this is considered degraded. Default 0.05 (5 %). */\n\treadonly degradedRate?: number\n\t/** Rate at which the signal saturates to dead. Default 0.50 (50 %). */\n\treadonly deadRate?: number\n\t/**\n\t * Soft minimum sample count: until this many samples land, factor=1\n\t * regardless of rate (avoids \"1 error in 1 request → 100 %\" panics).\n\t * Default 10.\n\t */\n\treadonly minSamples?: number\n\t/** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/** Optional injected clock for tests. */\n\treadonly now?: () => number\n}\n\nexport interface ErrorRateSignalHandle extends SyncSignal {\n\t/** Record a successful request (call from app middleware). */\n\trecordSuccess(): void\n\t/** Record a failed request (call from app middleware). */\n\trecordError(): void\n\t/** Erase the window. Useful for tests. */\n\treset(): void\n}\n\ninterface Sample {\n\treadonly t: number\n\treadonly e: boolean\n}\n\nfunction parseWindow(w: ErrorRateOptions['window']): number {\n\tif (typeof w === 'number') {\n\t\tif (!Number.isFinite(w) || w <= 0) {\n\t\t\tthrow new Error(`errorRateSignal: window must be > 0, got ${w}`)\n\t\t}\n\t\treturn w\n\t}\n\tconst m = /^(\\d+)(s|m)$/.exec(w)\n\tif (m === null) {\n\t\tthrow new Error(`errorRateSignal: invalid window '${w}', expected '5s' / '1m' / number`)\n\t}\n\tconst n = Number.parseInt(m[1] as string, 10)\n\tconst unit = m[2]\n\tconst ms = unit === 's' ? n * 1000 : n * 60_000\n\tif (ms <= 0) {\n\t\tthrow new Error(`errorRateSignal: parsed window ${ms}ms must be > 0`)\n\t}\n\treturn ms\n}\n\nexport function errorRateSignal(opts: ErrorRateOptions): ErrorRateSignalHandle {\n\tconst windowMs = parseWindow(opts.window)\n\tconst degradedRate = opts.degradedRate ?? 0.05\n\tconst deadRate = opts.deadRate ?? 0.5\n\tconst minSamples = opts.minSamples ?? 10\n\tconst weight = opts.weight ?? 0.2\n\tconst now = opts.now ?? Date.now\n\n\tif (degradedRate < 0 || degradedRate > 1) {\n\t\tthrow new Error(`errorRateSignal: degradedRate must be in [0, 1], got ${degradedRate}`)\n\t}\n\tif (deadRate <= degradedRate || deadRate > 1) {\n\t\tthrow new Error(`errorRateSignal: deadRate must be in (degradedRate, 1], got ${deadRate}`)\n\t}\n\tif (!Number.isFinite(minSamples) || minSamples < 1) {\n\t\tthrow new Error(`errorRateSignal: minSamples must be >= 1, got ${minSamples}`)\n\t}\n\n\t// Linked-list-style ring; we shift the head when entries age out. For\n\t// fleets at 100 req/s with a 5 s window that's ~500 entries — well\n\t// within memory budget. Phase C swaps this for the OTel subscription.\n\tconst samples: Sample[] = []\n\n\tfunction pruneExpired(t: number): void {\n\t\tconst cutoff = t - windowMs\n\t\twhile (samples.length > 0 && (samples[0] as Sample).t < cutoff) {\n\t\t\tsamples.shift()\n\t\t}\n\t}\n\n\treturn {\n\t\tname: `recent${Math.round(windowMs / 1000)}sErrorRate`,\n\t\tweight,\n\t\trecordSuccess(): void {\n\t\t\tsamples.push({ t: now(), e: false })\n\t\t},\n\t\trecordError(): void {\n\t\t\tsamples.push({ t: now(), e: true })\n\t\t},\n\t\treset(): void {\n\t\t\tsamples.length = 0\n\t\t},\n\t\tread(): SignalReading {\n\t\t\tconst t = now()\n\t\t\tpruneExpired(t)\n\t\t\tif (samples.length === 0 || samples.length < minSamples) {\n\t\t\t\t// Insufficient traffic → don't penalise. Return rate=0 +\n\t\t\t\t// factor=1 (visible in JSON for ops triage).\n\t\t\t\treturn { value: 0, healthFactor: 1 }\n\t\t\t}\n\t\t\tlet errors = 0\n\t\t\tfor (const s of samples) {\n\t\t\t\tif (s.e) errors++\n\t\t\t}\n\t\t\tconst rate = errors / samples.length\n\n\t\t\tlet factor: number\n\t\t\tif (rate <= degradedRate) factor = 1\n\t\t\telse if (rate >= deadRate) factor = 0\n\t\t\telse {\n\t\t\t\tconst span = deadRate - degradedRate\n\t\t\t\tfactor = 1 - (rate - degradedRate) / span\n\t\t\t}\n\n\t\t\t// Round to 4 decimal places — wire JSON stays compact and the\n\t\t\t// extra precision is meaningless for ops use.\n\t\t\tconst valueRounded = Math.round(rate * 10_000) / 10_000\n\n\t\t\treturn {\n\t\t\t\tvalue: valueRounded,\n\t\t\t\thealthFactor: factor,\n\t\t\t}\n\t\t},\n\t}\n}\n","/**\n * `memoryPressureSignal` — RSS / cgroup memory.max ratio (ADR-111 §4.3).\n *\n * On Linux containers we read the cgroup v2 memory limit from\n * `/sys/fs/cgroup/memory.max`. Then `pressure = process.memoryUsage().rss /\n * limit`. ADR-111 §4.3 default thresholds:\n * degradedRatio = 0.85 (85 % → degraded)\n * deadRatio = 0.95 (95 % → dead)\n *\n * Mapping observed-pressure → healthFactor:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━━━━━━━━━━┓\n * │ ┃\n * 0.0 ┤ ┗━━━━━━━━━\n * ┼──────────────────┼─────────► pressure (0..1)\n * 0 degradedRatio deadRatio\n *\n * Graceful fallback (the cardinal-rule deference):\n * - cgroup file missing → unknown=true (signal ignored)\n * - cgroup file has 'max' → unlimited container, ratio undefined → unknown\n * - file unreadable / parse → unknown=true\n *\n * `unknown=true` makes the scoring strategy ignore the signal — we never\n * pretend to know memory pressure on a host where we can't measure it.\n *\n * cgroup v1 (legacy) lives at `/sys/fs/cgroup/memory/memory.limit_in_bytes`\n * — supported via `cgroupV1Path` for operators on older kernels.\n */\n\nimport { readFileSync } from 'node:fs'\nimport type { SignalReading, SyncSignal } from '../types'\n\nexport interface MemoryPressureOptions {\n\t/** Pressure below this is fully healthy (factor=1). Default 0.85. */\n\treadonly degradedRatio?: number\n\t/** Pressure above this is fully dead (factor=0). Default 0.95. */\n\treadonly deadRatio?: number\n\t/**\n\t * Custom cgroup v2 memory limit path. Default `/sys/fs/cgroup/memory.max`.\n\t */\n\treadonly cgroupV2Path?: string\n\t/**\n\t * Optional cgroup v1 fallback path. Default\n\t * `/sys/fs/cgroup/memory/memory.limit_in_bytes`. Used when v2 path is\n\t * unreadable AND `cgroupV1Path` is set or v2 doesn't exist.\n\t */\n\treadonly cgroupV1Path?: string\n\t/** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/**\n\t * Optional injected `process.memoryUsage` for tests.\n\t * Default: `process.memoryUsage`.\n\t */\n\treadonly memoryUsage?: () => { rss: number }\n\t/** Optional injected file reader for tests. */\n\treadonly readFile?: (path: string) => string\n}\n\n/**\n * cgroup v1 reports a sentinel value (`9223372036854771712` =\n * `Number.MAX_SAFE_INTEGER` neighbourhood, well > 2^53) when the limit is\n * unconstrained. cgroup v2 uses the literal string `max`. Both collapse\n * to \"unlimited → unknown\".\n */\nconst CGROUP_V1_UNLIMITED_FLOOR = 1e18\n\nfunction tryRead(reader: (p: string) => string, path: string): string | null {\n\ttry {\n\t\treturn reader(path)\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Read the container memory limit in bytes. Returns `null` if unknown\n * (file missing, unparseable, or unlimited).\n *\n * Exposed for direct tests of the cgroup parsing logic.\n */\nexport function readContainerMemoryLimit(\n\treader: (p: string) => string,\n\tv2Path: string,\n\tv1Path: string,\n): number | null {\n\t// 1. Try cgroup v2 first: it is the modern default for current Linux runtimes.\n\tconst v2 = tryRead(reader, v2Path)\n\tif (v2 !== null) {\n\t\tconst trimmed = v2.trim()\n\t\tif (trimmed === 'max' || trimmed === '') return null\n\t\tconst n = Number.parseInt(trimmed, 10)\n\t\tif (!Number.isFinite(n) || n <= 0) return null\n\t\t// Some kernels report ridiculous \"max-ish\" sentinel values via v2.\n\t\tif (n > CGROUP_V1_UNLIMITED_FLOOR) return null\n\t\treturn n\n\t}\n\t// 2. Fall back to cgroup v1 if v2 is unreadable.\n\tconst v1 = tryRead(reader, v1Path)\n\tif (v1 === null) return null\n\tconst trimmed = v1.trim()\n\tconst n = Number.parseInt(trimmed, 10)\n\tif (!Number.isFinite(n) || n <= 0) return null\n\tif (n > CGROUP_V1_UNLIMITED_FLOOR) return null\n\treturn n\n}\n\nexport function memoryPressureSignal(opts: MemoryPressureOptions = {}): SyncSignal {\n\tconst degradedRatio = opts.degradedRatio ?? 0.85\n\tconst deadRatio = opts.deadRatio ?? 0.95\n\tconst v2Path = opts.cgroupV2Path ?? '/sys/fs/cgroup/memory.max'\n\tconst v1Path = opts.cgroupV1Path ?? '/sys/fs/cgroup/memory/memory.limit_in_bytes'\n\tconst weight = opts.weight ?? 0.2\n\tconst memoryUsage =\n\t\topts.memoryUsage ??\n\t\t((): { rss: number } => {\n\t\t\tconst m = process.memoryUsage()\n\t\t\treturn { rss: m.rss }\n\t\t})\n\tconst readFile = opts.readFile ?? ((p: string) => readFileSync(p, 'utf8'))\n\n\tif (degradedRatio <= 0 || degradedRatio >= 1) {\n\t\tthrow new Error(`memoryPressureSignal: degradedRatio must be in (0, 1), got ${degradedRatio}`)\n\t}\n\tif (deadRatio <= degradedRatio || deadRatio > 1) {\n\t\tthrow new Error(\n\t\t\t`memoryPressureSignal: deadRatio must be in (degradedRatio, 1], got ${deadRatio}`,\n\t\t)\n\t}\n\n\t// Cache the cgroup limit: the kernel does not change it at runtime. One\n\t// readSync per process boot is cheap; per poll tick is wasteful.\n\tlet cachedLimit: number | null | undefined\n\tfunction getLimit(): number | null {\n\t\tif (cachedLimit === undefined) {\n\t\t\tcachedLimit = readContainerMemoryLimit(readFile, v2Path, v1Path)\n\t\t}\n\t\treturn cachedLimit\n\t}\n\n\treturn {\n\t\tname: 'memoryPressure',\n\t\tweight,\n\t\tread(): SignalReading {\n\t\t\tconst limit = getLimit()\n\t\t\tif (limit === null) {\n\t\t\t\t// No container limit known — degrade gracefully to \"unknown\"\n\t\t\t\t// rather than report a bogus ratio against host RAM.\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tlet rss: number\n\t\t\ttry {\n\t\t\t\trss = memoryUsage().rss\n\t\t\t} catch {\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tif (!Number.isFinite(rss) || rss < 0) {\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tconst ratio = rss / limit\n\n\t\t\tlet factor: number\n\t\t\tif (ratio <= degradedRatio) factor = 1\n\t\t\telse if (ratio >= deadRatio) factor = 0\n\t\t\telse {\n\t\t\t\tconst span = deadRatio - degradedRatio\n\t\t\t\tfactor = 1 - (ratio - degradedRatio) / span\n\t\t\t}\n\n\t\t\t// 3-decimal precision keeps the wire JSON small.\n\t\t\tconst valueRounded = Math.round(ratio * 1000) / 1000\n\t\t\treturn { value: valueRounded, healthFactor: factor }\n\t\t},\n\t}\n}\n","/**\n * `pingSignal` — generic binary readiness signal for any sub-system the\n * service depends on (Postgres, Redis, Kafka, S3, an upstream API, …).\n *\n * The signal calls `ping()` every poll tick and reports:\n * - `healthFactor: 1` if the ping resolves (any value) within the timeout\n * - `healthFactor: 0` if the ping throws OR times out\n *\n * Binary — no partial-credit semantics. For richer scoring use a custom\n * signal that returns a graded `healthFactor`; `pingSignal` is the\n * canonical \"subsystem reachable yes/no\" primitive that 99% of dependency\n * probes need.\n *\n * `databaseSignal` and `redisSignal` are thin convenience wrappers\n * with idiomatic defaults for those two ubiquitous dependencies.\n *\n * @example Generic\n * ```ts\n * import { pingSignal } from '@sylphx/sdk/health'\n *\n * app.use('*', withHealth.hono({\n * signals: [\n * pingSignal({\n * name: 'upstream-api',\n * ping: async () => { await fetch('https://upstream.example/_ping') },\n * }),\n * ],\n * }))\n * ```\n *\n * @example Database + Redis\n * ```ts\n * import { withHealth, databaseSignal, redisSignal } from '@sylphx/sdk/health'\n *\n * app.use('*', withHealth.hono({\n * signals: [\n * databaseSignal({ ping: () => pool.query('SELECT 1') }),\n * redisSignal({ ping: () => redis.ping() }),\n * ],\n * }))\n * ```\n */\n\nimport type { AsyncSignal, SignalReading } from '../types'\n\nexport interface PingSignalOptions {\n\t/** Stable signal name. Appears in the wire JSON `signals.<name>` map. */\n\treadonly name: string\n\t/**\n\t * Async function the SDK calls every poll tick. Resolve (any value) =\n\t * healthy. Throw / reject = unhealthy. Timeout → unhealthy.\n\t */\n\treadonly ping: () => Promise<unknown>\n\t/**\n\t * Per-tick timeout in milliseconds. Default 500ms — fast enough to\n\t * fold into a `/healthz` response without holding the event loop, slow\n\t * enough to avoid false negatives on a momentarily-slow dependency.\n\t * Tune up for dependencies that legitimately take longer (e.g. S3 list).\n\t */\n\treadonly timeoutMs?: number\n\t/** Weight in the weighted-product score. Default 1.0. */\n\treadonly weight?: number\n}\n\nconst DEFAULT_TIMEOUT_MS = 500\n\nexport function pingSignal(opts: PingSignalOptions): AsyncSignal {\n\tif (typeof opts.ping !== 'function') {\n\t\tthrow new Error(`pingSignal[${opts.name}]: ping must be a function`)\n\t}\n\tif (typeof opts.name !== 'string' || opts.name.length === 0) {\n\t\tthrow new Error('pingSignal: name must be a non-empty string')\n\t}\n\tconst timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS\n\tif (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n\t\tthrow new Error(`pingSignal[${opts.name}]: timeoutMs must be > 0, got ${timeoutMs}`)\n\t}\n\tconst weight = opts.weight ?? 1.0\n\n\treturn {\n\t\tname: opts.name,\n\t\tweight,\n\t\tasync read(): Promise<SignalReading> {\n\t\t\tlet timer: ReturnType<typeof setTimeout> | undefined\n\t\t\tconst timeoutPromise = new Promise<'timeout'>((resolve) => {\n\t\t\t\ttimer = setTimeout(() => resolve('timeout'), timeoutMs)\n\t\t\t})\n\t\t\ttry {\n\t\t\t\tconst result = await Promise.race([opts.ping().then(() => 'ok' as const), timeoutPromise])\n\t\t\t\tif (result === 'timeout') {\n\t\t\t\t\treturn { value: 'timeout', healthFactor: 0 }\n\t\t\t\t}\n\t\t\t\treturn { value: 'ok', healthFactor: 1 }\n\t\t\t} catch (err) {\n\t\t\t\tconst message = err instanceof Error ? err.message : String(err)\n\t\t\t\treturn { value: message.slice(0, 120), healthFactor: 0 }\n\t\t\t} finally {\n\t\t\t\tif (timer) clearTimeout(timer)\n\t\t\t}\n\t\t},\n\t}\n}\n\n// ─── canonical convenience wrappers ─────────────────────────────────────────\n\nexport interface DependencyPingOptions {\n\treadonly ping: () => Promise<unknown>\n\treadonly timeoutMs?: number\n\treadonly weight?: number\n\t/** Override the signal name. Defaults to the subsystem name. */\n\treadonly name?: string\n}\n\n/**\n * Postgres / SQL-database readiness — convenience over `pingSignal` with\n * `name: 'database'`. Wrap the caller's `pool.query('SELECT 1')` or\n * equivalent.\n */\nexport function databaseSignal(opts: DependencyPingOptions): AsyncSignal {\n\treturn pingSignal({\n\t\tname: opts.name ?? 'database',\n\t\tping: opts.ping,\n\t\t...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n\t\t...(opts.weight !== undefined ? { weight: opts.weight } : {}),\n\t})\n}\n\n/**\n * Redis / KV readiness — convenience over `pingSignal` with `name: 'redis'`.\n * Wrap the caller's `redis.ping()` or equivalent.\n */\nexport function redisSignal(opts: DependencyPingOptions): AsyncSignal {\n\treturn pingSignal({\n\t\tname: opts.name ?? 'redis',\n\t\tping: opts.ping,\n\t\t...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n\t\t...(opts.weight !== undefined ? { weight: opts.weight } : {}),\n\t})\n}\n","/**\n * `queueDepthSignal` — backpressure indicator (ADR-111 §4.3).\n *\n * Generic signal: the app provides a `getter` that returns the current\n * length of whatever queue the operator wants probed (BullMQ, RabbitMQ,\n * in-memory work pool, …). The signal does NOT own the queue — the app\n * does. We just measure.\n *\n * Mapping observed-depth → healthFactor:\n *\n * healthFactor\n * ▲\n * 1.0 ┤━━━━━━━━━━━━━━━━━━┓\n * │ ┃ linear interpolation\n * 0.0 ┤ ┗━━━━━━━━━━━━\n * ┼──────────────────┼─────────► depth\n * 0 fullThreshold\n *\n * Below `fullThreshold` → factor 1. At `fullThreshold` → factor 0. Above\n * → factor 0. The implicit \"degraded\" zone is `[0.5 × fullThreshold,\n * fullThreshold]` — depth at half-full produces factor 0.5. Operators\n * tune `fullThreshold` to whatever their queue reasonably hits at peak\n * load; \"100% full\" means \"drain new traffic\", not \"kill the workload\" (the\n * three-tier gate at the sidecar handles the kill decision).\n *\n * `getter` errors are swallowed — a thrown getter produces `unknown=true`\n * (so the scoring strategy ignores this signal, instead of falsely\n * reporting score=0). The app's bug shouldn't masquerade as a sidecar\n * decision.\n */\n\nimport type { AsyncSignal, SignalReading } from '../types'\n\nexport interface QueueDepthOptions {\n\t/**\n\t * Sync or async getter the SDK calls every poll tick. Must return a\n\t * non-negative integer. Throws → reading marked `unknown=true`.\n\t */\n\treadonly getter: () => number | Promise<number>\n\t/**\n\t * Depth at which the queue is considered \"full\" (factor=0). Linear\n\t * interp from 0..fullThreshold. No default — operator-specific.\n\t */\n\treadonly fullThreshold: number\n\t/**\n\t * Optional below-which factor is always 1 (a \"soft floor\"). Default 0\n\t * — the linear interp starts at depth 0.\n\t */\n\treadonly healthyBelow?: number\n\t/** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */\n\treadonly weight?: number\n\t/** Custom signal name. Default `queueDepth`. */\n\treadonly name?: string\n}\n\nexport function queueDepthSignal(opts: QueueDepthOptions): AsyncSignal {\n\tif (typeof opts.getter !== 'function') {\n\t\tthrow new Error('queueDepthSignal: getter must be a function')\n\t}\n\tif (!Number.isFinite(opts.fullThreshold) || opts.fullThreshold <= 0) {\n\t\tthrow new Error(`queueDepthSignal: fullThreshold must be > 0, got ${opts.fullThreshold}`)\n\t}\n\tconst healthyBelow = opts.healthyBelow ?? 0\n\tif (!Number.isFinite(healthyBelow) || healthyBelow < 0 || healthyBelow >= opts.fullThreshold) {\n\t\tthrow new Error(\n\t\t\t`queueDepthSignal: healthyBelow must be in [0, fullThreshold), got ${healthyBelow}`,\n\t\t)\n\t}\n\tconst weight = opts.weight ?? 0.2\n\n\treturn {\n\t\tname: opts.name ?? 'queueDepth',\n\t\tweight,\n\t\tasync read(): Promise<SignalReading> {\n\t\t\tlet raw: unknown\n\t\t\ttry {\n\t\t\t\traw = await opts.getter()\n\t\t\t} catch {\n\t\t\t\t// Cardinal-rule deference: getter blew up → unknown.\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tif (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) {\n\t\t\t\treturn { value: 0, healthFactor: 1, unknown: true }\n\t\t\t}\n\t\t\tconst depth = raw\n\n\t\t\tlet factor: number\n\t\t\tif (depth <= healthyBelow) factor = 1\n\t\t\telse if (depth >= opts.fullThreshold) factor = 0\n\t\t\telse {\n\t\t\t\tconst span = opts.fullThreshold - healthyBelow\n\t\t\t\tfactor = 1 - (depth - healthyBelow) / span\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tvalue: depth,\n\t\t\t\thealthFactor: factor,\n\t\t\t}\n\t\t},\n\t}\n}\n","/**\n * `@sylphx/sdk/health` — Phase B multi-signal health score (ADR-111 §4).\n *\n * Apps register signals (event-loop lag, queue depth, error rate, memory\n * pressure, …); the SDK folds them into a continuous score in `[0, 1]`;\n * the **sidecar** maps the score to liveness / readiness / drain via the\n * three-tier gate. The SDK's responsibility ends at exposing the score.\n *\n * The wire format the sidecar parses (ADR-111 §3.2.4):\n *\n * ```json\n * {\n * \"score\": 0.92,\n * \"signals\": {\n * \"eventLoopLagMs\": 12,\n * \"queueDepth\": 3,\n * \"recent5sErrorRate\": 0.001,\n * \"memoryPressure\": 0.45\n * },\n * \"signalFactors\": {\n * \"eventLoopLagMs\": 1,\n * \"queueDepth\": 0.8,\n * \"recent5sErrorRate\": 0.99,\n * \"memoryPressure\": 0.7\n * },\n * \"lastTickAt\": \"2026-05-03T12:34:56.789Z\"\n * }\n * ```\n *\n * Default signals (if `sylphxHealth()` called with no `signals`):\n * - `eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 })` (weight 1.0)\n *\n * Three-tier gate (decided by **sidecar**, not SDK; ADR-111 §4.4):\n *\n * | Score | Liveness | Readiness | Effect |\n * | -------------- | -------- | --------- | --------------------- |\n * | > 0.8 | 200 | 200 | Normal traffic |\n * | (0.5, 0.8] | 200 | 503 | Drain new, no kill |\n * | <= 0.5 | 503 | 503 | Kill after threshold |\n *\n * Apps don't need to think about probe semantics — that's the sidecar's\n * job. The app just exposes the score. See `apps/health-agent/` for the\n * sidecar implementation.\n *\n * @example Worked example — OpenClaw under PDF-extract load (ADR-111 §4.5):\n *\n * ```text\n * eventLoopLagMs = 6000 → factor 0.4\n * queueDepth = 12 → factor 1.0\n * errorRate = 0.002 → factor 1.0\n * memoryPressure = 0.55 → factor 1.0\n * score = 0.4^0.4 × 1.0^0.6 ≈ 0.69\n *\n * → falls in [0.5, 0.8] → sidecar drains traffic, doesn't kill.\n * Pod gets to finish PDF extraction.\n * ```\n */\n\nimport {\n\ttype CausalityHandle,\n\ttype CausalityOption,\n\ttype CausalityOptions,\n\tcreateCausalityHandle,\n\ttype UpstreamCause,\n} from './causality'\nimport { evaluateEffect as buildEvaluateEffect } from './effects'\nimport { createNodeHandler, createWebHandler, type HealthEvaluator } from './handler'\nimport {\n\tcreateHistoryRecorder,\n\ttype HistoryEntry,\n\ttype HistoryOption,\n\ttype HistoryQuery,\n\ttype HistoryRecorder,\n\ttype HistoryRecorderOptions,\n\ttype VerificationResult,\n} from './history'\nimport {\n\tcreateOtelEmitter,\n\ttype OtelEmitter,\n\ttype OtelEmitterOptions,\n\ttype OtelOption,\n} from './otel'\nimport { defaultScoringStrategy } from './scoring'\nimport { eventLoopLagSignal } from './signals/event-loop-lag'\nimport type { HealthScore, Signal, SylphxHealthOptions } from './types'\nimport {\n\tstartUnixSocketServer,\n\ttype UnixSocketServerHandle,\n\ttype UnixSocketServerOptions,\n} from './unix-socket-server'\n\nexport {\n\tBAGGAGE_KEY_CAUSE,\n\tBAGGAGE_KEY_CHAIN,\n\ttype CausalityHandle,\n\ttype CausalityOption,\n\ttype CausalityOptions,\n\tcreateCausalityHandle,\n\tDEFAULT_CAUSE_THRESHOLD,\n\tMAX_CHAIN_LENGTH,\n\ttype UpstreamCause,\n} from './causality'\nexport {\n\tcreateNodeHandler,\n\tcreateWebHandler,\n\ttype HealthEvaluator,\n} from './handler'\nexport {\n\tcanonicalizeSignals,\n\tcreateHistoryRecorder,\n\tcreateInMemoryHistoryStore,\n\tDEFAULT_HISTORY_CAPACITY,\n\tGENESIS_PREV_HASH,\n\ttype HistoryEntry,\n\ttype HistoryOption,\n\ttype HistoryQuery,\n\ttype HistoryRecorder,\n\ttype HistoryRecorderOptions,\n\ttype HistoryStore,\n\ttype InMemoryHistoryStoreOptions,\n\ttype VerificationResult,\n\tverifyChain,\n} from './history'\nexport {\n\ttype ExpressHealthMiddleware,\n\ttype ExpressNextLike,\n\ttype ExpressRequestLike,\n\ttype ExpressResponseLike,\n\ttype FastifyHealthPlugin,\n\ttype FastifyInstanceLike,\n\ttype FastifyReplyLike,\n\ttype FetchHealthHandler,\n\ttype HonoContextLike,\n\ttype HonoHealthMiddleware,\n\ttype HonoNextLike,\n\ttype WithHealthOptions,\n\twithHealth,\n} from './middleware'\nexport {\n\tcreateOtelEmitter,\n\tEVENT_NAMES as OTEL_EVENT_NAMES,\n\tMETRIC_NAMES as OTEL_METRIC_NAMES,\n\ttype OtelEmitter,\n\ttype OtelEmitterOptions,\n\ttype OtelOption,\n\ttype OtelStaticAttributes,\n\tSPAN_ATTRIBUTES as OTEL_SPAN_ATTRIBUTES,\n} from './otel'\nexport { defaultScoringStrategy, weightedProduct } from './scoring'\nexport type {\n\tErrorRateOptions,\n\tErrorRateSignalHandle,\n} from './signals/error-rate'\nexport { errorRateSignal } from './signals/error-rate'\nexport type { EventLoopLagOptions } from './signals/event-loop-lag'\nexport { eventLoopLagSignal } from './signals/event-loop-lag'\nexport type { MemoryPressureOptions } from './signals/memory-pressure'\nexport { memoryPressureSignal } from './signals/memory-pressure'\nexport type { DependencyPingOptions, PingSignalOptions } from './signals/ping'\nexport { databaseSignal, pingSignal, redisSignal } from './signals/ping'\nexport type { QueueDepthOptions } from './signals/queue-depth'\nexport { queueDepthSignal } from './signals/queue-depth'\n// Re-exports — public surface.\nexport type {\n\tAsyncSignal,\n\tHealthRelease,\n\tHealthReleaseInput,\n\tHealthScore,\n\tHealthSnapshot,\n\tScoringStrategy,\n\tSignal,\n\tSignalBase,\n\tSignalReading,\n\tSylphxHealthOptions,\n\tSyncSignal,\n} from './types'\nexport { HealthError } from './types'\nexport type {\n\tUnixSocketServerHandle,\n\tUnixSocketServerOptions,\n} from './unix-socket-server'\n\n/**\n * The handle returned by `sylphxHealth()`. Owns the registered signals,\n * exposes evaluation in both Promise + Effect form, and produces an HTTP\n * handler / Unix-socket server.\n *\n * Call `dispose()` during graceful shutdown to release per-signal\n * resources (e.g. the `monitorEventLoopDelay()` histogram).\n */\nexport interface SylphxHealth extends HealthEvaluator {\n\t/** All signals registered (read-only). */\n\treadonly signals: ReadonlyArray<Signal>\n\t/** Snapshot evaluation as a Promise. */\n\tevaluate(): Promise<HealthScore>\n\t/** Snapshot evaluation as an Effect (per Rule 21 / ADR-058 Amendment). */\n\treadonly evaluateEffect: ReturnType<typeof buildEvaluateEffect>\n\t/**\n\t * Web Fetch API HTTP handler — works under Hono, Bun.serve, Next.js\n\t * route.ts, itty-router, Hattip. Always returns 200 + JSON; the\n\t * sidecar applies the three-tier 200/503 gate.\n\t */\n\thandler(): (req?: Request) => Promise<Response>\n\t/** Node.js classic `(req, res)` handler — for Express / classic Fastify. */\n\tnodeHandler(): ReturnType<typeof createNodeHandler>\n\t/**\n\t * Bind a Bun Unix-domain socket and serve the same JSON. The sidecar\n\t * polls `/var/run/sylphx/health.sock` by default (ADR-111 §3.2.4).\n\t */\n\tserveUnixSocket(opts?: UnixSocketServerOptions): UnixSocketServerHandle\n\t/**\n\t * Most recent score returned by `evaluate()`. Returns null before\n\t * the first evaluation. Used by `withHealth.*` middleware to inject\n\t * cause baggage on non-probe requests (ADR-143).\n\t */\n\tgetLastScore(): HealthScore | null\n\t/**\n\t * Run `fn` within an OTel context that has our local cause baggage\n\t * set, derived from the most recent `evaluate()`. When causality is\n\t * disabled or the OTel API is unavailable, runs `fn()` directly.\n\t * Middleware consumers don't usually call this — they let\n\t * `withHealth.*` wire it automatically. Exposed for advanced users\n\t * who build custom transport wrappers.\n\t */\n\trunWithCause<T>(fn: () => T | Promise<T>): Promise<T>\n\t/**\n\t * Retrieve audit-grade history entries (ADR-144). Returns an empty\n\t * array when history is disabled. Operators use this for compliance\n\t * evidence + post-incident reconstruction.\n\t */\n\tgetHistory(query?: HistoryQuery): Promise<readonly HistoryEntry[]>\n\t/**\n\t * Verify the integrity of the recorded history chain over `query`\n\t * (defaults to all available entries). Returns `verified: true`\n\t * when the SHA-256 chain matches end-to-end; otherwise reports the\n\t * first sequence where tampering is detected.\n\t */\n\tverifyHistory(query?: HistoryQuery): Promise<VerificationResult>\n\t/**\n\t * Tear down all registered signals. Idempotent. Call during graceful\n\t * shutdown to release histograms, file watchers, etc.\n\t */\n\tdispose(): void\n}\n\n/**\n * Build a `SylphxHealth` instance.\n *\n * @example Hono integration:\n * ```ts\n * import { Hono } from 'hono'\n * import {\n * sylphxHealth,\n * eventLoopLagSignal,\n * queueDepthSignal,\n * errorRateSignal,\n * memoryPressureSignal,\n * } from '@sylphx/sdk/health'\n *\n * const errors = errorRateSignal({ window: '5s', degradedRate: 0.05 })\n *\n * const health = sylphxHealth({\n * signals: [\n * eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 }),\n * queueDepthSignal({ getter: () => queue.size, fullThreshold: 1000 }),\n * errors,\n * memoryPressureSignal({ degradedRatio: 0.85 }),\n * ],\n * })\n *\n * const app = new Hono()\n * app.get('/healthz', health.handler())\n *\n * // Track requests for the error-rate signal\n * app.use(async (c, next) => {\n * try { await next(); errors.recordSuccess() }\n * catch (err) { errors.recordError(); throw err }\n * })\n *\n * // Or — primary transport for the sidecar:\n * health.serveUnixSocket() // → /var/run/sylphx/health.sock\n * ```\n *\n * @example Worked example — OpenClaw under PDF-extract load (ADR-111 §4.5):\n *\n * ```text\n * eventLoopLagMs = 6000 → factor 0.4\n * queueDepth = 12 → factor 1.0\n * errorRate = 0.002 → factor 1.0\n * memoryPressure = 0.55 → factor 1.0\n * score = 0.4^0.4 × 1.0^0.6 ≈ 0.69\n *\n * → falls in [0.5, 0.8] → sidecar drains traffic, doesn't kill.\n * Pod gets to finish PDF extraction.\n * ```\n */\n/**\n * Resolve the `SylphxHealthOptions.otel` flag into an emitter (or null).\n * Default: emission ON with default settings. Setting `otel: false`\n * disables emission entirely. Passing an object overrides individual\n * settings.\n */\nfunction resolveOtelEmitter(otelOpt: OtelOption | undefined): OtelEmitter | null {\n\tif (otelOpt === false) return null\n\tconst emitterOpts: OtelEmitterOptions = otelOpt && typeof otelOpt === 'object' ? otelOpt : {}\n\treturn createOtelEmitter(emitterOpts)\n}\n\n/**\n * Resolve the `SylphxHealthOptions.causality` flag into a handle (or null).\n * Default: causality ON with default threshold (0.5) per ADR-143. Setting\n * `causality: false` disables both read and write entirely. Passing an\n * object overrides threshold / serviceName / chain behaviour.\n */\nfunction resolveCausalityHandle(opt: CausalityOption | undefined): CausalityHandle | null {\n\tif (opt === false) return null\n\tconst causalityOpts: CausalityOptions = opt && typeof opt === 'object' ? opt : {}\n\treturn createCausalityHandle(causalityOpts)\n}\n\n/**\n * Resolve the `SylphxHealthOptions.history` flag into a recorder (or null).\n * Default: history ON with the in-memory ring-buffer store (ADR-144).\n * Setting `history: false` disables recording entirely. Passing an object\n * supplies a persistent store + capacity override.\n */\nfunction resolveHistoryRecorder(opt: HistoryOption | undefined): HistoryRecorder | null {\n\tif (opt === false) return null\n\tconst recorderOpts: HistoryRecorderOptions = opt && typeof opt === 'object' ? opt : {}\n\treturn createHistoryRecorder(recorderOpts)\n}\n\nfunction clampHealthFactor(value: number): number {\n\tif (!Number.isFinite(value)) return 0\n\tif (value < 0) return 0\n\tif (value > 1) return 1\n\treturn value\n}\n\nconst HEALTH_RELEASE_KEYS = [\n\t'version',\n\t'commitSha',\n\t'imageDigest',\n\t'imageTag',\n\t'deploymentId',\n\t'buildId',\n\t'replicaId',\n] as const\n\nfunction resolveHealthRelease(\n\tinput: SylphxHealthOptions['release'],\n): NonNullable<HealthScore['release']> | undefined {\n\tif (!input) return undefined\n\ttry {\n\t\tconst value = typeof input === 'function' ? input() : input\n\t\tif (!value) return undefined\n\t\tconst release: Record<string, string | null> = {}\n\t\tfor (const key of HEALTH_RELEASE_KEYS) {\n\t\t\tconst raw = value[key]\n\t\t\tif (raw === null) {\n\t\t\t\trelease[key] = null\n\t\t\t} else if (typeof raw === 'string') {\n\t\t\t\tconst trimmed = raw.trim()\n\t\t\t\tif (trimmed.length > 0) release[key] = trimmed\n\t\t\t}\n\t\t}\n\t\treturn Object.keys(release).length > 0\n\t\t\t? (release as NonNullable<HealthScore['release']>)\n\t\t\t: undefined\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\nexport function sylphxHealth(opts: SylphxHealthOptions = {}): SylphxHealth {\n\t// ADR-111 §4.6: zero-config default — register a single\n\t// event-loop-lag signal so apps get a meaningful score without any\n\t// boilerplate. Once the app registers richer signals it overrides this.\n\tconst signals: Signal[] =\n\t\topts.signals && opts.signals.length > 0\n\t\t\t? [...opts.signals]\n\t\t\t: [eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000, weight: 1 })]\n\n\tconst scoringStrategy = opts.scoringStrategy ?? defaultScoringStrategy()\n\tconst now = opts.now ?? ((): Date => new Date())\n\n\t// ADR-142 — OpenTelemetry emission is on by default. The emitter\n\t// internally graceful-falls-back when @opentelemetry/api is not\n\t// installed (returns noop emit()). Explicit `otel: false` opts out.\n\tconst otelEmitter: OtelEmitter | null = resolveOtelEmitter(opts.otel)\n\n\t// ADR-143 — causality propagation is on by default. Read upstream\n\t// cause from baggage on every evaluate(); writes happen at the\n\t// `withHealth.*` middleware boundary on non-/healthz requests (or\n\t// via the explicit `runWithCause()` API surface).\n\tconst causality: CausalityHandle | null = resolveCausalityHandle(opts.causality)\n\n\t// ADR-144 — audit-grade history recording. Default-on with the\n\t// in-memory ring-buffer store; persistent compliance-grade stores\n\t// plug in via the `HistoryStore` interface.\n\tconst historyRecorder: HistoryRecorder | null = resolveHistoryRecorder(opts.history)\n\n\tlet disposed = false\n\tlet lastScore: HealthScore | null = null\n\n\tconst evaluate = async (): Promise<HealthScore> => {\n\t\tif (disposed) {\n\t\t\tthrow new Error('sylphxHealth: instance disposed')\n\t\t}\n\t\t// ADR-143 §2.2 — read upstream cause BEFORE signal evaluation so\n\t\t// the cause is available to the emitter even if signal reads take\n\t\t// long. Non-throwing.\n\t\tconst upstreamCause: UpstreamCause | null = causality ? causality.readCause() : null\n\n\t\t// Read all signals in parallel — async-getter signals (queueDepth)\n\t\t// shouldn't serialise behind sync ones. `Promise.all` propagates a\n\t\t// thrown signal directly to the caller; signals are expected to\n\t\t// internally swallow errors and return `unknown=true` instead, so\n\t\t// we never hit this path in normal operation.\n\t\tconst readings = await Promise.all(\n\t\t\tsignals.map(async (signal) => ({\n\t\t\t\tsignal,\n\t\t\t\treading: await signal.read(),\n\t\t\t})),\n\t\t)\n\n\t\tconst score = scoringStrategy(readings)\n\n\t\tconst signalsMap: Record<string, number | string | boolean> = {}\n\t\tconst signalFactors: Record<string, number> = {}\n\t\tfor (const { signal, reading } of readings) {\n\t\t\tsignalsMap[signal.name] = reading.value\n\t\t\tsignalFactors[signal.name] =\n\t\t\t\treading.unknown === true ? 1 : clampHealthFactor(reading.healthFactor)\n\t\t}\n\n\t\tconst release = resolveHealthRelease(opts.release)\n\t\tconst result: HealthScore = {\n\t\t\tscore,\n\t\t\tsignals: signalsMap,\n\t\t\tsignalFactors,\n\t\t\t...(release ? { release } : {}),\n\t\t\tlastTickAt: now().toISOString(),\n\t\t}\n\n\t\tlastScore = result\n\n\t\t// ADR-142 / ADR-143 — emit OTel metric / span attributes for the\n\t\t// result, including upstream cause when present. Non-throwing by\n\t\t// contract — emit() failures must not interfere with /healthz.\n\t\tif (otelEmitter) {\n\t\t\totelEmitter.emit(result, upstreamCause)\n\t\t}\n\n\t\t// ADR-144 — record an audit-grade history entry. Fire-and-forget\n\t\t// so a slow / failing store never blocks /healthz; recorder\n\t\t// catches its own errors per cardinal rule.\n\t\tif (historyRecorder) {\n\t\t\tvoid historyRecorder.record(result)\n\t\t}\n\n\t\treturn result\n\t}\n\n\tconst evaluator: HealthEvaluator = { evaluate }\n\n\tconst evaluateEffect = buildEvaluateEffect(evaluate)\n\n\tconst runWithCause = async <T>(fn: () => T | Promise<T>): Promise<T> => {\n\t\tif (!causality || !lastScore) return await fn()\n\t\treturn await causality.runWithCause(fn, lastScore)\n\t}\n\n\tconst getHistory = async (query: HistoryQuery = {}): Promise<readonly HistoryEntry[]> => {\n\t\tif (!historyRecorder) return []\n\t\treturn await historyRecorder.range(query)\n\t}\n\n\tconst verifyHistory = async (query: HistoryQuery = {}): Promise<VerificationResult> => {\n\t\tif (!historyRecorder) {\n\t\t\treturn {\n\t\t\t\tverified: false,\n\t\t\t\tentriesChecked: 0,\n\t\t\t\tfirstBreakAtSequence: null,\n\t\t\t\treason: 'history recording is disabled on this instance',\n\t\t\t}\n\t\t}\n\t\treturn await historyRecorder.verify(query)\n\t}\n\n\treturn {\n\t\tsignals,\n\t\tevaluate,\n\t\tevaluateEffect,\n\t\thandler(): (req?: Request) => Promise<Response> {\n\t\t\treturn createWebHandler(evaluator)\n\t\t},\n\t\tnodeHandler(): ReturnType<typeof createNodeHandler> {\n\t\t\treturn createNodeHandler(evaluator)\n\t\t},\n\t\tserveUnixSocket(unixOpts?: UnixSocketServerOptions): UnixSocketServerHandle {\n\t\t\treturn startUnixSocketServer(evaluator, unixOpts)\n\t\t},\n\t\tgetLastScore(): HealthScore | null {\n\t\t\treturn lastScore\n\t\t},\n\t\trunWithCause,\n\t\tgetHistory,\n\t\tverifyHistory,\n\t\tdispose(): void {\n\t\t\tif (disposed) return\n\t\t\tdisposed = true\n\t\t\tif (otelEmitter) otelEmitter.dispose()\n\t\t\tif (causality) causality.dispose()\n\t\t\tif (historyRecorder) historyRecorder.dispose()\n\t\t\tfor (const s of signals) {\n\t\t\t\ttry {\n\t\t\t\t\ts.dispose?.()\n\t\t\t\t} catch {\n\t\t\t\t\t// dispose must not throw — swallow to guarantee idempotency\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n}\n"],"mappings":"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","names":["cachedApi","trimmed","n","evaluateEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Sylphx SDK - State-of-the-art platform SDK with pure functions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
}
|
|
100
100
|
},
|
|
101
101
|
"dependencies": {
|
|
102
|
-
"@sylphx/contract": "0.
|
|
102
|
+
"@sylphx/contract": "0.14.0",
|
|
103
103
|
"jose": "^6.1.3",
|
|
104
104
|
"rrweb": "^1.1.3",
|
|
105
105
|
"web-vitals": "5.1.0"
|