import { Effect } from 'effect';
import { Meter, Tracer } from '@opentelemetry/api';
import { monitorEventLoopDelay } from 'node:perf_hooks';

/**
 * Audit-grade health history for `@sylphx/sdk/health` (ADR-144).
 *
 * Every `evaluate()` produces a tamper-evident `HistoryEntry` linked to
 * the previous entry via a SHA-256 prev-hash chain (the same primitive
 * AWS CloudTrail uses for digest-based log file integrity validation).
 * Operators run `sylphx health verify` to walk the chain offline and
 * detect any post-hoc tampering.
 *
 * # Storage is pluggable
 *
 * The SDK ships an `InMemoryHistoryStore` (ring buffer, FIFO eviction)
 * as the reference implementation. Customers wanting compliance-grade
 * durability (SOC 2 Type II, GDPR Article 32, HIPAA 164.312(b)) plug
 * their own persistent `HistoryStore` — a thin interface that the SDK
 * calls with each new entry. Adapter packages for Postgres / S3 / cold
 * storage ship separately.
 *
 * # Cardinal rule (carried from ADR-141 / 142 / 143)
 *
 * Recording failures never throw upstream. The `/healthz` response is
 * the load-bearing contract; the history record is observability
 * augmentation. A store that throws during `append()` produces a logged
 * warning (in dev) and is otherwise a no-op for that evaluation.
 *
 * # Wire format normative
 *
 * Per ADR-144 §3 — sorted-key canonical signal JSON, base16 SHA-256
 * hashes, six-decimal-place score precision, `|` separator for the
 * entry-hash preimage. Stable from day one.
 */

/**
 * One tamper-evident history entry. The `prevHash` links to the previous
 * entry's `entryHash`; entry 0 (`sequence === 0`) uses the genesis
 * placeholder `"0".repeat(64)`. Stable wire shape — additive evolution
 * only.
 */
interface HistoryEntry {
    /** Monotonic, 0-indexed per service. */
    readonly sequence: number;
    /** ISO-8601 timestamp matching `HealthScore.lastTickAt`. */
    readonly evaluatedAt: string;
    /** Aggregate score in `[0, 1]`. */
    readonly score: number;
    /** Base16 SHA-256 of canonicalized signal map. */
    readonly signalsDigest: string;
    /** Base16 SHA-256 of previous entry's `entryHash` (`"0"*64` at sequence 0). */
    readonly prevHash: string;
    /** Base16 SHA-256 of `(sequence|evaluatedAt|score|signalsDigest|prevHash)`. */
    readonly entryHash: string;
}
/** Query for `HistoryStore.range`. */
interface HistoryQuery {
    /** Inclusive lower sequence. Omit for "from the beginning available". */
    readonly fromSequence?: number;
    /** Inclusive upper sequence. Omit for "to the most recent". */
    readonly toSequence?: number;
    /** Inclusive lower timestamp (ISO-8601). */
    readonly fromTimestamp?: string;
    /** Inclusive upper timestamp (ISO-8601). */
    readonly toTimestamp?: string;
    /** Maximum entries returned. Implementations may cap further. */
    readonly limit?: number;
}
/** Pluggable persistence adapter. Implementations may be sync or async. */
interface HistoryStore {
    /** Append one entry. MUST persist before resolving for compliance use. */
    append(entry: HistoryEntry): Promise<void> | void;
    /** Retrieve entries matching `query` in ascending sequence order. */
    range(query: HistoryQuery): Promise<readonly HistoryEntry[]> | readonly HistoryEntry[];
    /** Most recent entry, or null when empty. */
    latest(): Promise<HistoryEntry | null> | HistoryEntry | null;
    /** Optional capacity hint for ring-buffer stores. */
    readonly capacity?: number;
}
/** Outcome of chain verification — `verified: false` always carries a reason. */
interface VerificationResult {
    readonly verified: boolean;
    readonly entriesChecked: number;
    readonly firstBreakAtSequence: number | null;
    readonly reason: string | null;
}
/** Options for the in-memory reference store. */
interface InMemoryHistoryStoreOptions {
    /** Ring-buffer capacity. Default 1000. */
    readonly capacity?: number;
}
/** Options for the recorder bound to a `sylphxHealth` instance. */
interface HistoryRecorderOptions {
    /** Underlying store. Defaults to `InMemoryHistoryStore`. */
    readonly store?: HistoryStore;
    /** Capacity passed to the default in-memory store. Default 1000. */
    readonly capacity?: number;
}
/** The flag value passed via `sylphxHealth({ history })`. */
type HistoryOption = boolean | HistoryRecorderOptions;
/** Genesis prev-hash for the very first entry per service. */
declare const GENESIS_PREV_HASH: string;
/** Default ring-buffer size when callers don't override. */
declare const DEFAULT_HISTORY_CAPACITY = 1000;
/**
 * Sorted-key JSON canonicalization. Same signals in different insertion
 * order produce the same string — guarantees `signalsDigest` stability
 * across V8 / Bun / browser JSON.stringify implementations.
 */
declare function canonicalizeSignals(signals: Record<string, number | string | boolean>): string;
/** Build an in-memory ring-buffer history store. */
declare function createInMemoryHistoryStore(opts?: InMemoryHistoryStoreOptions): HistoryStore;
/**
 * Records every `HealthScore` as a hash-chained entry. Owns a single
 * `nextSequence` counter + the `prevHash` value carried forward across
 * recordings. The recorder is per-instance (per-service); the chain is
 * never shared across services.
 */
interface HistoryRecorder {
    /** Backing store. Exposed for advanced callers (e.g. tests + verification). */
    readonly store: HistoryStore;
    /** Record one score. Resolves to the persisted entry (or null on failure). */
    record(score: HealthScore): Promise<HistoryEntry | null>;
    /** Convenience: store.range. */
    range(query: HistoryQuery): Promise<readonly HistoryEntry[]>;
    /** Convenience: store.latest. */
    latest(): Promise<HistoryEntry | null>;
    /**
     * Verify the chain over a query window. Walks entries in sequence
     * order, recomputing each `entryHash` and confirming it matches the
     * next entry's `prevHash`. Returns the first break with a reason.
     */
    verify(query: HistoryQuery): Promise<VerificationResult>;
    /** Tear down internal references. Idempotent. */
    dispose(): void;
}
/** Build a recorder bound to a (defaulted) in-memory store. */
declare function createHistoryRecorder(opts?: HistoryRecorderOptions): HistoryRecorder;
/**
 * Walk an ordered (by sequence) chain of entries, recomputing each
 * `entryHash` and confirming it matches the next entry's `prevHash`.
 * Returns the first break encountered.
 *
 * Edge cases:
 *   - Empty input → `verified: true, entriesChecked: 0`. Vacuous truth.
 *   - First entry has `prevHash !== GENESIS_PREV_HASH` AND `sequence === 0`
 *     → break ("genesis prev-hash mismatch").
 *   - First entry's `sequence > 0` but `prevHash === GENESIS_PREV_HASH`
 *     → this is a sub-window query; we accept and chain forward from
 *     `entries[0]`.
 */
