import { type BecomeCapabilityInput, type BecomeLinkState, type BecomeMintRefusal, type BoundedBecomeRevoke, type CapabilityExchange, type CapabilityFilter, type CapabilityId, type CapabilityMintInput, type CapabilityPage, type CapabilityRecord, type CheckSubject, type Decision, type DomainEventInput, type EntityRef, type Instant, type MintedCapability, type Node, type PermissionKey, type PlatformActorId, type PrincipalBecomeCapabilityInput, type PrincipalId } from '@substrat-run/contracts'; import { PermissionDenied, type Holdings, type PermissionChecker } from './permission-checker.js'; import type { ScopedSql, SqlValue } from './scope-host.js'; /** * The two spine tables, kernel-owned so no vertical carries a migration for them. Shared by * both adapters' `KERNEL_DDL` (interpolated, like `IDEMPOTENCY_DDL`), so the shape * self-host builds and the shape production builds cannot part company. */ export declare const CAPABILITY_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_capabilities (\n id TEXT PRIMARY KEY,\n -- #1672: SHA-256 hex of the secret. The secret itself is stored NOWHERE \u2014 it leaves\n -- the kernel once, in the mint's return value, and a lookup is this indexed equality.\n token_hash TEXT NOT NULL UNIQUE,\n -- 'act': the holder acts AS the capability ({ capability } on the spine), bounded by\n -- entity + permissions (+ operations + attachments). 'become': exchanging it yields `principal`.\n mode TEXT NOT NULL,\n label TEXT,\n entity_type TEXT,\n entity_id TEXT,\n -- JSON array of permission keys (act). The capability's whole authority: no\n -- `capability:` tuple is ever written, so none can be forged through the tuple table.\n permissions TEXT,\n -- JSON array of operation names, or NULL = any operation the keys allow (act).\n operations TEXT,\n -- NULL = no attachment read opt-in; 'read' explicitly allows attachment readers.\n attachments TEXT,\n principal TEXT,\n -- #1686: a principal-minted 'become' only \u2014 the digest of what `principal` held when it was\n -- minted (`holdingsDigest`). The exchange recomputes it, and a link whose principal's\n -- holdings have changed since is revoked rather than exchanged. NULL on every other row.\n target_digest TEXT,\n -- #1686: why the kernel itself revoked it ('holdings-changed'); NULL for any other revoke.\n revoked_reason TEXT,\n -- #1686: JSON capabilityAction \u2014 what a principal-minted 'become' also does at its exchange\n -- ({\"kind\":\"bind\",\"entity\":\u2026}, a binding invite). NULL = a plain become, or an 'act' row.\n action TEXT,\n -- JSON capabilityAuthor: a principal id (a module minted it, and its authority is\n -- re-checked on every use) or {\"platform\": \u2026} (HostAdmin minted it).\n minted_by TEXT NOT NULL,\n minted_at TEXT NOT NULL,\n expires_at TEXT,\n -- NULL = unlimited. A use is an EXCHANGE of the secret, never an invocation.\n max_uses INTEGER,\n uses INTEGER NOT NULL DEFAULT 0,\n last_used_at TEXT,\n -- Tombstone, never a delete: a capability that once admitted someone is evidence\n -- of why they were admitted (K-4, K-21).\n revoked_at TEXT,\n revoked_by TEXT\n );\n CREATE INDEX IF NOT EXISTS _substrat_capabilities_entity\n ON _substrat_capabilities (entity_type, entity_id);\n -- #1672: what an exchange hands out instead of the secret \u2014 a session token, of which\n -- again only the hash is kept. Each session acts as its capability until the earlier of\n -- its own expiry and the capability's; the capability is re-read on every invoke, so\n -- revoking it ends every session at once \u2014 a revoke touches only the capability row,\n -- and it is that per-invoke read, not a cascade here, that refuses the next call.\n -- Expired rows are pruned on exchange, at most CAPABILITY_SESSION_PRUNE_BATCH at a time,\n -- on the only path that adds one \u2014 so an exchange's write is bounded however many expired.\n CREATE TABLE IF NOT EXISTS _substrat_capability_sessions (\n token_hash TEXT PRIMARY KEY,\n capability_id TEXT NOT NULL,\n created_at TEXT NOT NULL,\n expires_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS _substrat_capability_sessions_expiry\n ON _substrat_capability_sessions (expires_at);\n"; /** The tables `CAPABILITY_DDL` builds — what a copy of a scope leaves behind (`capabilitiesForLoad`, #1686). */ export declare const CAPABILITY_TABLE_NAMES: readonly ['_substrat_capabilities', '_substrat_capability_sessions']; /** One capability row as the spine stores it — snake_case, because that is what both adapters `SELECT`. */ export interface CapabilityRow { id: string; mode: string; label: string | null; entity_type: string | null; entity_id: string | null; permissions: string | null; operations: string | null; attachments: string | null; principal: string | null; minted_by: string; minted_at: string; expires_at: string | null; max_uses: number | null; uses: number; last_used_at: string | null; revoked_at: string | null; revoked_by: string | null; /** #1686: a principal-minted `become`'s holdings digest at mint; NULL elsewhere. */ target_digest?: string | null; /** #1686: why the kernel itself revoked it; NULL otherwise. */ revoked_reason?: string | null; /** #1686: a `become`'s carried action (JSON `capabilityAction`); NULL otherwise. */ action?: string | null; } /** The row the permission checker reads for a capability subject (`ScopeTupleReader.capability`). */ export declare function capabilityByIdQuery(id: string): { sql: string; params: SqlValue[]; }; /** * The SELECT behind every read of a scope's capabilities — `ctx.capabilities.list` and the * operator's `HostAdmin.listCapabilities` (#1686) both build it here, so "newest first" and * "live only unless asked" have one definition. Columns are `CAPABILITY_COLUMNS`, which never * includes `token_hash`: no reader of this query can return a hash. The filter is re-parsed * (bounded: `limit` is 1..200, default 50; a `cursor` must be a capability id) and every value * is bound. * * Keyset-paged on `id`, the table's primary key, walked backwards: a ULID id IS creation * order, and a record minted mid-walk gets a LARGER id than any cursor already handed out, so * a concurrent mint can neither repeat a record nor make the walk skip one. `extra` reads that * many rows beyond the page, which is how `readCapabilityPage` knows more follow. */ export declare function capabilityListQuery(raw?: CapabilityFilter, extra?: number): { sql: string; params: SqlValue[]; }; /** A read that names no `limit` gets this many records. */ export declare const DEFAULT_CAPABILITY_LIMIT = 50; /** * One page of the operator's read. Reads one row past the page: `nextCursor` is the last * returned record's id when that extra row exists, and `null` when the walk is complete — so * the caller can always tell "that is all" from "there is more", and a console never has to * guess from a full page whether it truncated. */ export declare function readCapabilityPage(sql: ScopedSql, filter?: CapabilityFilter): CapabilityPage; /** * Is this capability USABLE — not revoked, not expired? The ONE predicate for it: the * permission checker, the session door and the exchange all call this, so the three can * never disagree about whether a revoked or expired capability still acts. Same shape as * the evaluator's `live()` for tuples, and ISO instants compare lexically. */ export declare const capabilityLive: (row: Pick, now: string) => boolean; /** * May this capability's secret be EXCHANGED now — usable, and under its use limit? The use * limit bounds exchanges only: a session handed out by an earlier exchange keeps acting * after the limit is reached, until the capability expires or is revoked. */ export declare const capabilityExchangeable: (row: Pick, now: string) => boolean; /** * What an `act` capability grants, decoded for the checker — or `null` for anything that * cannot act (a `become` capability, a row whose JSON does not decode). Null DENIES: a row * the checker cannot read in full grants nothing, rather than whatever part of it parsed. */ export interface CapabilityGrantView { entity: EntityRef; permissions: PermissionKey[]; /** Null when a platform actor minted it — which an `act` capability never is. */ mintedBy: PrincipalId | null; } export declare function capabilityGrantOf(row: CapabilityRow): CapabilityGrantView | null; /** The record a caller reads back — never the secret, never its hash. */ export declare function capabilityRecordOf(row: CapabilityRow): CapabilityRecord; /** A fresh capability secret: 256 bits, base64url, prefixed. Only its hash is ever stored. */ export declare function mintCapabilitySecret(): string; /** * SHA-256 hex of a secret or session token (Web Crypto — the same call in workerd, node and * browsers). Unkeyed on purpose: with 256 bits of entropy there is no dictionary to defend * against, and a key would be one more secret to hold and rotate. */ export declare function capabilityTokenHash(token: string): Promise; /** * How many expired sessions one exchange prunes, at most. An unlimited link can hand out any * number of sessions, so pruning every expired row at once would make one visitor's request * an unbounded write — and a Durable Object's transaction a large one. A fixed batch keeps * each exchange's cost constant, and the table still drains: every exchange takes another * batch, and an expired session acts as nobody whether or not it has been pruned yet. */ export declare const CAPABILITY_SESSION_PRUNE_BATCH = 100; /** * The text a value would persist as — ONE serialization of the COMPLETE record, keys * included, byte values decoded as UTF-8. The guard scans this rather than walking chosen * fields, so a secret used as an entity id, a request kind, an object key or a BLOB * parameter is found exactly as one in a payload value is: there is no field list to * forget a field from. The secret's alphabet (base64url behind `sbcap_`) contains nothing * JSON escapes, so the serialization contains the secret exactly when the value does. */ export declare function persistedText(value: unknown): string; /** Does `value`, serialized as it would persist (`persistedText`), contain any of `secrets`? */ export declare function carriesSecret(value: unknown, secrets: readonly string[]): boolean; /** What a recorded response carries in place of a minted secret. */ export declare const WITHHELD_SECRET = "[capability secret withheld]"; /** * `value` with every minted secret replaced by `WITHHELD_SECRET` — what an idempotency * recording stores. A replayed mint therefore returns the placeholder, never the secret: * the caller that lost the first response mints again, which is the honest outcome, since * the alternative is a plaintext secret in a spine table for a day. * * Done on the serialization, like the guard, so a secret in a key is withheld as surely as * one in a value. The recording is JSON already, so round-tripping through it changes * nothing the recording would have kept — and a value carrying no secret is returned as is. */ export declare function redactSecrets(value: T, secrets: readonly string[]): T; /** * `text` with every minted secret replaced by `WITHHELD_SECRET` — the string twin of * `redactSecrets`, for a value that is text rather than a record (#1746: `ctx.log`). * * Not `redactSecrets(text)`: that one works on the serialization and parses it back, and a * bare string's serialization is the string itself, so the parse throws on any text that is * not JSON — which, in a log line, is all of it. */ export declare function redactSecretText(text: string, secrets: readonly string[]): string; /** * The tripwire (see above): refuse a write whose COMPLETE record, as it would persist, * carries a secret this invocation minted. Thrown from `ctx.emit` (the whole parsed event), * `ctx.requestPlatform` (the whole parsed request) and `ctx.sql` (the statement and every * parameter, bytes decoded) while the minting invocation runs. */ export declare function assertNoSecret(where: string, record: unknown, secrets: readonly string[]): void; /** `ctx.sql` with every statement — its text and all its parameters — held to `assertNoSecret`. */ export declare function guardSecrets(inner: ScopedSql, secrets: readonly string[]): ScopedSql; /** What an operation handler sees as `ctx.capabilities`. Nothing here checks a permission for the caller — the operation's own `assertAllowed` comes first. */ export interface CapabilityVerbs { /** * Mint an `act` capability over one entity and return its secret — the only time the * secret exists outside the caller's hands. Build the link from it (as a URL FRAGMENT, * so it never reaches a server log or a `Referer`) and return it once. * * **Delegation, never elevation, and on every use.** Every key is re-checked here on the * entity with the operation's own check, so a principal can only mint what they hold * there; and the checker re-checks the minter on every later use, so a capability never * grants more than its minter holds NOW. Revoke the minter's access and their links stop * granting it. * * Only a principal may mint — a capability, connection or schedule cannot delegate. * Transactional with the operation: a mint whose operation throws never happened. */ mint(input: CapabilityMintInput): Promise; /** * Revoke an `act` capability. Takes effect on the very next check, for every session it * handed out. Allowed to anyone who could have minted it — every key it carries, held on * its entity — which is `ctx.revoke`'s rule. Idempotent on an already-revoked one. */ revoke(id: CapabilityId): Promise; /** This scope's capabilities, newest first. Revoked ones only when asked for. */ list(filter?: CapabilityFilter): CapabilityRecord[]; } export interface CapabilityVerbDeps { /** RAW spine access inside the operation's own transaction — not the guarded `ctx.sql`. */ sql: ScopedSql; /** Who is minting. */ subject: CheckSubject; /** The operation's instant. */ now: Instant; /** The operation's OWN check — the same one `ctx.check` and `ctx.grant` use. */ check: (permission: PermissionKey, entity?: EntityRef) => Promise; /** `ctx.emit` — stamps the minter as actor, K-34 authorization, the operation. */ emit: (event: DomainEventInput) => void; /** Is this an operation registered on this host? An allowlist naming none is a typo. */ isOperation: (name: string) => boolean; /** K-42's read-only refusal, for the effecting verbs. */ assertWrites: (verb: string) => void; /** This invocation's minted secrets — appended to by `mint`, read by the secret guards. */ minted: string[]; } export declare function createCapabilityVerbs(deps: CapabilityVerbDeps): CapabilityVerbs; /** The pseudo-operation an exchange's spine event names, on `attachments.upload`'s pattern. */ export declare const CAPABILITY_EXCHANGE_OPERATION = "capabilities.exchange"; /** * Trade a secret for what it grants — ONE counted use. Runs inside the scope's * serialization domain and a transaction the adapter opened; `emit` writes the * `capability.exercised` event with the capability as its actor. * * The use is taken with an `UPDATE … WHERE uses = ?` on the row just read, so it is atomic * even without the scope's single-writer guarantee: two exchanges racing for a single-use * capability cannot both see `uses = 0` and both win. * * `null` for an unknown, expired, revoked or used-up secret — one answer for all four, so * a caller probing for secrets learns nothing it can act on. Refused exchanges are not * recorded: their volume is whatever an attacker chooses, and with 256 bits of entropy * there is nothing a record of guesses would ever show (#130 is the rate control). */ export declare function exchangeCapability(deps: { sql: ScopedSql; now: Instant; emit: (capability: CapabilityId, event: DomainEventInput) => void; /** * What a principal holds now (#1686) — the checker's `holdings` at this scope. A * principal-minted `become` is exchanged only while its principal's holdings digest is the * one recorded at mint; absent, such a link is refused. */ holdings?: (principal: PrincipalId) => Promise; /** * Does `principal` hold `permission` on `entity` now (#1686) — the checker's `check`. A link * carrying a `bind` is exchanged only while its minter still holds the binding's key on the * record; absent, such a link is refused. */ allowed?: (principal: PrincipalId, permission: PermissionKey, entity: EntityRef) => Promise; }, secret: unknown, /** * The only mode the caller can handle. A secret of the OTHER mode answers `null` without * taking a use: a claim link pasted into a share-link exchange must not be spent by a * route that would then throw away the principal it yielded. */ mode?: 'act' | 'become'): Promise; /** * The session door's decision, re-made on EVERY invoke inside the operation's transaction * (impersonation's per-invoke re-read, for the same reason): a stub is a capability of its * own and nothing takes it away, so a revocation checked only when the stub was minted * would stop everyone except the one caller holding it. * * Refuses an unknown or expired session and a revoked or expired capability as * `unauthenticated` — the credential no longer works, and re-opening a live link is the * remedy — and an operation outside the capability's allowlist as `forbidden`. None of * these is a K-35 denial: no permission key was checked, and the denial log records * enforced checks. The handler's own checks, once it runs, are. * `attachmentRead` is true only at the dedicated attachment reader boundary; ordinary * operation resolution leaves the independent attachment field out of its decision. */ export declare function resolveCapabilitySession(sql: ScopedSql, sessionHash: string, now: Instant, operation: string, attachmentRead?: boolean): CapabilityId; /** * The refusal of an attachment WRITE through a capability (#1686) — an upload or a remove, * whatever keys the capability carries. The ONE refusal for it: both adapters' capability * attachment surfaces throw this, so "a link share cannot write a file" has one wording. * * Why not the write key's own check: a capability that carries `writePermission` would pass * it. A link share's use is delivering files to whoever holds the link; letting it put bytes * into the tenant's store, or take them out, is a different grant that nothing has designed * — so the surface refuses it structurally, and the key never gets a say. * * It IS a K-35 denial when the target's write key is known: the session was live, the call * was an ordinary attempt, and the log should show the capability trying. `permission` is * that key; left out (a remove naming an id this scope does not know) the refusal still * throws — the same answer either way, so a holder cannot learn which ids exist — but * carries no key, and the denial log records only enforced checks. */ export declare function capabilityAttachmentWriteRefused(capability: CapabilityId, operation: 'attachments.upload' | 'attachments.remove', node: { tenantId: Node['tenantId']; scopeId: Node['scopeId']; }, permission?: PermissionKey): PermissionDenied; /** Is `token` shaped like a session token at all? Checked before it is hashed or sent anywhere. */ export declare const plausibleSessionToken: (token: unknown) => token is string; /** Is `secret` shaped like a capability secret at all — the exchange's own first check (#1686). */ export declare const plausibleCapabilitySecret: (secret: unknown) => secret is string; /** * The platform's mint (`HostAdmin.mintCapability`) — a `become` capability, which is the * only kind the platform mints and the only way one is minted in this first cut (see * `becomeCapabilityInput`). No spine event: a platform actor's act is the admin log's to * record, and the adapter records it there. */ /** * A platform mint's input, parsed and held to its one rule beyond the shape: an expiry in * the future. Its own function so a host whose storage sits across an RPC boundary can run * it on the near side, where a typed refusal still reaches the caller as one — the same * check `mintBecomeCapability` makes, not a second copy of it. */ export declare function checkBecomeInput(raw: BecomeCapabilityInput, now: Instant): BecomeCapabilityInput; export declare function mintBecomeCapability(sql: ScopedSql, raw: BecomeCapabilityInput, actor: PlatformActorId, now: Instant): Promise; /** * Where each named `become` link stands (#1686), in the order asked — what a pending-invite list * shows beside each invite. A capability the scope does not hold reads as `revoked`, never `open`. * * A principal-minted link that would otherwise read `open` is judged as its exchange would judge * it: its principal's holdings are read now (`holdings`) and compared with the digest taken at * mint, and a link whose principal changed since reads `revoked` / `holdings-changed` — before * anybody has tried it. Nothing is written on a read: the exchange is what revokes it. One with no * digest, or with no `holdings` to judge it by, reads `revoked`, as its exchange refuses it. */ export declare function readBecomeLinkStates(sql: ScopedSql, ids: readonly string[], now: Instant, holdings?: (principal: PrincipalId) => Promise): Promise; /** * `becomeLinkStates`' bound on how many links one call names (`BECOME_LINK_STATES_MAX_IDS`): * refused over it, never truncated — a caller asks in pages. Its own function so a host whose * storage sits across an RPC boundary checks on the near side, where the typed refusal survives. */ export declare function assertBecomeLinkStateIds(ids: readonly string[]): void; /** * A canonical digest of what a principal holds (#1686): the role KEYS it holds at the node, as * assigned, the permissions granted to it directly at the node, and its entity-narrowed grants — * at scope and tenant level, through its orgs — each sorted and deduplicated, so two reads of the * same holdings in any order give the same digest. Taken when a principal mints a `become` for it, * and again at the exchange: a role assigned or taken away, a grant added or removed, an org joined * or left, all change it, and the link dies. * * Role KEYS, not their expansions, deliberately: a role's definition is the vertical's code, * re-projected on a push and reviewed at the permission-diff checkpoint — nothing an inviter * controls. Expanding it would revoke every open invite at a role whenever a release adds a key to * that role. The price, by design: a REDEFINITION can make the invitee hold more than the minter * held when it minted — a release that widens the role widens what the link becomes. That is a * change only code or staff make (`defineRole` has no HTTP route), never an inviter. * * What the digest deliberately does not see, so nobody relies on it as a reach check: * - the LEVEL a grant is held at — a key moved between the scope and the tenant node leaves it * unchanged, as the mint bound compares the same flattened set (`holdingsOf` merges the levels); * - reach that grows under a narrowed grant's entity through `ctx.link` / `ctx.relink` — the * minter's coverage of that entity was checked through `check`, whose reach grows the same way; * - module authority decided from a principal column (an assignee, an author) rather than a tuple * — neither the bound nor the digest sees it. * None of these three lets the link yield more than its minter held (a role redefinition can, by * design — see above): the identity a link binds is per * (scope, sub), so what it becomes is this principal in this scope only. The first two are judged * by the same checker, over the same flattened set and the same walk, that bounded the minter; * the third is a row the scope itself points at this principal, as it would for any member it * names — and a member invite's principal is minted fresh, so no row names it when its link is. */ export declare function holdingsDigest(held: Holdings): Promise; /** * The platform's revoke (`HostAdmin.revokeCapability`) — of any capability in the scope, * the operator's lever for a leaked link. Returns the row as it stood before, for the admin * log, or `undefined` when there is no such capability. Idempotent. */ export declare function revokeCapabilityAsPlatform(sql: ScopedSql, rawId: string, actor: PlatformActorId, now: Instant): CapabilityRecord | undefined; /** The pseudo-operation a principal's `become` mint names on its spine event. */ export declare const CAPABILITY_BECOME_MINT_OPERATION = "capabilities.mint-become"; /** * May `caller` mint a `become` capability for `target` at `node`? (#1686) Becoming someone * acquires everything they hold, so the bound is the assignment bound (K-21) applied to the * whole of it: the caller must already hold, at the node, every permission the target holds * there, and must be able to exercise every entity-narrowed grant the target holds on that same * entity. Anything less mints impersonation upward — a lead inviting a stranger in as the owner. * * The bound is evaluated at mint, against what the target holds then; the exchange does not * re-check the minter. It compares the target's holdings instead: a link whose principal's * holdings have changed since the mint is revoked rather than exchanged (`exchangeCapability`). So a target holding nothing at the node is refused outright: its empty set would * cover trivially, and the link would yield whatever that principal is granted later. A member * invite never meets this — it grants the role before it mints. * * It is also refused for a target some `become` capability has already been exchanged into in * this scope (`target-already-claimed`): a principal-minted `become` is for a seat nobody has * taken, never a second key to someone who already exists. Whether a login is bound to the * target outside the scope (an identity directory) is not visible here; the one caller today, * a member invite, always targets a principal it has just minted. * * When the mint may go ahead, the target's `holdingsDigest` — what the mint records, and what the * exchange compares against. Otherwise the refusal, as the host answers it — a `Coverage` naming * what the caller lacks, as `canAssign`'s does, `target-holds-nothing`, or * `target-already-claimed`. A checker with * no `holdings` cannot say what the target holds, so the bound refuses rather than guess. */ export declare function becomeMintCheck(deps: { sql: ScopedSql; checker: Pick; }, caller: PrincipalId, target: PrincipalId, node: Node, /** * #1686: the record a binding invite binds its claimant to. The caller must hold the binding's * key on it, and it must exist and name nobody (`judgeBind`) — the same judgement the exchange * makes again. */ bind?: { entity: EntityRef; }): Promise; /** Does `principal` hold a key on an entity at `node` now — the checker's `check`, as a binding's bound asks it (#1686). */ export declare const allowedFor: (checker: Pick, principal: PrincipalId, node: Node) => (permission: PermissionKey, entity: EntityRef) => Promise; /** * A principal's `become` mint (#1686) — the write a member invite makes once `becomeMintCheck` * has said yes, in the same scope task. Recorded with the principal as its minter, and on the * spine as `capability.become-minted` through `emit`, whose actor the host stamps as `minter`. * No expiry unless one is given, as an invite has never had one; the use limit is required. */ export declare function mintBecomeCapabilityAsPrincipal(deps: { sql: ScopedSql; now: Instant; emit: (event: DomainEventInput) => void; }, minter: PrincipalId, raw: PrincipalBecomeCapabilityInput, /** `becomeMintCheck`'s answer: the target's holdings now, which the exchange compares against. */ targetDigest: string): Promise; /** * Revoke a `become` capability a PRINCIPAL minted (#1686) — what withdrawing a member invite * does to its link, recorded with `by` as the revoker. Only such a row: a platform-minted one * (an owner claim link) is the platform's to revoke, and an `act` one is `ctx.capabilities`'. * * Bounded here, not only by the caller: `by` must be the link's minter, or hold everything its * principal holds now — whoever could have minted it, as a module's revoke of an `act` link is * open to whoever could have minted that. A refusal writes nothing. `revoked` is true when this * call revoked it; false when it was already revoked or is not such a row. */ export declare function revokeBecomeCapabilityAsPrincipal(deps: { sql: ScopedSql; checker: Pick; node: Node; now: Instant; }, rawId: string, by: PrincipalId): Promise; //# sourceMappingURL=capability.d.ts.map