/** * Row-level validation for user-authored SensorMapOverride entries. * * See docs/future/sensor-map.md §3.7 for the full failure table. * * Two-phase validation, split so buildEffectiveMap can dedup+merge * duplicate entries BEFORE semantic checks (§3.3.2 later-wins): * * Phase 1 — `validateOverrideIdentity(input: unknown)`: check the * identity fields (dataPoint present + non-empty, stationMac if * present is MAC-shaped). Returns the identity or a rejection * message. Runs on raw, untyped JSON. * * Phase 2 — `validateOverrideBody(merged, identity, defaultRow)`: * run runtime type checks on every remaining field, then apply the * semantic rules from §3.7. Returns the fully-typed * `SensorMapOverride` on success. Runs on already-merged input. * * `validateOverride(input, defaultRow)` is a convenience that composes * both phases for single-entry validation. * * Warnings are structured (`OverrideWarning` — code + field + * message) rather than plain strings so buildEffectiveMap can * attribute them to the specific merge fragment responsible and so * the UI can dedupe on `code` + `field` without parsing text. */ import type { DefaultSensorRow, Measurement, SensorMapOverride } from './types.js'; /** Strict MAC-address regex per §3.3.1. Case-insensitive hex + colon. */ export declare const STATION_MAC_REGEX: RegExp; /** * Measurements that are `kind: motion` but cannot cross a threshold — * their wrappers hardcode `threshold: Infinity`. `validateOverrideBody` * warn-strips threshold / triggerEnabled / triggerDirection on these so * a user-supplied value isn't silently ignored. See finding-#4 review. * Exported so the editor's vocabulary projection can hide the trigger * controls for exactly the measurements this strip governs (PR #57 * review F3) — the engine stays the single authority. */ export declare const NON_TRIGGERING_MEASUREMENTS: ReadonlyArray; /** * Maximum length of a `numeric` measurement's literal `unitLabel` * (§19.9), counted in Unicode code points AFTER trimming so multi-byte * symbols like `µg/m³` are preserved. Presentation only; a longer label * is rejected loudly rather than silently truncated. */ export declare const MAX_UNIT_LABEL_CODEPOINTS = 16; /** * Structured warning — code + optional field + message. `code` * identifies the warning class for machine consumers; `field` names * the offending SensorMapOverride field when applicable. Text form * is `message`. */ export interface OverrideWarning { code: string; field?: string; message: string; } /** * ValidationResult's error variant is FLAT: `code`, `field`, `message` * live directly on the result. This preserves the pre-refactor * `result.message` API (tests + docs still read it) while giving * callers the structured `code` + `field` they need for per-field * error attribution. * * `field` is set for field-scoped rejections and identifies the * fragment whose value caused the rejection via the merge provenance * map. For row-scope failures (missing required field, kind × * measurement incompatibility) it's undefined. For the unknown-key * cases (`unknown-key`, `wrapper-id-forbidden`) it holds the * offending input key even though that key is outside the * SensorMapOverride vocabulary — the merge provenance map records * every input key, so this still routes attribution correctly. */ export type ValidationResult = { status: 'ok'; validated: SensorMapOverride; warnings: OverrideWarning[]; } | { status: 'error'; code: string; field?: string; message: string; warnings: OverrideWarning[]; }; /** Identity extracted from Phase 1 validation. `stationMac` is uppercased if present. */ export interface OverrideIdentity { dataPoint: string; stationMac?: string; } export type IdentityResult = { status: 'ok'; identity: OverrideIdentity; } | { status: 'error'; message: string; }; /** * Phase 1: identity-only validation. Accepts raw `unknown` (from * JSON) and produces the identity key that buildEffectiveMap uses * for dedup. Does NOT validate the body — a duplicate entry that's * individually incomplete may still be valid after merge. */ export declare function validateOverrideIdentity(input: unknown): IdentityResult; /** * Phase 2: body validation on an already-merged record. Enforces * runtime types on every field before promoting to a typed * SensorMapOverride, then applies the semantic rules from §3.7. * * `merged` is the raw record after dedup+merge (still untyped). The * identity is passed in because Phase 1 already validated + normalized * it — we don't re-check. */ export declare function validateOverrideBody(merged: Record, identity: OverrideIdentity, defaultRow: DefaultSensorRow | undefined): ValidationResult; /** * Convenience: compose Phase 1 + Phase 2 for single-entry * validation. buildEffectiveMap uses the phases directly so it can * dedup+merge between them; simpler callers use this. */ export declare function validateOverride(input: unknown, defaultRow: DefaultSensorRow | undefined): ValidationResult; //# sourceMappingURL=validation.d.ts.map