declare function verifyChain(entries: ReadonlyArray<HistoryEntry>): Promise<VerificationResult>;

/**
 * Causality-aware health propagation for `@sylphx/sdk/health` (ADR-143).
 *
 * Reads + writes the OpenTelemetry Baggage entry `sylphx.health.cause`
 * with value `<signal>@<service>[,<signal>@<service>...]` per ADR-143 §3.1.
 * Append-only `sylphx.health.cause_chain` carries the hop chain so
 * operators can reconstruct multi-hop incident paths.
 *
 * # Read side (downstream)
 *
 * On every `evaluate()`, the SDK reads `propagation.getActiveBaggage()`
 * before computing the score. When the baggage carries an
 * `sylphx.health.cause` entry, the resulting `HealthScore.upstreamCause`
 * surfaces the parsed causes + chain. The OTel emitter then sets
 * `service.health.cause_root` / `cause_signal` / `cause_chain` span
 * attributes and increments the `health.downstream_cause` Counter.
 *
 * # Write side (upstream — the service whose signal is failing)
 *
 * `runWithCause(fn, score)` wraps an async function in a new OTel
 * context where the cause baggage is set. Used by `withHealth.*`
 * middleware to inject baggage on every non-/healthz request so that
 * downstream calls within that request inherit the cause via standard
 * OTel context propagation (W3C Baggage header on HTTP, native
 * baggage on gRPC, queue metadata on Kafka/Cloud Tasks).
 *
 * # Graceful fallback
 *
 * `@opentelemetry/api` is an optional peer dependency (see ADR-142
 * §2.3). When the dynamic import fails — or when the runtime exposes
 * only a partial OTel API — the handle transitions to `'unavailable'`,
 * every method becomes a no-op, and `runWithCause` runs the inner
 * function with no context modification. The `/healthz` contract is
 * unchanged in either case (ADR-143 §2.5 cardinal rule).
 *
 * # Cardinal rules (carried from ADR-141 / ADR-142)
 *
 * - Propagation never throws. Errors in `readCause`, `encodeCause`,
 *   `runWithCause` are caught and downgraded to no-op behaviour.
 * - The HTTP response shape is unchanged. Baggage is augmentation;
 *   probe surface is the load-bearing contract.
 */

declare const BAGGAGE_KEY_CAUSE = "sylphx.health.cause";
declare const BAGGAGE_KEY_CHAIN = "sylphx.health.cause_chain";
/** Default threshold below which a signal triggers cause propagation. */
declare const DEFAULT_CAUSE_THRESHOLD = 0.5;
/** Maximum number of hops persisted in the chain baggage (ADR-143 §5). */
declare const MAX_CHAIN_LENGTH = 5;
/**
 * The flag value passed by callers via `sylphxHealth({ causality })` /
 * `withHealth.hono({ causality })`. `true` (default) enables propagation
 * with sensible defaults; `false` disables it; an object configures it.
 */
type CausalityOption = boolean | CausalityOptions;
interface CausalityOptions {
    /**
     * Below this signal factor, the SDK considers the signal "causing
     * degradation" and emits a cause entry. Default: `0.5` — matches the
     * ADR-111 kill threshold. Range: `(0, 1]`.
     */
    readonly threshold?: number;
    /**
     * Override the service name used in cause entries. Falls back to
     * `process.env.OTEL_SERVICE_NAME` then `process.env.SERVICE_NAME`
     * then `"unknown"`. Operators of multi-tenant runtimes typically
     * override this per evaluator.
     */
    readonly serviceName?: string;
    /**
     * When true (default), the SDK appends the local service name to
     * `sylphx.health.cause_chain` baggage so downstream operators can
     * reconstruct multi-hop incident paths.
     */
    readonly propagateChain?: boolean;
}
/** Parsed view of an upstream cause read from baggage. */
interface UpstreamCause {
    /**
     * The list of `<signal>@<service>` pairs carried in the baggage entry.
     * Operators read `causes[0]` for the most-significant root; the full
     * list preserves multi-cause concurrent failures.
     */
    readonly causes: ReadonlyArray<{
        readonly signal: string;
        readonly service: string;
    }>;
    /**
     * The hop chain — services that observed this cause and forwarded it.
     * Most recent service is at the tail. `chain.length === 0` means the
     * baggage entry was just minted (no downstream services have observed
     * it yet — typically only at the moment-of-incident origin).
     */
    readonly chain: ReadonlyArray<string>;
}
/** Operational handle the SDK uses to read + write cause baggage. */
interface CausalityHandle {
    /** Async loader state — exposed for tests + diagnostics. */
    readonly state: 'loading' | 'ready' | 'unavailable' | 'disposed';
    /** Read upstream cause from active OTel baggage. Returns null when absent. */
    readCause(): UpstreamCause | null;
    /**
     * Encode the local cause string for baggage. Returns null when none of
     * the signals' factors are below the configured threshold.
     */
    encodeCause(score: HealthScore): string | null;
    /**
     * Run `fn` within a new OTel context that has our cause + chain
     * baggage entries set. Used by the middleware to inject baggage on
     * every non-/healthz request. When OTel API is unavailable, this
     * degrades to `fn()` directly.
     */
    runWithCause<T>(fn: () => T | Promise<T>, score: HealthScore): Promise<T>;
    /** Tear down internal references. Idempotent. */
    dispose(): void;
}
/**
 * Build a causality handle. Returns a handle whose state starts at
 * `'loading'`; emit / read operations are no-ops during loading. Once
 * the dynamic import resolves, state transitions to `'ready'` (or
 * `'unavailable'` when OTel API is absent).
 */
declare function createCausalityHandle(opts?: CausalityOptions): CausalityHandle;

/**
 * Static attributes the emitter merges onto every metric / event /
 * span-attribute it produces. Use to stamp deployment metadata
 * (`deployment.env`, `git.sha`, …) that the OTel resource detector
 * doesn't pick up automatically.
 */
type OtelStaticAttributes = Readonly<Record<string, string | number | boolean>>;
interface OtelEmitterOptions {
    /**
     * Pre-built OTel Meter. When provided, the emitter uses it directly and
     * skips the async OTel API load. Common case: tests, sandboxes, multi-
     * tenant runtimes wiring a custom MeterProvider per request.
     */
    readonly meter?: Meter;
    /**
     * Pre-built OTel Tracer. Same intent as `meter`. The emitter uses the
     * tracer only to read the active span when setting attributes /
     * events; it does not create new spans.
     */
    readonly tracer?: Tracer;
    /**
     * When true, the emitter records a `health.evaluated` span event on
     * every evaluation (if a span is active). Default false — high-
     * frequency probe paths produce many events and most operators only
     * need the metric.
     */
    readonly events?: boolean;
    /** Static attributes merged onto every emitted metric / span attribute. */
    readonly attributes?: OtelStaticAttributes;
}
/**
 * The flag value passed by callers via `sylphxHealth({ otel })` /
 * `withHealth.hono({ otel })`. `true` (default) enables emission with
 * sensible defaults; `false` disables it; an object configures it.
 */
