import { audit } from '@voltro/plugin-audit/mixin'; import { ColumnDefinition } from '@voltro/database'; import { MixinDefinition } from '@voltro/database'; import { MixinFields } from '@voltro/database'; import { Schema } from 'effect'; import { Subject } from '@voltro/protocol'; /** * Guard a tenant-scoped mutation against cross-tenant spoofing. Throws * `TenantMismatch` if `input.tenantId` doesn't equal `subject.tenantId`. * * **What this does NOT cover, and what covers it instead.** This checks a * CLAIMED `input.tenantId` — it can only fire on a mutation that carries one and * whose author remembered to call it. It is a convention, not a boundary. The * boundary is in the store middleware: every write is tenant-enforced by * construction — inserts auto-stamp `tenantId` and refuse a null tenant, reads / * `updateMany` / `deleteMany` AND-merge `eq('tenantId', …)`, and a KEYED-BY-PK * write (`ctx.store.update(table, id, …)`, `delete`, `hardDelete`, `patchJson`) * resolves its target row inside the caller's tenant and fails with * `TenantRowNotFound` otherwise. A mutation that takes a row id from user input * is therefore safe whether or not it calls this. * * Reach for it when the input carries an explicit `tenantId` you are about to * USE (an admin form, a webhook payload) and you want a typed, early * `TenantMismatch` at the top of the handler rather than a store-level refusal * partway through. * * The subject's tenantId may be null (anonymous subjects without a * tenant scope); those callers can't write to ANY tenant so the guard * always throws — anonymous + tenant-scoped writes need a different * subject type (apiKey / serviceAccount) anyway. * * Usage in a mutation executor: * * const execute = async (input, ctx) => { * assertOwnTenant(input.tenantId, ctx.request.subject) * return ctx.store.insert(...) * } */ export declare const assertOwnTenant: (inputTenantId: string, subject: Subject) => void; export declare const tenant: () => MixinDefinition; declare type TenantFields = { tenantId: ColumnDefinition; } & MixinFields>; /** * Thrown by `assertOwnTenant` when a mutation's input claims a * different tenant than the authenticated subject. Declare it in your * mutation's `error: TenantMismatch` so callers can pattern-match on * `_tag === 'TenantMismatch'`. */ export declare class TenantMismatch extends TenantMismatch_base { } declare const TenantMismatch_base: Schema.TaggedErrorClass; } & { /** The tenant the input claimed. */ inputTenantId: typeof Schema.String; /** * The tenant the resolved subject actually belongs to. Empty * string for anonymous subjects that have no tenant scope at all — * those callers can't write to ANY tenant, so the mismatch is * absolute. (We use the empty string instead of null/optional so * the error schema is a flat struct of strings — easier on the * wire encoder.) */ subjectTenantId: typeof Schema.String; }>; export { }