/** * The removal fence (#1184): one row per (tenant, principal), stamped with the latest moment * that person was removed at the tenant level. The membership executor's add reads it inside * the directory unit that assigns the role, and applies nothing when it stands at or after * the request's `occurredAt - MEMBERSHIP_REMOVAL_SKEW_MS`. * * A table of its own rather than a read of the admin log, because K-21's audit contract * writes no row for a removal that took nothing — and a removal of someone whose add is still * on its way takes nothing, yet must still win. Every tenant-level `unassignRole` and every * `removeMember` raises it in the SAME unit as its revoke, staff's and the executor's alike, * a no-op included. Never lowered: a later removal moves it forward, an earlier one does not. * * The admin log is still the history. This is only the latest position. */ /** * The table. Interpolated into both adapters' directory DDL, so `lint:spine-ddl` sees the one * spelling on each side, and a directory dump carries it like every other directory table. */ export declare const MEMBERSHIP_FENCES_DDL = "\n CREATE TABLE IF NOT EXISTS _substrat_membership_fences (\n tenant_id TEXT NOT NULL,\n principal TEXT NOT NULL,\n removed_at TEXT NOT NULL,\n PRIMARY KEY (tenant_id, principal)\n );\n"; /** Raise `principal`'s fence to `removed_at`, never lower it. Binds tenant, principal, at. */ export declare const RAISE_MEMBERSHIP_FENCE_SQL = "\n INSERT INTO _substrat_membership_fences (tenant_id, principal, removed_at) VALUES (?, ?, ?)\n ON CONFLICT (tenant_id, principal) DO UPDATE SET removed_at = MAX(removed_at, excluded.removed_at)\n"; /** The fence, if it stands at or after an instant. Binds tenant, principal, since. */ export declare const MEMBERSHIP_FENCE_SINCE_SQL = "\n SELECT removed_at FROM _substrat_membership_fences\n WHERE tenant_id = ? AND principal = ? AND removed_at >= ?\n"; /** The fence's table name, as the DDL above spells it. */ export declare const MEMBERSHIP_FENCES_TABLE = "_substrat_membership_fences"; /** * The one-time backfill from the admin log: everyone removed at the tenant level before the * fence existed. Without it, an add requested back then and never effected — the backlog a host * that mounts the executor for the first time drains — would re-admit someone removed by hand * since, because the fence would have nothing to say about them. * * It reads who those removals named: an `unassignRole` row at the tenant node (`scope_id` NULL) * carries the assignment as `before.principalId`, a `removeMember` row the membership as * `before.principal`. A no-op removal wrote no row and is not recovered — nothing before the * fence recorded one. * * **Every backfilled fence stands at `at`, the adapter's clock at the moment it runs — never the * audit row's own time** (Codex round 4). Those rows were stamped by the wall clock, while a * request still queued from back then was stamped by the host's clock, which need not agree: a * host clock running ahead would put the copied time below the request's cutoff and the backlog * would re-admit the person. Stamping at backfill time is conservative instead: every request * emitted before it, for anyone ever removed, is refused. That takes nothing legitimate away — * anyone re-added before the executor existed got their role inline, by hand, and holds it * still — and an invite accepted more than `MEMBERSHIP_REMOVAL_SKEW_MS` after it is unaffected. * * `INSERT OR IGNORE`, so a fence the live path raised is never lowered by history. The adapters * run it on the construction that creates the table, and a directory restore runs it when the * dump did not carry the table, so it runs once per directory that never had it. */ export declare function membershipFencesBackfillSql(at: string): string; /** "Does the fence table exist yet?" — asked BEFORE the DDL, so the backfill runs once. */ export declare function membershipFencesTableExists(db: { all(sql: string, ...params: string[]): unknown[]; }): boolean; /** Does a dump carry the fence table? Compared without case, as SQLite resolves a name. */ export declare function dumpCarriesMembershipFences(names: readonly string[]): boolean; //# sourceMappingURL=membership-fence.d.ts.map