/**
 * Canary rollouts — ship a change to a stable slice of installs instead of
 * all-or-nothing.
 *
 * The problem this solves: the repo has ~49 `HF_*` / `PRODUCER_*` boolean
 * toggles, and every one of them is binary. A change is either off (and
 * therefore untested on real traffic) or on for everyone (and therefore a
 * fleet-wide bet). The parallel-drawElement router spent weeks in that gap:
 * default-off collected almost no signal, and flipping it default-on exposed
 * 100% of eligible installs at once. A percentage slice is the missing rung.
 *
 * Design notes worth knowing before you add one:
 *
 * - **Pure and universal.** No fs, no network, no `process` — the caller
 *   supplies the unit id and the overrides. That keeps this importable from
 *   the CLI, the producer, the engine, studio-server, the browser-side studio
 *   bundle, and the embeddable player alike.
 *
 * - **Independent slices.** The bucket is a hash of `feature:unitId`, NOT of
 *   `unitId` alone. If every canary bucketed on the id by itself, they would
 *   all select the SAME installs — one unlucky cohort would receive every
 *   experiment simultaneously, and no two rollouts could be read
 *   independently.
 *
 * - **Ramping is inclusive.** `bucket < percentage` means widening 10 → 25
 *   keeps every install that was already at 10. Cohorts never reshuffle, so
 *   before/after comparisons stay valid across a ramp.
 *
 * - **Stable per install, for the life of the install.** The same id and
 *   feature always resolve the same way, with no persisted state to keep in
 *   sync and nothing to look up at runtime.
 */
/** Why a canary resolved the way it did. Attach to telemetry — a rollout you
 *  can't segment by enrolment reason is a rollout you can't debug. */
export type CanaryReason = "forced_on" | "forced_off" | "in_cohort" | "out_of_cohort" | "no_unit_id" | "excluded" | "telemetry_opt_out";
export interface CanaryDecision {
    enabled: boolean;
    reason: CanaryReason;
    /** 0-99 slot this unit landed in for this feature; undefined when not computed. */
    bucket?: number;
}
export interface CanaryInput {
    /** Registry key, e.g. "de-parallel-router". Part of the hash, so each feature gets its own slice. */
    feature: string;
    /** Stable per-install id — the CLI's telemetry `anonymousId`. Missing/blank fails closed. */
    unitId: string | undefined;
    /** 0 = off for everyone, 100 = on for everyone. Values outside 0-100 are clamped. */
    percentage: number;
    /**
     * Explicit override, both directions — support escalations, dogfooding, a
     * bisect, or a panic-off. Always wins over the percentage.
     */
    override?: boolean | undefined;
    /**
     * Exclude this unit from percentage-based enrolment (an explicit override
     * still applies). Callers pass `isCI` here: CI installs regenerate their
     * config constantly, so their ids are ephemeral — they would hop cohorts
     * between runs, adding noise to the rollout signal while telling you
     * nothing about real users.
     */
    exclude?: boolean | undefined;
}
/** The 0-99 slot a unit occupies for a given feature. Exported for tests and diagnostics. */
export declare function canaryBucket(feature: string, unitId: string): number;
/**
 * Resolve whether a feature is on for this unit.
 *
 * Fails closed on a missing id: the canary exists to bound blast radius, so
 * "we don't know who this is" must mean "not enrolled", never "enrol
 * everyone".
 */
export declare function evaluateCanary(input: CanaryInput): CanaryDecision;
/**
 * Parse a canary override from an env-var value.
 *
 * Accepts the spellings people actually type. Returns undefined for
 * unset/empty so the percentage decides — matching how the existing HF_*
 * knobs treat a set-but-empty var, and avoiding the failure mode where an
 * exported-but-empty variable silently forces a feature on.
 */
export declare function parseCanaryOverride(raw: string | undefined): boolean | undefined;
/**
 * Property-name prefix for canary assignments on telemetry events.
 *
 * PostHog treats `$feature/<key>` as a first-class flag property: breakdowns,
 * funnels split by cohort and the experiment surfaces all key on it. Emitting
 * assignments in that shape means the analysis tooling works on a canary with
 * nothing configured server-side — the decision still happens locally and
 * offline, which the render path requires (no render-time network calls, and
 * behaviour must not depend on analytics being reachable).
 *
 * The `canary-` infix is deliberate. A real PostHog flag namespace already
 * exists in this project, owned by the web app (e.g. `enable-chat-tab`, set by
 * posthog-js). Namespacing guarantees a canary key can never alias a real flag
 * key and have the two fight over the same property.
 */
export declare const CANARY_FEATURE_PREFIX = "$feature/canary-";
/** `de-parallel-router` → `$feature/canary-de-parallel-router`. */
export declare function canaryFeatureKey(name: string): string;
/**
 * Companion key carrying WHY a canary resolved as it did.
 *
 * Deliberately outside the `$feature/` namespace: PostHog treats those as flag
 * values and a non-boolean there would corrupt the flag's own breakdowns. This
 * is an ordinary property that sits alongside.
 *
 * `de-parallel-router` → `canary_reason_de_parallel_router`.
 */
export declare function canaryReasonKey(name: string): string;
/**
 * Build the telemetry properties for a set of resolved canaries.
 *
 * Emits EVERY registered canary, not just the enrolled ones, because absent
 * and `"false"` mean different things: absent is "this build predates the
 * canary", `"false"` is "this build has it and this install is not enrolled".
 * Collapsing those makes a ramp unreadable — you cannot tell a control group
 * from an old version.
 *
 * Values are the strings `"true"` / `"false"` to match how PostHog records
 * boolean flag values, so the property is directly comparable to a real flag.
 *
 * **The reason rides alongside when supplied**, under `canary_reason_<name>`.
 * Without it the assignment alone is ambiguous in the one case that matters:
 * an install reporting both `"true"` and `"false"` for a canary whose
 * percentage never moved is indistinguishable from a developer toggling
 * `HF_CANARY_*`. The first calibration read hit exactly that wall — 304
 * installs reported both values and the genuinely anomalous ones could not be
 * separated from deliberate overrides. The registry doc deferred this until
 * the stability check came back dirty; it did.
 *
 * `reason` is optional so existing callers keep working; a caller that has the
 * full decision should pass it.
 */
export declare function canaryFeatureProperties(entries: ReadonlyArray<{
    name: string;
    enabled: boolean;
    reason?: CanaryReason;
}>): Record<string, string>;
//# sourceMappingURL=canary.d.ts.map