import { type PermissionKey, type PlatformActorId, type TenantId } from '@substrat-run/contracts'; import type { HostAdmin } from '@substrat-run/kernel'; /** * Reconcile a connection's grants toward what its connector DECLARES (#726 gap 2). * * The gap this closes: a connection's grants could only ever be written alongside a * credential. Both writing doors — the dashboard's connect flow and the tenant relay — * write the secret and then loop `grants`, so the remedy for "a capability is missing" * was "re-type your Scrive credential", on a rotation path that, done wrong, replaces a * working one. And the #592 reconcile did not help: it gathers grants that ALREADY exist * as directory rows and delivers them to scopes, so it repairs a dropped *delivery* and * never a grant that was never made. * * So a missing capability was both invisible (until the read-back) and disproportionate * to repair. `protocol:attach` was absent from a live connection for months for exactly * this reason (#716). * * **Why this is not the grant-only button that was declined.** That button would have let * a human add an arbitrary permission to a connection from a console — an authority * decision, taken by someone, with no tenant principal behind it, which is precisely the * laundering connections.md §3.5.1 forbids. This decides nothing. It materializes a * requirement the CONNECTOR declared in code, the same way a module's declared schedules * are projected as `system:` grants at provisioning: no one chose it, so there * is no act to attribute, and `grantedBy` being the platform actor is honest rather than * a stand-in for a person. What a connection may do still follows from a declaration that * lands in a diff — it just no longer needs a credential to deliver. * * **A floor, never a ceiling.** Declared keys a connection lacks are granted; nothing is * ever revoked. `lint:connector-grants` checks that same floor against the dashboard's * catalog, and the two agreeing is the point: a connection may legitimately hold more than * its connector declares — a second connector on the same provider, a key granted for a * path not modelled here — and a reconcile that pruned to the declaration would revoke * authority nobody asked it to touch. Shrinking a declaration is therefore never * destructive; it simply stops healing that key. * * Granted TENANT-WIDE (`scopeId: null`), which is what makes it reach installs that do not * exist yet: #592 materializes tenant-wide rows per scope at provision and reconcile, so a * scope created next week holds it without anyone replaying anything. A permission the * connection already holds live — tenant-wide or on any scope — is left exactly as it is, * so this never layers a second row over a working scope-targeted grant. */ export interface ConnectionGrantReconcileDeps { admin: HostAdmin; actor: PlatformActorId; /** * The standing grants each connector declares, by provider — e.g. * `{ scrive: SCRIVE_CONNECTION_GRANTS }`. A provider absent here is never healed, so a * host that configures nothing behaves exactly as it did before. * * Standing grants only. A connector's per-dispatch reads are authorized by the delivery * itself (#726 remedy B) and need no grant at all, so a key that appears here is by * definition one the RETURN path needs — the path that runs top-level, outside any * delivery, and therefore has nothing else to derive authority from. */ declared: Readonly>; } /** What one pass changed, for the caller's log. */ export interface ConnectionGrantReconcileReport { granted: { connectionId: string; permission: PermissionKey; }[]; } export declare function reconcileConnectionGrants(deps: ConnectionGrantReconcileDeps, tenantId: TenantId, vertical: string | null | undefined): Promise; //# sourceMappingURL=connection-grants.d.ts.map