/**
 * RelationshipBehaviorStore — the durable, deterministic behavioral baseline per
 * principal (Pillar 3, §7.1). This is the substrate the RelationshipAnomalyScorer
 * reads to answer "does this request feel like THEM?".
 *
 * Why a dedicated store (vs reusing RelationshipManager):
 *   RelationshipManager is generic, cross-platform relationship memory (themes,
 *   notes, a free-text communicationStyle). It has no structured per-request history
 *   — no action-tier histogram, no time-of-day distribution, no message-shape stats
 *   — which is exactly what a CHEAP, DETERMINISTIC anomaly baseline needs. Rather
 *   than retrofit privacy-sensitive structured tracking onto the generic manager,
 *   this module keeps a small, Slack-permission-scoped, privacy-respecting baseline:
 *   SHAPE only (which action labels, what tier, what hour, how long the message was),
 *   NEVER message content. The seam mirrors the rest of the permissions module — it
 *   depends on nothing in core; the gate/observer inject it.
 *
 * Privacy: we store action LABELS, tier counts, hour-of-day counts, and coarse
 * message-length stats. We do NOT store message text, topics, or any free text.
 *
 * State category: `slack-relationship-baselines` (state-coherence-registry.json).
 * Design: docs/specs/SLACK-ORG-INTEGRATION-SPEC.md §7.1–7.2, §7.6.
 */
/** A single recorded interaction's SHAPE (never content). */
export interface BehaviorObservation {
    /** Action label from the intent classifier (e.g. 'read', 'prod-deploy'). */
    action: string;
    /** Sensitivity tier 0..4. */
    tier: number;
    /** Local hour-of-day 0..23 the request arrived. */
    hour: number;
    /** Message length in characters (coarse style signal). */
    length: number;
    /** Whether the message carried urgency/pressure language (cheap style signal). */
    urgent: boolean;
}
/**
 * A time-bucketed slice of a principal's history (poisoning-resistance, Phase-3
 * follow-ups #2/#3b). One bucket per rolling window (`bucketMs`, default 1 day).
 * Holds the SAME SHAPE counts as the cumulative profile, scoped to a window — so
 * the scorer can apply recency/decay weighting (a RECENT attacker burst can't durably
 * dominate the histogram) and the store can enforce a per-window observation-rate cap
 * (one session can't hammer the histogram to reshape it).
 *
 * Buckets are ADDITIVE: the cumulative `actionCounts`/`tierCounts`/`hourCounts`/length
 * sums on the profile are kept in lock-step so an old reader (or a downgrade) still sees
 * the same numbers it always did. A profile written before this hardening simply has no
 * `buckets` field and degrades to its cumulative form everywhere.
 */
export interface BehaviorBucket {
    /** Window start (epoch ms, floored to the bucket boundary) — drives decay weighting. */
    startMs: number;
    /** Observations recorded in this window (drives the per-window rate cap). */
    count: number;
    actionCounts: Record<string, number>;
    tierCounts: number[];
    hourCounts: number[];
    lengthSum: number;
    lengthSqSum: number;
    urgentCount: number;
}
/**
 * The aggregated, privacy-respecting baseline for one principal. All counts; no
 * content. Persisted as JSON; cheap to read and update.
 */
