db: agent enrollment schema (M4-4a, migration 0021) (#1482)
ci/woodpecker/push/publish Pipeline was canceled

This commit was merged in pull request #1482.
This commit is contained in:
2026-08-30 01:47:06 +00:00
parent ee815a72b1
commit 143ba0f57a
5 changed files with 6346 additions and 0 deletions
@@ -0,0 +1,47 @@
CREATE TYPE "public"."agent_outbox_status" AS ENUM('pending', 'processing', 'delivered');--> statement-breakpoint
CREATE TABLE "agent_audit_events" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"seq" bigint GENERATED ALWAYS AS IDENTITY (sequence name "agent_audit_events_seq_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
"event_type" text NOT NULL,
"actor_id" text NOT NULL,
"agent_id" uuid NOT NULL,
"correlation_id" text NOT NULL,
"causation_id" uuid,
"payload" jsonb NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "agent_audit_events_type_check" CHECK (event_type IN ('agent.enrolled', 'agent.enrollment.replayed'))
);
--> statement-breakpoint
CREATE TABLE "agent_idempotency_fence" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"operation" text NOT NULL,
"idempotency_key" text NOT NULL,
"actor_id" text NOT NULL,
"authorization_scope" text NOT NULL,
"payload_digest" text NOT NULL,
"replay_mode" text DEFAULT 'actor-bound' NOT NULL,
"outcome_agent_id" uuid NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "agent_idempotency_fence_replay_mode_check" CHECK (replay_mode IN ('actor-bound', 'shared'))
);
--> statement-breakpoint
CREATE TABLE "agent_outbox" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"event_id" uuid NOT NULL,
"correlation_id" text NOT NULL,
"status" "agent_outbox_status" DEFAULT 'pending' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"delivered_at" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "agents" ADD COLUMN "harness" text;--> statement-breakpoint
ALTER TABLE "agents" ADD COLUMN "enrolled_at" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "agent_audit_events" ADD CONSTRAINT "agent_audit_events_causation_id_agent_audit_events_id_fk" FOREIGN KEY ("causation_id") REFERENCES "public"."agent_audit_events"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "agent_outbox" ADD CONSTRAINT "agent_outbox_event_id_agent_audit_events_id_fk" FOREIGN KEY ("event_id") REFERENCES "public"."agent_audit_events"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "agent_audit_events_seq_idx" ON "agent_audit_events" USING btree ("seq");--> statement-breakpoint
CREATE INDEX "agent_audit_events_agent_seq_idx" ON "agent_audit_events" USING btree ("agent_id","seq");--> statement-breakpoint
CREATE INDEX "agent_audit_events_correlation_idx" ON "agent_audit_events" USING btree ("correlation_id");--> statement-breakpoint
CREATE UNIQUE INDEX "agent_idempotency_fence_operation_key_idx" ON "agent_idempotency_fence" USING btree ("operation","idempotency_key");--> statement-breakpoint
CREATE UNIQUE INDEX "agent_outbox_event_idx" ON "agent_outbox" USING btree ("event_id");--> statement-breakpoint
CREATE INDEX "agent_outbox_status_created_idx" ON "agent_outbox" USING btree ("status","created_at");
File diff suppressed because it is too large Load Diff
+7
View File
@@ -148,6 +148,13 @@
"when": 1787963521142, "when": 1787963521142,
"tag": "0020_special_betty_brant", "tag": "0020_special_betty_brant",
"breakpoints": true "breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1788053011351,
"tag": "0021_agent_enrollment",
"breakpoints": true
} }
] ]
} }
@@ -0,0 +1,412 @@
/**
* Agent enrollment schema witnesses — M4-4a, the schema-level half of the
* witness list in docs/plans/2026-08-29-agent-enrollment-command-design.md §5.
*
* Witnesses the guarantees migration 0021's tables themselves carry: the
* event-type CHECK, monotonic per-agent append order (`seq`), deletion-safe
* linkage (no foreign key from the events or fence tables into `agents` —
* rows survive a legacy CRUD DELETE of the agent), the causation self-FK,
* the outbox's FK/uniqueness/status shape, the fence's UNIQUE
* (operation, key) and replay-mode CHECK, and the nullable enrollment
* columns on `agents` (legacy rows insert without them). The command-level
* witnesses (never-echo, same-tx atomicity, replay semantics, correlation,
* CLI parity, fail-closed) belong to the M4-4b implementation slice.
*
* Two legs run the same witness body:
* - PGlite (WASM Postgres): always runs.
* - Real PostgreSQL: runs when DATABASE_URL is set — the binding witness;
* CI migrates ci-postgres before `pnpm test`.
*/
import { randomUUID } from 'node:crypto';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { sql } from 'drizzle-orm';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { createDb } from './client.js';
import { createPgliteDb } from './client-pglite.js';
import { runPgliteMigrations } from './migrate.js';
import { agentAuditEvents, agentIdempotencyFence, agentOutbox, agents } from './schema.js';
type AnyDb = {
db: {
insert: (t: unknown) => { values: (v: unknown) => Promise<unknown> };
execute: (q: unknown) => Promise<{ rows?: unknown[] } | unknown[]>;
};
close: () => Promise<void>;
};
/** Match a constraint failure anywhere along drizzle's cause chain. */
async function expectViolation(p: Promise<unknown>, re: RegExp, label = ''): Promise<void> {
let err: unknown;
try {
await p;
} catch (e) {
err = e;
}
expect(err, label || 'expected the statement to be refused').toBeDefined();
const messages: string[] = [];
let cur: unknown = err;
while (cur instanceof Error) {
messages.push(cur.message);
cur = (cur as { cause?: unknown }).cause;
}
expect(messages.join(' | '), label).toMatch(re);
}
function rows(res: { rows?: unknown[] } | unknown[]): Record<string, unknown>[] {
return (Array.isArray(res) ? res : (res.rows ?? [])) as Record<string, unknown>[];
}
/** Unique per-run prefix so real-PG runs never collide and clean up safely. */
const T = `agent-e-${randomUUID().slice(0, 8)}`;
type EventInsert = typeof agentAuditEvents.$inferInsert;
function eventRow(overrides: Partial<EventInsert> = {}): EventInsert {
return {
eventType: 'agent.enrolled',
actorId: `${T}-actor`,
agentId: randomUUID(),
correlationId: `${T}-corr-${randomUUID()}`,
payload: {
harness: 'claude-code',
provider: 'anthropic',
name: 'x',
credentialMode: 'reference',
},
...overrides,
};
}
type FenceInsert = typeof agentIdempotencyFence.$inferInsert;
function fenceRow(overrides: Partial<FenceInsert> = {}): FenceInsert {
return {
operation: 'agent.enroll',
idempotencyKey: `${T}-${randomUUID()}`,
actorId: `${T}-actor`,
authorizationScope: 'platform-user',
payloadDigest: `${T}-digest`,
outcomeAgentId: randomUUID(),
...overrides,
};
}
function witnessSuite(getHandle: () => AnyDb): void {
const db = () => getHandle().db as unknown as ReturnType<typeof createDb>['db'];
afterAll(async () => {
const d = db();
await d.execute(sql`DELETE FROM agent_outbox WHERE correlation_id LIKE ${T + '%'}`);
// Caused events first: the causation self-FK is RESTRICT.
await d.execute(
sql`DELETE FROM agent_audit_events WHERE correlation_id LIKE ${T + '%'} AND causation_id IS NOT NULL`,
);
await d.execute(sql`DELETE FROM agent_audit_events WHERE correlation_id LIKE ${T + '%'}`);
await d.execute(sql`DELETE FROM agent_idempotency_fence WHERE actor_id LIKE ${T + '%'}`);
await d.execute(sql`DELETE FROM agents WHERE name LIKE ${T + '%'}`);
});
// ── agents: nullable enrollment columns (no backfill semantics) ────────────
it('legacy agent rows insert without enrollment columns; enrolled rows carry both', async () => {
const legacyId = randomUUID();
await db()
.insert(agents)
.values({
id: legacyId,
name: `${T}-legacy`,
provider: 'anthropic',
model: 'claude-fable-5',
});
const legacy = rows(
await db().execute(sql`SELECT harness, enrolled_at FROM agents WHERE id = ${legacyId}`),
)[0]!;
expect(legacy['harness']).toBeNull();
expect(legacy['enrolled_at']).toBeNull();
const enrolledId = randomUUID();
await db()
.insert(agents)
.values({
id: enrolledId,
name: `${T}-enrolled`,
provider: 'anthropic',
model: 'claude-fable-5',
harness: 'claude-code',
enrolledAt: new Date(),
});
const enrolled = rows(
await db().execute(sql`SELECT harness, enrolled_at FROM agents WHERE id = ${enrolledId}`),
)[0]!;
expect(enrolled['harness']).toBe('claude-code');
expect(enrolled['enrolled_at']).not.toBeNull();
});
// ── agent_audit_events: CHECK, ordering, deletion-safe linkage ─────────────
it('accepts both declared event types and refuses an undeclared one', async () => {
await db()
.insert(agentAuditEvents)
.values(eventRow({ eventType: 'agent.enrolled' }));
await db()
.insert(agentAuditEvents)
.values(eventRow({ eventType: 'agent.enrollment.replayed' }));
await expectViolation(
db()
.insert(agentAuditEvents)
.values(eventRow({ eventType: 'agent.deleted' })),
/type_check|violates check/i,
'undeclared event type must be refused',
);
});
it('assigns strictly increasing seq in insert order for one agent', async () => {
const agentId = randomUUID();
const c1 = `${T}-seq-1-${randomUUID()}`;
const c2 = `${T}-seq-2-${randomUUID()}`;
await db()
.insert(agentAuditEvents)
.values(eventRow({ agentId, correlationId: c1 }));
await db()
.insert(agentAuditEvents)
.values(eventRow({ agentId, eventType: 'agent.enrollment.replayed', correlationId: c2 }));
const res = rows(
await db().execute(
sql`SELECT correlation_id, seq FROM agent_audit_events WHERE agent_id = ${agentId} ORDER BY seq ASC`,
),
);
expect(res.map((r) => r['correlation_id'])).toEqual([c1, c2]);
expect(Number(res[1]!['seq'])).toBeGreaterThan(Number(res[0]!['seq']));
});
it('has no foreign key into agents, and events survive agent deletion', async () => {
const fks = rows(
await db().execute(sql`
SELECT ccu.table_name AS referenced_table
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name AND ccu.constraint_schema = tc.constraint_schema
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = 'agent_audit_events'
`),
);
// The causation self-FK is the ONLY foreign key on the events table.
expect([...new Set(fks.map((r) => r['referenced_table']))]).toEqual(['agent_audit_events']);
const agentId = randomUUID();
await db()
.insert(agents)
.values({ id: agentId, name: `${T}-doomed`, provider: 'anthropic', model: 'claude-fable-5' });
const corr = `${T}-survive-${randomUUID()}`;
await db()
.insert(agentAuditEvents)
.values(eventRow({ agentId, correlationId: corr }));
await db().execute(sql`DELETE FROM agents WHERE id = ${agentId}`);
const after = rows(
await db().execute(
sql`SELECT agent_id FROM agent_audit_events WHERE correlation_id = ${corr}`,
),
);
expect(after).toHaveLength(1);
expect(after[0]!['agent_id']).toBe(agentId);
});
it('enforces the causation self-FK and RESTRICTs deleting a cause', async () => {
await expectViolation(
db()
.insert(agentAuditEvents)
.values(eventRow({ causationId: randomUUID() })),
/foreign key/i,
'causation must reference an existing event',
);
const causeCorr = `${T}-cause-${randomUUID()}`;
await db()
.insert(agentAuditEvents)
.values(eventRow({ correlationId: causeCorr }));
const cause = rows(
await db().execute(
sql`SELECT id FROM agent_audit_events WHERE correlation_id = ${causeCorr}`,
),
)[0]!;
await db()
.insert(agentAuditEvents)
.values(
eventRow({
eventType: 'agent.enrollment.replayed',
causationId: cause['id'] as string,
}),
);
await expectViolation(
db().execute(sql`DELETE FROM agent_audit_events WHERE id = ${cause['id'] as string}`),
/foreign key/i,
'a cause with dependent events must not be deletable',
);
});
// ── agent_outbox shape ─────────────────────────────────────────────────────
it('outbox rows require an existing event, one outbox row per event, closed status enum', async () => {
await expectViolation(
db()
.insert(agentOutbox)
.values({ eventId: randomUUID(), correlationId: `${T}-corr` }),
/foreign key/i,
'outbox must reference an existing event',
);
const corr = `${T}-ob-${randomUUID()}`;
await db()
.insert(agentAuditEvents)
.values(eventRow({ correlationId: corr }));
const event = rows(
await db().execute(sql`SELECT id FROM agent_audit_events WHERE correlation_id = ${corr}`),
)[0]!;
const eventId = event['id'] as string;
await db().insert(agentOutbox).values({ eventId, correlationId: corr });
await expectViolation(
db()
.insert(agentOutbox)
.values({ eventId, correlationId: `${T}-ob2` }),
/duplicate key|unique/i,
'one outbox record per event',
);
await expectViolation(
db().execute(
sql`INSERT INTO agent_outbox (event_id, correlation_id, status)
VALUES (${eventId}, ${`${T}-ob3`}, 'failed')`,
),
/invalid input value for enum|22P02/i,
'status outside pending/processing/delivered must be refused',
);
});
it('outbox FK RESTRICTs event deletion while the outbox row exists', async () => {
const corr = `${T}-obr-${randomUUID()}`;
await db()
.insert(agentAuditEvents)
.values(eventRow({ correlationId: corr }));
const event = rows(
await db().execute(sql`SELECT id FROM agent_audit_events WHERE correlation_id = ${corr}`),
)[0]!;
await db()
.insert(agentOutbox)
.values({ eventId: event['id'] as string, correlationId: corr });
await expectViolation(
db().execute(sql`DELETE FROM agent_audit_events WHERE id = ${event['id'] as string}`),
/foreign key/i,
);
});
// ── agent_idempotency_fence: (operation, key) uniqueness, mode CHECK ───────
it('refuses a duplicate (operation, key) pair but allows the same key under another operation', async () => {
const key = `${T}-fence-${randomUUID()}`;
await db()
.insert(agentIdempotencyFence)
.values(fenceRow({ idempotencyKey: key }));
await expectViolation(
db()
.insert(agentIdempotencyFence)
.values(fenceRow({ idempotencyKey: key })),
/duplicate key|unique/i,
'fence uniqueness is (operation, key)',
);
// Same key, different operation identifier: a distinct fence.
await db()
.insert(agentIdempotencyFence)
.values(fenceRow({ idempotencyKey: key, operation: 'agent.other' }));
});
it('defaults replay mode to actor-bound and refuses an undeclared mode', async () => {
const key = `${T}-mode-${randomUUID()}`;
await db()
.insert(agentIdempotencyFence)
.values(fenceRow({ idempotencyKey: key }));
const row = rows(
await db().execute(
sql`SELECT replay_mode FROM agent_idempotency_fence WHERE idempotency_key = ${key}`,
),
)[0]!;
expect(row['replay_mode']).toBe('actor-bound');
await expectViolation(
db()
.insert(agentIdempotencyFence)
.values(fenceRow({ replayMode: 'unbound' as 'actor-bound' })),
/replay_mode_check|violates check/i,
'a mode outside actor-bound/shared must be refused',
);
});
it('fence has no foreign key at all, and rows survive agent deletion', async () => {
const fks = rows(
await db().execute(sql`
SELECT ccu.table_name AS referenced_table
FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name AND ccu.constraint_schema = tc.constraint_schema
WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = 'agent_idempotency_fence'
`),
);
expect(fks).toHaveLength(0);
const agentId = randomUUID();
await db()
.insert(agents)
.values({
id: agentId,
name: `${T}-fdoomed`,
provider: 'anthropic',
model: 'claude-fable-5',
});
const key = `${T}-fsurvive-${randomUUID()}`;
await db()
.insert(agentIdempotencyFence)
.values(fenceRow({ idempotencyKey: key, outcomeAgentId: agentId }));
await db().execute(sql`DELETE FROM agents WHERE id = ${agentId}`);
const after = rows(
await db().execute(
sql`SELECT outcome_agent_id FROM agent_idempotency_fence WHERE idempotency_key = ${key}`,
),
);
expect(after).toHaveLength(1);
expect(after[0]!['outcome_agent_id']).toBe(agentId);
});
}
// ── Leg 1: PGlite (always runs — local witness signal) ───────────────────────
describe('agent enrollment schema witnesses — PGlite', () => {
let dir: string;
let handle: ReturnType<typeof createPgliteDb>;
beforeAll(async () => {
dir = mkdtempSync(join(tmpdir(), 'agent-enroll-witness-'));
handle = createPgliteDb(dir);
await runPgliteMigrations(handle);
});
afterAll(async () => {
await handle.close();
rmSync(dir, { recursive: true, force: true });
});
witnessSuite(() => handle as unknown as AnyDb);
});
// ── Leg 2: real PostgreSQL (binding witness, ci-postgres in CI) ──────────────
const hasPostgres = Boolean(process.env['DATABASE_URL']);
describe.skipIf(!hasPostgres)('agent enrollment schema witnesses — real PostgreSQL', () => {
let handle: ReturnType<typeof createDb>;
beforeAll(() => {
handle = createDb(process.env['DATABASE_URL']!);
});
afterAll(async () => {
await handle.close();
});
witnessSuite(() => handle as unknown as AnyDb);
});
+111
View File
@@ -302,6 +302,11 @@ export const agents = pgTable(
skills: jsonb('skills').$type<string[]>(), skills: jsonb('skills').$type<string[]>(),
isSystem: boolean('is_system').notNull().default(false), isSystem: boolean('is_system').notNull().default(false),
config: jsonb('config'), config: jsonb('config'),
// Enrollment (M4-4, docs/plans/2026-08-29-agent-enrollment-command-design.md §4).
// NULL on both marks a legacy (non-enrolled) row; no backfill — enrollment
// is a fact the rank-4 command creates, not one to invent for existing rows.
harness: text('harness'),
enrolledAt: timestamp('enrolled_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
}, },
@@ -1279,3 +1284,109 @@ export const hierarchyOutbox = pgTable(
index('hierarchy_outbox_status_created_idx').on(t.status, t.createdAt), index('hierarchy_outbox_status_created_idx').on(t.status, t.createdAt),
], ],
); );
// ---------------------------------------------------------------------------
// Agent enrollment (M4-4) — rank-4 command family audit/outbox/fence stores.
// Design: docs/plans/2026-08-29-agent-enrollment-command-design.md §4.
// Pattern reuse from the hierarchy audit/outbox pair, separate store. Audit
// rows reference the agent by snapshot id, deliberately with NO FK, so audit
// history survives agent deletion through the legacy CRUD DELETE path.
// Idempotency for this family lives in agent_idempotency_fence (contract 3
// §4.3 envelope, ratified into contract 5 §4 via contract 3 §7 item 4) — the
// audit and outbox tables carry no idempotency key of their own.
export const AGENT_AUDIT_EVENT_TYPES = [
// Semantic mutation event of agent.enroll.
'agent.enrolled',
// Non-mutation access class: a passing idempotent replay appends this and
// nothing else (accessing principal, current correlation id, fence-row
// reference in the payload).
'agent.enrollment.replayed',
] as const;
export const agentAuditEvents = pgTable(
'agent_audit_events',
{
id: uuid('id').primaryKey().defaultRandom(),
// Global append order; per-agent ordering is a filter on agent_id ordered
// by seq.
seq: bigint('seq', { mode: 'number' }).notNull().generatedAlwaysAsIdentity(),
eventType: text('event_type').notNull(),
// No FK: audit events outlive every principal and every target.
actorId: text('actor_id').notNull(),
agentId: uuid('agent_id').notNull(),
correlationId: text('correlation_id').notNull(),
causationId: uuid('causation_id').references((): AnyPgColumn => agentAuditEvents.id, {
onDelete: 'restrict',
}),
// Immutable snapshot at event time; never carries credential material
// (§3.1 rule 1: actor, agent id, harness, provider, name, credentialMode).
payload: jsonb('payload').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
uniqueIndex('agent_audit_events_seq_idx').on(t.seq),
index('agent_audit_events_agent_seq_idx').on(t.agentId, t.seq),
index('agent_audit_events_correlation_idx').on(t.correlationId),
check(
'agent_audit_events_type_check',
sql`event_type IN ('agent.enrolled', 'agent.enrollment.replayed')`,
),
],
);
export const agentOutboxStatusEnum = pgEnum('agent_outbox_status', [
'pending',
'processing',
'delivered',
]);
export const agentOutbox = pgTable(
'agent_outbox',
{
id: uuid('id').primaryKey().defaultRandom(),
// FK into the append-only events table: never dangles, RESTRICT is safe.
eventId: uuid('event_id')
.notNull()
.references(() => agentAuditEvents.id, { onDelete: 'restrict' }),
correlationId: text('correlation_id').notNull(),
status: agentOutboxStatusEnum('status').notNull().default('pending'),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
deliveredAt: timestamp('delivered_at', { withTimezone: true }),
},
(t) => [
uniqueIndex('agent_outbox_event_idx').on(t.eventId),
index('agent_outbox_status_created_idx').on(t.status, t.createdAt),
],
);
// Contract 3 §4.3 fence shape. Uniqueness is (operation, key); the recorded
// replay mode is always 'actor-bound' for this family (`shared` is seed-only
// and refused at validation — design §3.1), but the column keeps the ratified
// envelope shape and serves the mode-mismatch collision check. The payload
// digest input EXCLUDES the credential value (design §3.1 rule 5).
export const agentIdempotencyFence = pgTable(
'agent_idempotency_fence',
{
id: uuid('id').primaryKey().defaultRandom(),
operation: text('operation').notNull(),
idempotencyKey: text('idempotency_key').notNull(),
// No FK: fence rows outlive principals, mirroring the audit tables.
actorId: text('actor_id').notNull(),
authorizationScope: text('authorization_scope').notNull(),
payloadDigest: text('payload_digest').notNull(),
replayMode: text('replay_mode').notNull().default('actor-bound'),
// Committed-outcome reference (the enrolled agent's id). Snapshot value,
// no FK: the fence must keep answering replays after a legacy DELETE.
outcomeAgentId: uuid('outcome_agent_id').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
uniqueIndex('agent_idempotency_fence_operation_key_idx').on(t.operation, t.idempotencyKey),
check(
'agent_idempotency_fence_replay_mode_check',
sql`replay_mode IN ('actor-bound', 'shared')`,
),
],
);