Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
# `hotp/encode-counter`
## Purpose
Encode an HOTP counter as the 8-byte big-endian block required by RFC 4226
§5.1. `bigint` is the canonical input type because `Number` loses precision
past `2^53` and the RFC permits counters up to `2^64 − 1`. Shared by HOTP
itself, by OCRA's optional `C` directive, and by OCRA's `T` timestamp block
(which is also written as an 8-byte big-endian integer per RFC 6287 §5.1).
## Features
- BigInt input — no precision loss across the full 0..2^64-1 range.
- Range guard: negative counters and counters past `2^64-1` raise
`OtpError(OTP.INVALID_OPTIONS)`.
- Pure: returns a freshly-allocated `Uint8Array(8)` per call.
## Usage: Basic Example
```ts
import { encodeCounter } from "@suaveplan/otp";
const block = encodeCounter(1n);
// block === Uint8Array([0, 0, 0, 0, 0, 0, 0, 1])
```
## Usage: Advanced Example
```ts
import { encodeCounter } from "@suaveplan/otp";
// Counter past Number.MAX_SAFE_INTEGER round-trips losslessly.
const block = encodeCounter((1n << 53n) + 1n);
// `block` is exactly 8 bytes; the high bytes carry the counter's high bits.
```
## API Reference
- `encodeCounter(counter: bigint): Uint8Array` — returns an 8-byte
big-endian block. Negative input raises `OtpError(OTP.INVALID_OPTIONS)`;
values >= 2^64 raise the same code.
## Implementation Notes
The function loops from byte index 7 back to 0 shifting `counter` right by 8
bits each step. The mask `0xffn` keeps the operation inside the `bigint`
domain so no implicit `Number` conversion can lose precision. The function
is the single source of truth for big-endian counter framing — both HOTP and
OCRA route through it so any future endianness change propagates atomically.
The upper-bound check `counter > MAX_U64` catches the niche case where a
caller passes a programmatically-generated counter past the RFC 4226
limit. Without the check the encoder would silently truncate the high
bits and produce two distinct counters that hash to the same byte block,
which would silently break the per-counter uniqueness invariant HOTP
relies on. The lower-bound check on negative counters covers the
symmetric case for callers who derive counters via `BigInt()` arithmetic
that may go negative under bug conditions.

View File

@@ -0,0 +1,56 @@
# `hotp/hmac`
## Purpose
Algorithm-routing HMAC façade for the OTP family. SHA-256 and SHA-512 route
through `@suaveplan/crypto-hash` (the canonical platform HMAC); SHA-1 routes
through `@noble/hashes/sha1` directly because crypto-hash deliberately does
not expose SHA-1 — it is mandated only by legacy OTP RFCs and must not be
presented as a general-purpose option elsewhere in the platform.
## Features
- Three algorithms: SHA-1, SHA-256, SHA-512.
- SHA-1 isolated to the OTP family — no leakage into the wider crypto API.
- Returns a freshly-allocated `Uint8Array` of the algorithm's digest size.
## Usage: Basic Example
```ts
import { hmacFor } from "@suaveplan/otp";
const tag = hmacFor("SHA1", key, message);
```
## Usage: Advanced Example
```ts
import { hmacFor } from "@suaveplan/otp";
// Build the OCRA HMAC input directly — useful for KAT-driven test fixtures.
const tag = hmacFor("SHA256", key, suiteAsciiPlusZeroPlusDataInput);
```
## API Reference
- `hmacFor(algorithm: HotpAlgorithm, key: Uint8Array, data: Uint8Array): Uint8Array`
— returns the HMAC tag.
- `HotpAlgorithm = "SHA1" | "SHA256" | "SHA512"`.
## Implementation Notes
Routing inside this module rather than at the call site keeps the SHA-1
exception local to OTP. The crypto-hash boundary uses dashed names
(`"SHA-256"`); this module accepts the OTP-RFC-style undashed names
(`"SHA256"`) and translates internally. Adding a new algorithm here is the
only change required — `hotp.ts`, `totp.ts`, and `ocra.ts` all consume this
single dispatch point.
The SHA-1 routing decision is intentional: `@suaveplan/crypto-hash` is the
canonical platform façade for hashing across the project, and exposing
SHA-1 there would invite consumers to use it for non-OTP purposes. By
isolating the SHA-1 dependency to the OTP module, the rest of the platform
keeps a no-SHA-1 posture without breaking RFC 4226 / 6238 / 6287
compliance. The noble HMAC primitive used here is the same implementation
crypto-hash routes through under the hood for SHA-256 / SHA-512, so the
two paths behave identically apart from the chosen hash.

View File