type OtelOption = boolean | OtelEmitterOptions;
interface OtelEmitter {
    /**
     * Emit metrics / span attributes / event for one HealthScore.
     * `upstreamCause` (ADR-143) carries the parsed causality baggage so the
     * emitter can record `health.downstream_cause` counter +
     * `service.health.cause_*` span attributes. Non-throwing.
     */
    emit(score: HealthScore, upstreamCause?: UpstreamCause | null): void;
    /**
     * Tear down any cached instruments. Called from `sylphxHealth.dispose()`
     * during graceful shutdown. The OTel SDK owns the underlying meter
     * lifecycle; this method only releases the emitter's caches.
     */
    dispose(): void;
    /** Readiness state — exposed for tests. `loading` is transient; `ready` and `unavailable` are terminal. */
    readonly state: 'loading' | 'ready' | 'unavailable' | 'disposed';
}
/** Metric names — pinned by ADR-142 §3.1 + ADR-143 §3.3, stable from day one. */
declare const METRIC_NAMES: {
    readonly score: "health.score";
    readonly signalFactor: "health.signal.factor";
    readonly signalValue: "health.signal.value";
    readonly signalValueState: "health.signal.value_state";
    /** ADR-143 §3.3 — increments per evaluation when an upstream cause is observed. */
    readonly downstreamCause: "health.downstream_cause";
};
/** Span attribute names — pinned by ADR-142 §2.1.3 + ADR-143 §3.2, stable from day one. */
declare const SPAN_ATTRIBUTES: {
    readonly score: "service.health.score";
    readonly signalFactor: (name: string) => string;
    /** ADR-143 §3.2 — the upstream service that originated the failure chain. */
    readonly causeRoot: "service.health.cause_root";
    /** ADR-143 §3.2 — the signal at the root that failed. */
    readonly causeSignal: "service.health.cause_signal";
    /** ADR-143 §3.2 — comma-delimited hop chain (oldest to newest). */
    readonly causeChain: "service.health.cause_chain";
};
/** Span event name — pinned by ADR-142 §2.1.4. */
declare const EVENT_NAMES: {
    readonly evaluated: "health.evaluated";
};
/**
 * Build an OTel emitter.
 *
 * Returns a sealed emitter object whose `emit()` is initially a no-op
 * while the OTel API loads in the background. Once loaded, all subsequent
 * `emit()` calls produce metrics / span attributes / events per ADR-142.
 *
 * When `@opentelemetry/api` is not installed in the host project (the
 * common case for hobby projects), the emitter transitions to
 * `unavailable` and all `emit()` calls remain no-ops for the lifetime of
 * the instance. No exceptions, no warnings.
 */
declare function createOtelEmitter(opts?: OtelEmitterOptions): OtelEmitter;

/**
 * Type SSOT for `@sylphx/sdk/health` (ADR-111 §4).
 *
 * Pure runtime types — framework-free, no schema library required at the
 * SDK boundary. Apps that want Standard Schema validation (per ADR-084)
 * can wrap the wire-format `HealthSnapshot` in their own schema.
 *
 * The wire shape (`HealthSnapshot`) is the **stable contract** the
 * `sylphx-health-agent` sidecar parses (see ADR-111 §3.2.4 +
 * `apps/health-agent/src/app-poll.ts::parseAppHealthBody`). Do not break it.
 */
/**
 * One reading produced by a `Signal.read()`. The aggregator turns each
 * reading into a `factor` in `[0, 1]` then folds them into the score via
 * the configured `ScoringStrategy`.
 *
 * `value` is the raw observation (ms, ratio, count, …) — surfaced verbatim
 * in the wire snapshot so operators can debug from JSON without re-running
 * the signal logic.
 *
 * `healthFactor` is the normalised health in `[0, 1]`:
 *   - `1` = fully healthy
 *   - `0` = dead (or "we don't know" if `unknown=true` and the policy says so)
 *
 * `unknown=true` means the signal **could not be measured this tick** (e.g.
 * cgroup file unreadable). Scoring strategies treat unknown signals as
 * `factor=1` (don't penalise an app for our own missing data).
 */
interface SignalReading {
    readonly value: number | string | boolean;
    readonly healthFactor: number;
    readonly unknown?: boolean;
}
/**
 * A health signal — one named, weighted measurement the score is built from.
 *
 * Two variants:
 *   - `SyncSignal`  — `read(): SignalReading`            (e.g. event-loop lag)
 *   - `AsyncSignal` — `read(): Promise<SignalReading>`   (e.g. queue depth
 *                                                          fetched over IPC)
 *
 * The discriminated `Signal` union accepts both. The aggregator awaits
 * each reading uniformly via `Promise.resolve(signal.read())`.
 *
 * Implementations are pure — no internal mutation outside the closure-
 * captured monitor state (e.g. `monitorEventLoopDelay()`). Stop /
 * cleanup are exposed via `dispose()` for tests and graceful shutdown.
 */
interface SignalBase {
    /** Unique stable identifier; appears in the wire `signals.<name>` map. */
    readonly name: string;
    /**
     * Weight in the weighted-product score. Strictly `> 0` for active
     * signals; `0` is a no-op signal (kept for compatibility with
     * conditional registration but skipped in scoring).
     */
    readonly weight: number;
    /**
     * Tear down any background work (timers, monitors, file watchers).
     * Called during graceful shutdown and from test cleanup.
     */
    dispose?(): void;
}
interface SyncSignal extends SignalBase {
    read(): SignalReading;
}
interface AsyncSignal extends SignalBase {
    read(): Promise<SignalReading>;
}
type Signal = SyncSignal | AsyncSignal;
/**
 * The complete health score + signal breakdown produced by `health.evaluate()`.
 *
 * `score` is the normalised aggregate in `[0, 1]`. Signal payload is
 * verbatim values from each `SignalReading.value` so operators can
 * cross-reference Grafana dashboards with the JSON.
 */
interface HealthScore {
    readonly score: number;
    readonly signals: Record<string, number | string | boolean>;
    /**
     * Normalised per-signal health factors in [0, 1].
     *
     * Additive to the ADR-111 wire shape: existing readers can ignore it,
     * while ADR-142 OTel emission uses it as the SSOT for
     * `health.signal.factor`.
     */
    readonly signalFactors?: Record<string, number>;
    readonly lastTickAt: string;
}
/**
 * Wire format the sidecar's `app-poll.ts::parseAppHealthBody` consumes.
 *
 * Pinned by ADR-111 §3.2.4 — keep stable. The sidecar tolerates extra
 * keys; do NOT remove existing ones without sequencing the sidecar update
 * first.
 */
type HealthSnapshot = HealthScore;
/**
 * Tagged error type for the Effect API. Promise consumers see the same
 * `message` via `Error.message` — the tag is for `Effect.catchTag`.
 */
declare class HealthError extends Error {
    readonly _tag: "HealthError";
    readonly cause?: unknown;
    constructor(message: string, cause?: unknown);
}
/**
 * `ScoringStrategy` collapses N readings → one `score` in `[0, 1]`.
 *
 * Default is `weightedProduct` (see `scoring.ts`). Custom strategies can
 * be plugged in via `sylphxHealth({ scoringStrategy: myStrategy })`.
 */
