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:
@@ -0,0 +1,70 @@
|
||||
# upload-token
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements `CdnUploadToken` issuance and verification (DTP-19). Tokens authorise a single chunk write to the CDN and carry a server signature over the canonical bytes plus a content-hash binding so the CDN can refuse to store anything other than what the server attested.
|
||||
|
||||
## Features
|
||||
|
||||
- `signUploadToken(token, serverSecretKey, options?)` — fills `expiresAt` from the injected `Clock` + `ttlMs` and signs.
|
||||
- `verifyUploadToken(token, serverPubkeyHex, clock)` — full check: expiry, hex decode, Ed25519 verify.
|
||||
- `canonicalSerializeUploadToken(token)` — deterministic byte-level encoding shared by issuer and verifier.
|
||||
- `UPLOAD_TOKEN_TTL_DEFAULT_MS` (60 s), `UPLOAD_TOKEN_TTL_MAX_MS` (300 s) — pinned constants the spec dashboards can reference.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
signUploadToken,
|
||||
verifyUploadToken,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
const tok = signUploadToken(
|
||||
{
|
||||
fileId,
|
||||
chunkId: 0,
|
||||
chunkHash: "blake3:abc",
|
||||
uploaderPubkey: peerId,
|
||||
} as never,
|
||||
serverSecretKey,
|
||||
{ clock: () => Date.now() }
|
||||
);
|
||||
verifyUploadToken(tok, serverPubkeyHex, () => Date.now());
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
DTP_CODES,
|
||||
DriveTemplateError,
|
||||
signUploadToken,
|
||||
verifyUploadToken,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
async function handleCdnUpload(token, body) {
|
||||
try {
|
||||
verifyUploadToken(token, serverPubkeyHex, () => Date.now());
|
||||
} catch (err) {
|
||||
if (err instanceof DriveTemplateError && err.code === DTP_CODES.CDN_BAD_SIGNATURE) {
|
||||
return new Response("forbidden", { status: 403 });
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (`blake3:${blake3Hex(body)}` !== token.chunkHash) {
|
||||
return new Response("hash mismatch", { status: 422 });
|
||||
}
|
||||
return store(token.fileId, token.chunkId, body);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `signUploadToken(token, serverSecretKey, options?: { clock: Clock; ttlMs?: number }): CdnUploadToken`.
|
||||
- `verifyUploadToken(token: CdnUploadToken, serverPubkeyHex: string, clock: Clock): true`.
|
||||
- `canonicalSerializeUploadToken(token: Omit<CdnUploadToken, "serverSignature">): Uint8Array`.
|
||||
- Constants: `UPLOAD_TOKEN_TTL_DEFAULT_MS`, `UPLOAD_TOKEN_TTL_MAX_MS`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The TTL upper bound is enforced at sign time to prevent a misconfigured caller from minting tokens that outlive the request that asked for them. Verification trusts `@noble/curves` to return `false` for malformed signatures — there's no separate try/catch, which removes a dead branch and keeps the success path branch-free in the hot path. The CDN-side replay protection (the `(fileId, chunkId, nonce)` short LRU mentioned in DTP-19) lives in the CDN service rather than this module; the protocol only mandates the token shape.
|
||||
@@ -0,0 +1,94 @@
|
||||
# buildCdnChunkUrl
|
||||
|
||||
## Purpose
|
||||
|
||||
Deterministic URL builder for the reversed-ID CDN layout described in `drive-template-v1` `DTP-17`. Given a `CdnFallbackConfig` and a `(fileId, chunkId)` pair, produces the canonical chunk URL: `<baseUrl>/<reversed[0]>/<reversed[1]>/.../<reversed[levels-1]>/<fileId>_<chunkIdPadded>.chunk`. Reversing the file id distributes leaves randomly across the directory tree regardless of how IDs were generated (sequential timestamps, hash prefixes, etc.) — three default directory levels with a 16-character hex alphabet give 4,096 leaf directories, enough to keep any single directory under 250 entries at 1 M files.
|
||||
|
||||
## Features
|
||||
|
||||
- Deterministic — identical inputs produce byte-identical URLs.
|
||||
- Parameterised by `levels` (directory depth) and `padding` (chunk-id zero-pad width) so deployments can tune for their file count and chunk count.
|
||||
- Handles trailing-slash normalisation on `baseUrl`.
|
||||
- Validates inputs aggressively: empty file id, negative or non-integer `levels` / `padding` / `chunkId`, `levels > fileId.length` all throw `DriveTemplateError(DTP.TEMPLATE_MALFORMED)`.
|
||||
- Pure — no I/O, no side effects, no closures over external state.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { buildCdnChunkUrl } from "@suaveplan/drive-template/cdn";
|
||||
|
||||
const url = buildCdnChunkUrl(
|
||||
{
|
||||
baseUrl: "https://chunks.example.com",
|
||||
pathScheme: "reverse-id",
|
||||
levels: 3,
|
||||
padding: 9,
|
||||
},
|
||||
"abcdef1234567890",
|
||||
47,
|
||||
);
|
||||
// → "https://chunks.example.com/0/9/8/abcdef1234567890_000000047.chunk"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
buildCdnChunkUrl,
|
||||
DEFAULT_CDN_LEVELS,
|
||||
DEFAULT_CDN_PADDING,
|
||||
} from "@suaveplan/drive-template/cdn";
|
||||
|
||||
// Build URLs for every chunk in a small file.
|
||||
const cfg = {
|
||||
baseUrl: "https://cdn.example.com/", // trailing slash is normalised
|
||||
pathScheme: "reverse-id" as const,
|
||||
levels: DEFAULT_CDN_LEVELS,
|
||||
padding: DEFAULT_CDN_PADDING,
|
||||
};
|
||||
|
||||
const fileId = "f-1729a4f72c3b";
|
||||
const totalChunks = 5;
|
||||
const urls = Array.from({ length: totalChunks }, (_, chunkId) =>
|
||||
buildCdnChunkUrl(cfg, fileId, chunkId),
|
||||
);
|
||||
|
||||
console.log(urls);
|
||||
// [
|
||||
// "https://cdn.example.com/b/3/c/f-1729a4f72c3b_000000000.chunk",
|
||||
// "https://cdn.example.com/b/3/c/f-1729a4f72c3b_000000001.chunk",
|
||||
// "https://cdn.example.com/b/3/c/f-1729a4f72c3b_000000002.chunk",
|
||||
// "https://cdn.example.com/b/3/c/f-1729a4f72c3b_000000003.chunk",
|
||||
// "https://cdn.example.com/b/3/c/f-1729a4f72c3b_000000004.chunk",
|
||||
// ]
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `const DEFAULT_CDN_LEVELS`
|
||||
|
||||
The literal `3`. Default directory depth for `reverse-id` URLs. With a 16-character hex alphabet this gives 4,096 leaf directories.
|
||||
|
||||
### `const DEFAULT_CDN_PADDING`
|
||||
|
||||
The literal `9`. Default chunk-id zero-pad width. Supports up to 999,999,999 chunks per file (~64 PB at 64 KiB chunks).
|
||||
|
||||
### `function buildCdnChunkUrl(cfg, fileId, chunkId): string`
|
||||
|
||||
- `cfg: CdnFallbackConfig` — the CDN configuration. Trailing slashes on `baseUrl` are normalised away.
|
||||
- `fileId: string` — the 32-byte hex file identifier. Must be non-empty.
|
||||
- `chunkId: number` — non-negative integer chunk id.
|
||||
- Returns the canonical URL.
|
||||
- Throws `DriveTemplateError(DTP.TEMPLATE_MALFORMED)` for invalid inputs.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The reversal logic is implemented as a small loop rather than `String.prototype.split('').reverse().join('')` because the latter creates an intermediate array; for hot paths (a CDN fetch loop running over thousands of chunks) the loop is measurably faster.
|
||||
|
||||
The `levels` parameter is allowed to be `0`, which produces URLs with no directory levels — `<baseUrl>/<fileId>_<chunkIdPadded>.chunk`. This is occasionally useful for small CDNs where the directory split adds operational complexity without solving an actual hot-directory problem.
|
||||
|
||||
The `padding` parameter MAY be `0`, which omits zero-padding entirely. This is wire-compatible with CDNs that already serve un-padded chunk ids; the suaveplan convention is `9` for new deployments.
|
||||
|
||||
The function is intentionally pure and synchronous. All async behavior — fetching, retry, signature verification — lives in `drive-transport-https`. Keeping the URL builder pure makes it testable in isolation and reusable from any context (browser, Node, edge worker).
|
||||
|
||||
When `cfg.pathScheme` is set to a value other than `"reverse-id"`, the builder currently has no other implementation; future schemes (forward-id, deep-hash) would dispatch on the discriminator inside this module.
|
||||
@@ -0,0 +1,50 @@
|
||||
# canonical
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements `canonicalSerialize`, the deterministic byte-level encoding that gates every server signature on a `DriveTemplate` (DTP-03). Two implementations of the function MUST produce byte-identical output for identical input regardless of in-memory key order. JSON is deliberately rejected because object-key ordering is not portable across runtimes.
|
||||
|
||||
## Features
|
||||
|
||||
- Hand-rolled big-endian binary writer (`ByteWriter`) with `uint8` / `uint16` / `uint32` / `uint64` / `bool` / `string` / `bytes` primitives plus length-prefixed `optional*` helpers.
|
||||
- Fixed field order across the entire template tree — `schemaVersion`, `driveId`, `issuer`, `issuedAt`, `expiresAt`, `nonce`, `policy`, `cdnFallback`, `rootFolder`.
|
||||
- Recursive folder/file walker that tags each node with `TAG_FOLDER` (0x01) / `TAG_FILE` (0x02) so the decoder can reconstruct the tree without a separate manifest.
|
||||
- Encryption-aware: `inline` and `retroactive` `KeyDistribution` variants are tagged 1 / 2 respectively; the `wrappedKey` blob and `keyAuthority` string follow.
|
||||
- Excludes `serverSignature` — the bytes the issuer signs MUST not include the signature itself.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { canonicalSerialize } from "@suaveplan/drive-template";
|
||||
|
||||
const bytes = canonicalSerialize({
|
||||
schemaVersion: 1,
|
||||
driveId: "drive-1",
|
||||
rootFolder: { name: "/", children: [] },
|
||||
policy: { defaultPolicy: { autoDownload: false, meshAllowed: true, cdnAllowed: true } },
|
||||
issuer: "00".repeat(32),
|
||||
issuedAt: Date.now(),
|
||||
expiresAt: Date.now() + 60_000,
|
||||
nonce: new Uint8Array(16).fill(7),
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { canonicalSerialize, signTemplate } from "@suaveplan/drive-template";
|
||||
|
||||
// canonicalSerialize is the inputs-side; signTemplate runs Ed25519 over it.
|
||||
const tpl = signTemplate(buildTemplate(), serverSecretKey);
|
||||
const recanon = canonicalSerialize({ ...tpl, serverSignature: undefined as never });
|
||||
console.log(`signed bytes: ${recanon.length}`);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `canonicalSerialize(template: Omit<DriveTemplate, "serverSignature">): Uint8Array`.
|
||||
- Internal-only `ByteWriter` class with explicit primitives — not exported.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `ByteWriter` accumulates `Uint8Array` parts and concatenates once at `finish()` to avoid quadratic growth. `optionalUint64` / `optionalUint32` / `optionalString` write a discriminator byte first so receivers know whether the field follows; this trades two bytes per optional for unambiguous parsing. The retention policy is encoded as a tag (`0` = unset, `1` = `permanent`, `2` = `lru`, `3` = `{ maxAgeMs }`) so future variants can extend the union without breaking older readers — they will see an unknown tag and reject the template through the schema-version pin.
|
||||
@@ -0,0 +1,59 @@
|
||||
# sign
|
||||
|
||||
## Purpose
|
||||
|
||||
Server-side signing helper that turns an unsigned `DriveTemplate` into a fully-formed envelope (DTP-04). Produces an Ed25519 signature over `canonicalSerialize(template)` and returns a new template object with the `serverSignature` field populated. The function never fabricates timestamps or nonces; the caller is responsible for supplying a fresh 16-byte nonce, a recent `issuedAt`, and an `expiresAt` window inside the 24-hour issuance budget.
|
||||
|
||||
## Features
|
||||
|
||||
- `signTemplate(template, serverSecretKey)` — pure function; no global state.
|
||||
- Built on the deterministic `canonicalSerialize` byte stream so the signed payload is portable across runtimes.
|
||||
- Returns a frozen-shape `DriveTemplate` ready to broadcast on the bus or persist to the FileLocationIndex.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
randomPrivateKey,
|
||||
} from "@suaveplan/crypto-curve/ed25519";
|
||||
import { signTemplate } from "@suaveplan/drive-template";
|
||||
|
||||
const secret = randomPrivateKey();
|
||||
const tpl = signTemplate(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
driveId: "drive-1",
|
||||
rootFolder: { name: "/", children: [] },
|
||||
policy: { defaultPolicy: { autoDownload: false, meshAllowed: true, cdnAllowed: true } },
|
||||
issuer: "00".repeat(32),
|
||||
issuedAt: Date.now(),
|
||||
expiresAt: Date.now() + 60_000,
|
||||
nonce: new Uint8Array(16).fill(7),
|
||||
},
|
||||
secret
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
getPublicKey,
|
||||
randomPrivateKey,
|
||||
} from "@suaveplan/crypto-curve/ed25519";
|
||||
import { toHex } from "@suaveplan/crypto-utils";
|
||||
import { signTemplate, verifyTemplateSignature } from "@suaveplan/drive-template";
|
||||
|
||||
const secret = randomPrivateKey();
|
||||
const issuer = toHex(getPublicKey(secret));
|
||||
const tpl = signTemplate({ /* ... */ issuer } as never, secret);
|
||||
verifyTemplateSignature(tpl); // throws on any tampering
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `signTemplate(template: Omit<DriveTemplate, "serverSignature">, serverSecretKey: Uint8Array): DriveTemplate`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`signTemplate` is intentionally synchronous — `@noble/curves` Ed25519 is fast enough that a 4096-byte template signs in well under a millisecond on commodity hardware, and the async overhead would dwarf the actual work. Callers that need to sign in a worker thread can wrap in `queueMicrotask` themselves; baking that into the API would force every consumer through an unnecessary Promise. The sign path performs zero memory allocations beyond the canonical buffer and the resulting signature, which keeps it suitable for high-throughput template-issuance pipelines that mint thousands of distributions per minute.
|
||||
@@ -0,0 +1,51 @@
|
||||
# verify
|
||||
|
||||
## Purpose
|
||||
|
||||
Server-signature verification for `DriveTemplate` (DTP-04). Validates that `template.serverSignature` is a correct Ed25519 signature over `canonicalSerialize(template - sig)` under the issuer's public key (interpreted as a hex string and decoded to a 32-byte buffer). Tampering with any field — including `nonce`, `issuedAt`, or any nested folder/file — causes verification to fail.
|
||||
|
||||
## Features
|
||||
|
||||
- `verifyTemplateSignature(template)` — throws on mismatch or invalid input.
|
||||
- `decodeIssuerKey(hex)` — exposed helper that wraps `@suaveplan/crypto-utils.fromHex` so consumers see `DRIVE.TEMPLATE_SIG_INVALID` instead of a raw decode error.
|
||||
- Trusts `@noble/curves` to return `false` on malformed signatures (no separate try/catch).
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { verifyTemplateSignature } from "@suaveplan/drive-template";
|
||||
|
||||
verifyTemplateSignature(template);
|
||||
// throws DriveTemplateError(DRIVE.TEMPLATE_SIG_INVALID) on any tampering
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
DTP_CODES,
|
||||
DriveTemplateError,
|
||||
verifyTemplateSignature,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
function ingestPeerTemplate(tpl: import("@suaveplan/drive-template").DriveTemplate) {
|
||||
try {
|
||||
verifyTemplateSignature(tpl);
|
||||
} catch (err) {
|
||||
if (err instanceof DriveTemplateError && err.code === DTP_CODES.TEMPLATE_SIG_INVALID) {
|
||||
return reportTamperedTemplate(tpl.driveId);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
persist(tpl);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `verifyTemplateSignature(template: DriveTemplate): true` — returns `true` on success; throws `DriveTemplateError(DRIVE.TEMPLATE_SIG_INVALID)` otherwise.
|
||||
- `decodeIssuerKey(issuerHex: string): Uint8Array` — throws `DriveTemplateError(DRIVE.TEMPLATE_SIG_INVALID)` on malformed hex.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The verifier rebuilds `withoutSig` by destructuring out the `serverSignature` field rather than zeroing it in place; this matches the encoder's behaviour precisely and avoids any subtle aliasing bugs across runtimes. The function returns `true` on success rather than `void` so the type system can express "this template has been verified" downstream — the codebase pairs this with the `OpaqueKeyHandle` brand pattern used in `@suaveplan/vault` for the same effect. There is no async path; templates verify in well under a millisecond on commodity hardware so the synchronous API stays branch-free.
|
||||
@@ -0,0 +1,95 @@
|
||||
# DTP_CODES + DriveTemplateError
|
||||
|
||||
## Purpose
|
||||
|
||||
Frozen registry of error codes plus the error class for the `@suaveplan/drive-template` package. Eleven codes cover the complete failure surface: the eight-step validation pipeline that `Drive.hydrateFrom(template)` runs (version check, expiry, freshness, replay, issuer trust, signature, recursion depth, file consistency) plus three operational codes for CDN signature failure, inline-file signature failure, and key-grant rejection. Consumers branch on `code` programmatically; messages are for humans only.
|
||||
|
||||
## Features
|
||||
|
||||
- Eleven codes mapping one-to-one to the eight-step pipeline plus three operational paths.
|
||||
- `DTP_CODES` is frozen via `defineCodes` from `@suaveplan/error` — adding a code requires a code change and a spec update.
|
||||
- `DriveTemplateError extends SuaveplanError<DtpErrorCode>` — inherits cause-chain traversal, redaction, and JSON wire format.
|
||||
- Type-level exhaustiveness on switch statements via the literal-string `DtpErrorCode` union.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { DriveTemplateError, DTP_CODES } from "@suaveplan/drive-template/errors";
|
||||
|
||||
throw new DriveTemplateError(
|
||||
DTP_CODES.TEMPLATE_EXPIRED,
|
||||
"template expired at 2026-01-01T00:00:00Z",
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
DriveTemplateError,
|
||||
DTP_CODES,
|
||||
type DtpErrorCode,
|
||||
} from "@suaveplan/drive-template/errors";
|
||||
|
||||
const remediation: Record<DtpErrorCode, string> = {
|
||||
"DTP.TEMPLATE_VERSION_UNSUPPORTED": "Update the client; this template uses a newer schema.",
|
||||
"DTP.TEMPLATE_EXPIRED": "Request a fresh template from the issuer.",
|
||||
"DTP.TEMPLATE_STALE": "Request a fresh template; this one is more than 24h old.",
|
||||
"DTP.TEMPLATE_REPLAY": "Template nonce already seen; the issuer must mint a new one.",
|
||||
"DTP.TEMPLATE_UNTRUSTED_ISSUER": "Add the issuer to globalPolicy.trustedKeyAuthorities.",
|
||||
"DTP.TEMPLATE_SIG_INVALID": "Template signature does not match the canonical bytes; possible tampering.",
|
||||
"DTP.TEMPLATE_TOO_DEEP": "Folder recursion exceeds the 32-level cap.",
|
||||
"DTP.TEMPLATE_MALFORMED": "Per-file consistency violated; check chunkSize, totalChunks, and manifest fields.",
|
||||
"DTP.CDN_BAD_SIGNATURE": "CDN response failed Ed25519 verification; falling back to mesh.",
|
||||
"DTP.INLINE_SIG_INVALID": "Inline file's stored signature does not match the bytes.",
|
||||
"DTP.KEY_GRANT_REJECTED": "Key grant signature, recipient, or issuer did not match the template.",
|
||||
};
|
||||
|
||||
try {
|
||||
// hydrate code...
|
||||
} catch (err) {
|
||||
if (err instanceof DriveTemplateError) {
|
||||
console.error(`[${err.code}] ${err.message}`);
|
||||
console.error("Remediation:", remediation[err.code]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `const DTP_CODES`
|
||||
|
||||
Frozen object mapping uppercase code names to wire-format strings. Eleven entries:
|
||||
|
||||
**Eight-step validation pipeline** (matches `DTP-05` ordering):
|
||||
|
||||
- `TEMPLATE_VERSION_UNSUPPORTED` — `template.schemaVersion !== 1`. Future schema versions ship as new packages.
|
||||
- `TEMPLATE_EXPIRED` — `Date.now() >= template.expiresAt`.
|
||||
- `TEMPLATE_STALE` — `Date.now() - template.issuedAt > 24h`. Templates issued more than 24h ago are rejected even if not yet expired.
|
||||
- `TEMPLATE_REPLAY` — `(driveId, nonce)` already seen in the local LRU cache (last 1024 entries).
|
||||
- `TEMPLATE_UNTRUSTED_ISSUER` — `template.issuer` is not in `globalPolicy.trustedKeyAuthorities`.
|
||||
- `TEMPLATE_SIG_INVALID` — Ed25519 verification of `serverSignature` over `canonicalSerialize(template - sig)` failed.
|
||||
- `TEMPLATE_TOO_DEEP` — folder recursion exceeds 32 levels.
|
||||
- `TEMPLATE_MALFORMED` — per-file consistency violated: `chunkSize <= 164`, `totalChunks * chunkSize < size`, `manifestKey` empty, etc.
|
||||
|
||||
**Operational**:
|
||||
|
||||
- `CDN_BAD_SIGNATURE` — chunk fetched from CDN failed Ed25519 sig verification. Drive falls back to mesh.
|
||||
- `INLINE_SIG_INVALID` — inline file's stored Ed25519 signature does not match `Ed25519-sign(driveSecretKey, fileId ‖ bytes)`.
|
||||
- `KEY_GRANT_REJECTED` — incoming `KeyGrantEnvelope` failed validation: issuer mismatch, recipient mismatch, sig invalid, or expired.
|
||||
|
||||
### `type DtpErrorCode`
|
||||
|
||||
Union: `(typeof DTP_CODES)[keyof typeof DTP_CODES]`.
|
||||
|
||||
### `class DriveTemplateError extends SuaveplanError<DtpErrorCode>`
|
||||
|
||||
Constructor: `new DriveTemplateError(code, message, options?)`. Inherits all `SuaveplanError` features.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The eight pipeline codes are ordered by check sequence in `DTP-05`. Implementations MUST throw the first applicable code — they are mutually exclusive in a single hydrate call. The three operational codes are independent: a single drive may emit multiple `CDN_BAD_SIGNATURE` and `INLINE_SIG_INVALID` errors during normal operation; both are observable signals, not blockers.
|
||||
|
||||
`KEY_GRANT_REJECTED` is the only error code whose `context` field carries structured detail by convention. Implementations should populate `context` with `{ reason: 'untrusted-issuer' | 'wrong-recipient' | 'bad-signature' | 'expired' }` so callers can branch on the specific failure.
|
||||
|
||||
The `error-codes.spec-conformance.test.ts` enforces (1) the registry is frozen, (2) every code value starts with `DTP.`, (3) the eleven names match what the spec requires.
|
||||
@@ -0,0 +1,63 @@
|
||||
# grant-validator
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Implements `validateKeyGrant`, the receiver-side validator for retroactive `KeyGrantEnvelope` payloads (DTP-21). Validates each grant in the spec-mandated order — issuer ≡ template's `keyAuthority`, recipient ≡ our pubkey, Ed25519 signature, optional expiry — before the drive unwraps the wrapped key.
|
||||
|
||||
## Features
|
||||
|
||||
- `validateKeyGrant(grant, options)` — single-call validator returning `true` on success.
|
||||
- `canonicalSerializeKeyGrant(grant)` — deterministic byte-level encoding shared by issuer and verifier.
|
||||
- Strict failure ordering so the highest-trust check (issuer authority) runs first; cheap checks fail fast.
|
||||
- Trusts `@noble/curves` to return `false` for malformed signatures (no try/catch).
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { validateKeyGrant } from "@suaveplan/drive-template";
|
||||
|
||||
validateKeyGrant(grant, {
|
||||
expectedKeyAuthority: template.encryption.keyAuthority,
|
||||
ourPubkey: peerId,
|
||||
clock: () => Date.now(),
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
DTP_CODES,
|
||||
DriveTemplateError,
|
||||
validateKeyGrant,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
function onKeyGrantReceived(grant, expectedKeyAuthority, ourPubkey) {
|
||||
try {
|
||||
validateKeyGrant(grant, {
|
||||
expectedKeyAuthority,
|
||||
ourPubkey,
|
||||
clock: () => Date.now(),
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof DriveTemplateError && err.code === DTP_CODES.KEY_GRANT_REJECTED) {
|
||||
return reportSuspiciousGrant(grant);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return unwrapAndStore(grant.wrappedKey, grant.fileId);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `validateKeyGrant(grant: KeyGrantPayload, options: ValidateKeyGrantOptions): true` — throws `DriveTemplateError(DRIVE.KEY_GRANT_REJECTED)` on any failure.
|
||||
- `interface ValidateKeyGrantOptions` — `{ expectedKeyAuthority: PeerId; ourPubkey: PeerId; clock: Clock }`.
|
||||
- `canonicalSerializeKeyGrant(grant: Omit<KeyGrantPayload, "issuerSignature">): Uint8Array`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The expiry check fires AFTER the signature check so an expired-but-otherwise-valid grant still produces the correct rejection code (`KEY_GRANT_REJECTED`) without ever exposing the unwrapped key. The four-step order (issuer / recipient / signature / expiry) matches DTP-21's prose exactly so spec drift would surface as a test failure rather than a subtle behavioural shift. The canonical-serialize helper writes a length-prefixed wrapped-key buffer plus the optional `expiresAt` field (encoded as zero when absent so the wire bytes stay stable across implementations); issuer-side signing code MUST traverse the same canonical encoder for verification to succeed downstream.
|
||||
@@ -0,0 +1,60 @@
|
||||
# template.schema
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Zod boundary schemas for `@suaveplan/drive-template`. The codec / validate modules invoke `safeParse` on decoded JSON before any logic touches the payload, satisfying DTP-26's "schema validation before payload processing" mandate. The schemas mirror the wire types in `src/types/template.types.ts` exactly — adding a field to one without the other will fail compilation.
|
||||
|
||||
## Features
|
||||
|
||||
- `DriveTemplateSchema` — top-level template; refines `nonce` to exactly 16 bytes.
|
||||
- `FolderTemplateSchema` — recursive via `z.lazy()`.
|
||||
- `FileTemplateSchema` — every required field plus optional encryption.
|
||||
- `GlobalPolicySchema` / `PerFilePolicySchema` / `RetentionPolicySchema` — policy tree.
|
||||
- `CdnFallbackConfigSchema` — bounded `levels` (≤ 64), `padding` (≤ 32), URL-shaped `baseUrl`/`uploadEndpoint`.
|
||||
- `CdnUploadTokenSchema` — token wire shape.
|
||||
- `KeyGrantPayloadSchema` / `FileEncryptionSchema` — encryption / key-distribution wire shapes.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { DriveTemplateSchema } from "@suaveplan/drive-template";
|
||||
|
||||
const r = DriveTemplateSchema.safeParse(decodedFromJson);
|
||||
if (!r.success) throw new Error(r.error.message);
|
||||
const tpl = r.data;
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
CdnFallbackConfigSchema,
|
||||
DriveTemplateSchema,
|
||||
FolderTemplateSchema,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
function isValidCdn(input: unknown): boolean {
|
||||
return CdnFallbackConfigSchema.safeParse(input).success;
|
||||
}
|
||||
|
||||
function ingestFolder(folder: unknown) {
|
||||
const r = FolderTemplateSchema.safeParse(folder);
|
||||
if (!r.success) return { error: r.error.message };
|
||||
return { folder: r.data };
|
||||
}
|
||||
|
||||
const allTemplateFields = DriveTemplateSchema.shape;
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `DriveTemplateSchema`, `FolderTemplateSchema`, `FileTemplateSchema`.
|
||||
- `GlobalPolicySchema`, `CdnFallbackConfigSchema`, `CdnUploadTokenSchema`.
|
||||
- `FileEncryptionSchema`, `KeyGrantPayloadSchema`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`FolderTemplateSchema` uses Zod's `z.lazy()` to break the recursive type cycle; the cast to `FolderTemplateZodShape` is the canonical Zod pattern for self-referential schemas. The `nonce` refinement enforces exactly 16 bytes — the spec is precise here because shorter nonces are guessable and longer ones waste bus bandwidth. The `levels` upper bound (64) is a defence against accidental misconfiguration; real CDNs use 2–3 levels, so 64 is generous headroom but well below the point where a path explodes. Every object schema is `.strict()` so misspelt or extraneous fields fail validation rather than passing through silently.
|
||||
@@ -0,0 +1,65 @@
|
||||
# template.types
|
||||
|
||||
## Purpose
|
||||
|
||||
Holds the cross-module type vocabulary for `@suaveplan/drive-template`: `DriveTemplate`, `FolderTemplate`, `FileTemplate`, `GlobalPolicy`, `PerFilePolicy`, `RetentionPolicy`, `CdnFallbackConfig`, `CdnUploadToken`, `FileEncryption`, `KeyDistribution`, `KeyGrantPayload`. Every wire-format payload, validator input, and codec option references a type from this file so the package shares a single language for template-shaped data without circular imports between feature directories.
|
||||
|
||||
## Features
|
||||
|
||||
- `DriveTemplate` — the dehydrated drive description with `serverSignature`.
|
||||
- `FolderTemplate` / `FileTemplate` — recursive tree nodes.
|
||||
- `PerFilePolicy` / `RetentionPolicy` — autoDownload / meshAllowed / cdnAllowed flags + retention enum.
|
||||
- `GlobalPolicy` — defaults, cache budget, concurrency cap, trusted authorities.
|
||||
- `CdnFallbackConfig` — reverse-id URL builder configuration.
|
||||
- `CdnUploadToken` — server-signed CDN write authorisation.
|
||||
- `FileEncryption` / `KeyDistribution` — `inline` vs `retroactive` key delivery.
|
||||
- `KeyGrantPayload` — bus-borne retroactive grant envelope payload.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
DriveTemplate,
|
||||
FolderTemplate,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
declare const tpl: DriveTemplate;
|
||||
declare const folder: FolderTemplate;
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
CdnUploadToken,
|
||||
KeyGrantPayload,
|
||||
PerFilePolicy,
|
||||
RetentionPolicy,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
function policyDefaults(): PerFilePolicy {
|
||||
const retention: RetentionPolicy = { maxAgeMs: 30 * 24 * 60 * 60 * 1000 };
|
||||
return { autoDownload: false, meshAllowed: true, cdnAllowed: true, retention };
|
||||
}
|
||||
|
||||
function tokenLooksFresh(t: CdnUploadToken, now: number): boolean {
|
||||
return t.expiresAt > now;
|
||||
}
|
||||
|
||||
function isRetroactive(grant: KeyGrantPayload): boolean {
|
||||
return grant.expiresAt === undefined || grant.expiresAt > Date.now();
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `interface DriveTemplate` — `{ schemaVersion: 1, driveId, rootFolder, cdnFallback?, policy, issuer, issuedAt, expiresAt, nonce, serverSignature }`.
|
||||
- `interface FolderTemplate` — `{ name, children: ReadonlyArray<FolderTemplate | FileTemplate> }`.
|
||||
- `interface FileTemplate` — `{ name, fileLocId, fileId, size, totalChunks, chunkSize, checksum, flags, encryption?, manifestKey, manifestSig }`.
|
||||
- `interface PerFilePolicy`, `type RetentionPolicy`, `interface GlobalPolicy`.
|
||||
- `interface CdnFallbackConfig`, `interface CdnUploadToken`, `type CdnPathScheme`.
|
||||
- `interface FileEncryption`, `type KeyDistribution`, `interface KeyGrantPayload`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`schemaVersion` is a literal `1` to lock the shape — bumping schema versions ships as a new package (`@suaveplan/drive-template-v2`), never as in-place modifications. `nonce` is a 16-byte `Uint8Array` rather than a hex string so the wire format doesn't waste bytes on encoding overhead. `manifestSig` and `wrappedKey` are raw `Uint8Array` bufs for the same reason. Every field is `readonly` so consumers can't accidentally mutate a verified template — once `verifyTemplateSignature` returns `true`, the template is treated as immutable through the rest of the hydration pipeline.
|
||||
@@ -0,0 +1,53 @@
|
||||
# depth
|
||||
|
||||
## Purpose
|
||||
|
||||
Folder-depth check for `DriveTemplate.rootFolder` (DTP-08). Counts from the root (depth 0) and rejects the first folder reaching depth `MAX_FOLDER_DEPTH` (32) with `DRIVE.TEMPLATE_TOO_DEEP`. The cap is exclusive — depth 32 is allowed; depth 33 triggers rejection. Files do not count toward the depth — only `FolderTemplate` chains.
|
||||
|
||||
## Features
|
||||
|
||||
- `assertFolderDepth(folder)` — single-call walker.
|
||||
- `MAX_FOLDER_DEPTH` constant pinned at 32 so dashboards can reference the same source-of-truth as the spec.
|
||||
- `path` accumulator threaded through the recursion so `DriveTemplateError.context.path` names the offending node — handy when the hydration UI wants to surface a "this drive's folder structure is too deep" error pointing at the path that broke.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { assertFolderDepth } from "@suaveplan/drive-template";
|
||||
|
||||
assertFolderDepth(template.rootFolder);
|
||||
// throws DriveTemplateError(DRIVE.TEMPLATE_TOO_DEEP) past depth 32
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
assertFolderDepth,
|
||||
DTP_CODES,
|
||||
DriveTemplateError,
|
||||
MAX_FOLDER_DEPTH,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
function describeDepth(folder: import("@suaveplan/drive-template").FolderTemplate) {
|
||||
try {
|
||||
assertFolderDepth(folder);
|
||||
return `valid (≤ ${MAX_FOLDER_DEPTH})`;
|
||||
} catch (err) {
|
||||
if (err instanceof DriveTemplateError && err.code === DTP_CODES.TEMPLATE_TOO_DEEP) {
|
||||
const ctx = err.context as { path?: string };
|
||||
return `too deep at ${ctx.path ?? "<root>"}`;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `assertFolderDepth(folder: FolderTemplate): void` — throws `DriveTemplateError(DRIVE.TEMPLATE_TOO_DEEP)` past depth 32.
|
||||
- `MAX_FOLDER_DEPTH: 32`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The walker treats files as leaves — it returns immediately when `"children" in node` is false. This keeps the cap a structural folder limit rather than a "max number of files in any path" limit, which is what consumers want when they're trying to limit display-tree complexity. The path accumulator is built with the spread operator on each recursive call rather than mutated; the depth-32 limit means at most 32 levels of array allocation per validation, which is negligible.
|
||||
@@ -0,0 +1,54 @@
|
||||
# file
|
||||
|
||||
## Purpose
|
||||
|
||||
Per-file consistency invariants for every `FileTemplate` (DTP-09). Files that fail any invariant are rejected with `DRIVE.TEMPLATE_MALFORMED` and a `context` object naming the offending file path. The walker recurses through the folder tree so the failure surfaces with the full path from the root.
|
||||
|
||||
## Features
|
||||
|
||||
- `assertFolderFileInvariants(folder)` — recurses, checking every nested file.
|
||||
- `assertFile(file, path)` — single-file check, used directly in the hydration scheduler when files arrive piecemeal.
|
||||
- `MIN_CHUNK_SIZE_EXCLUSIVE` constant (164) — the chunk-format header overhead requires at least one payload byte.
|
||||
- Five gates: `chunkSize > 164`, `totalChunks * chunkSize >= size`, `manifestKey` non-empty, `checksum` matches `'blake3:<hex>'` or `'sha256:<hex>'`, `manifestSig` non-empty.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { assertFile } from "@suaveplan/drive-template";
|
||||
|
||||
assertFile(file, "/photos/wedding.jpg");
|
||||
// throws DriveTemplateError(DRIVE.TEMPLATE_MALFORMED) on any invariant violation
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
assertFolderFileInvariants,
|
||||
DTP_CODES,
|
||||
DriveTemplateError,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
function reportFileFailures(folder) {
|
||||
try {
|
||||
assertFolderFileInvariants(folder);
|
||||
return null;
|
||||
} catch (err) {
|
||||
if (err instanceof DriveTemplateError && err.code === DTP_CODES.TEMPLATE_MALFORMED) {
|
||||
const ctx = err.context as { file: string; issue: string };
|
||||
return { file: ctx.file, issue: ctx.issue };
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `assertFolderFileInvariants(folder: FolderTemplate): void`.
|
||||
- `assertFile(file: FileTemplate, path: string): void`.
|
||||
- `MIN_CHUNK_SIZE_EXCLUSIVE: 164` — exclusive lower bound for `chunkSize`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The 164-byte chunk-format overhead matches `@suaveplan/drive-plugin-chunks` `HEADER_SIZE`. Keeping `chunkSize` strictly greater than the header ensures every chunk carries at least one byte of payload — otherwise the manifest would describe a file that consists entirely of headers with no bytes. The checksum regex accepts both `blake3:<hex>` and `sha256:<hex>` forms so consumers running on platforms without BLAKE3 native bindings can still ship templates checksummed via SHA-256. Folder traversal accumulates the path through the spread operator, which keeps the failure context's `file` field human-readable for any UI surfacing the error.
|
||||
@@ -0,0 +1,74 @@
|
||||
# order
|
||||
|
||||
## Purpose
|
||||
|
||||
Implements the 8-step validation pipeline that `Drive.hydrateFrom(template)` runs against every incoming `DriveTemplate` (DTP-05). Failure at any step aborts before the next; the error code is pinned to the spec so monitoring dashboards can attach SLOs per stage.
|
||||
|
||||
## Features
|
||||
|
||||
- `validateTemplate(template, ctx)` — runs the full 8-step pipeline.
|
||||
- Step order is fixed: schema-version, expiry, issuance window, replay LRU, trusted issuer, server signature, folder depth, per-file invariants.
|
||||
- Replay LRU commit happens AFTER the signature check so a tampered template doesn't pollute the cache.
|
||||
- `ValidationContext` carries the injectable `Clock` and the per-client `TemplateNonceLru` so tests can step time deterministically.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
TemplateNonceLru,
|
||||
validateTemplate,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
const lru = new TemplateNonceLru();
|
||||
validateTemplate(template, {
|
||||
clock: () => Date.now(),
|
||||
nonceLru: lru,
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
DTP_CODES,
|
||||
DriveTemplateError,
|
||||
TemplateNonceLru,
|
||||
validateTemplate,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
const lru = new TemplateNonceLru();
|
||||
|
||||
async function hydrateFrom(template) {
|
||||
try {
|
||||
validateTemplate(template, {
|
||||
clock: () => Date.now(),
|
||||
nonceLru: lru,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof DriveTemplateError) {
|
||||
switch (err.code) {
|
||||
case DTP_CODES.TEMPLATE_EXPIRED:
|
||||
case DTP_CODES.TEMPLATE_STALE:
|
||||
return refreshTemplate();
|
||||
case DTP_CODES.TEMPLATE_SIG_INVALID:
|
||||
case DTP_CODES.TEMPLATE_UNTRUSTED_ISSUER:
|
||||
return alertOps(err);
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
return realisTree(template);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `validateTemplate(template: DriveTemplate, ctx: ValidationContext): void`.
|
||||
- `interface ValidationContext` — `{ clock: Clock; nonceLru: TemplateNonceLru }`.
|
||||
- `ISSUANCE_WINDOW_MS: number` — fixed 24-hour issuance window pinned by DTP-07.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `trustedKeyAuthorities` allowlist is treated as advisory when empty — an empty array means "no allowlist configured" rather than "deny all". This matches the spec's `globalPolicy.trustedKeyAuthorities` being `optional`, but it surfaces as a hard precondition in the validator so the failure mode is well-defined. The replay-LRU `remember` call is the last side effect in the pipeline so an exception thrown in any prior step rolls back cleanly. Step ordering is structurally important: cheap synchronous checks come first so a malformed template never reaches the cryptographic verification path.
|
||||
@@ -0,0 +1,51 @@
|
||||
# replay
|
||||
|
||||
## Purpose
|
||||
|
||||
Per-client LRU of `(driveId, nonce)` tuples used to reject replayed templates (DTP-06). The cache holds at most `LRU_CAPACITY` entries (default 1024); inserting a new entry that exceeds the cap evicts the least-recently-used one. The LRU is kept in-memory only; the spec calls out that `Drive.initialize()` rebuilds it from the FileLocationIndex.
|
||||
|
||||
## Features
|
||||
|
||||
- `TemplateNonceLru` — bounded LRU keyed on `(driveId, nonce-hex)`.
|
||||
- `has(driveId, nonce)` — query without mutation.
|
||||
- `remember(driveId, nonce)` — insert; auto-evicts oldest past the cap.
|
||||
- `clear()` / `size` / `capacity` — utility surface for tests + persistence hooks.
|
||||
- `TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY` — pinned at 1024 per DTP-06.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { TemplateNonceLru } from "@suaveplan/drive-template";
|
||||
|
||||
const lru = new TemplateNonceLru();
|
||||
lru.remember("drive-1", new Uint8Array(16).fill(7));
|
||||
console.log(lru.has("drive-1", new Uint8Array(16).fill(7))); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY,
|
||||
TemplateNonceLru,
|
||||
} from "@suaveplan/drive-template";
|
||||
|
||||
const lru = new TemplateNonceLru(TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY);
|
||||
|
||||
async function rebuildFromFileLocationIndex(seen: Iterable<{ driveId: string; nonce: Uint8Array }>) {
|
||||
lru.clear();
|
||||
for (const entry of seen) {
|
||||
lru.remember(entry.driveId, entry.nonce);
|
||||
}
|
||||
return lru.size;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class TemplateNonceLru` — `has`, `remember`, `clear`, `size`, `capacity`.
|
||||
- `TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY: 1024`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The implementation uses an insertion-ordered `Map` and re-inserts duplicates on `remember` to refresh recency. The keys are `<driveId>:<nonce-hex>` strings rather than tuple objects so `Map`'s `===` equality works without needing a custom key class. The capacity check fires AFTER the insert so the eviction policy is "drop the actual oldest", not "drop the entry we're about to overwrite" — this matters when a template re-uses the same `(driveId, nonce)` shortly after another distribution. Persistence support lives in `Drive.initialize()` rather than this module: the LRU rebuilds from the FileLocationIndex on startup so a process restart does not open a replay window.
|
||||
Reference in New Issue
Block a user