Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
# Capability Spec Delta: biometrics
**Package**: `@suaveplan/biometrics`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions.
## ADDED Requirements
### Requirement: Platform detection
`BiometricAuth` SHALL auto-detect the runtime platform on instantiation unless a `BiometricDelegate` is explicitly provided. Detection order:
1. Capacitor — `typeof (window as any).Capacitor !== 'undefined'`
2. Electrobun — `typeof (globalThis as any).electrobun !== 'undefined'`
3. macOS keychain — `typeof process !== 'undefined' && process.platform === 'darwin'` (Node/Bun runtime) with safe fallback to `NoOpBiometricDelegate` when `process` is undefined
4. Unsupported — default to `NoOpBiometricDelegate`
#### Scenario: Capacitor runtime detected
- **WHEN** `BiometricAuth` is instantiated in a Capacitor WebView
- **THEN** it uses `CapacitorBiometricDelegate` by default
#### Scenario: Unsupported platform falls back to noop
- **WHEN** `BiometricAuth` is instantiated in a plain browser with no native bridge
- **THEN** it uses `NoOpBiometricDelegate`
#### Scenario: Explicit delegate overrides detection
- **WHEN** `BiometricAuth` is constructed with an explicit `delegate`
- **THEN** the provided delegate is used regardless of platform
**Verified by:** `src/platform/platform.test.ts::"selects Capacitor delegate in Capacitor runtime"` AND `src/platform/platform.test.ts::"falls back to noop delegate on unsupported platform"` AND `src/auth/auth.test.ts::"uses explicit delegate over auto-detected platform"`
---
### Requirement: Delegate pattern
All platform-specific biometric logic MUST be isolated behind the `BiometricDelegate` interface:
```typescript
interface BiometricDelegate {
authenticate(reason: string): Promise<boolean>;
getAvailability(): Promise<BiometricAvailability>;
}
```
`BiometricAuth` MUST delegate every `authenticate()` and `getAvailability()` call to the active delegate without modification of return values.
#### Scenario: Auth delegated to active delegate
- **WHEN** `biometricAuth.authenticate("Unlock vault")` is called
- **THEN** the active delegate's `authenticate` method is invoked with the same reason string
#### Scenario: Availability delegated to active delegate
- **WHEN** `biometricAuth.getAvailability()` is called
- **THEN** the active delegate's `getAvailability` method is invoked and its result returned unmodified
**Verified by:** `src/delegate/delegate.test.ts::"authenticate delegates to active delegate with exact reason"` AND `src/delegate/delegate.test.ts::"getAvailability returns delegate result unmodified"`
---
### Requirement: Secure-enclave boolean-only results
`authenticate(reason)` MUST return `Promise<boolean>`:
- `true` — biometric verification succeeded
- `false` — biometric verification failed or was cancelled by the user
The promise MUST reject with `BiometricError` only for unrecoverable system errors (e.g., hardware malfunction). Under no circumstances MUST biometric templates, fingerprint images, face geometry, match scores, or confidence levels be returned to callers or logged.
#### Scenario: Successful auth returns true
- **WHEN** the delegate's biometric prompt succeeds
- **THEN** `authenticate` resolves to `true`
#### Scenario: Cancelled auth returns false
- **WHEN** the user cancels the biometric prompt
- **THEN** `authenticate` resolves to `false`
#### Scenario: No biometric data exposed
- **WHEN** `authenticate` is called on any delegate
- **THEN** the resolved value is strictly `boolean` and no biometric metadata is present
**Verified by:** `src/auth/auth.test.ts::"resolves true on successful biometric verification"` AND `src/auth/auth.test.ts::"resolves false when user cancels"` AND `src/auth/auth.spec-conformance.test.ts::"authenticate return type is strictly boolean with no biometric metadata"`
---
### Requirement: Availability querying
`getAvailability()` MUST return `Promise<BiometricAvailability>`:
```typescript
interface BiometricAvailability {
available: boolean;
type?: BiometricType;
reason?: UnavailabilityReason;
}
```
`type` MUST be present when `available` is `true`. `reason` MUST be present when `available` is `false`.
#### Scenario: Available Face ID
- **WHEN** `getAvailability` is called on iOS with Face ID enrolled
- **THEN** it returns `{ available: true, type: 'faceId' }`
#### Scenario: No hardware
- **WHEN** `getAvailability` is called on a device with no biometric hardware
- **THEN** it returns `{ available: false, reason: 'noHardware' }`
#### Scenario: Not enrolled
- **WHEN** `getAvailability` is called on a device with hardware but no enrolled biometrics
- **THEN** it returns `{ available: false, reason: 'notEnrolled' }`
**Verified by:** `src/availability/availability.test.ts::"returns faceId type when Face ID is available"` AND `src/availability/availability.test.ts::"returns noHardware reason when no biometric hardware exists"` AND `src/availability/availability.test.ts::"returns notEnrolled reason when hardware exists but no biometrics enrolled"`
---
### Requirement: No biometric data exposure
No implementation file in the package MAY expose, log, or transmit biometric templates, fingerprint minutiae, face maps, iris patterns, or match scores. Delegate implementations MUST rely exclusively on platform APIs that return boolean success/failure. Any platform API that returns richer data MUST have that data discarded before returning to the caller.
#### Scenario: Delegate discards rich platform data
- **WHEN** a platform API returns a detailed result object containing a confidence score
- **THEN** the delegate extracts only the boolean success field and discards the rest
**Verified by:** `src/delegate/delegate.spec-conformance.test.ts::"discards confidence score from platform API result"` AND `src/auth/auth.spec-conformance.test.ts::"no biometric fields appear in error context or logs"`
---
### Requirement: Structured errors
All thrown errors MUST extend `BiometricError` which extends `SuaveplanError`. Error codes MUST be registered via `registerBiometricErrorCodes()` called as a side-effect of importing `src/index.ts`. The following codes MUST be defined:
- `BIOMETRIC.NOT_AVAILABLE` — biometric authentication is not available on this platform
- `BIOMETRIC.HARDWARE_ERROR` — unrecoverable hardware or system error during authentication
**Verified by:** `src/errors/errors.test.ts::"all error classes extend BiometricError"` AND `src/errors/errors.spec-conformance.test.ts::"error codes are registered on module import"`
---
### Requirement: Injectable Clock for deterministic timeout testing
`BiometricAuth` options MUST include an optional `clock?: Clock`. When provided, all authentication timeout deadlines MUST be driven by the injected clock instead of the global time source. This enables deterministic testing of timeout paths without real delays.
**Verified by:** `src/auth/auth.test.ts::"uses injected clock for authentication timeout"` AND `src/auth/auth.test.ts::"uses injected clock for availability deadline"`
---
### Requirement: 100% coverage
Vitest thresholds MUST be set to 100/100/100/100. `sideEffects: false` MUST be declared in `package.json`.
**Verified by:** `vitest.config.ts`