type ScoringStrategy = (readings: ReadonlyArray<{
    signal: Signal;
    reading: SignalReading;
}>) => number;
interface SylphxHealthOptions {
    /**
     * Signals to register. If omitted, defaults to a single
     * `eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 })` per
     * ADR-111 §4.6 ("sane out-of-box").
     */
    readonly signals?: ReadonlyArray<Signal>;
    /**
     * Strategy used to fold readings into a score. Defaults to
     * `weightedProduct` from `scoring.ts`.
     */
    readonly scoringStrategy?: ScoringStrategy;
    /**
     * Optional injected clock — used by tests for deterministic
     * `lastTickAt` timestamps.
     */
    readonly now?: () => Date;
    /**
     * OpenTelemetry emission (ADR-142). When `true` (default), every
     * `evaluate()` records `health.score` / `health.signal.*` metrics and
     * sets `service.health.score` on the active span. `false` disables
     * emission entirely. An object configures advanced behaviour (custom
     * meter, span events, static attributes).
     *
     * Graceful fallback: if `@opentelemetry/api` is not installed in the
     * host project, the emitter no-ops cleanly — no warning, no exception.
     */
    readonly otel?: OtelOption;
    /**
     * Causality-aware health propagation (ADR-143). When `true` (default),
     * every `evaluate()` reads upstream cause from active OTel baggage
     * and surfaces it on the metric stream (`health.downstream_cause`
     * counter + `service.health.cause_root` span attribute). The
     * `withHealth.*` middleware additionally writes the local cause into
     * the request context baggage on every non-`/healthz` request so
     * downstream services inherit causality via standard OTel
     * propagation. `false` disables both read and write. An object
     * tunes the threshold + service-name + chain behaviour.
     *
     * Graceful fallback: if `@opentelemetry/api` is not installed, the
     * handle becomes `'unavailable'` and propagation is a no-op.
     */
    readonly causality?: CausalityOption;
    /**
     * Audit-grade health history (ADR-144). When `true` (default), every
     * `evaluate()` records a tamper-evident `HistoryEntry` into the
     * configured `HistoryStore` (default: in-memory ring buffer with
     * capacity 1000). Entries form a SHA-256 prev-hash chain — operators
     * (and compliance auditors) can replay the chain to detect post-hoc
     * tampering.
     *
     * Set `false` to disable recording. Pass an object to plug a
     * persistent store (Postgres / S3 / OpenSearch) or override capacity.
     *
     * The in-memory store is suitable for testing + low-stakes single-pod
     * deployments. SOC 2 / GDPR / HIPAA compliance requires a persistent
     * adapter; the interface is public and stable so any backend works.
     */
    readonly history?: HistoryOption;
}

/**
 * Effect TS integration for `@sylphx/sdk/health` (Rule 21 / ADR-058 Amendment).
 *
 * Effect-native services should NEVER call `Effect.runPromise` inside
 * business logic — this module exposes `evaluateEffect` so they can fold
 * the health computation into their own fiber graph.
 *
 * `effect` is an OPTIONAL peer dependency — apps that don't import this
 * module never pull it in (sideEffects: false + tree-shaking guarantees
 * this; verified in tests). The `Effect` type is imported from the
 * peer-dep at runtime; consumers either provide it or never reach this
 * code path.
 */

/**
 * Lift a `() => Promise<HealthScore>` evaluator into an Effect-native value.
 *
 * Errors thrown by the evaluator are tagged `HealthError` so callers can
 * use `Effect.catchTag('HealthError', …)` for typed recovery.
 *
 * @example
 * ```ts
 * import { Effect } from 'effect'
 * import { sylphxHealth } from '@sylphx/sdk/health'
 *
 * const health = sylphxHealth({ signals: [...] })
 *
 * const program = Effect.gen(function* () {
 *   const { score, signals } = yield* health.evaluateEffect
 *   yield* Effect.log(`health=${score.toFixed(2)} signals=${JSON.stringify(signals)}`)
 *   return score
 * })
 *
 * // Only the entry point runs the Effect (Rule 21).
 * const finalScore = await Effect.runPromise(program)
 * ```
 */
declare function evaluateEffect(evaluator: () => Promise<HealthScore>): Effect.Effect<HealthScore, HealthError, never>;

/**
 * Universal HTTP handler for `@sylphx/sdk/health` (ADR-111 §4.2).
 *
 * Framework-agnostic: returns either a `Web Fetch API` `Response`
 * (Hono / Bun.serve / itty-router / Next.js routes) or, via thin adapters,
 * a Node `(req, res) => void` (Express / Fastify-as-classic-handler).
 *
 * The handler returns HTTP 200 for scores at or above the direct-probe
 * liveness floor and HTTP 503 below it. The response body is always the
 * complete HealthSnapshot, so the ADR-111 sidecar can still read the score
 * even when direct kubelet probes need a status-coded failure.
 */

/**
 * Minimal contract a `health` instance must expose for the handler to
 * call. The full `health` object satisfies this implicitly via
 * `evaluate()` from `./index.ts`.
 */
interface HealthEvaluator {
    evaluate(): Promise<HealthScore>;
}
/**
 * Build a Web-Fetch-style handler. Suitable for:
 *   - Hono: `app.get('/healthz', health.handler())`
 *   - Bun.serve: `fetch: health.handler()`
 *   - Next.js route.ts: `export const GET = health.handler()`
 *   - itty-router / Hattip / standard `(Request) => Response` runtimes
 */
declare function createWebHandler(source: HealthEvaluator): (req?: Request) => Promise<Response>;
/**
 * Build a Node-style `(req, res)` handler — for Express / classic Fastify.
 *
 * Adapter that delegates to `createWebHandler()` so logic stays in one
 * place. Imported lazily by callers that need it; doesn't drag any node
 * types into Web-only code paths.
 */
interface NodeIncoming {
    method?: string;
    url?: string;
}
interface NodeOutgoing {
    statusCode: number;
    setHeader(name: string, value: string): void;
    end(body?: string): void;
}
declare function createNodeHandler(source: HealthEvaluator): (req: NodeIncoming, res: NodeOutgoing) => Promise<void>;

/**
 * Unix-socket server for `@sylphx/sdk/health` (ADR-111 §3.2.4 + §4.2).
 *
 * The sidecar polls the app over a Unix socket — `/var/run/sylphx/health.sock`
 * by default. The shared volume (`emptyDir` mount, see ADR-111 §3.2.2) is
 * provisioned by the reconciler when the sidecar is injected. This server
 * binds Bun's native `Bun.serve({ unix: <path> })` and serves the same
 * JSON the HTTP handler does.
 *
 * Graceful shutdown is essential — the socket file lingers on disk if not
 * cleaned, and the sidecar's first reconnect attempt after a redeploy
 * would hit a stale inode. `shutdown()` calls `server.stop()` AND
 * unlinks the socket file. Process signal ownership stays with the app
 * entry point; SDK library code only returns an explicit shutdown handle.
 */

