# Capability Spec: data-masking **Package**: `@suaveplan/data-masking` **Version**: 0.0.0 (genesis) **Status**: ADDED --- ## ADDED Requirements ### DM-01 — Strategy contract Every `MaskingStrategy` MUST expose `{ name, mask(value, options?): MaskResult }`. Built-in strategies `hash`, `partial`, `redact`, `maskEmail`, `maskPhone`, `maskIp`, `maskCreditCard` MUST all implement this contract. ### DM-02 — Registry `MaskingRegistry.register(name, strategy)` MUST reject duplicates unless `overwrite: true`. `MaskingRegistry.resolve(name)` MUST throw `DATA_MASKING.UNKNOWN_STRATEGY` when the name is absent. ### DM-03 — Idempotency `DataMasker.mask` MUST be idempotent: `mask(mask(x)) === mask(x)` (structural equality) for every built-in strategy on PII inputs. ### DM-04 — Type detection `TypeDetector.detect(value)` MUST return an array of `DetectionResult { type, start, end, confidence }`. Built-in `SensitiveDataType` values MUST include `email`, `phone`, `credit-card`, `ssn`, `ipv4`, `ipv6`, `jwt`, `api-key`. ### DM-05 — Conditional masking `ConditionalMasker.apply(value, ctx)` MUST evaluate the `MaskingPolicy` with `MaskingContext { role, permissions: FieldPermission[] }`. Policies MUST either mask, allow, or redact per-field. Unknown policies MUST surface as `DATA_MASKING.INVALID_POLICY`. ### DM-06 — Audit trail `MaskingAudit.record(entry)` MUST write `AuditEntry { at, actor?, field, strategy, outcome }` to every registered `AuditSink`. Sink failures MUST be captured and surfaced as `DATA_MASKING.AUDIT_SINK_FAILED` without aborting the mask itself. ### DM-07 — Credit-card mask `maskCreditCard` MUST preserve the last 4 digits and validate Luhn; non-Luhn inputs MUST pass through unchanged (they're not actually credit cards) and record a lower-confidence detection result. ### DM-08 — Observability Every mask call MUST record `data_masking.mask.duration_ms` histogram and `data_masking.masked.total{type}` counter. Masked values MUST NOT appear in OTel attributes.