import type { SwitchSql } from './system-switch.js'; /** Where a dump came from, as the caller of a restore, fork or carry knows it (#1869). */ export interface RepointSource { /** The scope the dump was captured from. */ scopeId: string; /** * True when the platform exported the dump itself (a fork, snapshot, preview, carry, adopt * or rebind), so its provenance is a fact rather than a claim. The re-point is then exact * even when the dump holds no grant on its source: nothing moves, and there is no fallback. */ exact?: boolean; } /** * Re-point a dump's scope-level grants at the scope it lands in, the step every restore, fork * and preview carry ends with (#1869). One definition for both adapters' `importDump`, so the * DO and the pure host cannot move different rows. It was a `LIKE 'scope:%'`, which ignores * case and so also moved an entity grant typed `Scope` or `SCOPE`. * * Scope-level grants are stored as `object = 'scope:'`, naming the scope the dump * was captured FROM, so a copy landing anywhere else has to move them. Entity-narrowed * grants are stored as `:` and travel with the dump unchanged. * * - **Exact**, when the source is named and either the dump holds a `scope:` row or * the source is `exact` (platform-exported): `scope:` and nothing else moves. An * entity grant whose type is spelled `scope` in any case stays put (the write verbs refuse * that type since #1856, but rows written before it are still stored). When the source IS * the destination (a carry onto a new version) nothing moves at all. * - **Fallback**, for a dump the caller supplied, when no source is named (a caller that * predates the field) or the dump holds no `scope:` row (its stated provenance does * not describe its rows, as when `substrat scope restore` stamps a local world with the * target's id): every object whose first six characters are exactly `scope:`. That is the * rule before #1869 minus LIKE's case folding, so `Scope:` and `SCOPE:` still stay * put. An entity typed exactly `scope` cannot be told apart from a node grant here, and moves. * * A caller-supplied dump that holds its source's grants AND grants on a THIRD scope, shaped * like a scope node (`scope:`) and neither the source nor the destination, is refused: * its stated provenance describes only part of it, and moving the rest would guess. The * refusal throws inside the load's transaction, so the target keeps what it held. A platform * copy (`exact`) is not refused: there a `scope:` row authorized nothing in the source it * was exported from, so it is left untouched and authorizes nothing here either. A * `scope:` whose id is not a scope id's shape is an entity grant, and is left alone. * * The probe and the update are separate statements rather than one with an `EXISTS` in its * `WHERE`: the update rewrites the very rows the probe looks for, and the answer must not * change part-way through. * * **Which row wins a collision (#1882).** `(subject, relation, object)` is the primary key, * so a moved row can land on a row the dump already holds for the destination. That used to * be `UPDATE OR REPLACE`, so the moved row always won, `expires_at` and `revoked_at` with it: * a revoked or expired source row could replace a live grant on the destination. Now each * row is ranked, and the higher rank is kept: * * 1. **live**: not revoked, and not expired at `now`; * 2. **revoked**: a K-21 tombstone, the evidence of a revocation (#1659's reconcile never * re-seats a grant it finds tombstoned, so losing one would un-revoke it); * 3. **expired**: not revoked, but past its `expires_at`, which is evidence of nothing. * * | moved row \ destination row | live | revoked | expired | * |-----------------------------|-------------|-------------|-------------| * | live | destination | moved | moved | * | revoked | destination | destination | moved | * | expired | destination | destination | destination | * * On a tie the destination row stays. Two moved rows that would land on the same key (only * the fallback moves more than one object) are settled by the same rank, and on a tie by the * lower `object`, before either meets the destination. Whichever live row is kept takes the * EARLIEST expiry among the live rows for its key (no expiry counting as the latest), so a * restore never widens a grant's life, whichever side it came from. The loser is deleted: it cannot stay under its old object, because a row naming another scope * is exactly what a later restore of this scope refuses. Everything runs inside the load's * transaction, and the final `UPDATE` is a plain one, so a collision this missed fails the * restore instead of replacing a row. * * `COLLATE BINARY` is written out on every comparison. Since #1883 the tuples table is always * built from the kernel's DDL, never the dump's, so its columns are BINARY already; the * collation stays as a second guard, so this function never depends on the table it is * handed having the right one. */ export declare function repointScopeGrants(sql: SwitchSql, destScopeId: string, source: RepointSource | undefined, /** The instant `expires_at` is judged against, as the adapter's checker judges it. */ now: string): void; //# sourceMappingURL=scope-repoint.d.ts.map