/** Bun-typed minimum for the server we need to control. */
interface BunServer {
    stop(force?: boolean): void;
    url?: {
        href: string;
    } | null;
}
interface UnixSocketServerOptions {
    /** Absolute path to bind. Defaults to `/var/run/sylphx/health.sock`. */
    readonly path?: string;
    /**
     * Override unlink behavior — useful for tests where the test runner
     * already owns the socket cleanup.
     */
    readonly unlinkOnShutdown?: boolean;
    /**
     * Override the Bun runtime resolver. Used by tests to verify the
     * "no-Bun" error path without leaving a Bun-only test environment.
     * Production code reads `globalThis.Bun` directly.
     */
    readonly bunRuntime?: {
        serve: (cfg: unknown) => BunServer;
    } | null;
}
interface UnixSocketServerHandle {
    readonly path: string;
    readonly server: BunServer;
    /** Stop the server AND unlink the socket file (unless `unlinkOnShutdown=false`). */
    shutdown(): Promise<void>;
}

/**
 * Framework middleware helpers for `@sylphx/sdk/health` (ADR-141 Phase 0).
 *
 * Zero-Config one-liners for the most common JS server frameworks. Each
 * helper creates a `sylphxHealth()` instance internally and returns a
 * framework-idiomatic middleware / plugin / handler that serves `/healthz`.
 *
 * For advanced control (custom signals, scoring strategies, Unix-socket
 * IPC for the sidecar), import `sylphxHealth` directly — these helpers
 * are wrappers around the same factory.
 *
 * @example Hono
 * ```ts
 * import { Hono } from 'hono'
 * import { withHealth } from '@sylphx/sdk/health'
 *
 * const app = new Hono()
 * app.use('*', withHealth.hono())
 * ```
 *
 * @example Express
 * ```ts
 * import express from 'express'
 * import { withHealth } from '@sylphx/sdk/health'
 *
 * const app = express()
 * app.use(withHealth.express())
 * ```
 *
 * @example Fastify
 * ```ts
 * import Fastify from 'fastify'
 * import { withHealth } from '@sylphx/sdk/health'
 *
 * const fastify = Fastify()
 * await fastify.register(withHealth.fastify())
 * ```
 *
 * @example Bun.serve / Next.js / Deno / edge runtimes
 * ```ts
 * import { withHealth } from '@sylphx/sdk/health'
 *
 * const handleHealth = withHealth.fetch()
 * Bun.serve({
 *   port: 3000,
 *   fetch(req) {
 *     if (new URL(req.url).pathname === '/healthz') return handleHealth(req)
 *     return new Response('hello')
 *   },
 * })
 * ```
 *
 * Structural types are used for the framework signatures so this module
 * has **zero peer dependencies** on Hono / Express / Fastify. The user's
 * framework supplies the actual types at the call site via TypeScript's
 * structural typing.
 */

/**
 * Common options for every framework helper. Extends `SylphxHealthOptions`
 * (signals, scoringStrategy, now) with a `path` override.
 */
interface WithHealthOptions extends SylphxHealthOptions {
    /**
     * HTTP path to serve the health endpoint on. Defaults to `/healthz` —
     * the de-facto Kubernetes convention adopted by Spring Boot, FastAPI,
     * and the IETF `application/health+json` draft.
     */
    readonly path?: string;
    /**
     * Pre-built `SylphxHealth` instance. When provided, options other than
     * `path` are ignored and the helper just wires the instance up. Use
     * this when you need to share one health instance across multiple
     * frameworks (e.g. Hono middleware + Unix-socket IPC for the sidecar).
     */
    readonly instance?: SylphxHealth;
    /**
     * Bind the same health score on the sidecar IPC socket.
     *
     * Default: auto-enable when the platform injects
     * `SYLPHX_HEALTH_APP_SOCKET` into the app container. This keeps the
     * one-line helper enough for ADR-111 health-agent score polling while
     * remaining a no-op on local/dev/edge runtimes without the env var.
     *
     * Set `false` to opt out, `true` to bind the default socket path, or
     * pass `{ path, required }` for explicit control. `required` defaults
     * to `false` so non-Bun runtimes never fail module import just because
     * they share the public SDK.
     */
    readonly unixSocket?: boolean | {
        readonly path?: string;
        readonly required?: boolean;
        readonly unlinkOnShutdown?: boolean;
    };
}
/**
 * Minimal structural type for a Hono request context. We only need
 * `c.req.path` to decide whether the request hits the health endpoint.
 */
interface HonoContextLike {
    readonly req: {
        readonly path: string;
    };
}
/** Hono `Next` is a parameterless async callback that yields to the next middleware. */
type HonoNextLike = () => Promise<void>;
/**
 * Hono middleware that responds to `GET /healthz` and yields otherwise.
 *
 * The returned function carries a `.health` reference (the underlying
 * `SylphxHealth` instance) and a `.dispose()` method for graceful
 * shutdown. Wire `.dispose()` into the app entry point's existing
 * graceful-shutdown coordinator.
 *
 * ```ts
 * const health = withHealth.hono()
 * app.use('*', health)
 * registerShutdown(() => health.dispose())
 * ```
 */
interface HonoHealthMiddleware {
    (c: HonoContextLike, next: HonoNextLike): Promise<Response | undefined>;
    readonly health: SylphxHealth;
    dispose(): void;
}
declare function honoHelper(opts?: WithHealthOptions): HonoHealthMiddleware;
/** Express-style request — only `url` is needed to match `/healthz`. */
interface ExpressRequestLike {
    readonly url?: string;
    readonly method?: string;
}
/** Express-style response — subset required by the Node handler. */
interface ExpressResponseLike {
    statusCode: number;
    setHeader(name: string, value: string): void;
    end(body?: string): void;
}
/** Express-style next callback — called when the request is not for /healthz. */
type ExpressNextLike = (err?: unknown) => void;
interface ExpressHealthMiddleware {
    (req: ExpressRequestLike, res: ExpressResponseLike, next: ExpressNextLike): void;
    readonly health: SylphxHealth;
    dispose(): void;
}
declare function expressHelper(opts?: WithHealthOptions): ExpressHealthMiddleware;
/**
 * Minimal structural type for a Fastify instance — only the `.get` route
 * registration is needed. The plugin signature matches Fastify's
 * encapsulated-plugin contract: `(instance, opts) => Promise<void>`.
 */
interface FastifyInstanceLike {
    get(path: string, handler: (req: unknown, reply: FastifyReplyLike) => Promise<unknown> | unknown): unknown;
}
/** Minimal Fastify reply — subset required by the health response. */
interface FastifyReplyLike {
    code(code: number): FastifyReplyLike;
    header(name: string, value: string): FastifyReplyLike;
    send(body?: unknown): unknown;
}
interface FastifyHealthPlugin {
    (instance: FastifyInstanceLike, opts?: unknown): Promise<void>;
    readonly health: SylphxHealth;
    dispose(): void;
}
declare function fastifyHelper(opts?: WithHealthOptions): FastifyHealthPlugin;
interface FetchHealthHandler {
    (req?: Request): Promise<Response>;
    readonly health: SylphxHealth;
    dispose(): void;
}
declare function fetchHelper(opts?: WithHealthOptions): FetchHealthHandler;
/**
 * One-line health-check wiring for popular JS server frameworks.
 *
 * All helpers internally create a `sylphxHealth()` instance with sane
 * defaults (single event-loop-lag signal) and serve `/healthz` returning
 * the standard `HealthScore` JSON.
 *
 * Each returned helper exposes the underlying `SylphxHealth` instance
 * via `.health` and a `.dispose()` shortcut for graceful shutdown.
 *
 * See `sylphxHealth()` for advanced use (custom signals, scoring, IPC).
 */
