import { type Coverage, type PrincipalId } from '@substrat-run/contracts'; import type { SwitchSql } from './system-switch.js'; /** * A scope's ROLE ROSTER and the two bounded writes over it (#1150), shared by both adapters * so the roster a dashboard reads and the rows a removal tombstones are one definition. * * Scope-level role assignments are `(principal:, role:, scope:)` tuples in the * scope's own `_substrat_tuples`. That is where an invite's grant (`assignScopeRoleBounded`) * and the install's owner seat land, so it is the whole of "who works this installed * vertical" at scope level. Tenant-level roles live in the directory and are not this. * * The SQL here is plain statements over the adapter's handle. The BOUND (§5.1, K-21) is the * adapter's: it resolves roles and asks the checker inside the same scope task that then * calls `applyScopeRoleChange`, so nothing lands between the check and the write. */ /** One live scope-level role assignment. */ export interface ScopeRoleHolder { principal: PrincipalId; roleKey: string; } /** * Every live scope-level role assignment at `scopeId` — not tombstoned, not expired at `now`. * `principal` narrows it to one holder. Expiry is judged as an instant, not as ISO text, so a * grant written with an offset (`+14:00`) is read the way the checker reads it. */ export declare function scopeRoleHolders(sql: SwitchSql, scopeId: string, now: string, principal?: PrincipalId): ScopeRoleHolder[]; /** * Tombstone `revoke` and grant `grant` for one principal at one scope — the write half of a * bounded change or removal, run by the adapter in the same scope task as its bound. The * tombstone is K-21's (the row stays, the walk skips it); the grant is `INSERT OR REPLACE`, * an explicit grant clearing an earlier tombstone exactly as `assignScopeRole` does. */ export declare function applyScopeRoleChange(sql: SwitchSql, scopeId: string, principal: PrincipalId, change: { revoke: readonly string[]; grant: string | null; }, at: string): void; /** The caller's bound for one role at the scope, or `null` when the tenant defines no such role. */ export type RoleBound = (roleKey: string) => Promise; /** * The adapter's own synchronous transaction (`db.transaction(run)()` on SQLite, the write * revision's `transactionSync` in the Durable Object). The writes of one change run inside it, * after every bound has passed, so a statement that fails part-way — the grant after the * tombstone, the third of four tombstones — takes the earlier ones with it. */ export type Atomically = (run: () => T) => T; /** * Move `principal` from `from` to `to` (#1150) — the whole decision, for an adapter to run in * ONE scope task: `from` must be held, `to` must be defined, the caller's bound over both, then * the tombstone and the grant together, in one transaction. `not-held` and `unknown-to` wrote nothing; neither does * a coverage that does not cover. The adapter turns the two refusals into its own errors. */ export declare function changeScopeRole(sql: SwitchSql, scopeId: string, principal: PrincipalId, from: string, to: string, now: string, bound: RoleBound, atomically: Atomically): Promise; /** * Take every scope role `principal` holds (#1150), for an adapter to run in ONE scope task: the * caller's bound over each (a role the tenant no longer defines is taken without one), then all * the tombstones in one transaction, or none. `revoked` names what was taken. */ export declare function revokeScopeRoles(sql: SwitchSql, scopeId: string, principal: PrincipalId, now: string, bound: RoleBound, atomically: Atomically): Promise<{ coverage: Coverage; revoked: string[]; }>; //# sourceMappingURL=scope-role-admin.d.ts.map