/** * The anomaly feature space. D36. * * Features first, algorithm later (D18), so this file computes features and * makes no attempt to classify. Every value here is derived from a bounded * window of recent observations, never from raw payloads. * * The immediate purpose is the diversity signal D37 requires: the trust * dimension must not have its uncertainty ceiling lifted on volume alone, and * "was that volume varied or monotonous" is the question that gates it. */ /** One observation, reduced to the least SG needs to measure shape. */ export type ObservationTrace = { /** Milliseconds since the epoch. */ readonly at: number; /** Which declared scope, or a coarse label. Never a payload. */ readonly scope: string; }; /** * The feature vector. All values are dimensionless or in units of time, so a * later model consumes numbers rather than reconstructing meaning. * * Computed from a bounded window (D36, `DEFAULT_WINDOW_SIZE`), not from a * streaming estimator. That is a decision rather than a shortcut: over a window * this size the values here are exact, a streaming estimator would only * approximate them, and the bound is itself required: an unbounded per-entity * history is a memory-growth surface and a retention problem (D6, D22). A * streaming form becomes worth writing only if the window ever needs to be large, * which nothing in the model currently asks for. */ export type BehaviorFeatures = { /** Observations in the window. */ readonly count: number; /** Distinct scopes seen. Low values mean repetitive attention. */ readonly distinctScopes: number; /** * Shannon entropy over scope frequency, normalised to [0,1] against the maximum * the *observed* scopes allow. Read it as evenness: 1 means attention was spread * perfectly evenly over whatever scopes were visited, 0 means every observation * landed in the same one. * * Normalised against `log2(distinctScopes)` rather than `log2(count)`, see D46. * The window-size denominator made this a proxy for `distinctScopes` and tied * the value to how many scopes the *application* has, so the same behavior * scored differently in a small app than in a large one. Breadth is * `distinctScopes`'s job; this measures balance. */ readonly scopeEntropy: number; /** * Coefficient of variation of inter-arrival gaps: standard deviation over mean. * * This is the discriminating feature the design notes single out: the tell is * not the delay but the *shape* of the randomness. Human activity is bursty * (high CV); fixed sleeps approach 0, and a flat `uniform(a, b)` sits at a * characteristic low value well below human traffic. */ readonly interArrivalCv: number; /** Mean gap in milliseconds, or `undefined` with fewer than two observations. */ readonly meanGapMs: number | undefined; /** Fraction of observations that repeated the immediately preceding scope. */ readonly immediateRepeatRatio: number; }; export declare function behaviorFeatures(window: readonly ObservationTrace[]): BehaviorFeatures; /** * Does observed behavior vary enough for low variance to be believed? D37. * * Returns `undefined` when the window is too small to judge: which is not the * same claim as monotonous, and is why `anomalyConcurs` is three-valued. */ export declare function diversityConcurs(features: BehaviorFeatures, thresholds?: DiversityThresholds): boolean | undefined; export type DiversityThresholds = { readonly minObservations: number; readonly minDistinctScopes: number; readonly minScopeEntropy: number; readonly minInterArrivalCv: number; }; /** * PoC defaults. Every one of these is a guess until D30 puts them against real * traffic: they are deliberately lenient, because a wrong threshold here * withholds escalation rather than manufacturing a false positive. */ export declare const DEFAULT_DIVERSITY: DiversityThresholds; /** How many observations are retained per entity. Bounded on purpose. */ export declare const DEFAULT_WINDOW_SIZE = 20; export declare function pushObservation(window: readonly ObservationTrace[], trace: ObservationTrace, limit?: number): readonly ObservationTrace[]; /** * Thresholds for the farming check. D50, corrected twice by D54. */ export type VelocityThresholds = { /** Observations per hour above which the rate is worth a second look. */ readonly maxObsPerHour: number; /** Observations needed before a rate is measured at all. */ readonly minObservations: number; /** * How regular the gaps must also be, coefficient of variation *below* this: * before a high rate is read as farming rather than as a fast human. */ readonly maxInterArrivalCv: number; }; export declare const DEFAULT_VELOCITY: VelocityThresholds; /** * Observations per hour over the window, or `undefined` when there is too little * to measure. * * Exported because it is the measurement: a host that wants the number should not * have to infer it from a boolean. On its own it is **not** a farming signal. */ export declare function observationRate(window: readonly ObservationTrace[], minObservations?: number): number | undefined; /** * Is this entity farming trust? D50, corrected by D54. * * A conjunction of rate and *gap regularity*, and the history of both halves is * worth keeping: * * 1. D50 shipped a pure rate check. Generated traffic falsified it immediately, a * busy operator at one action per 45 seconds and a power user at one per 20 * seconds both tripped it. Humans are simply capable of being fast, so rate * alone is exactly the false positive the central constraint forbids. * 2. The first correction added the anomaly score (D52) as the second half. That * falsified too: HealthMe's `power-user` reads one scope repeatedly and scores * 0.56, because the composite penalises narrow attention as well as regular * timing. Legitimate people do use one feature over and over. * * What actually separates a farmer from a fast human is neither speed nor breadth * but the *shape of the gaps*: which is the one thing the design notes single out, * and the one thing a farmer cannot fix without giving up the volume it needs. A * jittered fixed sleep still reads at `cv ~= 0.05`; bursty human activity sits above * `1.0`. * * Returns `undefined` when the window cannot support the judgement, which is not the * same claim as "not farming". */ export declare function farmingSuspected(window: readonly ObservationTrace[], thresholds?: VelocityThresholds): boolean | undefined; /** * How much of an earned positive should count, given the shape of the gaps. D55. * * The farming answer that acts on *intake* rather than on the decision. D4 argued * against acting on mass at decision time and D49 accepted that, which left farming * with no available lever: but "how much is this observation worth" is a different * question from "what should this mean do", and it is asked earlier. * * Returns a factor in [0,1]: full credit for bursty human-shaped activity, * proportionally less as gaps approach machine regularity. Deliberately continuous * rather than a cliff, so there is no boundary to sit just outside of. * * **Asymmetric, and that asymmetry is the whole design.** Negative evidence is never * discounted. A regular client earns trust more slowly; it is never distrusted for * being regular. Measured across the full regularity range, a client that only ever * earns positives cannot be advised worse than `OBSERVE`, which costs a user * nothing: so withholding credit cannot become punishment. * * `undefined` window state (too few observations) yields full credit: absence of a * measurement is not grounds to withhold anything. */ export declare function positiveCredit(window: readonly ObservationTrace[], thresholds?: VelocityThresholds): number; //# sourceMappingURL=behavior.d.ts.map