declare const withHealth: {
    /** Hono middleware. Mount with `app.use('*', withHealth.hono())`. */
    readonly hono: typeof honoHelper;
    /** Express / Connect / Polka middleware. Mount with `app.use(withHealth.express())`. */
    readonly express: typeof expressHelper;
    /** Fastify plugin. Register with `await fastify.register(withHealth.fastify())`. */
    readonly fastify: typeof fastifyHelper;
    /**
     * Pure Web Fetch handler. Works with Bun.serve, Next.js route
     * handlers, Deno, Cloudflare Workers, and any other `Request →
     * Response` runtime.
     */
    readonly fetch: typeof fetchHelper;
};

/**
 * Scoring strategies (ADR-111 §4 — three-tier health gate).
 *
 * The default `weightedProduct` strategy is multiplicative — a single bad
 * signal can drag the whole score down (any factor of 0 → score 0). This is
 * the right semantic for **liveness**: one critical subsystem dead means
 * the runtime is not ready, regardless of how healthy the rest is.
 *
 * Mathematics:
 *   score = ∏ factor_i ^ weight_i   (over signals where weight > 0 AND
 *                                    reading is not `unknown`)
 *
 * Where weights are normalised to sum=1 first, so absolute weight values
 * don't matter — only the ratios do. This keeps user expectations sane:
 * `[w=2, w=2]` and `[w=10, w=10]` produce identical scores.
 *
 * Edge cases (deterministic, never throw):
 *   - empty input            → 1 (perfect health, nothing to penalise)
 *   - all weights = 0        → 1 (no active signals)
 *   - all readings `unknown` → 1 (we can't see; cardinal-rule fallback
 *                                  lives at the sidecar boundary, not
 *                                  here — ADR-111 §3.2.5)
 *   - factor < 0 / NaN       → clamped to 0
 *   - factor > 1             → clamped to 1
 *   - weight < 0             → clamped to 0 (ignored)
 *
 * The clamps make us **safe by construction** — a misconfigured signal
 * cannot push the score outside `[0, 1]`. Tests exercise all branches.
 */

/**
 * Weighted geometric mean — the default scoring strategy.
 *
 * @example
 *   weightedProduct([
 *     { signal: { name: 'lag', weight: 0.4 }, reading: { healthFactor: 0.4 } },
 *     { signal: { name: 'q',   weight: 0.6 }, reading: { healthFactor: 1.0 } },
 *   ])
 *   // → 0.4^0.4 × 1.0^0.6 ≈ 0.693 — ADR-111 §4.5 worked example
 */
declare const weightedProduct: ScoringStrategy;
/**
 * Convenience: build a default scoring strategy.
 *
 * Reserved for future: weighted-min, weighted-mean, etc. For now there's
 * one strategy and `weightedProduct` is the only export — this keeps the
 * surface narrow until a concrete second use-case appears.
 */
declare function defaultScoringStrategy(): ScoringStrategy;

/**
 * `errorRateSignal` — request-error-rate over a sliding window (ADR-111 §4.3).
 *
 * Phase B uses an in-memory ring buffer of {timestamp, isError} samples.
 * Phase C will replace this with an OTel collector subscription so the
 * sidecar gets the same data without per-process bookkeeping; for now the
 * in-memory path keeps the SDK self-contained.
 *
 * Mapping observed-rate → healthFactor:
 *
 *      healthFactor
 *           ▲
 *      1.0  ┤━━━━━━━━━━━━━━━━━━━┓
 *           │                   ┃ linear interpolation
 *      0.0  ┤                   ┗━━━━━━━━━━
 *           ┼───────────────────┼────────►  error rate (0..1)
 *           0           degradedRate     deadRate
 *
 * Default thresholds match ADR-111 §4.3 row 3:
 *   degradedRate = 0.05  (5 % errors → degraded)
 *   deadRate     = 0.50  (50 % errors → dead)
 *
 * `recordSuccess()` / `recordError()` are pushed by the app on each
 * request (or wired into Hono / Express middleware — the SDK provides
 * primitives, not framework integrations). Zero-traffic windows produce
 * `factor=1` (no requests = no errors = healthy by convention).
 */

interface ErrorRateOptions {
    /** Sliding window length. Accepts ms (number) or '5s' / '1m' shorthand. */
    readonly window: number | `${number}s` | `${number}m`;
    /** Rate above this is considered degraded. Default 0.05 (5 %). */
    readonly degradedRate?: number;
    /** Rate at which the signal saturates to dead. Default 0.50 (50 %). */
    readonly deadRate?: number;
    /**
     * Soft minimum sample count: until this many samples land, factor=1
     * regardless of rate (avoids "1 error in 1 request → 100 %" panics).
     * Default 10.
     */
    readonly minSamples?: number;
    /** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */
    readonly weight?: number;
    /** Optional injected clock for tests. */
    readonly now?: () => number;
}
interface ErrorRateSignalHandle extends SyncSignal {
    /** Record a successful request (call from app middleware). */
    recordSuccess(): void;
    /** Record a failed request (call from app middleware). */
    recordError(): void;
    /** Erase the window. Useful for tests. */
    reset(): void;
}
declare function errorRateSignal(opts: ErrorRateOptions): ErrorRateSignalHandle;

/**
 * `eventLoopLagSignal` — main-thread blocking detector (ADR-111 §4.3).
 *
 * Measures Node/Bun's libuv event-loop delay using `monitorEventLoopDelay()`
 * from `node:perf_hooks`. The monitor is a histogram updated by libuv at a
 * configurable resolution (default 10 ms here — every tick measures lag
 * between expected wake and actual wake). We report the **max** observed
 * since the last `read()` then `reset()` — that's the worst-case stall
 * the app suffered in the polling window.
 *
 * Mapping observed-lag-ms → healthFactor ∈ [0, 1]:
 *
 *      healthFactor
 *           ▲
 *      1.0  ┤━━━━━━━━━┓
 *           │         ┃ linear interpolation
 *           │         ┃
 *      0.0  ┤         ┗━━━━━━━━━━━━━
 *           ┼─────────┼──────────┼──────►  observed lag (ms)
 *           0    degradedMs    deadMs
 *
 * Below `degradedMs` → factor 1 (healthy). Above `deadMs` → factor 0 (dead).
 * In-between → linear interpolation. ADR-111 §4.3 default thresholds:
 *   degradedMs = 5000  (5 s — same order as ADR-110's 10 s probe timeout)
 *   deadMs     = 30000 (30 s — definitely wedged)
 *
 * Bun-compatibility: Bun ships `monitorEventLoopDelay()` with the same
 * shape as Node 16+. Verified against `bun:1.3` in this repo's CI.
 */