@@ -0,0 +1,79 @@
# `hotp/hotp`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs --> - hotp.schema — Zod boundary schema for `HotpOptions`…
- [hotp.schema](./hotp.schema.md) — Zod boundary schema for `HotpOptions`. Strict-mode parsing rejects unknown keys (so silent typos in caller configuratio…
- hotp.schema — Zod boundary schema for `HotpOptions`…
- [hotp.schema](./hotp.schema.md) — Zod boundary schema for `HotpOptions`. Strict-mode parsing rejects unknown keys (so silent typos in caller configuratio…
- hotp.schema — Zod boundary schema for `HotpOptions`…
- [hotp.schema](./hotp.schema.md) — Zod boundary schema for `HotpOptions`. Strict-mode parsing rejects unknown keys (so silent typos in caller configuratio…
- hotp.schema — Zod boundary schema for `HotpOptions`…
- [hotp.schema](./hotp.schema.md) — Zod boundary schema for `HotpOptions`. Strict-mode parsing rejects unknown keys (so silent typos in caller configuratio…
- [hotp.schema](./hotp.schema.md) — Zod boundary schema for `HotpOptions`. Strict-mode parsing rejects unknown keys (so silent typos in caller configuratio…
## Purpose
RFC 4226 HOTP — HMAC-based one-time password. Computes
`value = DT(HMAC-H(K, C)) mod 10^digits` and returns a `digits`-length
zero-padded decimal string. Counter is `bigint` (RFC 4226 §5.1 mandates an
8-byte big-endian counter; `Number` loses precision past `2^53`).
## Features
- RFC 4226 dynamic truncation per §5.3 step 2.
- SHA-1 default; SHA-256 / SHA-512 widening per RFC 6238 §1.2.
- 6 / 7 / 8-digit output.
- Zod-validated options at the boundary; `INVALID_DIGITS` mapped via the
zod-map for fields that fail enum/literal-union checks.
- Empty-secret rejection with `OTP.INVALID_SECRET`.
## Usage: Basic Example
```ts
import { hotp } from "@suaveplan/otp";
const secret = new TextEncoder().encode("12345678901234567890");
const code = hotp(secret, 0n);
// code === "755224" (RFC 4226 Appendix D vector)
```
## Usage: Advanced Example
```ts
import { hotp } from "@suaveplan/otp";
const code = hotp(secret, (1n << 53n) + 1n, {
algorithm: "SHA512",
digits: 8,
});
// 8-digit SHA-512 HOTP at a counter past Number.MAX_SAFE_INTEGER
```
## API Reference
- `hotp(secret: Uint8Array, counter: bigint, opts?: HotpOptions): string`
— returns the `digits`-length decimal code.
- `HotpOptions` — `{ algorithm?: "SHA1" | "SHA256" | "SHA512"; digits?: 6 | 7 | 8 }`.
- Throws `OtpError(OTP.INVALID_OPTIONS | OTP.INVALID_DIGITS)` on Zod
failures and `OtpError(OTP.INVALID_SECRET)` on empty `secret`.
## Implementation Notes
Counter encoding lives in `encode-counter.ts` and is shared with OCRA's
`C` directive. Dynamic truncation extracts a 31-bit value from the HMAC by
masking the high bit to avoid sign-extension surprises across HMAC
implementations. Modular reduction uses a precomputed `POW10` table indexed
by `digits` rather than `Math.pow` so the modulus is an exact integer.
The Zod schema rejects unknown keys at the strict-mode boundary so callers
who construct options from JSON or external configuration cannot smuggle
typo'd fields past the API. Field-level Zod failures flow through
`mapZodToOtp` so a bad `digits` value surfaces as `OTP.INVALID_DIGITS`
rather than the generic `OTP.INVALID_OPTIONS`. The empty-secret guard is
runtime-only because Zod's bytes type does not constrain length.

View File

@@ -0,0 +1,61 @@
# `hotp/hotp.schema`
## Purpose
Zod boundary schema for `HotpOptions`. Strict-mode parsing rejects unknown
keys (so silent typos in caller configuration surface immediately) and
narrows `algorithm` and `digits` to their literal-union values. Mismatches
flow through `mapZodToOtp` so the boundary error carries a specific code
(`INVALID_DIGITS` for the `digits` field, `INVALID_OPTIONS` otherwise).
## Features
- `algorithm` enum: `"SHA1" | "SHA256" | "SHA512"`.
- `digits` literal union: `6 | 7 | 8`.
- Strict-mode object — unknown keys are rejected.
## Usage: Basic Example
```ts
import { HotpOptionsSchema } from "@suaveplan/otp";
HotpOptionsSchema.parse({ algorithm: "SHA1", digits: 6 });
```
## Usage: Advanced Example
```ts
import { HotpOptionsSchema } from "@suaveplan/otp";
// Programmatic option builder — feed parsed result directly into hotp().
const opts = HotpOptionsSchema.parse(JSON.parse(userJson));
```
## API Reference
- `HotpOptionsSchema` — Zod object schema with `.strict()`.
- `HotpOptionsInput` — type alias for the parsed shape.
## Implementation Notes
The strict-mode check is the second-line defence behind TypeScript's compile
checks: callers using `as never` or constructing options from JSON can still
slip values past the type system. Pinning the schema here keeps the runtime
contract aligned with the type-level contract.
The schema is intentionally narrow — it covers only the surface that
HOTP itself needs. Callers building their own option bags around HOTP
(e.g. a vault item type that wraps HOTP with extra metadata) should
extend this schema using Zod's `.extend()` or compose it via a discriminated
union, rather than relaxing the strict-mode setting here. Keeping the base
schema tight prevents accidental option-shape drift across the OTP family
and means every caller benefits from the same validation guarantees.
The `algorithm` enum covers exactly the three hashes RFC 4226 / 6238 §1.2
allow for OTP. SHA-3 family hashes are not part of any OTP RFC and would
be rejected at this boundary even though `@noble/hashes` provides them.
The `digits` literal union mirrors RFC 4226 §5.3 (`6`) plus the RFC 6238
§1.2 widening (`7`, `8`). RFC 6287 OCRA additionally permits digits=4,
which is handled in the OCRA suite parser rather than here because the
HOTP primitive does not need to expose it directly.