7.1 KiB
Add @suaveplan/api-plugin-vault — per-user vault routes wrapping @suaveplan/vault (3-way plug-and-play)
Package: @suaveplan/api-plugin-vault (NEW, server-tier)
Status: PROPOSED
Type: new-package
Position: depends on add-api-plugin-auth (+ optionally add-api-plugin-user)
- Spec delta: ./specs/api-plugin-vault/spec.md
Why
Genesis already ships @suaveplan/vault — the universal encrypted-vault library (open/unlock/lock FSM, items + attachments, container backed by DriveLike). What's missing is the HTTP surface: operators building an app with @suaveplan/api want /vault/items routes that look up the authenticated principal, scope the vault to that user, and apply Better-Auth's session for authorisation. This plugin ships exactly that — three CRUD route groups (items, attachments, shares — items only for v1) plus a vaultService for downstream plugins.
Third instance of the 3-way plug-and-play contract: SurrealQL schema for vault ownership/sharing metadata, Zod schemas at every request boundary, OpenAPI routes registered through definePluginRoutes. The encryption / decryption stays inside @suaveplan/vault; this plugin is the HTTP wrapper + per-user scoping layer only.
What changes
Net-new package at 0.1.0. ADDED:
Plugin factory + descriptor
createVaultPlugin(options): PluginDescriptor<VaultPluginConfig>—definePluginconsumer withdependencies: ["auth"](user plugin optional).optionsZod-validated.VaultPluginOptions(Zod-validated):{ routePrefix?: string (default "/vault"); storage: DriveLike; maxItemSizeBytes?: number (default 10 * 1024 * 1024); applySchemaOnInit?: boolean (default true); logger?: LoggerLike }.storageis theDriveLikeinstance the vault library writes encrypted blobs to (operator supplies — could be@suaveplan/storage-s3-backed@suaveplan/drive, local fs, etc.).
Routes (5)
All built with RouteBuilder; all gated by requireAuth() upstream. Every route scopes the vault to c.get("user")!.id:
GET /vault/items— list items owned by the principal. Response:VaultItemListResponseSchema(paginated).POST /vault/items— create a new item. Body:CreateVaultItemBodySchema. Response:VaultItemResponseSchema.GET /vault/items/:id— fetch a single item. Response:VaultItemResponseSchema. 404 +VAULT_ITEM_NOT_FOUNDwhen the id doesn't exist or isn't owned by the principal.PATCH /vault/items/:id— update fields. Body:UpdateVaultItemBodySchema(strict mode). Response:VaultItemResponseSchema.DELETE /vault/items/:id— remove. Response:{ deleted: true }. Owner-only orVAULT_FORBIDDEN.
vaultService
vaultServiceregistered viactx.registerService("vaultService", ...). Surface:{ openForPrincipal(principal: Principal, password: string): Promise<VaultHandle>; listItemsForOwner(ownerId: string): Promise<VaultItemSummary[]>; close(handle: VaultHandle): Promise<void> }.- ServiceRegistry augmentation:
declare module "@suaveplan/api-plugin/service-registry" { interface ServiceRegistry { vaultService: VaultService } }. - The principal's unlock password is NEVER stored — passed per request via a header (
X-Vault-Unlock) or extracted from a separate per-user secret store. Default extractor:headerExtractor("X-Vault-Unlock")(operators override viaoptions.unlockExtractor).
.surql schema (plug-and-play point 1)
./schemaexportsVAULT_OWNERSHIP_SCHEMA(frozen string) — DDL for two tables:vault_item:id,ownerId: record<user>(record-link),containerKey: string,title: string,kind: string,metadata: option<object>,createdAt,updatedAt. Indexed on(ownerId, kind)and(ownerId, createdAt DESC).vault_share: forward-declared for future sharing-route follow-up (ownerId: record<user>,granteeId: record<user>,itemId: record<vault_item>,permissions: array<string>,grantedAt,revokedAt: option<datetime>).
- DDL uses
DEFINE TABLE OVERWRITEfor idempotency. Genesis-original (no upstream port; the vault library doesn't define ownership metadata — that's HTTP-layer concern). applyVaultSchema(adapter): Promise<void>convenience helper.onInitapplies viamigrationRunner(preferred) ordbfallback.
Zod schemas (plug-and-play point 2) — exposed at ./schemas/*
./schemas/vault-item—VaultItemSchema,VaultItemSummarySchema,VaultItemResponseSchema,VaultItemListResponseSchema,CreateVaultItemBodySchema,UpdateVaultItemBodySchema(strict mode)../schemas/options—VaultPluginOptionsSchema.
Errors + observability
VaultPluginError extends SuaveplanError<VaultPluginErrorCode>viadefineCodes("VAULT_PLUGIN", [...]). Codes:VAULT_PLUGIN_INVALID_INPUT,VAULT_PLUGIN_ITEM_NOT_FOUND,VAULT_PLUGIN_FORBIDDEN,VAULT_PLUGIN_UNLOCK_FAILED,VAULT_PLUGIN_ITEM_TOO_LARGE,VAULT_PLUGIN_SERVICE_NOT_AVAILABLE,VAULT_PLUGIN_SCHEMA_APPLY_FAILED,VAULT_PLUGIN_ROUTE_PREFIX_COLLISION,VAULT_PLUGIN_UNLOCK_HEADER_MISSING.- OTel: counter
vault.items.created_total{outcome}, countervault.items.read_total{outcome}, countervault.items.updated_total{outcome}, countervault.items.deleted_total{outcome}, histogramvault.item.size_bytes(on create + update — distribution of item sizes).
Subpath exports
., ./plugin/plugin, ./routes/routes, ./services/vault-service, ./schemas/vault-item, ./schemas/options, ./schema/schema, ./error-codes/error-codes, ./otel/instruments, ./types/types.
Out of scope
- Sharing / collaborator endpoints. The
vault_sharetable is forward-declared in the schema but no routes ship — follow-upapi-plugin-vault-sharing. - Attachments routes (
/vault/items/:id/attachments). Vault library supports attachments; HTTP routes are a follow-upapi-plugin-vault-attachments. - Vault discovery / cross-user search. Per-user only in v1.
- Vault key escrow / recovery flows. Operator concern; vault library has primitives, this plugin doesn't surface them.
- Multi-vault per user. One vault per user in v1; multi-vault is a follow-up.
- WebDAV / SFTP gateways. HTTP routes only.
Source mapping
This package is genesis-original. Design intent draws from:
- The 3-way plug-and-play contract established by
add-api-plugin-auth. @suaveplan/vaultshipped surface (VaultManager, item/attachment APIs).@suaveplan/api-plugin-auth/middleware/require-auth+Principal.- The legacy
enterprise-api-plugin-vault.mdproposal's route-surface intent (verbatim:/vault/itemsCRUD).
| Requirement | Source |
|---|---|
definePlugin consumer w/ dependencies: ["auth"] |
@suaveplan/api-plugin |
Per-user scoping via c.get("user").id |
@suaveplan/api-plugin-auth (sibling) |
| Vault encryption / FSM | @suaveplan/vault (shipped) |
RouteBuilder end-to-end Zod |
@suaveplan/api-openapi (shipped) |
3-way plug-and-play (.surql + Zod + OpenAPI) |
genesis architectural principle |
Impact
Affected dependents
None today — net-new. Future api-plugin-vault-sharing / api-plugin-vault-attachments follow-ups will depend on this.
Catalog additions
None — every dep already in catalogs.