interface EventLoopLagOptions {
    /** Lag below this is fully healthy (factor=1). Default 5000 ms. */
    readonly degradedMs?: number;
    /** Lag above this is fully dead (factor=0). Default 30000 ms. */
    readonly deadMs?: number;
    /**
     * Histogram resolution (ms). Default 10 ms — the smaller, the more
     * accurate the max but the higher the libuv accounting overhead.
     * Node defaults are also 10 ms.
     */
    readonly resolutionMs?: number;
    /** Weight in the weighted-product score. Default 0.4 (ADR-111 §4.3). */
    readonly weight?: number;
    /**
     * Optional injected monitor (tests). Defaults to a fresh
     * `monitorEventLoopDelay()` instance.
     */
    readonly monitor?: ReturnType<typeof monitorEventLoopDelay>;
}
/**
 * Build an `event-loop-lag` signal. The returned object owns a started
 * histogram monitor; call `dispose()` during shutdown / between tests.
 */
declare function eventLoopLagSignal(opts?: EventLoopLagOptions): SyncSignal;

/**
 * `memoryPressureSignal` — RSS / cgroup memory.max ratio (ADR-111 §4.3).
 *
 * On Linux containers we read the cgroup v2 memory limit from
 * `/sys/fs/cgroup/memory.max`. Then `pressure = process.memoryUsage().rss /
 * limit`. ADR-111 §4.3 default thresholds:
 *   degradedRatio = 0.85  (85 % → degraded)
 *   deadRatio     = 0.95  (95 % → dead)
 *
 * Mapping observed-pressure → healthFactor:
 *
 *      healthFactor
 *           ▲
 *      1.0  ┤━━━━━━━━━━━━━━━━━━┓
 *           │                  ┃
 *      0.0  ┤                  ┗━━━━━━━━━
 *           ┼──────────────────┼─────────►  pressure (0..1)
 *           0           degradedRatio    deadRatio
 *
 * Graceful fallback (the cardinal-rule deference):
 *   - cgroup file missing      → unknown=true (signal ignored)
 *   - cgroup file has 'max'    → unlimited container, ratio undefined → unknown
 *   - file unreadable / parse  → unknown=true
 *
 * `unknown=true` makes the scoring strategy ignore the signal — we never
 * pretend to know memory pressure on a host where we can't measure it.
 *
 * cgroup v1 (legacy) lives at `/sys/fs/cgroup/memory/memory.limit_in_bytes`
 * — supported via `cgroupV1Path` for operators on older kernels.
 */

interface MemoryPressureOptions {
    /** Pressure below this is fully healthy (factor=1). Default 0.85. */
    readonly degradedRatio?: number;
    /** Pressure above this is fully dead (factor=0). Default 0.95. */
    readonly deadRatio?: number;
    /**
     * Custom cgroup v2 memory limit path. Default `/sys/fs/cgroup/memory.max`.
     */
    readonly cgroupV2Path?: string;
    /**
     * Optional cgroup v1 fallback path. Default
     * `/sys/fs/cgroup/memory/memory.limit_in_bytes`. Used when v2 path is
     * unreadable AND `cgroupV1Path` is set or v2 doesn't exist.
     */
    readonly cgroupV1Path?: string;
    /** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */
    readonly weight?: number;
    /**
     * Optional injected `process.memoryUsage` for tests.
     * Default: `process.memoryUsage`.
     */
    readonly memoryUsage?: () => {
        rss: number;
    };
    /** Optional injected file reader for tests. */
    readonly readFile?: (path: string) => string;
}
declare function memoryPressureSignal(opts?: MemoryPressureOptions): SyncSignal;

/**
 * `pingSignal` — generic binary readiness signal for any sub-system the
 * service depends on (Postgres, Redis, Kafka, S3, an upstream API, …).
 *
 * The signal calls `ping()` every poll tick and reports:
 *   - `healthFactor: 1` if the ping resolves (any value) within the timeout
 *   - `healthFactor: 0` if the ping throws OR times out
 *
 * Binary — no partial-credit semantics. For richer scoring use a custom
 * signal that returns a graded `healthFactor`; `pingSignal` is the
 * canonical "subsystem reachable yes/no" primitive that 99% of dependency
 * probes need.
 *
 * `databaseSignal` and `redisSignal` are thin convenience wrappers
 * with idiomatic defaults for those two ubiquitous dependencies.
 *
 * @example Generic
 * ```ts
 * import { pingSignal } from '@sylphx/sdk/health'
 *
 * app.use('*', withHealth.hono({
 *   signals: [
 *     pingSignal({
 *       name: 'upstream-api',
 *       ping: async () => { await fetch('https://upstream.example/_ping') },
 *     }),
 *   ],
 * }))
 * ```
 *
 * @example Database + Redis
 * ```ts
 * import { withHealth, databaseSignal, redisSignal } from '@sylphx/sdk/health'
 *
 * app.use('*', withHealth.hono({
 *   signals: [
 *     databaseSignal({ ping: () => pool.query('SELECT 1') }),
 *     redisSignal({ ping: () => redis.ping() }),
 *   ],
 * }))
 * ```
 */

interface PingSignalOptions {
    /** Stable signal name. Appears in the wire JSON `signals.<name>` map. */
    readonly name: string;
    /**
     * Async function the SDK calls every poll tick. Resolve (any value) =
     * healthy. Throw / reject = unhealthy. Timeout → unhealthy.
     */
    readonly ping: () => Promise<unknown>;
    /**
     * Per-tick timeout in milliseconds. Default 500ms — fast enough to
     * fold into a `/healthz` response without holding the event loop, slow
     * enough to avoid false negatives on a momentarily-slow dependency.
     * Tune up for dependencies that legitimately take longer (e.g. S3 list).
     */
    readonly timeoutMs?: number;
    /** Weight in the weighted-product score. Default 1.0. */
    readonly weight?: number;
}
declare function pingSignal(opts: PingSignalOptions): AsyncSignal;
interface DependencyPingOptions {
    readonly ping: () => Promise<unknown>;
    readonly timeoutMs?: number;
    readonly weight?: number;
    /** Override the signal name. Defaults to the subsystem name. */
    readonly name?: string;
}
/**
 * Postgres / SQL-database readiness — convenience over `pingSignal` with
 * `name: 'database'`. Wrap the caller's `pool.query('SELECT 1')` or
 * equivalent.
 */
declare function databaseSignal(opts: DependencyPingOptions): AsyncSignal;
/**
 * Redis / KV readiness — convenience over `pingSignal` with `name: 'redis'`.
 * Wrap the caller's `redis.ping()` or equivalent.
 */
declare function redisSignal(opts: DependencyPingOptions): AsyncSignal;

