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,89 @@
|
||||
# vault shares
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`shares.ts` implements the share-link orchestration for `vault.shares.createLink` and `vault.shares.revokeLink`. The repository wraps the per-share key envelope under a KEK derived from the vault key + share id, then delegates the actual link creation / revocation / status check to a pluggable `DriveSharingLike` adapter (production wires this to `@suaveplan/drive-sharing`; tests use a stub).
|
||||
|
||||
The wrapped envelope keeps share-key plaintext out of `drive-sharing`'s reach: the partition store sees only ciphertext + a 24-byte nonce. Even a fully-compromised drive-sharing service cannot recover the shared item's plaintext without the vault key.
|
||||
|
||||
## Features
|
||||
|
||||
- `createLink(input) → ShareLink` — wrap the item plaintext, register the link, return metadata.
|
||||
- `revokeLink(id)` — delegate to drive-sharing; remove local metadata.
|
||||
- `isActive(id) → boolean` — fast local-metadata short-circuit + drive-sharing canonical check.
|
||||
- `list()` — snapshot of share metadata.
|
||||
- Drive failures throw `DRIVE.FAILURE` with the underlying error chained.
|
||||
- Missing-link reads / revokes throw `SHARE.NOT_FOUND`.
|
||||
- Per-share KEK derived via `deriveItemKey(vaultKey, "share/<itemId>")` and zeroized after wrap.
|
||||
- AEAD AAD bound to `"vault/v1/share/<itemId>"` so a wrapped envelope cannot be replayed against a different item.
|
||||
- OTel histogram + counter per `createLink` / `revokeLink` with `op` and `status` labels.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { VaultShares } from "@suaveplan/vault/shares/shares";
|
||||
|
||||
const shares = new VaultShares({
|
||||
driveSharing,
|
||||
partitionId: "vault-abc",
|
||||
vaultKey,
|
||||
clock: Date.now,
|
||||
rng: undefined,
|
||||
metadata: state.shares,
|
||||
onMutation: () => scheduleFlush(),
|
||||
});
|
||||
|
||||
const link = await shares.createLink({
|
||||
itemId: "i1",
|
||||
itemPlaintext: TextEncoder().encode(JSON.stringify(item)),
|
||||
expiresAt: Date.now() + 86_400_000,
|
||||
maxViews: 5,
|
||||
});
|
||||
console.log(link.url);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { VaultShares } from "@suaveplan/vault/shares/shares";
|
||||
import { VaultError, VAULT_CODES } from "@suaveplan/vault/errors";
|
||||
|
||||
// Revoke + verify pattern.
|
||||
async function safeRevoke(shares: VaultShares, id: string) {
|
||||
try {
|
||||
await shares.revokeLink(id);
|
||||
} catch (err) {
|
||||
if (
|
||||
VaultError.is(err) &&
|
||||
err.code === VAULT_CODES["SHARE.NOT_FOUND"]
|
||||
) {
|
||||
return false; // already gone
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
const stillActive = await shares.isActive(id);
|
||||
return !stillActive;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class VaultShares`.
|
||||
- `constructor(options: SharesOptions)`.
|
||||
- `createLink(input: CreateLinkInput): Promise<ShareLink>` — throws `DRIVE.FAILURE` / `ITEM.VALIDATION_FAILED`.
|
||||
- `revokeLink(id: string): Promise<void>` — throws `SHARE.NOT_FOUND` / `DRIVE.FAILURE`.
|
||||
- `isActive(id: string): Promise<boolean>`.
|
||||
- `list(): ShareLink[]`.
|
||||
- `interface SharesOptions` — `{ driveSharing, partitionId, vaultKey, clock, rng, metadata, onMutation }`.
|
||||
- `interface CreateLinkInput` — `{ itemId, itemPlaintext, expiresAt?, maxViews? }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The KEK is derived per-share via `deriveItemKey(vaultKey, "share/<itemId>")`. Using a synthetic key path keeps share KEKs distinct from item keys derived from the same vault key under genuine item ids.
|
||||
- `wrappedKey` returned by `createLink` to drive-sharing is opaque ciphertext bytes — the adapter never sees the unwrapped per-share key. Even if the adapter is fully compromised, the share recipient's vault-key derivation is required to unwrap the envelope.
|
||||
- `revokeLink` delegates to `DriveSharingLike.revokeLink` first, then removes local metadata only on success. A drive-sharing failure leaves the local metadata intact so a retry hits the same code path.
|
||||
- `isActive` short-circuits on missing local metadata before calling the adapter; this avoids spurious network round-trips for already-revoked links and keeps `revokedLink === false` consistent with `metadata.delete`.
|
||||
- The OTel `outcome` counter uses a fixed allowlist (`success | not_found | rejected`); `revoked` is not an outcome here — once a link is revoked it is gone from local metadata and any further calls fall under `not_found`.
|
||||
Reference in New Issue
Block a user