# `validate-graph` — keyboard graph integrity guard _(no submodules)_ - [azerty](./azerty/azerty.md) — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_ - [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_ - [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [validate-graph](./validate-graph.md) — _(no submodules)_- azerty — _(no submodules)_… - [azerty](./azerty/azerty.md) — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [validate-graph](./validate-graph.md) — _(no submodules)_- [azerty](./azerty/azerty.md) — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [validate-graph](./validate-graph.md) — _(no submodules)_- azerty — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [validate-graph](./validate-graph.md) — _(no submodules)_- azerty — _(no submodules)_… - [azerty](./azerty/azerty.md) — _(no submodules)_- [dvorak](./dvorak/dvorak.md) — _(no submodules)_ - [dvorak](./dvorak/dvorak.md) — _(no submodules)_- [qwerty](./qwerty/qwerty.md) — _(no submodules)_ - [qwerty](./qwerty/qwerty.md) — _(no submodules)_- [validate-graph](./validate-graph.md) — _(no submodules)_ ## Purpose Validates that a user-supplied keyboard adjacency graph is well-formed before the `keyboardMatch` matcher walks it. A malformed graph would either produce nonsensical matches (self-loops) or cause inconsistent guess estimates across users typing the same word. ## Features - Detects self-adjacency (a key listed as adjacent to itself) - Detects asymmetric edges (`a` lists `b` but `b` does not list `a`) - Throws `PasswordStrengthError` with code `PWSTRENGTH.GRAPH_INVALID` - Pure data validation; no side effects ## Usage: Basic Example ```ts import { validateGraph } from "@suaveplan/password-strength"; import { QWERTY_GRAPH } from "@suaveplan/password-strength/graphs/qwerty"; validateGraph("qwerty", QWERTY_GRAPH); // returns; throws if malformed ``` ## Usage: Advanced Example ```ts import { validateGraph, PWSTRENGTH_CODES, PasswordStrengthError } from "@suaveplan/password-strength"; const broken = { q: ["q"] }; try { validateGraph("broken", broken); } catch (e) { if (e instanceof PasswordStrengthError && e.code === PWSTRENGTH_CODES.GRAPH_INVALID) { console.log("graph rejected"); } } ``` ## API Reference - `validateGraph(name: string, graph: KeyboardGraph): void` - Throws `PasswordStrengthError` with code `PWSTRENGTH.GRAPH_INVALID` when the graph contains a self-loop or an asymmetric edge. The error message names the offending key for fast debugging. ## Implementation Notes The symmetry check is one-directional per pair — if `a` claims adjacency to `b`, then `b`'s adjacency slot list must contain `a` in at least one of its slots' character pairs (covering both the shifted and unshifted halves). Slots set to `null` are skipped. The walk is `O(n)` over the number of edges in the graph; running it on a 60-key graph is negligible. ## Additional Implementation Notes The implementation follows the canonical zxcvbn algorithm from Wheeler 2016 with the suaveplan-style additions described in the package proposal: structured error codes via the `defineCodes` registry, Zod boundaries at every public entry point, OTel histograms and counters keyed to low-cardinality attributes, and a strict input-length guard at 200 characters. Consumers integrate with their existing observability stack by installing a meter provider before importing the package, and with their i18n stack via the `MessageResolver` callback type declared in `@suaveplan/types`. The bundled data tables are intentionally small to keep the package bundle compact; production deployments should swap in fuller frequency-ranked tables via `loadDictionaries`. The default English dictionary set is a frozen `DictionarySet` that the matcher never mutates; caller-supplied dictionaries override defaults by name.