/** * FairnessSweep — governed, replayable algorithmic-fairness evaluation built on * the REAL HoloScript experiment engine (not a re-implementation): * * - the single bias sweep runs through `ExperimentOrchestrator` (+ its * `ProvenanceTracker`), wrapping the institution's decision model as a * `SolverHandle` whose `getStats()` returns the disparity scalars; and * - the robustness band runs through `UncertaintyQuantification` (seeded LHS), * so `getScalarDistribution('adverseImpactRatio').percentiles.{p5,p95}` IS * the 90% confidence band and `.min` is the worst case. * * Core stays domain-free (D.007): the model, the cohort, and the population * perturbation are all INJECTED. No banking / insurance / healthcare vocabulary * lives here — the `.holo` domain bridge supplies the real model + point-in-time * data + the drift/noise semantics for a given vertical. * * @see ./FairnessReceipt — the sovereign receipt + Guarantee-6 replay primitives * @see ../experiment/ExperimentOrchestrator — the real sweep engine * @see ../UncertaintyQuantification — the real seeded-LHS robustness engine */ import { type DeterminismGrade, type FairnessMetrics, type FairnessReceipt, type FairnessRobustnessReceipt, type RobustnessBand } from './FairnessReceipt'; import type { HashMode } from '../sha256'; import { type FairnessJurisdiction, type JurisdictionAuditSummary, type JurisdictionConfig } from './JurisdictionConfig'; /** One evaluated record: a protected-attribute value + an opaque feature payload. */ export interface FairnessRecord { /** Protected-attribute value (e.g. "A" / "B" / "group-1"). Domain-free. */ group: string; /** Feature payload consumed by the decision model. */ features: Record; } /** The decision model under test — injected, so core never sees its internals. */ export interface FairnessModel { /** Stable model identifier. */ id: string; /** Approve (true) / deny (false) a single record's features. */ decide(features: Record): boolean; /** Content that uniquely determines this model's behavior (weights / version). */ fingerprint(): unknown; /** * Self-declared reproducibility grade (default 'exact'). The engine MEASURES * this via a double-run and auto-downgrades an over-claim — never trusts it * blind. `tolerance` is the max |Δ adverse-impact ratio| for non-exact grades. */ determinism?: { grade: DeterminismGrade; tolerance?: number; }; } /** A single ensemble replicate's perturbation knobs. */ export interface FairnessPerturbation { /** Population-drift magnitude for this replicate. */ driftShift: number; /** Measurement-noise magnitude for this replicate. */ noiseScale: number; /** Deterministic uniform stream, seeded per replicate. */ rng: () => number; } /** * Perturb a base cohort for one robustness replicate. Domain-specific (which * feature drifts, for which subgroup) — supplied by the `.holo` bridge. A * feature-agnostic default ({@link defaultPerturber}) is used when omitted. */ export type CohortPerturber = (cohort: readonly FairnessRecord[], p: FairnessPerturbation) => FairnessRecord[]; /** Seeded uniform PRNG in [0,1). Exported so injected perturbers can stay deterministic. */ export declare function mulberry32(seed: number): () => number; /** * Feature-agnostic default perturbation: bootstrap-resample the cohort, then add * symmetric measurement noise to every feature (scaled by `noiseScale`). Drift * is genuinely domain-specific (which subgroup, which feature shifts), so the * default applies none — pass a `perturber` to model population drift for a * given vertical. Kept honest: this default proves the harness reuse, not a * vertical's real drift physics. */ export declare const defaultPerturber: CohortPerturber; /** * Compute disparity metrics over a set of group-tagged decisions. Generalizes * the EEOC 4/5ths rule to N groups: adverse-impact ratio = min/max of the * per-group approval rates; demographic-parity diff = max − min. */ export declare function analyzeDisparity(decisions: ReadonlyArray<{ group: string; approved: boolean; }>): FairnessMetrics; export interface FairnessSweepOptions { /** Protected attribute label recorded on the receipt (e.g. "group"). */ protectedAttribute?: string; /** Seed recorded on the receipt as part of the replay key. */ seed?: number; /** Fixed issuance timestamp (wall-clock in prod; fixed for byte-stable tests). */ issuedAt?: string; /** Override the per-decision regulator crosswalk. */ regulatoryMapping?: Record; /** Named or custom jurisdiction bundle that selects legally required tests. */ jurisdiction?: FairnessJurisdiction | JurisdictionConfig; /** Hash mode: 'fnv1a' (default) or 'sha256' (adversarial-peer opt-in). */ hashMode?: HashMode; /** * Measure determinism: re-run the model over the cohort a second time and * compare. Bit-reproducible → grade 'exact'; divergent → auto-downgrade to a * measured 'quantized'/'statistical' grade + tolerance. An over-claimed * `model.determinism.grade='exact'` is corrected, never trusted (closes F-1). */ verifyDeterminism?: boolean; } export interface FairnessSweepResult { receipt: FairnessReceipt; metrics: FairnessMetrics; inputHash: string; modelHash: string; weightStrategy: string; /** Digest over the per-record decisions (re-derivable by a validator). */ decisionDigest: string; /** The grade recorded on the receipt (measured if verifyDeterminism was set). */ replayDeterminism: DeterminismGrade; /** Jurisdiction-specific required-test evidence when requested. */ jurisdiction?: JurisdictionAuditSummary; } /** * Run one fairness evaluation through the REAL `ExperimentOrchestrator` (a * single-variant grid sweep → one solver run, tracked by ProvenanceTracker) and * emit a per-decision {@link FairnessReceipt}. */ export declare function runFairnessSweep(model: FairnessModel, cohort: readonly FairnessRecord[], options?: FairnessSweepOptions): Promise; export interface FairnessRobustnessOptions extends FairnessSweepOptions { /** Number of LHS replicates (default 200). */ replicates?: number; /** Population-drift range [min, max] swept by LHS (default [-0.05, 0.10]). */ driftRange?: [number, number]; /** Measurement-noise range [min, max] swept by LHS (default [0, 0.05]). */ noiseRange?: [number, number]; /** Domain-specific cohort perturber (default {@link defaultPerturber}). */ perturber?: CohortPerturber; } export interface FairnessRobustnessResult { receipt: FairnessRobustnessReceipt; band: RobustnessBand; verdict: FairnessRobustnessReceipt['verdict']; ensembleHash: string; ratios: number[]; } /** * Run a seeded-LHS robustness sweep through the REAL * `UncertaintyQuantification` engine and emit a {@link FairnessRobustnessReceipt}. * * The LHS-sampled drift/noise values are applied to the per-replicate config and * read back by the wrapped solver, which perturbs the cohort and emits the * `adverseImpactRatio` scalar. UQ's reproducible LHS (fixed seed) makes the band * — and its `ensembleHash` — byte-identical on replay. */ export declare function runFairnessRobustness(model: FairnessModel, cohort: readonly FairnessRecord[], options?: FairnessRobustnessOptions): Promise; //# sourceMappingURL=FairnessSweep.d.ts.map