import { type FilesAdapter } from "@vendoai/core"; import { type VendoStore } from "./store.js"; /** 02-store §5 — every table in §2's public map. The erase API cascades the * matching data across all of them; `vendo_meta` holds schema metadata (schema * version, boot id), never user data, so no selector ever matches it and its * count stays 0. Listed so the report provably covers the whole map. * * `vendo_secrets` used to sit in that same class, and for a stated reason: * every row was name-keyed HOST config, which no subject or app selector can * reach. Tenant connectors broke that premise — their vault name CARRIES the * org that owns them (`tenantConnectorSecretPrefix`) — so the subject axis now * reaches those rows and only those rows. Deliberately not a blanket sweep: a * host's own `API_TOKEN` still belongs to the deployment, not to any person, * and erasing a subject must not disarm it. * * `vendo_effects` used to be in that same never-matched class, because the * frozen v1 shape had no subject column — its `outcome` holds real tool output * and survived an erase forever. The 2026-07-30 contract amendment added * `subject`, so the subject axis now reaches it like any other owned table. * * `vendo_quarantine` (v9) is emphatically NOT in that class. A retention sweep * lifts rows out of a live collection, and they are still the same person's * data on the other side — so the sweep copies each row's subject and app id * into columns on the way in (retention.ts) and both cascades below match * them. Without that, quarantining would be a way for data to outlive an * erasure, which is the exact hole `vendo_effects` sat in. * * `vendo_idempotency_ledger` (v8) is in the never-matched class TODAY, and not * because it holds nothing: `result` is a recorded response body and can carry * the caller's own data. Its key is (tenant, op, key) and its shape is the * console's, so no subject or app selector can reach a row — the same gap * `vendo_effects` sat in until it was given a subject. Listed here so the gap * is visible in the report rather than forgotten in the schema. */ export declare const ERASE_TABLES: readonly ["vendo_meta", "vendo_apps", "vendo_records", "vendo_blobs", "vendo_threads", "vendo_thread_messages", "vendo_effects", "vendo_grants", "vendo_approvals", "vendo_audit", "vendo_automations", "vendo_runs", "vendo_secrets", "vendo_mcp_clients", "vendo_mcp_grants", "vendo_knowledge_docs", "vendo_knowledge_chunks", "vendo_workspace_files", "vendo_workspace_history", "vendo_app_grants", "vendo_idempotency_ledger", "vendo_quarantine", "vendo_usage"]; export type EraseTable = typeof ERASE_TABLES[number]; /** * The app-database leg of both cascades. * * An app's own data is not a `vendo_*` row and cannot be reached by a selector * over this schema: it lives in the app's own SQL database, behind the * `AppDatabase` adapter the deployment selected. So the cascade reaches it * through this port, which `@vendoai/apps` fills (`createAppSql` — it knows the * physical names, and it is the ONLY place that does, so nothing here has a * second copy of that convention to drift from). * * THREADED, never defaulted, for the reason `files` is: a host on a Cloud app * database whose erase quietly ran against the local Postgres instead would get * rows deleted and every app table left behind — a deletion request answered * with a receipt. Composition passes the SAME adapter the rest of the * deployment runs on; a caller that passes none erases no app SQL, which is * visible here rather than silently wrong. */ export interface EraseAppSql { /** Every `mine.` table this person holds in this app, and their place in the app's schema log. For an app they merely USED — an org app outlives the member who leaves it. */ forget(appId: string, subject: string): Promise; /** The app's whole database: `shared.` and every person's `mine.`. */ drop(appId: string): Promise; } /** Rows deleted per table, plus the workspace content deleted behind the files * adapter, plus a count of the workspace content objects erased. That last is * its own axis because a workspace file's content is EITHER inline in the row * OR a blob reached through `blob_ref` (the row is the only pointer), and with * a host-wired `files:` adapter the blobs are not `vendo_blobs` rows at all — * so neither the table counts nor `vendo_blobs` alone tell a GDPR audit how * many pieces of user content this erase actually destroyed. * * It is a COUNT OF OBJECTS, never bytes: one per content-bearing workspace row * removed, inline or blob. The name says `objects` because that is the unit it * measures. * * What it deliberately does NOT carry is the app-database leg * ({@link EraseAppSql}): that leg drops SCHEMAS and TABLES, not rows of this * map, and a made-up row count for it would be the one number in a GDPR * receipt that means nothing. The guarantee is proven by reading back through * the app's own door, not by this report. */ export type EraseReport = Record & { workspace_content_objects: number; }; /** * 02-store §5 — the store-level erase API: by subject (full erasure) or by * app, cascading the matching data across every table of §2's map (the count * lives in `ERASE_TABLES`, which the conformance suite pins to the DDL). It is * the ONLY sanctioned deletion path for `vendo_audit` rows — the routed door * refuses audit deletion (§2); this API reaches the tables directly. * Policy engines and schedulers stay out of scope: hosts call this from their * own jobs, and host SQL remains available for everything else. */ export declare function eraseStore(store: VendoStore, options: { files: FilesAdapter; appSql?: EraseAppSql; }): { /** Full erasure of one subject: their apps (each one's engine rows and its whole SQL database), their `mine.` tables inside every app they merely used, their automations and the runs those fired, plus every subject-keyed or subject-ref'd row. */ bySubject(subject: string): Promise; /** Erase one app: its row, its SQL database, state, app-scoped grants and audit rows, and app-ref'd generic/door rows. (Threads, approvals and — since v11 — runs have no app axis: the subject selector covers them, through their automation in the runs' case.) */ byApp(appId: string): Promise; /** Erase ONE conversation's files: the workspace rows under `/user/threads/`, their history, and the blobs those rows were the only pointer to. Its transcript, its messages and its harness state are a DIFFERENT cascade (`transcripts.deleteThread`, one transaction) — this is only the half that lives in the workspace and behind the files adapter. */ byThread(threadId: string): Promise; /** Erase ONE workspace path and everything under it, for ONE owner: the live rows, their history, and the blobs those rows were the only pointer to. The owner is required because `/user/**` means a different file in every subject's workspace — the other selectors here carry the tenant in the path (`/user/threads/`, `/user/apps/`) and this one cannot. It exists for the staging waypoint (`/user/uploads/**`), which is neither a thread nor an app and so is reachable by no other axis, while both ways a file leaves staging — the turn's re-home and the janitor's sweep — only tombstone it, leaving the object behind under that unreachable address. */ byWorkspacePath(owner: string, path: string): Promise; }; //# sourceMappingURL=erase.d.ts.map