/** * The canvas KV chokepoint — interactive-canvas-apps.md Parts C, D, E, H. * * Every door (signed-in browser, link bearer, the agent) authenticates on * its own and hands this module a RESOLVED principal. This module never * sees a cookie or a token. It owns every rule: * * key grammar and limits (Part I) * who may write this canvas (Part D: policy × manifest × relation) * reserved prefixes cfg/ evt/ ui// req/ * author-bound overwrites (H.2, unless the manifest shares the prefix) * the req/* status cap (Part E: collaborators SUGGEST, owners QUEUE) * the envelope { v, by, at } — `by` is stamped here, never accepted * * It lives in the SDK because two processes need it: the portal serves * browsers, the worker serves the agent. */ import type { SessionAccessSnapshot } from "./cms.js"; export declare const CANVAS_KV_KEY_MAX = 200; export declare const CANVAS_KV_VALUE_MAX_BYTES: number; export declare const CANVAS_KV_MAX_KEYS = 1000; export declare const CANVAS_KV_MAX_BYTES: number; export declare const CANVAS_KV_LIST_PAGE = 200; export type CanvasKvPrincipal = { kind: "user"; provider: string; subject: string; isAdmin: boolean; label?: string | null; } | { kind: "agent"; sessionId: string; } | { kind: "link"; writerId: string; label?: string | null; writeEnabled: boolean; }; export type CanvasKvRelation = "owner" | "admin" | "collaborator" | "viewer" | "link" | "agent"; export interface CanvasKvBy { kind: "user" | "link" | "agent"; id: string; label: string | null; } export interface CanvasKvMe extends CanvasKvBy { relation: CanvasKvRelation; canWrite: boolean; } export interface CanvasKvEntry { key: string; v: unknown; by: CanvasKvBy; at: string; rev: number; } export interface CanvasKvViewer { me: CanvasKvMe; /** owner | readers | link — the owner's policy for this canvas. */ policy: "owner" | "readers" | "link"; /** The app's declared switch, or null when the document declares none. */ manifestKv: { write: "owner" | "viewers"; shared: string[]; } | null; /** True for owner, admin, agent, and session writers: they may touch any row and queue requests. */ privileged: boolean; } export type CanvasKvWriteOp = { op: "put"; key: string; value: unknown; ifMatch?: number | null; } | { op: "delete"; key: string; ifMatch?: number | null; }; export interface CanvasKvWriteResult { key: string; ok: boolean; rev?: number; /** Set when the chokepoint changed what was written (a collaborator's req/* status). */ capped?: "suggested"; error?: string; code?: "FORBIDDEN" | "CONFLICT" | "INVALID_KEY" | "INVALID_REQUEST" | "TOO_LARGE" | "QUOTA" | "NOT_FOUND"; } export declare class CanvasKvError extends Error { code: string; constructor(code: string, message: string); } export declare function validateCanvasKvKey(key: unknown): string | null; export declare function validateCanvasKvSlot(slot: unknown): number | null; /** `app/task/*` matches `app/task/x` and `app/task/x/y`; `*` alone matches everything. */ export declare function canvasKvGlobMatches(glob: string, key: string): boolean; export declare function principalBy(principal: CanvasKvPrincipal): CanvasKvBy; /** * Who this principal is to this canvas, and whether they may write. * * Session writers ALWAYS get canvas write (the agent law, D.1). Session * readers write only when the owner's policy admits them AND the app * declared `kv.write: "viewers"` (D.3 — two switches, neither alone opens * the door). A link bearer writes only under policy `link` with a * read/write link; that door is not built yet, so `writeEnabled` is false * everywhere today. */ export declare function resolveCanvasKvViewer(principal: CanvasKvPrincipal, snapshot: SessionAccessSnapshot | null, settings: { kvAccess: "owner" | "readers" | "link"; kvManifest: unknown; } | null): CanvasKvViewer; /** * The per-key rule for one write. Returns the (possibly capped) envelope * value to store, or a refusal. Pure: the caller supplies the existing row. */ export declare function decideCanvasKvWrite(viewer: CanvasKvViewer, op: CanvasKvWriteOp, existing: { by: CanvasKvBy | null; } | null): { ok: true; value?: unknown; capped?: "suggested"; } | { ok: false; code: CanvasKvWriteResult["code"]; error: string; }; export interface CanvasKvStore { getSessionAccess(sessionId: string, viewer: { provider: string; subject: string; }): Promise; getCanvasKvSettings(sessionId: string, slot: number): Promise<{ kvAccess: "owner" | "readers" | "link"; kvManifest: unknown; latestRev: number; } | null>; canvasKvGet(sessionId: string, slot: number, key: string): Promise<{ key: string; value: any; rev: number; updatedAt: string; } | null>; canvasKvList(sessionId: string, slot: number, prefix: string | null, limit: number, afterKey: string | null): Promise>; canvasKvWrite(sessionId: string, slot: number, key: string, value: unknown | null, ifMatch: number | null, limits: { maxKeys: number; maxBytes: number; maxValueBytes: number; }): Promise<{ status: string; rev: number; sizeBytes: number | null; }>; } export interface CanvasKvReadResult { entries: CanvasKvEntry[]; nextAfter: string | null; me: CanvasKvMe; policy: "owner" | "readers" | "link"; manifestKv: CanvasKvViewer["manifestKv"]; } /** Door-agnostic read: the page's `canvas-kv-ready` payload and every list/get. */ export declare function readCanvasKv(store: CanvasKvStore, sessionId: string, slot: number, principal: CanvasKvPrincipal, query?: { prefix?: string | null; limit?: number | null; after?: string | null; key?: string | null; }): Promise; /** Door-agnostic write: one or more ops, each answered individually. */ export declare function writeCanvasKv(store: CanvasKvStore, sessionId: string, slot: number, principal: CanvasKvPrincipal, ops: CanvasKvWriteOp[]): Promise<{ results: CanvasKvWriteResult[]; me: CanvasKvMe; }>; //# sourceMappingURL=canvas-kv.d.ts.map