export interface PrincipalBehaviorProfile {
    slackUserId: string;
    /** Total observations recorded — the DEPTH of the baseline (drives confidence). */
    interactionCount: number;
    /** Count per action label — the principal's normal repertoire. */
    actionCounts: Record<string, number>;
    /** Count per sensitivity tier (index 0..4). */
    tierCounts: number[];
    /** Count per local hour-of-day (index 0..23) — the principal's normal rhythm. */
    hourCounts: number[];
    /** Running mean + count of message length, for a coarse style baseline (Welford-free, count-weighted). */
    lengthSum: number;
    lengthSqSum: number;
    /** How often this principal uses urgency language at baseline (0..1 derived from count). */
    urgentCount: number;
    /** First / last observation ISO timestamps. */
    firstSeen: string;
    lastSeen: string;
    /**
     * OPTIONAL time-bucketed history (poisoning-resistance #2/#3b). Newest bucket last.
     * Absent on a pre-hardening profile — the scorer degrades to the cumulative counts.
     * When present, the cumulative counts above remain the exact sum of all buckets'
     * counts (backward-compat invariant), so old readers are unaffected.
     */
    buckets?: BehaviorBucket[];
}
/** Recording knobs for the rate-cap (#3b) + decay bucket sizing (#2). All optional. */
export interface BehaviorStoreOptions {
    /**
     * Length of one decay/rate-cap bucket window (ms). Default 1 day. The recency-decay
     * weighting (applied at scoring time) and the per-window observation cap are both
     * keyed off this window.
     */
    bucketMs?: number;
    /**
     * Per-principal observation-rate cap (#3b): the MAX observations RECORDED into a
     * single bucket window. Excess observations in the window are DROPPED (logged via
     * `onCapDrop`), not recorded — so one session can't hammer the histogram to reshape
     * it. Default 50/day. Set to a falsy/non-positive value to disable the cap.
     */
    maxObservationsPerWindow?: number;
    /** Cap how many buckets are retained (bounds file growth). Default 90 (≈90 days). */
    maxBuckets?: number;
    /** Best-effort callback when an observation is dropped by the rate cap (for logging). */
    onCapDrop?: (slackUserId: string, windowStartMs: number, droppedCount: number) => void;
}
export declare const DEFAULT_BUCKET_MS: number;
export declare const DEFAULT_MAX_OBSERVATIONS_PER_WINDOW = 50;
export declare const DEFAULT_MAX_BUCKETS = 90;
export declare class RelationshipBehaviorStore {
    private readonly file;
    private readonly now;
    private readonly bucketMs;
    private readonly maxObservationsPerWindow;
    private readonly maxBuckets;
    private readonly onCapDrop?;
    constructor(stateDir: string, now?: () => string, options?: BehaviorStoreOptions);
    /** The bucket-window length (ms) this store records into — read by the scorer for decay. */
    get bucketWindowMs(): number;
    get path(): string;
    /**
     * Record an observation for a principal, growing their baseline. Best-effort:
     * a write failure is swallowed (this is observe-only infra; it must never break
     * the message path). No-op for an unsafe key (defensive).
     */
    record(slackUserId: string, obs: BehaviorObservation): void;
    /**
     * Resolve (and lazily migrate) the bucket covering `nowMs`, pruning to `maxBuckets`.
     * Backfills a missing `buckets` array on a pre-hardening profile WITHOUT moving its
     * cumulative counts into a bucket (those counts predate bucketing and have unknown
     * timestamps — they keep weight as the un-decayable "legacy" base; see the scorer's
     * decay logic). New observations land in fresh, timestamped buckets.
     */
    private currentBucket;
    /** The current baseline for a principal, or undefined if none recorded yet. */
    profileFor(slackUserId: string | undefined): PrincipalBehaviorProfile | undefined;
    /** All profiles (for the read route / inspection). */
    all(): Record<string, PrincipalBehaviorProfile>;
    private readAll;
    private writeAll;
}
/** Mean message length, or undefined when there is no data. */
export declare function meanLength(prof: PrincipalBehaviorProfile): number | undefined;
/** Population standard deviation of message length, or undefined when <2 samples. */
export declare function stdLength(prof: PrincipalBehaviorProfile): number | undefined;
/** Fraction of baseline interactions in a given hour (0..1). */
export declare function hourFraction(prof: PrincipalBehaviorProfile, hour: number): number;
/** Baseline calendar age in milliseconds (now − firstSeen), or 0 when unknown. */
export declare function baselineAgeMs(prof: PrincipalBehaviorProfile, nowMs: number): number;
/**
 * The decayed-effective SHAPE view a scorer should read instead of raw cumulative
 * counts. Same fields as the profile, but every count is the decay-weighted sum across
 * buckets PLUS the legacy (pre-bucketing) base at full weight.
 */
export interface DecayedProfileView {
    effectiveCount: number;
    actionCounts: Record<string, number>;
    tierCounts: number[];
    hourCounts: number[];
    lengthSum: number;
    lengthSqSum: number;
    urgentCount: number;
}
export interface DecayOptions {
    /** Now (epoch ms) — drives bucket age. */
    nowMs: number;
    /** One bucket window length (ms). Must match the store's bucketMs. */
    bucketMs: number;
    /** Decay half-life in bucket-windows. Default 30 (≈30 days at a 1-day bucket). */
    halfLifeWindows?: number;
}
/**
 * Compute the decay-weighted effective baseline. The legacy base = cumulative totals
 * MINUS the sum of all buckets (i.e. the part of history that predates bucketing). That
 * base keeps full weight (weight 1.0); each bucket is weighted by 0.5^(ageWindows/halfLife).
 *
 * A profile with no buckets → its entire cumulative form is the legacy base at full
 * weight → the decayed view equals the cumulative view (perfect backward-compat). A
 * fully-bucketed profile decays purely on bucket age.
 */
export declare function decayedView(prof: PrincipalBehaviorProfile, opts: DecayOptions): DecayedProfileView;
/** Mean message length from a decayed view, or undefined when there is no weight. */
export declare function decayedMeanLength(view: DecayedProfileView): number | undefined;
/** Population std of message length from a decayed view, or undefined when <2 effective. */
export declare function decayedStdLength(view: DecayedProfileView): number | undefined;
/** Fraction of decayed interactions in a given hour (0..1). */
export declare function decayedHourFraction(view: DecayedProfileView, hour: number): number;
import type { BaselineProvider, PrincipalBaseline } from './AnomalyScorer.js';
import type { Principal } from './types.js';
export declare class StoreBaselineProvider implements BaselineProvider {
    private readonly store;
    constructor(store: RelationshipBehaviorStore);
    baselineFor(principal: Principal): PrincipalBaseline | undefined;
}
//# sourceMappingURL=RelationshipBehaviorStore.d.ts.map