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,92 @@
# Opaque key handles
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`opaque-handle.ts` lets the broader `vault` layer hold a typed reference
to a SecureBuffer without ever seeing the raw bytes. The handle is a
frozen, brand-symbol-tagged object containing only a stable `id`, a
`length`, and a `_zeroize` thunk. The mapping back to the SecureBuffer is
held in a module-level `WeakMap` keyed by the handle, so a leaked handle
on its own does not yield key material.
## Features
- `wrap(SecureBuffer): OpaqueKeyHandle` — frozen handle with a unique
symbol id, the buffer's length, and a `_zeroize` callable.
- `unwrap(OpaqueKeyHandle): SecureBuffer` — package-internal resolver,
validates the brand and throws when the handle is malformed or the
underlying buffer has been GC'd.
- `isValidHandle(value): value is OpaqueKeyHandle` — runtime brand check
for values arriving across an `unknown`-typed boundary.
## Usage: Basic Example
```ts
import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer";
import { wrap } from "@suaveplan/crypto-vault/handle/opaque-handle";
const buf = new SecureBuffer(32);
buf.fillRandom();
const handle = wrap(buf);
console.log(handle.length); // 32
handle._zeroize();
console.log(buf.isClosed); // true
```
## Usage: Advanced Example
```ts
import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer";
import {
isValidHandle,
wrap,
} from "@suaveplan/crypto-vault/handle/opaque-handle";
function passToVaultLayer(handle: unknown) {
if (!isValidHandle(handle)) {
throw new Error("vault layer received a non-handle value");
}
// The vault layer stores the handle but cannot read the underlying bytes.
return handle;
}
const wrapped = wrap(new SecureBuffer(32));
passToVaultLayer(wrapped);
```
## API Reference
### `wrap(buffer: SecureBuffer): OpaqueKeyHandle`
Returns a frozen handle. The handle's `_zeroize` thunk forwards to the
buffer's `close()` method.
### `unwrap(handle: OpaqueKeyHandle): SecureBuffer`
Resolves the handle back to the underlying SecureBuffer. Throws
`CryptoVaultError(CRYPTOVAULT.OPAQUE.INVALID_HANDLE)` for missing brand,
unknown handle, or GC'd buffer. **Internal — vault-side packages
within `@suaveplan/*` may import this; consumers outside the project
should treat handles as opaque.**
### `isValidHandle(value: unknown): value is OpaqueKeyHandle`
Runtime brand check.
## Implementation Notes
- The handle uses a `unique symbol` brand so the TypeScript compiler can
refuse foreign objects masquerading as `OpaqueKeyHandle`.
- The `WeakMap<OpaqueKeyHandle, SecureBuffer>` ensures that once the
handle becomes unreachable, the SecureBuffer link is also collected.
Callers that want to keep the buffer alive must keep a strong reference
to the handle.
- The `_zeroize` field deliberately starts with an underscore to flag
it as an implementation detail rather than a stable public method.
- This module is the boundary that prevents the `vault` layer from
needing direct `Uint8Array` access to derived keys; the orchestrator
hands handles to AEAD operations and the AEAD layer calls `unwrap`
immediately before passing the bytes into the noble cipher.