/**
 * `queueDepthSignal` — backpressure indicator (ADR-111 §4.3).
 *
 * Generic signal: the app provides a `getter` that returns the current
 * length of whatever queue the operator wants probed (BullMQ, RabbitMQ,
 * in-memory work pool, …). The signal does NOT own the queue — the app
 * does. We just measure.
 *
 * Mapping observed-depth → healthFactor:
 *
 *      healthFactor
 *           ▲
 *      1.0  ┤━━━━━━━━━━━━━━━━━━┓
 *           │                  ┃ linear interpolation
 *      0.0  ┤                  ┗━━━━━━━━━━━━
 *           ┼──────────────────┼─────────►  depth
 *           0           fullThreshold
 *
 * Below `fullThreshold` → factor 1. At `fullThreshold` → factor 0. Above
 * → factor 0. The implicit "degraded" zone is `[0.5 × fullThreshold,
 * fullThreshold]` — depth at half-full produces factor 0.5. Operators
 * tune `fullThreshold` to whatever their queue reasonably hits at peak
 * load; "100% full" means "drain new traffic", not "kill the workload" (the
 * three-tier gate at the sidecar handles the kill decision).
 *
 * `getter` errors are swallowed — a thrown getter produces `unknown=true`
 * (so the scoring strategy ignores this signal, instead of falsely
 * reporting score=0). The app's bug shouldn't masquerade as a sidecar
 * decision.
 */

interface QueueDepthOptions {
    /**
     * Sync or async getter the SDK calls every poll tick. Must return a
     * non-negative integer. Throws → reading marked `unknown=true`.
     */
    readonly getter: () => number | Promise<number>;
    /**
     * Depth at which the queue is considered "full" (factor=0). Linear
     * interp from 0..fullThreshold. No default — operator-specific.
     */
    readonly fullThreshold: number;
    /**
     * Optional below-which factor is always 1 (a "soft floor"). Default 0
     * — the linear interp starts at depth 0.
     */
    readonly healthyBelow?: number;
    /** Weight in the weighted-product score. Default 0.2 (ADR-111 §4.3). */
    readonly weight?: number;
    /** Custom signal name. Default `queueDepth`. */
    readonly name?: string;
}
declare function queueDepthSignal(opts: QueueDepthOptions): AsyncSignal;

/**
 * `@sylphx/sdk/health` — Phase B multi-signal health score (ADR-111 §4).
 *
 * Apps register signals (event-loop lag, queue depth, error rate, memory
 * pressure, …); the SDK folds them into a continuous score in `[0, 1]`;
 * the **sidecar** maps the score to liveness / readiness / drain via the
 * three-tier gate. The SDK's responsibility ends at exposing the score.
 *
 * The wire format the sidecar parses (ADR-111 §3.2.4):
 *
 * ```json
 * {
 *   "score": 0.92,
 *   "signals": {
 *     "eventLoopLagMs": 12,
 *     "queueDepth": 3,
 *     "recent5sErrorRate": 0.001,
 *     "memoryPressure": 0.45
 *   },
 *   "signalFactors": {
 *     "eventLoopLagMs": 1,
 *     "queueDepth": 0.8,
 *     "recent5sErrorRate": 0.99,
 *     "memoryPressure": 0.7
 *   },
 *   "lastTickAt": "2026-05-03T12:34:56.789Z"
 * }
 * ```
 *
 * Default signals (if `sylphxHealth()` called with no `signals`):
 *   - `eventLoopLagSignal({ degradedMs: 5000, deadMs: 30000 })` (weight 1.0)
 *
 * Three-tier gate (decided by **sidecar**, not SDK; ADR-111 §4.4):
 *
 * | Score          | Liveness | Readiness | Effect                |
 * | -------------- | -------- | --------- | --------------------- |
 * | > 0.8          | 200      | 200       | Normal traffic        |
 * | (0.5, 0.8]     | 200      | 503       | Drain new, no kill    |
 * | <= 0.5         | 503      | 503       | Kill after threshold  |
 *
 * Apps don't need to think about probe semantics — that's the sidecar's
 * job. The app just exposes the score. See `apps/health-agent/` for the
 * sidecar implementation.
 *
 * @example Worked example — OpenClaw under PDF-extract load (ADR-111 §4.5):
 *
 * ```text
 *   eventLoopLagMs = 6000 → factor 0.4
 *   queueDepth = 12       → factor 1.0
 *   errorRate = 0.002     → factor 1.0
 *   memoryPressure = 0.55 → factor 1.0
 *   score = 0.4^0.4 × 1.0^0.6 ≈ 0.69
 *
 *   → falls in [0.5, 0.8] → sidecar drains traffic, doesn't kill.
 *     Pod gets to finish PDF extraction.
 * ```
 */

/**
 * The handle returned by `sylphxHealth()`. Owns the registered signals,
 * exposes evaluation in both Promise + Effect form, and produces an HTTP
 * handler / Unix-socket server.
 *
 * Call `dispose()` during graceful shutdown to release per-signal
 * resources (e.g. the `monitorEventLoopDelay()` histogram).
 */
interface SylphxHealth extends HealthEvaluator {
    /** All signals registered (read-only). */
    readonly signals: ReadonlyArray<Signal>;
    /** Snapshot evaluation as a Promise. */
    evaluate(): Promise<HealthScore>;
    /** Snapshot evaluation as an Effect (per Rule 21 / ADR-058 Amendment). */
    readonly evaluateEffect: ReturnType<typeof evaluateEffect>;
    /**
     * Web Fetch API HTTP handler — works under Hono, Bun.serve, Next.js
     * route.ts, itty-router, Hattip. Always returns 200 + JSON; the
     * sidecar applies the three-tier 200/503 gate.
     */
    handler(): (req?: Request) => Promise<Response>;
    /** Node.js classic `(req, res)` handler — for Express / classic Fastify. */
    nodeHandler(): ReturnType<typeof createNodeHandler>;
    /**
     * Bind a Bun Unix-domain socket and serve the same JSON. The sidecar
     * polls `/var/run/sylphx/health.sock` by default (ADR-111 §3.2.4).
     */
    serveUnixSocket(opts?: UnixSocketServerOptions): UnixSocketServerHandle;
    /**
     * Most recent score returned by `evaluate()`. Returns null before
     * the first evaluation. Used by `withHealth.*` middleware to inject
     * cause baggage on non-probe requests (ADR-143).
     */
    getLastScore(): HealthScore | null;
    /**
     * Run `fn` within an OTel context that has our local cause baggage
     * set, derived from the most recent `evaluate()`. When causality is
     * disabled or the OTel API is unavailable, runs `fn()` directly.
     * Middleware consumers don't usually call this — they let
     * `withHealth.*` wire it automatically. Exposed for advanced users
     * who build custom transport wrappers.
     */
    runWithCause<T>(fn: () => T | Promise<T>): Promise<T>;
    /**
     * Retrieve audit-grade history entries (ADR-144). Returns an empty
     * array when history is disabled. Operators use this for compliance
     * evidence + post-incident reconstruction.
     */
    getHistory(query?: HistoryQuery): Promise<readonly HistoryEntry[]>;
    /**
     * Verify the integrity of the recorded history chain over `query`
     * (defaults to all available entries). Returns `verified: true`
     * when the SHA-256 chain matches end-to-end; otherwise reports the
     * first sequence where tampering is detected.
     */
    verifyHistory(query?: HistoryQuery): Promise<VerificationResult>;
    /**
     * Tear down all registered signals. Idempotent. Call during graceful
     * shutdown to release histograms, file watchers, etc.
     */
    dispose(): void;
}
declare function sylphxHealth(opts?: SylphxHealthOptions): SylphxHealth;

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 };
