From 102a7b606bd492201e3d731a86397a9cfe4eb998 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Sun, 12 Jul 2026 23:10:21 -0500 Subject: [PATCH 1/3] feat(tess): persist durable session state --- apps/gateway/src/agent/agent.module.ts | 13 +- .../src/agent/tess-durable-session.dto.ts | 10 + .../tess-durable-session.repository.test.ts | 336 ++ .../agent/tess-durable-session.repository.ts | 454 ++ .../src/agent/tess-durable-session.service.ts | 93 + .../command-authorization.service.spec.ts | 57 +- .../commands/command-authorization.service.ts | 124 +- apps/gateway/src/commands/commands.module.ts | 9 +- .../src/commands/runtime-approval-verifier.ts | 23 + docs/scratchpads/tess-m2-002-durable-state.md | 63 + docs/tess/ARCHITECTURE.md | 27 +- packages/agent/src/index.ts | 1 + .../agent/src/tess-durable-session.test.ts | 287 ++ packages/agent/src/tess-durable-session.ts | 489 ++ .../db/drizzle/0012_tess_durable_state.sql | 68 + .../drizzle/0013_tess_checkpoint_history.sql | 5 + .../0014_tess_outbox_channel_scope.sql | 5 + packages/db/drizzle/meta/0012_snapshot.json | 4172 ++++++++++++++++ packages/db/drizzle/meta/0013_snapshot.json | 4214 ++++++++++++++++ packages/db/drizzle/meta/0014_snapshot.json | 4220 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 21 + packages/db/src/schema.ts | 107 + 22 files changed, 14785 insertions(+), 13 deletions(-) create mode 100644 apps/gateway/src/agent/tess-durable-session.dto.ts create mode 100644 apps/gateway/src/agent/tess-durable-session.repository.test.ts create mode 100644 apps/gateway/src/agent/tess-durable-session.repository.ts create mode 100644 apps/gateway/src/agent/tess-durable-session.service.ts create mode 100644 apps/gateway/src/commands/runtime-approval-verifier.ts create mode 100644 docs/scratchpads/tess-m2-002-durable-state.md create mode 100644 packages/agent/src/tess-durable-session.test.ts create mode 100644 packages/agent/src/tess-durable-session.ts create mode 100644 packages/db/drizzle/0012_tess_durable_state.sql create mode 100644 packages/db/drizzle/0013_tess_checkpoint_history.sql create mode 100644 packages/db/drizzle/0014_tess_outbox_channel_scope.sql create mode 100644 packages/db/drizzle/meta/0012_snapshot.json create mode 100644 packages/db/drizzle/meta/0013_snapshot.json create mode 100644 packages/db/drizzle/meta/0014_snapshot.json diff --git a/apps/gateway/src/agent/agent.module.ts b/apps/gateway/src/agent/agent.module.ts index bb1c1a4..8e90ef2 100644 --- a/apps/gateway/src/agent/agent.module.ts +++ b/apps/gateway/src/agent/agent.module.ts @@ -10,14 +10,17 @@ import { ProvidersController } from './providers.controller.js'; import { SessionsController } from './sessions.controller.js'; import { AgentConfigsController } from './agent-configs.controller.js'; import { RoutingController } from './routing/routing.controller.js'; +import { TessDurableSessionRepository } from './tess-durable-session.repository.js'; +import { TessDurableSessionService } from './tess-durable-session.service.js'; import { CoordModule } from '../coord/coord.module.js'; import { McpClientModule } from '../mcp-client/mcp-client.module.js'; import { SkillsModule } from '../skills/skills.module.js'; import { GCModule } from '../gc/gc.module.js'; import { LogModule } from '../log/log.module.js'; +import { CommandsModule } from '../commands/commands.module.js'; +import { CommandRuntimeApprovalVerifier } from '../commands/runtime-approval-verifier.js'; import { AGENT_RUNTIME_PROVIDER_REGISTRY, - DenyRuntimeApprovalVerifier, RUNTIME_APPROVAL_VERIFIER, RUNTIME_PROVIDER_AUDIT_SINK, RuntimeProviderAuditService, @@ -26,13 +29,15 @@ import { @Global() @Module({ - imports: [CoordModule, McpClientModule, SkillsModule, GCModule, LogModule], + imports: [CoordModule, McpClientModule, SkillsModule, GCModule, LogModule, CommandsModule], providers: [ ProviderService, ProviderCredentialsService, RoutingService, RoutingEngineService, SkillLoaderService, + TessDurableSessionRepository, + TessDurableSessionService, { provide: AGENT_RUNTIME_PROVIDER_REGISTRY, useFactory: (): AgentRuntimeProviderRegistry => new AgentRuntimeProviderRegistry(), @@ -42,10 +47,9 @@ import { provide: RUNTIME_PROVIDER_AUDIT_SINK, useExisting: RuntimeProviderAuditService, }, - DenyRuntimeApprovalVerifier, { provide: RUNTIME_APPROVAL_VERIFIER, - useExisting: DenyRuntimeApprovalVerifier, + useExisting: CommandRuntimeApprovalVerifier, }, RuntimeProviderService, AgentService, @@ -58,6 +62,7 @@ import { RoutingService, RoutingEngineService, SkillLoaderService, + TessDurableSessionService, RuntimeProviderService, AGENT_RUNTIME_PROVIDER_REGISTRY, ], diff --git a/apps/gateway/src/agent/tess-durable-session.dto.ts b/apps/gateway/src/agent/tess-durable-session.dto.ts new file mode 100644 index 0000000..9248797 --- /dev/null +++ b/apps/gateway/src/agent/tess-durable-session.dto.ts @@ -0,0 +1,10 @@ +import type { RuntimeProviderRequestContext } from './runtime-provider-registry.service.js'; + +/** Server-side request for a replay-safe provider message. */ +export interface TessProviderOutboxDto { + sessionId: string; + idempotencyKey: string; + correlationId: string; + content: string; + context: RuntimeProviderRequestContext; +} diff --git a/apps/gateway/src/agent/tess-durable-session.repository.test.ts b/apps/gateway/src/agent/tess-durable-session.repository.test.ts new file mode 100644 index 0000000..c9c4336 --- /dev/null +++ b/apps/gateway/src/agent/tess-durable-session.repository.test.ts @@ -0,0 +1,336 @@ +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { eq, sql, tessInbox } from '@mosaicstack/db'; +import { DurableSessionCoordinator, type DurableSessionIdentity } from '@mosaicstack/agent'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { createPgliteDb, runPgliteMigrations, type DbHandle } from '@mosaicstack/db'; +import { TessDurableSessionRepository } from './tess-durable-session.repository.js'; +import { TessDurableSessionService } from './tess-durable-session.service.js'; + +const IDENTITY: DurableSessionIdentity = { + sessionId: 'tess-pglite-session', + tenantId: 'tenant-pglite', + ownerId: 'tess-owner', + providerId: 'fleet', + runtimeSessionId: 'nova', +}; + +describe('TessDurableSessionRepository', () => { + let dataDir: string | undefined; + let handle: DbHandle | undefined; + let previousAuthSecret: string | undefined; + + beforeEach((): void => { + previousAuthSecret = process.env['BETTER_AUTH_SECRET']; + process.env['BETTER_AUTH_SECRET'] = 'tess-durable-state-test-sealing-key'; + }); + + afterEach(async (): Promise => { + await handle?.close(); + if (dataDir) rmSync(dataDir, { recursive: true, force: true }); + if (previousAuthSecret === undefined) delete process.env['BETTER_AUTH_SECRET']; + else process.env['BETTER_AUTH_SECRET'] = previousAuthSecret; + }); + + it('survives a full PGlite close/reopen mid-session without duplicate inbox or outbox side effects', async () => { + dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-recovery-')); + handle = createPgliteDb(dataDir); + await runPgliteMigrations(handle); + await seedOwner(handle); + + const beforeRestart = new DurableSessionCoordinator( + new TessDurableSessionRepository(handle.db), + ); + await beforeRestart.create(IDENTITY); + await beforeRestart.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'inbox-before-kill', + correlationId: 'correlation-before-kill', + content: 'resume after a kill', + }); + await beforeRestart.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'outbox-before-kill', + correlationId: 'correlation-before-kill', + channelId: 'cli', + kind: 'provider.send', + content: 'one response only', + }); + await beforeRestart.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-before-kill', + cursor: 'cursor-before-kill', + summary: 'restart-safe state', + compactionEpoch: 1, + }); + await beforeRestart.handoff({ + sessionId: IDENTITY.sessionId, + handoffId: 'handoff-before-kill', + destination: 'mos', + correlationId: 'correlation-before-kill', + checkpointId: 'checkpoint-before-kill', + status: 'pending', + }); + await beforeRestart.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-after-handoff', + cursor: 'cursor-after-handoff', + summary: 'newer state cannot strand the portable handoff', + compactionEpoch: 2, + }); + + await handle.close(); + handle = createPgliteDb(dataDir); + + const afterRestart = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); + const recovered = await afterRestart.recover(IDENTITY.sessionId); + const resumedHandoff = await afterRestart.resumeHandoff('handoff-before-kill'); + const handled: string[] = []; + const effects: string[] = []; + + await afterRestart.drainInbox(IDENTITY.sessionId, async (entry): Promise => { + handled.push(entry.idempotencyKey); + }); + await afterRestart.dispatchOutbox(IDENTITY.sessionId, async (entry): Promise => { + effects.push(entry.idempotencyKey); + }); + await afterRestart.drainInbox(IDENTITY.sessionId, async (entry): Promise => { + handled.push(entry.idempotencyKey); + }); + await afterRestart.dispatchOutbox(IDENTITY.sessionId, async (entry): Promise => { + effects.push(entry.idempotencyKey); + }); + + expect(recovered.identity).toEqual(IDENTITY); + expect(recovered.checkpoint).toMatchObject({ checkpointId: 'checkpoint-after-handoff' }); + expect(recovered.handoffs).toMatchObject([{ handoffId: 'handoff-before-kill' }]); + expect(resumedHandoff.checkpoint).toMatchObject({ checkpointId: 'checkpoint-before-kill' }); + expect(handled).toEqual(['inbox-before-kill']); + expect(effects).toEqual(['outbox-before-kill']); + }, 30_000); + + it('redacts sensitive durable payloads before persistence', async () => { + dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-redaction-')); + handle = createPgliteDb(dataDir); + await runPgliteMigrations(handle); + await seedOwner(handle); + + const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); + await coordinator.create(IDENTITY); + await coordinator.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'redacted-inbox', + correlationId: 'correlation-redaction', + content: 'api_key=super-secret-canary', + }); + await coordinator.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'redacted-outbox', + correlationId: 'correlation-redaction', + channelId: 'cli', + kind: 'provider.send', + content: 'email operator@example.test api_key=super-secret-canary', + }); + await coordinator.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'redacted-checkpoint', + cursor: 'bearer super-secret-canary', + summary: 'email operator@example.test', + compactionEpoch: 0, + }); + + const snapshot = await coordinator.snapshot(IDENTITY.sessionId); + const [persisted] = await handle.db + .select({ content: tessInbox.content }) + .from(tessInbox) + .where(eq(tessInbox.idempotencyKey, 'redacted-inbox')); + + expect(JSON.stringify(snapshot)).not.toContain('super-secret-canary'); + expect(JSON.stringify(snapshot)).not.toContain('operator@example.test'); + expect(persisted?.content).not.toContain('super-secret-canary'); + expect(persisted?.content).not.toContain('[REDACTED]'); + }, 30_000); + + it('rejects database inbox and outbox idempotency-key conflicts', async () => { + dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-idempotency-conflict-')); + handle = createPgliteDb(dataDir); + await runPgliteMigrations(handle); + await seedOwner(handle); + + const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); + await coordinator.create(IDENTITY); + await coordinator.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'inbox-conflict', + correlationId: 'correlation-inbox', + content: 'original inbox', + }); + await coordinator.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'outbox-conflict', + correlationId: 'correlation-outbox', + channelId: 'cli', + kind: 'provider.send', + content: 'original outbox', + }); + + await expect( + coordinator.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'inbox-conflict', + correlationId: 'forged-correlation', + content: 'original inbox', + }), + ).rejects.toThrow(/idempotency conflict/); + await expect( + coordinator.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'outbox-conflict', + correlationId: 'correlation-outbox', + channelId: 'forged-channel', + kind: 'provider.send', + content: 'original outbox', + }), + ).rejects.toThrow(/idempotency conflict/); + }, 30_000); + + it('does not requeue a live outbox claim during a normal scoped dispatch', async () => { + dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-live-claim-')); + handle = createPgliteDb(dataDir); + await runPgliteMigrations(handle); + await seedOwner(handle); + + const repository = new TessDurableSessionRepository(handle.db); + const coordinator = new DurableSessionCoordinator(repository); + const runtimeProviders = { sendMessage: vi.fn().mockResolvedValue(undefined) }; + const service = new TessDurableSessionService(repository, runtimeProviders as never); + const input = { + sessionId: IDENTITY.sessionId, + idempotencyKey: 'live-effect', + correlationId: 'correlation-live', + content: 'must not duplicate', + context: { + actorScope: { userId: IDENTITY.ownerId, tenantId: IDENTITY.tenantId }, + channelId: 'cli', + correlationId: 'correlation-live', + }, + }; + + await coordinator.create(IDENTITY); + await service.queueProviderSend(input); + expect(await repository.claimOutbox(IDENTITY.sessionId)).toMatchObject({ + status: 'processing', + }); + + await service.dispatchProviderOutbox(IDENTITY.sessionId, input); + await expect( + service.recoverProviderSession(IDENTITY.sessionId, { + ...input, + context: { + ...input.context, + actorScope: { userId: 'intruder', tenantId: 'tenant-pglite' }, + }, + }), + ).rejects.toThrow(/scope or correlation mismatch/); + + expect(runtimeProviders.sendMessage).not.toHaveBeenCalled(); + expect(await coordinator.snapshot(IDENTITY.sessionId)).toMatchObject({ + outbox: [{ idempotencyKey: 'live-effect', status: 'processing' }], + }); + }, 30_000); + + it('rejects an outbox correlation mismatch before claiming the pending effect', async () => { + dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-outbox-mismatch-')); + handle = createPgliteDb(dataDir); + await runPgliteMigrations(handle); + await seedOwner(handle); + + const repository = new TessDurableSessionRepository(handle.db); + const coordinator = new DurableSessionCoordinator(repository); + const runtimeProviders = { sendMessage: vi.fn().mockResolvedValue(undefined) }; + const service = new TessDurableSessionService(repository, runtimeProviders as never); + const input = { + sessionId: IDENTITY.sessionId, + idempotencyKey: 'mismatch-effect', + correlationId: 'correlation-expected', + content: 'must remain pending', + context: { + actorScope: { userId: IDENTITY.ownerId, tenantId: IDENTITY.tenantId }, + channelId: 'cli', + correlationId: 'correlation-expected', + }, + }; + + await coordinator.create(IDENTITY); + await service.queueProviderSend(input); + await expect( + service.dispatchProviderOutbox(IDENTITY.sessionId, { + ...input, + correlationId: 'correlation-forged', + context: { ...input.context, correlationId: 'correlation-forged' }, + }), + ).rejects.toThrow(/scope or correlation mismatch/); + + expect(runtimeProviders.sendMessage).not.toHaveBeenCalled(); + expect(await coordinator.snapshot(IDENTITY.sessionId)).toMatchObject({ + outbox: [{ idempotencyKey: 'mismatch-effect', status: 'pending' }], + }); + }, 30_000); + + it('dispatches only the outbox record bound to the supplied correlation and channel', async () => { + dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-outbox-scope-')); + handle = createPgliteDb(dataDir); + await runPgliteMigrations(handle); + await seedOwner(handle); + + const repository = new TessDurableSessionRepository(handle.db); + const coordinator = new DurableSessionCoordinator(repository); + const runtimeProviders = { sendMessage: vi.fn().mockResolvedValue(undefined) }; + const service = new TessDurableSessionService(repository, runtimeProviders as never); + const first = { + sessionId: IDENTITY.sessionId, + idempotencyKey: 'scoped-effect-one', + correlationId: 'correlation-one', + content: 'first result', + context: { + actorScope: { userId: IDENTITY.ownerId, tenantId: IDENTITY.tenantId }, + channelId: 'cli', + correlationId: 'correlation-one', + }, + }; + const second = { + ...first, + idempotencyKey: 'scoped-effect-two', + correlationId: 'correlation-two', + content: 'second result', + context: { ...first.context, correlationId: 'correlation-two' }, + }; + + await coordinator.create(IDENTITY); + await service.queueProviderSend(first); + await service.queueProviderSend(second); + await service.dispatchProviderOutbox(IDENTITY.sessionId, first); + + expect(runtimeProviders.sendMessage).toHaveBeenCalledTimes(1); + expect(runtimeProviders.sendMessage).toHaveBeenCalledWith( + IDENTITY.providerId, + IDENTITY.runtimeSessionId, + { content: 'first result', idempotencyKey: 'scoped-effect-one' }, + first.context, + ); + expect(await coordinator.snapshot(IDENTITY.sessionId)).toMatchObject({ + outbox: [ + { idempotencyKey: 'scoped-effect-one', status: 'delivered' }, + { idempotencyKey: 'scoped-effect-two', status: 'pending' }, + ], + }); + }, 30_000); +}); + +async function seedOwner(handle: DbHandle): Promise { + await handle.db.execute(sql` + INSERT INTO users (id, name, email, email_verified, created_at, updated_at) + VALUES ('tess-owner', 'Tess Owner', 'tess-owner@example.test', false, now(), now()) + `); +} diff --git a/apps/gateway/src/agent/tess-durable-session.repository.ts b/apps/gateway/src/agent/tess-durable-session.repository.ts new file mode 100644 index 0000000..76f07e1 --- /dev/null +++ b/apps/gateway/src/agent/tess-durable-session.repository.ts @@ -0,0 +1,454 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + and, + asc, + desc, + eq, + tessCheckpoints, + tessHandoffs, + tessInbox, + tessOutbox, + tessSessions, + type Db, +} from '@mosaicstack/db'; +import { seal, unseal } from '@mosaicstack/auth'; +import { redactSensitiveContent } from '@mosaicstack/log'; +import type { + DurableCheckpoint, + DurableCheckpointInput, + DurableEnqueueResult, + DurableHandoff, + DurableHandoffInput, + DurableInboxEntry, + DurableInboxInput, + DurableInboxStatus, + DurableOutboxEntry, + DurableOutboxInput, + DurableOutboxStatus, + DurableSessionIdentity, + DurableSessionSnapshot, + DurableSessionStore, +} from '@mosaicstack/agent'; +import { DB } from '../database/database.module.js'; + +@Injectable() +export class TessDurableSessionRepository implements DurableSessionStore { + constructor(@Inject(DB) private readonly db: Db) {} + + async create(identity: DurableSessionIdentity): Promise { + await this.db + .insert(tessSessions) + .values({ + id: identity.sessionId, + tenantId: identity.tenantId, + ownerId: identity.ownerId, + providerId: identity.providerId, + runtimeSessionId: identity.runtimeSessionId, + }) + .onConflictDoNothing(); + + const existing = await this.session(identity.sessionId); + if (!existing || !sameIdentity(existing, identity)) { + throw new Error(`Durable Tess session identity conflict: ${identity.sessionId}`); + } + } + + async snapshot(sessionId: string): Promise { + const identity = await this.session(sessionId); + if (!identity) return null; + + const [inbox, outbox, checkpoints, handoffs] = await Promise.all([ + this.db + .select() + .from(tessInbox) + .where(eq(tessInbox.sessionId, sessionId)) + .orderBy(asc(tessInbox.createdAt)), + this.db + .select() + .from(tessOutbox) + .where(eq(tessOutbox.sessionId, sessionId)) + .orderBy(asc(tessOutbox.createdAt)), + this.db + .select() + .from(tessCheckpoints) + .where(eq(tessCheckpoints.sessionId, sessionId)) + .orderBy(desc(tessCheckpoints.compactionEpoch), desc(tessCheckpoints.createdAt)) + .limit(1), + this.db + .select() + .from(tessHandoffs) + .where(eq(tessHandoffs.sessionId, sessionId)) + .orderBy(asc(tessHandoffs.createdAt)), + ]); + + const checkpoint = checkpoints[0]; + return { + identity, + inbox: inbox.map(toInbox), + outbox: outbox.map(toOutbox), + ...(checkpoint ? { checkpoint: toCheckpoint(checkpoint) } : {}), + handoffs: handoffs.map(toHandoff), + }; + } + + async enqueueInbox(input: DurableInboxInput): Promise> { + const record: DurableInboxInput = { + ...input, + content: redactSensitiveContent(input.content).content, + }; + const inserted = await this.db + .insert(tessInbox) + .values({ ...record, content: seal(record.content), status: 'pending' }) + .onConflictDoNothing() + .returning({ status: tessInbox.status }); + if (inserted[0]) return { accepted: true, status: inserted[0].status }; + + const existing = await this.db + .select() + .from(tessInbox) + .where( + and( + eq(tessInbox.sessionId, input.sessionId), + eq(tessInbox.idempotencyKey, input.idempotencyKey), + ), + ) + .limit(1); + if (!existing[0]) throw new Error(`Durable Tess inbox enqueue failed: ${input.idempotencyKey}`); + const entry = toInbox(existing[0]); + if (!sameInbox(entry, record)) { + throw new Error(`Durable Tess inbox idempotency conflict: ${input.idempotencyKey}`); + } + return { accepted: false, status: entry.status }; + } + + async claimInbox(sessionId: string): Promise { + for (let attempt = 0; attempt < 3; attempt += 1) { + const candidate = await this.db + .select() + .from(tessInbox) + .where(and(eq(tessInbox.sessionId, sessionId), eq(tessInbox.status, 'pending'))) + .orderBy(asc(tessInbox.createdAt)) + .limit(1); + const entry = candidate[0]; + if (!entry) return null; + const claimed = await this.db + .update(tessInbox) + .set({ status: 'processing', updatedAt: new Date() }) + .where(and(eq(tessInbox.id, entry.id), eq(tessInbox.status, 'pending'))) + .returning(); + if (claimed[0]) return toInbox(claimed[0]); + } + return null; + } + + async completeInbox(sessionId: string, idempotencyKey: string): Promise { + await this.db + .update(tessInbox) + .set({ status: 'processed', updatedAt: new Date() }) + .where( + and( + eq(tessInbox.sessionId, sessionId), + eq(tessInbox.idempotencyKey, idempotencyKey), + eq(tessInbox.status, 'processing'), + ), + ); + } + + async releaseInbox(sessionId: string, idempotencyKey: string): Promise { + await this.db + .update(tessInbox) + .set({ status: 'pending', updatedAt: new Date() }) + .where( + and( + eq(tessInbox.sessionId, sessionId), + eq(tessInbox.idempotencyKey, idempotencyKey), + eq(tessInbox.status, 'processing'), + ), + ); + } + + async enqueueOutbox( + input: DurableOutboxInput, + ): Promise> { + const record: DurableOutboxInput = { + ...input, + content: redactSensitiveContent(input.content).content, + }; + const inserted = await this.db + .insert(tessOutbox) + .values({ ...record, content: seal(record.content), status: 'pending' }) + .onConflictDoNothing() + .returning({ status: tessOutbox.status }); + if (inserted[0]) return { accepted: true, status: inserted[0].status }; + + const existing = await this.db + .select() + .from(tessOutbox) + .where( + and( + eq(tessOutbox.sessionId, input.sessionId), + eq(tessOutbox.idempotencyKey, input.idempotencyKey), + ), + ) + .limit(1); + if (!existing[0]) + throw new Error(`Durable Tess outbox enqueue failed: ${input.idempotencyKey}`); + const entry = toOutbox(existing[0]); + if (!sameOutbox(entry, record)) { + throw new Error(`Durable Tess outbox idempotency conflict: ${input.idempotencyKey}`); + } + return { accepted: false, status: entry.status }; + } + + async claimOutbox(sessionId: string): Promise { + for (let attempt = 0; attempt < 3; attempt += 1) { + const candidate = await this.db + .select() + .from(tessOutbox) + .where(and(eq(tessOutbox.sessionId, sessionId), eq(tessOutbox.status, 'pending'))) + .orderBy(asc(tessOutbox.createdAt)) + .limit(1); + const entry = candidate[0]; + if (!entry) return null; + const claimed = await this.db + .update(tessOutbox) + .set({ status: 'processing', updatedAt: new Date() }) + .where(and(eq(tessOutbox.id, entry.id), eq(tessOutbox.status, 'pending'))) + .returning(); + if (claimed[0]) return toOutbox(claimed[0]); + } + return null; + } + + async claimOutboxByKey( + sessionId: string, + idempotencyKey: string, + ): Promise { + const claimed = await this.db + .update(tessOutbox) + .set({ status: 'processing', updatedAt: new Date() }) + .where( + and( + eq(tessOutbox.sessionId, sessionId), + eq(tessOutbox.idempotencyKey, idempotencyKey), + eq(tessOutbox.status, 'pending'), + ), + ) + .returning(); + return claimed[0] ? toOutbox(claimed[0]) : null; + } + + async completeOutbox(sessionId: string, idempotencyKey: string): Promise { + await this.db + .update(tessOutbox) + .set({ status: 'delivered', updatedAt: new Date() }) + .where( + and( + eq(tessOutbox.sessionId, sessionId), + eq(tessOutbox.idempotencyKey, idempotencyKey), + eq(tessOutbox.status, 'processing'), + ), + ); + } + + async releaseOutbox(sessionId: string, idempotencyKey: string): Promise { + await this.db + .update(tessOutbox) + .set({ status: 'pending', updatedAt: new Date() }) + .where( + and( + eq(tessOutbox.sessionId, sessionId), + eq(tessOutbox.idempotencyKey, idempotencyKey), + eq(tessOutbox.status, 'processing'), + ), + ); + } + + async checkpoint(input: DurableCheckpointInput): Promise { + const checkpoint: DurableCheckpointInput = { + ...input, + cursor: redactSensitiveContent(input.cursor).content, + summary: redactSensitiveContent(input.summary).content, + }; + const inserted = await this.db + .insert(tessCheckpoints) + .values({ + ...checkpoint, + cursor: seal(checkpoint.cursor), + summary: seal(checkpoint.summary), + }) + .onConflictDoNothing() + .returning({ checkpointId: tessCheckpoints.checkpointId }); + if (inserted[0]) return; + + const existing = await this.findCheckpoint(input.sessionId, input.checkpointId); + if (!existing || !sameCheckpoint(existing, checkpoint)) { + throw new Error(`Durable Tess checkpoint identity conflict: ${input.checkpointId}`); + } + } + + async findCheckpoint(sessionId: string, checkpointId: string): Promise { + const checkpoints = await this.db + .select() + .from(tessCheckpoints) + .where( + and( + eq(tessCheckpoints.sessionId, sessionId), + eq(tessCheckpoints.checkpointId, checkpointId), + ), + ) + .limit(1); + const checkpoint = checkpoints[0]; + return checkpoint ? toCheckpoint(checkpoint) : null; + } + + async handoff(input: DurableHandoffInput): Promise { + const checkpoint = await this.findCheckpoint(input.sessionId, input.checkpointId); + if (!checkpoint) { + throw new Error(`Durable Tess handoff checkpoint is unavailable: ${input.checkpointId}`); + } + const inserted = await this.db + .insert(tessHandoffs) + .values({ ...input }) + .onConflictDoNothing() + .returning({ handoffId: tessHandoffs.handoffId }); + if (inserted[0]) return; + + const existing = await this.findHandoff(input.handoffId); + if (!existing || !sameHandoff(existing, input)) { + throw new Error(`Durable Tess handoff identity conflict: ${input.handoffId}`); + } + } + + async findHandoff(handoffId: string): Promise { + const handoffs = await this.db + .select() + .from(tessHandoffs) + .where(eq(tessHandoffs.handoffId, handoffId)) + .limit(1); + const handoff = handoffs[0]; + return handoff ? toHandoff(handoff) : null; + } + + async requeueInFlight(sessionId: string): Promise { + // Inbox handlers are process-local work. A provider outbox claim may have + // reached an external target before a crash, so it is deliberately not + // replayed by generic recovery. + await this.db + .update(tessInbox) + .set({ status: 'pending', updatedAt: new Date() }) + .where(and(eq(tessInbox.sessionId, sessionId), eq(tessInbox.status, 'processing'))); + } + + private async session(sessionId: string): Promise { + const sessions = await this.db + .select() + .from(tessSessions) + .where(eq(tessSessions.id, sessionId)) + .limit(1); + const session = sessions[0]; + return session + ? { + sessionId: session.id, + tenantId: session.tenantId, + ownerId: session.ownerId, + providerId: session.providerId, + runtimeSessionId: session.runtimeSessionId, + } + : null; + } +} + +function sameIdentity(left: DurableSessionIdentity, right: DurableSessionIdentity): boolean { + return ( + left.sessionId === right.sessionId && + left.tenantId === right.tenantId && + left.ownerId === right.ownerId && + left.providerId === right.providerId && + left.runtimeSessionId === right.runtimeSessionId + ); +} + +function sameInbox(left: DurableInboxEntry, right: DurableInboxInput): boolean { + return ( + left.sessionId === right.sessionId && + left.idempotencyKey === right.idempotencyKey && + left.correlationId === right.correlationId && + left.content === right.content + ); +} + +function sameOutbox(left: DurableOutboxEntry, right: DurableOutboxInput): boolean { + return ( + left.sessionId === right.sessionId && + left.idempotencyKey === right.idempotencyKey && + left.correlationId === right.correlationId && + left.channelId === right.channelId && + left.kind === right.kind && + left.content === right.content + ); +} + +function sameCheckpoint(left: DurableCheckpoint, right: DurableCheckpointInput): boolean { + return ( + left.sessionId === right.sessionId && + left.checkpointId === right.checkpointId && + left.cursor === right.cursor && + left.summary === right.summary && + left.compactionEpoch === right.compactionEpoch + ); +} + +function sameHandoff(left: DurableHandoff, right: DurableHandoffInput): boolean { + return ( + left.sessionId === right.sessionId && + left.handoffId === right.handoffId && + left.destination === right.destination && + left.correlationId === right.correlationId && + left.checkpointId === right.checkpointId && + left.status === right.status + ); +} + +function toInbox(row: typeof tessInbox.$inferSelect): DurableInboxEntry { + return { + sessionId: row.sessionId, + idempotencyKey: row.idempotencyKey, + correlationId: row.correlationId, + content: unseal(row.content), + status: row.status, + }; +} + +function toOutbox(row: typeof tessOutbox.$inferSelect): DurableOutboxEntry { + return { + sessionId: row.sessionId, + idempotencyKey: row.idempotencyKey, + correlationId: row.correlationId, + channelId: row.channelId, + kind: row.kind, + content: unseal(row.content), + status: row.status, + }; +} + +function toCheckpoint(row: typeof tessCheckpoints.$inferSelect): DurableCheckpoint { + return { + sessionId: row.sessionId, + checkpointId: row.checkpointId, + cursor: unseal(row.cursor), + summary: unseal(row.summary), + compactionEpoch: row.compactionEpoch, + }; +} + +function toHandoff(row: typeof tessHandoffs.$inferSelect): DurableHandoff { + return { + sessionId: row.sessionId, + handoffId: row.handoffId, + destination: row.destination, + correlationId: row.correlationId, + checkpointId: row.checkpointId, + status: row.status, + }; +} diff --git a/apps/gateway/src/agent/tess-durable-session.service.ts b/apps/gateway/src/agent/tess-durable-session.service.ts new file mode 100644 index 0000000..095448b --- /dev/null +++ b/apps/gateway/src/agent/tess-durable-session.service.ts @@ -0,0 +1,93 @@ +import { ForbiddenException, Inject, Injectable } from '@nestjs/common'; +import { DurableSessionCoordinator } from '@mosaicstack/agent'; +import type { TessProviderOutboxDto } from './tess-durable-session.dto.js'; +import { TessDurableSessionRepository } from './tess-durable-session.repository.js'; +import { RuntimeProviderService } from './runtime-provider-registry.service.js'; + +/** + * Scoped gateway boundary for the canonical Tess state machine. It deliberately + * uses composition: raw state methods cannot be injected into channel, CLI, or + * MCP adapters without a server-derived actor/tenant/correlation context. + */ +@Injectable() +export class TessDurableSessionService { + private readonly coordinator: DurableSessionCoordinator; + + constructor( + @Inject(TessDurableSessionRepository) repository: TessDurableSessionRepository, + @Inject(RuntimeProviderService) private readonly runtimeProviders: RuntimeProviderService, + ) { + this.coordinator = new DurableSessionCoordinator(repository); + } + + async queueProviderSend(input: TessProviderOutboxDto): Promise { + const snapshot = await this.coordinator.snapshot(input.sessionId); + this.assertScope(snapshot.identity.ownerId, snapshot.identity.tenantId, input); + await this.coordinator.enqueueOutbox({ + sessionId: input.sessionId, + idempotencyKey: input.idempotencyKey, + correlationId: input.correlationId, + channelId: input.context.channelId, + kind: 'provider.send', + content: input.content, + }); + } + + async dispatchProviderOutbox(sessionId: string, input: TessProviderOutboxDto): Promise { + if (sessionId !== input.sessionId) { + throw new ForbiddenException('Durable Tess outbox session mismatch'); + } + const snapshot = await this.coordinator.snapshot(sessionId); + this.assertScope(snapshot.identity.ownerId, snapshot.identity.tenantId, input); + const pendingEntry = snapshot.outbox.find( + (entry): boolean => entry.idempotencyKey === input.idempotencyKey, + ); + if (!pendingEntry) return; + // Validate immutable routing before claiming. A caller with a mismatched + // correlation/channel must not strand a pending external side effect. + this.assertOutboxScope(pendingEntry, input); + await this.coordinator.dispatchOutboxEntry( + sessionId, + input.idempotencyKey, + async (entry): Promise => { + this.assertOutboxScope(entry, input); + await this.runtimeProviders.sendMessage( + snapshot.identity.providerId, + snapshot.identity.runtimeSessionId, + { content: entry.content, idempotencyKey: entry.idempotencyKey }, + input.context, + ); + }, + ); + } + + /** Startup/recovery-only path; normal queue/dispatch methods never requeue live work. */ + async recoverProviderSession(sessionId: string, input: TessProviderOutboxDto): Promise { + const snapshot = await this.coordinator.snapshot(sessionId); + this.assertScope(snapshot.identity.ownerId, snapshot.identity.tenantId, input); + await this.coordinator.recover(sessionId); + } + + private assertOutboxScope( + entry: { kind: string; correlationId: string; channelId: string }, + input: TessProviderOutboxDto, + ): void { + if ( + entry.kind !== 'provider.send' || + entry.correlationId !== input.correlationId || + entry.channelId !== input.context.channelId + ) { + throw new ForbiddenException('Durable Tess outbox scope or correlation mismatch'); + } + } + + private assertScope(ownerId: string, tenantId: string, input: TessProviderOutboxDto): void { + if ( + input.context.actorScope.userId !== ownerId || + input.context.actorScope.tenantId !== tenantId || + input.context.correlationId !== input.correlationId + ) { + throw new ForbiddenException('Durable Tess session scope or correlation mismatch'); + } + } +} diff --git a/apps/gateway/src/commands/command-authorization.service.spec.ts b/apps/gateway/src/commands/command-authorization.service.spec.ts index 7eb7034..831ffbb 100644 --- a/apps/gateway/src/commands/command-authorization.service.spec.ts +++ b/apps/gateway/src/commands/command-authorization.service.spec.ts @@ -12,8 +12,10 @@ const adminCommand: CommandDef = { }; const payload: SlashCommandPayload = { command: 'gc', conversationId: 'conversation-1' }; -function createService(role: string): CommandAuthorizationService { - const entries = new Map(); +function createService( + role: string, + entries: Map = new Map(), +): CommandAuthorizationService { const db = { select: () => ({ from: () => ({ where: () => ({ limit: async () => [{ role }] }) }) }), }; @@ -58,4 +60,55 @@ describe('CommandAuthorizationService', () => { (await service.authorize(adminCommand, payload, 'member-1', 'forged-approval-id')).allowed, ).toBe(false); }); + + it('denies a malformed durable approval expiry instead of treating it as unexpired', async (): Promise => { + const entries = new Map(); + const action = { + providerId: 'fleet', + sessionId: 'nova', + actorId: 'admin-1', + tenantId: 'tenant-1', + channelId: 'discord:operator', + correlationId: 'correlation-malformed-expiry', + }; + const service = createService('admin', entries); + const approval = await service.createRuntimeTerminationApproval(action); + expect(approval).not.toBeNull(); + const key = `tess:command-approval:${approval!.approvalId}`; + const stored = entries.get(key); + expect(stored).toBeDefined(); + entries.set(key, JSON.stringify({ ...JSON.parse(stored!), expiresAt: 'not-a-date' })); + + expect(await service.consumeRuntimeTerminationApproval(approval!.approvalId, action)).toBe( + false, + ); + }); + + it('persists and consumes one exact runtime termination approval across a service restart', async (): Promise => { + const entries = new Map(); + const action = { + providerId: 'fleet', + sessionId: 'nova', + actorId: 'admin-1', + tenantId: 'tenant-1', + channelId: 'discord:operator', + correlationId: 'correlation-1', + }; + const beforeRestart = createService('admin', entries); + const approval = await beforeRestart.createRuntimeTerminationApproval(action); + + const afterRestart = createService('admin', entries); + expect( + await afterRestart.consumeRuntimeTerminationApproval(approval!.approvalId, { + ...action, + sessionId: 'forged-session', + }), + ).toBe(false); + expect(await afterRestart.consumeRuntimeTerminationApproval(approval!.approvalId, action)).toBe( + true, + ); + expect(await afterRestart.consumeRuntimeTerminationApproval(approval!.approvalId, action)).toBe( + false, + ); + }); }); diff --git a/apps/gateway/src/commands/command-authorization.service.ts b/apps/gateway/src/commands/command-authorization.service.ts index ce7f499..4228a18 100644 --- a/apps/gateway/src/commands/command-authorization.service.ts +++ b/apps/gateway/src/commands/command-authorization.service.ts @@ -15,6 +15,22 @@ export interface CommandApproval { expiresAt: string; } +/** Exact immutable binding for a privileged runtime termination. */ +export interface RuntimeTerminationApprovalAction { + providerId: string; + sessionId: string; + actorId: string; + tenantId: string; + channelId: string; + correlationId: string; +} + +export interface RuntimeTerminationApproval extends RuntimeTerminationApprovalAction { + approvalId: string; + actionDigest: string; + expiresAt: string; +} + export interface CommandAuthorizationResult { allowed: boolean; reason?: string; @@ -75,6 +91,52 @@ export class CommandAuthorizationService { return approval; } + /** + * Uses the same `tess:command-approval:*` store and one-time deletion rule as + * command approvals. This deliberately avoids a parallel approval database. + */ + async createRuntimeTerminationApproval( + action: RuntimeTerminationApprovalAction, + ): Promise { + if (!this.hasRuntimeTerminationAction(action)) return null; + const role = await this.resolveRole(action.actorId); + if (role !== 'admin') return null; + + const approval: RuntimeTerminationApproval = { + approvalId: randomUUID(), + actionDigest: this.runtimeActionDigest(action), + ...action, + expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(), + }; + await this.redis.set(this.key(approval.approvalId), JSON.stringify(approval), 'EX', '300'); + return approval; + } + + async consumeRuntimeTerminationApproval( + approvalId: string, + action: RuntimeTerminationApprovalAction, + ): Promise { + const encoded = await this.redis.get(this.key(approvalId)); + if (!encoded) return false; + let approval: unknown; + try { + approval = JSON.parse(encoded); + } catch { + return false; + } + if ( + !this.isRuntimeTerminationApproval(approval) || + approval.actionDigest !== this.runtimeActionDigest(action) || + approval.actorId !== action.actorId || + approval.tenantId !== action.tenantId || + !this.isUnexpired(approval.expiresAt) + ) { + return false; + } + if ((await this.resolveRole(approval.actorId)) !== 'admin') return false; + return (await this.redis.del(this.key(approvalId))) === 1; + } + private async resolveRole(actorId: string): Promise { const [user] = await this.db .select({ role: usersTable.role }) @@ -98,12 +160,17 @@ export class CommandAuthorizationService { const key = this.key(approvalId); const encoded = await this.redis.get(key); if (!encoded) return false; - const parsed: unknown = JSON.parse(encoded); + let parsed: unknown; + try { + parsed = JSON.parse(encoded); + } catch { + return false; + } if ( - !this.isApproval(parsed) || + !this.isCommandApproval(parsed) || parsed.actorId !== actorId || parsed.actionDigest !== actionDigest || - Date.parse(parsed.expiresAt) <= Date.now() + !this.isUnexpired(parsed.expiresAt) ) return false; return (await this.redis.del(key)) === 1; @@ -121,13 +188,62 @@ export class CommandAuthorizationService { .digest('hex'); } - private isApproval(value: unknown): value is CommandApproval { + private hasRuntimeTerminationAction(action: RuntimeTerminationApprovalAction): boolean { + return [ + action.providerId, + action.sessionId, + action.actorId, + action.tenantId, + action.channelId, + action.correlationId, + ].every((value: string): boolean => value.trim().length > 0); + } + + private runtimeActionDigest(action: RuntimeTerminationApprovalAction): string { + return createHash('sha256') + .update( + JSON.stringify({ + providerId: action.providerId, + sessionId: action.sessionId, + actorId: action.actorId, + tenantId: action.tenantId, + channelId: action.channelId, + correlationId: action.correlationId, + }), + ) + .digest('hex'); + } + + private isUnexpired(expiresAt: unknown): expiresAt is string { + if (typeof expiresAt !== 'string') return false; + const expiresAtMs = Date.parse(expiresAt); + return Number.isFinite(expiresAtMs) && expiresAtMs > Date.now(); + } + + private isCommandApproval(value: unknown): value is CommandApproval { return ( typeof value === 'object' && value !== null && 'approvalId' in value && 'actionDigest' in value && 'actorId' in value && + 'expiresAt' in value && + 'command' in value + ); + } + + private isRuntimeTerminationApproval(value: unknown): value is RuntimeTerminationApproval { + return ( + typeof value === 'object' && + value !== null && + 'approvalId' in value && + 'actionDigest' in value && + 'actorId' in value && + 'tenantId' in value && + 'providerId' in value && + 'sessionId' in value && + 'channelId' in value && + 'correlationId' in value && 'expiresAt' in value ); } diff --git a/apps/gateway/src/commands/commands.module.ts b/apps/gateway/src/commands/commands.module.ts index 0d6c30d..09fb440 100644 --- a/apps/gateway/src/commands/commands.module.ts +++ b/apps/gateway/src/commands/commands.module.ts @@ -6,6 +6,7 @@ import { ReloadModule } from '../reload/reload.module.js'; import { CommandAuthorizationService } from './command-authorization.service.js'; import { CommandExecutorService } from './command-executor.service.js'; import { CommandRegistryService } from './command-registry.service.js'; +import { CommandRuntimeApprovalVerifier } from './runtime-approval-verifier.js'; import { COMMANDS_REDIS } from './commands.tokens.js'; const COMMANDS_QUEUE_HANDLE = 'COMMANDS_QUEUE_HANDLE'; @@ -26,9 +27,15 @@ const COMMANDS_QUEUE_HANDLE = 'COMMANDS_QUEUE_HANDLE'; }, CommandRegistryService, CommandAuthorizationService, + CommandRuntimeApprovalVerifier, + CommandExecutorService, + ], + exports: [ + CommandRegistryService, + CommandAuthorizationService, + CommandRuntimeApprovalVerifier, CommandExecutorService, ], - exports: [CommandRegistryService, CommandExecutorService], }) export class CommandsModule implements OnApplicationShutdown { constructor(@Inject(COMMANDS_QUEUE_HANDLE) private readonly handle: QueueHandle) {} diff --git a/apps/gateway/src/commands/runtime-approval-verifier.ts b/apps/gateway/src/commands/runtime-approval-verifier.ts new file mode 100644 index 0000000..b16294c --- /dev/null +++ b/apps/gateway/src/commands/runtime-approval-verifier.ts @@ -0,0 +1,23 @@ +import { Inject, Injectable } from '@nestjs/common'; +import type { + RuntimeApprovalVerifier, + RuntimeTerminationAction, +} from '../agent/runtime-provider-registry.service.js'; +import { CommandAuthorizationService } from './command-authorization.service.js'; + +/** + * Adapter from the provider registry's exact termination action to the shared, + * Redis-backed `tess:command-approval:*` store. It has no separate approval + * persistence or replay semantics. + */ +@Injectable() +export class CommandRuntimeApprovalVerifier implements RuntimeApprovalVerifier { + constructor( + @Inject(CommandAuthorizationService) + private readonly authorization: CommandAuthorizationService, + ) {} + + async consume(approvalRef: string, action: RuntimeTerminationAction): Promise { + return this.authorization.consumeRuntimeTerminationApproval(approvalRef, action); + } +} diff --git a/docs/scratchpads/tess-m2-002-durable-state.md b/docs/scratchpads/tess-m2-002-durable-state.md new file mode 100644 index 0000000..c2e3b80 --- /dev/null +++ b/docs/scratchpads/tess-m2-002-durable-state.md @@ -0,0 +1,63 @@ +# TESS-M2-002 — Durable Tess State + +- **Issue:** #708 +- **Task:** `TESS-M2-002` / `TESS-STA-001`, `TESS-SEC-007..008` +- **Branch:** `feat/tess-durable-state` +- **Base:** fresh `origin/main` at `e3b5113be21e51d015fa1ae54572929b2a4acd9f` +- **Budget assumption:** 38K task estimate; no explicit cap. Use focused TDD plus workspace validation. + +## Objective + +Persist a Tess session's immutable identity, inbox/outbox idempotency state, checkpoints, +handoffs, and approval bindings so a new service instance can recover it after a process +restart or context compaction without replaying a completed message or applied side effect. + +## Plan + +1. Write recovery/idempotency tests first in `packages/agent/src/tess-durable-session.test.ts`. +2. Add transport-neutral durable-state contracts/state machine in `packages/agent`. +3. Add canonical PostgreSQL schema/migration and a gateway Drizzle repository adapter. +4. Wire gateway service/module and reuse `tess:command-approval:*` durable approval semantics + for exact, actor/tenant/action-bound approval consumption. +5. Test PGlite restart recovery with separate service instances sharing the same durable DB. +6. Document the recovery/compaction operation and update Tess architecture evidence. +7. Run focused, cold-cache, workspace, migration, review, commit, push, and open PR to `main`. + +## Required Evidence + +| Requirement | Primary evidence | +| --- | --- | +| Restart recovery | Test creates a second coordinator over unchanged durable store after simulated process death. | +| No duplicate side effects | Duplicate ingress and post-restart dispatch assert one handler/effect invocation. | +| Compaction survival | Checkpoint/handoff/reconstructed state preserve the same session identity and pending records. | +| Durable approvals | Existing `tess:command-approval` record is consumed only once and survives a new authorization service instance. | +| Handoff | Stored handoff is portable and reconstructed without live process state. | + +## Progress + +- Intake complete: PRD AC-TESS-06, threat TM-07/TM-08, and verification matrix reviewed. +- Affected surfaces: `packages/agent`, `apps/gateway`, `packages/db`; auth/authorization and DB migration tests required. +- TDD is required (security authorization and critical state mutation). + +## Risks + +- An external provider action cannot be atomically committed with the database. The outbox + gives the receiver a stable idempotency key; generic recovery never replays an ambiguous + `processing` effect, and completed effects are never redispatched. +- PostgreSQL is canonical; PGlite is the local/restart test implementation. + +## Verification + +- TDD red: `pnpm --filter @mosaicstack/agent test src/tess-durable-session.test.ts` + initially failed because the durable-state module did not exist. +- Focused green: 7 agent state-machine tests; 6 PGlite repository tests (including + close/reopen recovery and encrypted-at-rest redaction); 5 durable-approval tests; DB migration tests. +- Full cold-cache green: `pnpm turbo run typecheck lint test --force` completed + 88 tasks with zero cache hits; `pnpm format:check` and `git diff --check` passed. +- Fresh worktree dependency install passed with + `pnpm install --frozen-lockfile --store-dir /home/jarvis/.local/share/pnpm/store`. + The default pnpm store path was inaccessible to this harness, so the explicit + user-owned store path was required. +- Codex review identified plaintext durable payload risk; resolved by AES-256-GCM sealing + after redaction, with an at-rest ciphertext assertion in the PGlite suite. +- Pending final clean review, commit, and PR. diff --git a/docs/tess/ARCHITECTURE.md b/docs/tess/ARCHITECTURE.md index 4c03162..858945e 100644 --- a/docs/tess/ARCHITECTURE.md +++ b/docs/tess/ARCHITECTURE.md @@ -41,7 +41,7 @@ Every call receives an immutable, server-derived actor/tenant/channel scope and `@mosaicstack/agent` owns the explicit `AgentRuntimeProviderRegistry`; duplicate provider IDs are rejected rather than replaced. Gateway owns `RuntimeProviderService`, which creates a frozen `RuntimeScope` from authenticated `ActorTenantScope` and trusted ingress channel/correlation metadata before every provider call. The service checks the declared provider capability before invoking a side effect and records metadata-only audit events (`providerId`, operation, outcome, actor/tenant/channel, correlation, and resource ID). It never records message bodies, idempotency keys, or approval references. -Termination is fail-closed: a runtime approval verifier must consume a one-time, exact action binding for the provider, session, actor, tenant, channel, and correlation ID before `terminate` reaches a provider. Until the durable verifier is wired, the default verifier denies termination. This internal service introduces no HTTP endpoint; later Discord, CLI, MCP, and provider adapters consume the same gateway boundary. +Termination is fail-closed: a runtime approval verifier consumes a one-time, exact action binding for the provider, session, actor, tenant, channel, and correlation ID before `terminate` reaches a provider. The verifier reuses the Redis-backed `tess:command-approval:*` store and its expiry/delete-on-consume semantics; it has no parallel approval store. This internal service introduces no HTTP endpoint; later Discord, CLI, MCP, and provider adapters consume the same gateway boundary. ## Authority Model @@ -56,7 +56,30 @@ Termination is fail-closed: a runtime approval verifier must consume a one-time, A Tess session has stable `sessionId`, `tenantId`, `ownerId`, provider/runtime identity, ingress bindings, cursor, checkpoint, inbox/outbox, and idempotency records. Discord and CLI bind to the same authorized session. Ownership is verified server-side on every list/read/attach/send/terminate operation. -Valkey may hold ephemeral coordination state; PostgreSQL is canonical for durable session bindings, approvals, audit, checkpoints, inbox/outbox, and idempotency. Pi session files are replay sources, not cross-agent truth. +Valkey holds the existing short-lived, one-time command-approval records; PostgreSQL is canonical for durable session bindings, checkpoints, inbox/outbox, and idempotency. Pi session files are replay sources, not cross-agent truth. + +### M2 Durable Recovery + +`@mosaicstack/agent` owns a transport-neutral state machine and `apps/gateway` provides its +PostgreSQL adapter. `tess_sessions` holds immutable identity; inbox/outbox records use a +per-session unique idempotency key and transition `pending → processing → processed|delivered`. +Checkpoints are immutable history scoped by session and checkpoint ID: the latest checkpoint +supports compaction recovery, while a handoff always resolves the exact checkpoint it references. +Recovery requeues only interrupted inbox work; an ambiguous `processing` outbox record is preserved +until separately authorized reconciliation can establish its external delivery state. + +Provider sends travel through the existing `RuntimeProviderService` with the persisted outbox +idempotency key. A normal dispatch claims exactly one outbox record and verifies its stored +correlation and channel against the server-derived request scope; it never requeues or drains +another live record. Inbox/outbox payloads and checkpoint cursor/summary pass through the existing +secret/PII redactor and AES-256-GCM sealing before persistence; decryption occurs only in the +scoped gateway repository path, and runtime audit remains metadata-only. + +An external effect cannot share a database transaction. If a process dies after an effect begins +but before its terminal outbox transition, automatic recovery does not replay that ambiguous claim. +It remains `processing` until separately authorized reconciliation can establish delivery state; +completed effects are never redispatched. Operators can therefore restart the gateway/Pi service, +reconstruct the session, and resume pending inbox work without relying on process-local state. ## Transport Strategy diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index c4f2fc8..68d9d14 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -2,3 +2,4 @@ export const VERSION = '0.0.0'; export * from './runtime-provider-registry.js'; export * from './tmux-fleet-runtime-provider.js'; +export * from './tess-durable-session.js'; diff --git a/packages/agent/src/tess-durable-session.test.ts b/packages/agent/src/tess-durable-session.test.ts new file mode 100644 index 0000000..dd2c500 --- /dev/null +++ b/packages/agent/src/tess-durable-session.test.ts @@ -0,0 +1,287 @@ +import { describe, expect, it } from 'vitest'; +import { + DurableSessionCoordinator, + InMemoryDurableSessionStore, + type DurableSessionIdentity, +} from './tess-durable-session.js'; + +const IDENTITY: DurableSessionIdentity = { + sessionId: 'tess-session-1', + tenantId: 'tenant-1', + ownerId: 'owner-1', + providerId: 'fleet', + runtimeSessionId: 'nova', +}; + +describe('DurableSessionCoordinator', () => { + it('reconstructs an exact session identity, pending inbox/outbox, checkpoint, and handoff after a simulated process restart', async () => { + const store = new InMemoryDurableSessionStore(); + const beforeRestart = new DurableSessionCoordinator(store); + + await beforeRestart.create(IDENTITY); + await beforeRestart.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'ingress-1', + correlationId: 'correlation-1', + content: 'continue the session', + }); + await beforeRestart.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'outbox-1', + correlationId: 'correlation-1', + channelId: 'cli', + kind: 'provider.send', + content: 'resumable response', + }); + await beforeRestart.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-1', + cursor: 'cursor-42', + summary: 'operator asked for recovery proof', + compactionEpoch: 0, + }); + await beforeRestart.handoff({ + sessionId: IDENTITY.sessionId, + handoffId: 'handoff-1', + destination: 'mos', + correlationId: 'correlation-1', + checkpointId: 'checkpoint-1', + status: 'pending', + }); + + // Simulate an ungraceful process death: no in-memory coordinator state survives. + const afterRestart = new DurableSessionCoordinator(store); + const recovered = await afterRestart.recover(IDENTITY.sessionId); + + expect(recovered.identity).toEqual(IDENTITY); + expect(recovered.inbox).toMatchObject([{ idempotencyKey: 'ingress-1', status: 'pending' }]); + expect(recovered.outbox).toMatchObject([{ idempotencyKey: 'outbox-1', status: 'pending' }]); + expect(recovered.checkpoint).toMatchObject({ + checkpointId: 'checkpoint-1', + cursor: 'cursor-42', + }); + expect(recovered.handoffs).toMatchObject([{ handoffId: 'handoff-1', status: 'pending' }]); + }); + + it('deduplicates duplicate ingress and never reprocesses an inbox record after restart or compaction', async () => { + const store = new InMemoryDurableSessionStore(); + const firstProcess = new DurableSessionCoordinator(store); + const handled: string[] = []; + + await firstProcess.create(IDENTITY); + await expect( + firstProcess.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'ingress-duplicate', + correlationId: 'correlation-2', + content: 'only process me once', + }), + ).resolves.toMatchObject({ accepted: true }); + await expect( + firstProcess.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'ingress-duplicate', + correlationId: 'correlation-2', + content: 'only process me once', + }), + ).resolves.toMatchObject({ accepted: false, status: 'pending' }); + await expect( + firstProcess.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'ingress-duplicate', + correlationId: 'forged-correlation', + content: 'only process me once', + }), + ).rejects.toThrow(/idempotency conflict/); + + await firstProcess.drainInbox(IDENTITY.sessionId, async (entry) => { + handled.push(entry.idempotencyKey); + }); + await firstProcess.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-after-inbox', + cursor: 'cursor-43', + summary: 'safe to compact', + compactionEpoch: 1, + }); + + const afterRestartAndCompaction = new DurableSessionCoordinator(store); + await afterRestartAndCompaction.recover(IDENTITY.sessionId); + await afterRestartAndCompaction.drainInbox(IDENTITY.sessionId, async (entry) => { + handled.push(entry.idempotencyKey); + }); + + expect(handled).toEqual(['ingress-duplicate']); + }); + + it('does not redispatch an already applied outbox side effect after replay, restart, or compaction', async () => { + const store = new InMemoryDurableSessionStore(); + const beforeRestart = new DurableSessionCoordinator(store); + const appliedEffects: string[] = []; + + await beforeRestart.create(IDENTITY); + await beforeRestart.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'effect-1', + correlationId: 'correlation-3', + channelId: 'cli', + kind: 'provider.send', + content: 'send exactly once', + }); + await expect( + beforeRestart.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'effect-1', + correlationId: 'correlation-3', + channelId: 'cli', + kind: 'provider.send', + content: 'send exactly once', + }), + ).resolves.toMatchObject({ accepted: false, status: 'pending' }); + + await beforeRestart.dispatchOutbox(IDENTITY.sessionId, async (entry) => { + appliedEffects.push(entry.idempotencyKey); + }); + await beforeRestart.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-after-effect', + cursor: 'cursor-44', + summary: 'effect persisted before compaction', + compactionEpoch: 1, + }); + + const afterRestartAndCompaction = new DurableSessionCoordinator(store); + await afterRestartAndCompaction.recover(IDENTITY.sessionId); + await afterRestartAndCompaction.dispatchOutbox(IDENTITY.sessionId, async (entry) => { + appliedEffects.push(entry.idempotencyKey); + }); + + expect(appliedEffects).toEqual(['effect-1']); + }); + + it('rejects outbox idempotency-key reuse when immutable effect data differs', async () => { + const store = new InMemoryDurableSessionStore(); + const coordinator = new DurableSessionCoordinator(store); + + await coordinator.create(IDENTITY); + await coordinator.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'outbox-conflict', + correlationId: 'correlation-outbox', + channelId: 'cli', + kind: 'provider.send', + content: 'original effect', + }); + await expect( + coordinator.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'outbox-conflict', + correlationId: 'correlation-outbox', + channelId: 'forged-channel', + kind: 'provider.send', + content: 'original effect', + }), + ).rejects.toThrow(/idempotency conflict/); + }); + + it('retains an ambiguous failed provider effect as processing until explicit recovery', async () => { + const store = new InMemoryDurableSessionStore(); + const beforeRestart = new DurableSessionCoordinator(store); + + await beforeRestart.create(IDENTITY); + await beforeRestart.enqueueOutbox({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'ambiguous-effect', + correlationId: 'correlation-ambiguous', + channelId: 'cli', + kind: 'provider.send', + content: 'preserve this effect claim', + }); + await expect( + beforeRestart.dispatchOutbox(IDENTITY.sessionId, async (): Promise => { + throw new Error('provider connection dropped after submit'); + }), + ).rejects.toThrow(/connection dropped/); + + const afterRestart = new DurableSessionCoordinator(store); + await afterRestart.recover(IDENTITY.sessionId); + const calls: string[] = []; + await afterRestart.dispatchOutbox(IDENTITY.sessionId, async (entry): Promise => { + calls.push(entry.idempotencyKey); + }); + + expect(calls).toEqual([]); + expect(await afterRestart.snapshot(IDENTITY.sessionId)).toMatchObject({ + outbox: [{ idempotencyKey: 'ambiguous-effect', status: 'processing' }], + }); + }); + + it('rejects a handoff-id replay with different immutable state', async () => { + const store = new InMemoryDurableSessionStore(); + const coordinator = new DurableSessionCoordinator(store); + + await coordinator.create(IDENTITY); + await coordinator.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-conflict', + cursor: 'cursor-conflict', + summary: 'handoff conflict proof', + compactionEpoch: 0, + }); + await coordinator.handoff({ + sessionId: IDENTITY.sessionId, + handoffId: 'handoff-conflict', + destination: 'mos', + correlationId: 'correlation-conflict', + checkpointId: 'checkpoint-conflict', + status: 'pending', + }); + + await expect( + coordinator.handoff({ + sessionId: IDENTITY.sessionId, + handoffId: 'handoff-conflict', + destination: 'forged-destination', + correlationId: 'correlation-conflict', + checkpointId: 'checkpoint-conflict', + status: 'pending', + }), + ).rejects.toThrow(/identity conflict/); + }); + + it('keeps a handoff portable and resumes it from its durable checkpoint without process-local references', async () => { + const store = new InMemoryDurableSessionStore(); + const source = new DurableSessionCoordinator(store); + + await source.create(IDENTITY); + await source.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-handoff', + cursor: 'cursor-45', + summary: 'portable state', + compactionEpoch: 2, + }); + await source.handoff({ + sessionId: IDENTITY.sessionId, + handoffId: 'handoff-portable', + destination: 'mos', + correlationId: 'correlation-4', + checkpointId: 'checkpoint-handoff', + status: 'pending', + }); + await source.checkpoint({ + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-later', + cursor: 'cursor-46', + summary: 'newer compacted state must not strand the handoff', + compactionEpoch: 3, + }); + + const receivingProcess = new DurableSessionCoordinator(store); + const handoff = await receivingProcess.resumeHandoff('handoff-portable'); + + expect(handoff.identity).toEqual(IDENTITY); + expect(handoff.checkpoint).toMatchObject({ checkpointId: 'checkpoint-handoff' }); + expect(handoff.handoff).toMatchObject({ handoffId: 'handoff-portable', destination: 'mos' }); + }); +}); diff --git a/packages/agent/src/tess-durable-session.ts b/packages/agent/src/tess-durable-session.ts new file mode 100644 index 0000000..d259640 --- /dev/null +++ b/packages/agent/src/tess-durable-session.ts @@ -0,0 +1,489 @@ +export interface DurableSessionIdentity { + sessionId: string; + tenantId: string; + ownerId: string; + providerId: string; + runtimeSessionId: string; +} + +export type DurableInboxStatus = 'pending' | 'processing' | 'processed'; +export type DurableOutboxStatus = 'pending' | 'processing' | 'delivered'; + +export interface DurableInboxInput { + sessionId: string; + idempotencyKey: string; + correlationId: string; + content: string; +} + +export interface DurableInboxEntry extends DurableInboxInput { + status: DurableInboxStatus; +} + +export interface DurableOutboxInput { + sessionId: string; + idempotencyKey: string; + correlationId: string; + channelId: string; + kind: string; + content: string; +} + +export interface DurableOutboxEntry extends DurableOutboxInput { + status: DurableOutboxStatus; +} + +export interface DurableCheckpointInput { + sessionId: string; + checkpointId: string; + cursor: string; + summary: string; + compactionEpoch: number; +} + +export interface DurableCheckpoint extends DurableCheckpointInput {} + +export interface DurableHandoffInput { + sessionId: string; + handoffId: string; + destination: string; + correlationId: string; + checkpointId: string; + status: 'pending' | 'accepted'; +} + +export interface DurableHandoff extends DurableHandoffInput {} + +export interface DurableSessionSnapshot { + identity: DurableSessionIdentity; + inbox: DurableInboxEntry[]; + outbox: DurableOutboxEntry[]; + checkpoint?: DurableCheckpoint; + handoffs: DurableHandoff[]; +} + +export interface DurableHandoffRecovery { + identity: DurableSessionIdentity; + checkpoint: DurableCheckpoint; + handoff: DurableHandoff; +} + +export interface DurableEnqueueResult { + accepted: boolean; + status: TStatus; +} + +/** + * A durable-state port. Implementations must atomically claim and complete work + * records. Recovery may requeue interrupted inbox work, but never an + * externally visible outbox effect: an ambiguous provider result remains + * claimed until a separately authorized reconciliation proves it safe. + */ +export interface DurableSessionStore { + create(identity: DurableSessionIdentity): Promise; + snapshot(sessionId: string): Promise; + enqueueInbox(input: DurableInboxInput): Promise>; + claimInbox(sessionId: string): Promise; + completeInbox(sessionId: string, idempotencyKey: string): Promise; + releaseInbox(sessionId: string, idempotencyKey: string): Promise; + enqueueOutbox(input: DurableOutboxInput): Promise>; + claimOutbox(sessionId: string): Promise; + claimOutboxByKey(sessionId: string, idempotencyKey: string): Promise; + completeOutbox(sessionId: string, idempotencyKey: string): Promise; + releaseOutbox(sessionId: string, idempotencyKey: string): Promise; + checkpoint(input: DurableCheckpointInput): Promise; + findCheckpoint(sessionId: string, checkpointId: string): Promise; + handoff(input: DurableHandoffInput): Promise; + findHandoff(handoffId: string): Promise; + requeueInFlight(sessionId: string): Promise; +} + +export class DurableSessionNotFoundError extends Error { + constructor(sessionId: string) { + super(`Durable Tess session not found: ${sessionId}`); + this.name = 'DurableSessionNotFoundError'; + } +} + +export class DurableSessionCoordinator { + constructor(private readonly store: DurableSessionStore) {} + + async create(identity: DurableSessionIdentity): Promise { + this.assertIdentity(identity); + await this.store.create(identity); + } + + async receive(input: DurableInboxInput): Promise> { + this.assertRecord(input.sessionId, input.idempotencyKey, input.correlationId, input.content); + return this.store.enqueueInbox(input); + } + + async enqueueOutbox( + input: DurableOutboxInput, + ): Promise> { + this.assertRecord( + input.sessionId, + input.idempotencyKey, + input.correlationId, + input.channelId, + input.content, + ); + if (input.kind.trim().length === 0) throw new Error('Durable outbox kind is required'); + return this.store.enqueueOutbox(input); + } + + async checkpoint(input: DurableCheckpointInput): Promise { + this.assertRecord(input.sessionId, input.checkpointId, input.cursor, input.summary); + if (!Number.isInteger(input.compactionEpoch) || input.compactionEpoch < 0) { + throw new Error('Durable checkpoint compaction epoch must be a non-negative integer'); + } + await this.store.checkpoint(input); + } + + async handoff(input: DurableHandoffInput): Promise { + this.assertRecord(input.sessionId, input.handoffId, input.destination, input.correlationId); + if (input.checkpointId.trim().length === 0) + throw new Error('Durable handoff checkpoint is required'); + await this.store.handoff(input); + } + + /** Read durable state without changing claim status; safe during normal operation. */ + async snapshot(sessionId: string): Promise { + const snapshot = await this.store.snapshot(sessionId); + if (!snapshot) throw new DurableSessionNotFoundError(sessionId); + return snapshot; + } + + /** Requeue interrupted inbox work during recovery; preserve ambiguous outbox claims. */ + async recover(sessionId: string): Promise { + await this.store.requeueInFlight(sessionId); + return this.snapshot(sessionId); + } + + async drainInbox( + sessionId: string, + handler: (entry: DurableInboxEntry) => Promise, + ): Promise { + for (;;) { + const entry = await this.store.claimInbox(sessionId); + if (!entry) return; + try { + await handler(entry); + } catch (error: unknown) { + await this.store.releaseInbox(sessionId, entry.idempotencyKey); + throw error; + } + // If this write fails after the handler succeeded, leave the record + // processing. A recovery path can retry it with its stable idempotency key. + await this.store.completeInbox(sessionId, entry.idempotencyKey); + } + } + + async dispatchOutbox( + sessionId: string, + dispatcher: (entry: DurableOutboxEntry) => Promise, + ): Promise { + for (;;) { + const entry = await this.store.claimOutbox(sessionId); + if (!entry) return; + // A provider failure can be ambiguous: it may occur after the receiver + // accepted the idempotency key. Preserve the claim for reconciliation. + await dispatcher(entry); + // Do not requeue an effect after it has been applied but before its + // terminal state could be persisted; recovery preserves the claim. + await this.store.completeOutbox(sessionId, entry.idempotencyKey); + } + } + + async dispatchOutboxEntry( + sessionId: string, + idempotencyKey: string, + dispatcher: (entry: DurableOutboxEntry) => Promise, + ): Promise { + const entry = await this.store.claimOutboxByKey(sessionId, idempotencyKey); + if (!entry) return; + // A provider failure can be ambiguous, so this stays processing until + // separately authorized reconciliation proves it safe to resolve. + await dispatcher(entry); + await this.store.completeOutbox(sessionId, entry.idempotencyKey); + } + + async resumeHandoff(handoffId: string): Promise { + const handoff = await this.store.findHandoff(handoffId); + if (!handoff) throw new Error(`Durable Tess handoff not found: ${handoffId}`); + const snapshot = await this.snapshot(handoff.sessionId); + const checkpoint = await this.store.findCheckpoint(handoff.sessionId, handoff.checkpointId); + if (!checkpoint) { + throw new Error(`Durable Tess handoff checkpoint is unavailable: ${handoff.checkpointId}`); + } + return { identity: snapshot.identity, checkpoint, handoff }; + } + + private assertIdentity(identity: DurableSessionIdentity): void { + this.assertRecord(identity.sessionId, identity.tenantId, identity.ownerId, identity.providerId); + if (identity.runtimeSessionId.trim().length === 0) { + throw new Error('Durable runtime session identity is required'); + } + } + + private assertRecord(...values: string[]): void { + if (values.some((value: string): boolean => value.trim().length === 0)) { + throw new Error('Durable session records require non-empty fields'); + } + } +} + +interface InMemorySessionState { + identity: DurableSessionIdentity; + inbox: Map; + outbox: Map; + checkpoints: Map; + handoffs: Map; +} + +/** Reference store for deterministic domain tests; production uses the gateway DB adapter. */ +export class InMemoryDurableSessionStore implements DurableSessionStore { + private readonly sessions = new Map(); + + async create(identity: DurableSessionIdentity): Promise { + const existing = this.sessions.get(identity.sessionId); + if (existing) { + if (!identitiesEqual(existing.identity, identity)) { + throw new Error(`Durable Tess session identity conflict: ${identity.sessionId}`); + } + return; + } + this.sessions.set(identity.sessionId, { + identity: copyIdentity(identity), + inbox: new Map(), + outbox: new Map(), + checkpoints: new Map(), + handoffs: new Map(), + }); + } + + async snapshot(sessionId: string): Promise { + const state = this.sessions.get(sessionId); + if (!state) return null; + const checkpoint = latestCheckpoint(state.checkpoints); + return { + identity: copyIdentity(state.identity), + inbox: [...state.inbox.values()].map(copyInbox), + outbox: [...state.outbox.values()].map(copyOutbox), + ...(checkpoint ? { checkpoint: copyCheckpoint(checkpoint) } : {}), + handoffs: [...state.handoffs.values()].map(copyHandoff), + }; + } + + async enqueueInbox(input: DurableInboxInput): Promise> { + const state = this.require(input.sessionId); + const existing = state.inbox.get(input.idempotencyKey); + if (existing) { + if (!sameInbox(existing, input)) { + throw new Error(`Durable Tess inbox idempotency conflict: ${input.idempotencyKey}`); + } + return { accepted: false, status: existing.status }; + } + state.inbox.set(input.idempotencyKey, { ...input, status: 'pending' }); + return { accepted: true, status: 'pending' }; + } + + async claimInbox(sessionId: string): Promise { + const state = this.require(sessionId); + const entry = [...state.inbox.values()].find( + (candidate: DurableInboxEntry): boolean => candidate.status === 'pending', + ); + if (!entry) return null; + entry.status = 'processing'; + return copyInbox(entry); + } + + async completeInbox(sessionId: string, idempotencyKey: string): Promise { + this.requireEntry(this.require(sessionId).inbox, idempotencyKey, 'inbox').status = 'processed'; + } + + async releaseInbox(sessionId: string, idempotencyKey: string): Promise { + this.requireEntry(this.require(sessionId).inbox, idempotencyKey, 'inbox').status = 'pending'; + } + + async enqueueOutbox( + input: DurableOutboxInput, + ): Promise> { + const state = this.require(input.sessionId); + const existing = state.outbox.get(input.idempotencyKey); + if (existing) { + if (!sameOutbox(existing, input)) { + throw new Error(`Durable Tess outbox idempotency conflict: ${input.idempotencyKey}`); + } + return { accepted: false, status: existing.status }; + } + state.outbox.set(input.idempotencyKey, { ...input, status: 'pending' }); + return { accepted: true, status: 'pending' }; + } + + async claimOutbox(sessionId: string): Promise { + const state = this.require(sessionId); + const entry = [...state.outbox.values()].find( + (candidate: DurableOutboxEntry): boolean => candidate.status === 'pending', + ); + if (!entry) return null; + entry.status = 'processing'; + return copyOutbox(entry); + } + + async claimOutboxByKey( + sessionId: string, + idempotencyKey: string, + ): Promise { + const entry = this.require(sessionId).outbox.get(idempotencyKey); + if (!entry || entry.status !== 'pending') return null; + entry.status = 'processing'; + return copyOutbox(entry); + } + + async completeOutbox(sessionId: string, idempotencyKey: string): Promise { + this.requireEntry(this.require(sessionId).outbox, idempotencyKey, 'outbox').status = + 'delivered'; + } + + async releaseOutbox(sessionId: string, idempotencyKey: string): Promise { + this.requireEntry(this.require(sessionId).outbox, idempotencyKey, 'outbox').status = 'pending'; + } + + async checkpoint(input: DurableCheckpointInput): Promise { + const checkpoints = this.require(input.sessionId).checkpoints; + const existing = checkpoints.get(input.checkpointId); + if (existing && !sameCheckpoint(existing, input)) { + throw new Error(`Durable Tess checkpoint identity conflict: ${input.checkpointId}`); + } + if (!existing) checkpoints.set(input.checkpointId, { ...input }); + } + + async findCheckpoint(sessionId: string, checkpointId: string): Promise { + const checkpoint = this.require(sessionId).checkpoints.get(checkpointId); + return checkpoint ? copyCheckpoint(checkpoint) : null; + } + + async handoff(input: DurableHandoffInput): Promise { + const state = this.require(input.sessionId); + if (!state.checkpoints.has(input.checkpointId)) { + throw new Error(`Durable Tess handoff checkpoint is unavailable: ${input.checkpointId}`); + } + const existing = state.handoffs.get(input.handoffId); + if (existing && !sameHandoff(existing, input)) { + throw new Error(`Durable Tess handoff identity conflict: ${input.handoffId}`); + } + if (!existing) state.handoffs.set(input.handoffId, { ...input }); + } + + async findHandoff(handoffId: string): Promise { + for (const state of this.sessions.values()) { + const handoff = state.handoffs.get(handoffId); + if (handoff) return copyHandoff(handoff); + } + return null; + } + + async requeueInFlight(sessionId: string): Promise { + const state = this.require(sessionId); + for (const entry of state.inbox.values()) { + if (entry.status === 'processing') entry.status = 'pending'; + } + } + + private require(sessionId: string): InMemorySessionState { + const state = this.sessions.get(sessionId); + if (!state) throw new DurableSessionNotFoundError(sessionId); + return state; + } + + private requireEntry( + records: Map, + idempotencyKey: string, + kind: string, + ): T { + const entry = records.get(idempotencyKey); + if (!entry) throw new Error(`Durable Tess ${kind} entry not found: ${idempotencyKey}`); + return entry; + } +} + +function identitiesEqual(left: DurableSessionIdentity, right: DurableSessionIdentity): boolean { + return ( + left.sessionId === right.sessionId && + left.tenantId === right.tenantId && + left.ownerId === right.ownerId && + left.providerId === right.providerId && + left.runtimeSessionId === right.runtimeSessionId + ); +} + +function sameInbox(left: DurableInboxEntry, right: DurableInboxInput): boolean { + return ( + left.sessionId === right.sessionId && + left.idempotencyKey === right.idempotencyKey && + left.correlationId === right.correlationId && + left.content === right.content + ); +} + +function sameOutbox(left: DurableOutboxEntry, right: DurableOutboxInput): boolean { + return ( + left.sessionId === right.sessionId && + left.idempotencyKey === right.idempotencyKey && + left.correlationId === right.correlationId && + left.channelId === right.channelId && + left.kind === right.kind && + left.content === right.content + ); +} + +function sameCheckpoint(left: DurableCheckpoint, right: DurableCheckpointInput): boolean { + return ( + left.sessionId === right.sessionId && + left.checkpointId === right.checkpointId && + left.cursor === right.cursor && + left.summary === right.summary && + left.compactionEpoch === right.compactionEpoch + ); +} + +function latestCheckpoint( + checkpoints: Map, +): DurableCheckpoint | undefined { + return [...checkpoints.values()].sort( + (left: DurableCheckpoint, right: DurableCheckpoint): number => + right.compactionEpoch - left.compactionEpoch, + )[0]; +} + +function sameHandoff(left: DurableHandoff, right: DurableHandoffInput): boolean { + return ( + left.sessionId === right.sessionId && + left.handoffId === right.handoffId && + left.destination === right.destination && + left.correlationId === right.correlationId && + left.checkpointId === right.checkpointId && + left.status === right.status + ); +} + +function copyIdentity(identity: DurableSessionIdentity): DurableSessionIdentity { + return { ...identity }; +} + +function copyInbox(entry: DurableInboxEntry): DurableInboxEntry { + return { ...entry }; +} + +function copyOutbox(entry: DurableOutboxEntry): DurableOutboxEntry { + return { ...entry }; +} + +function copyCheckpoint(checkpoint: DurableCheckpoint): DurableCheckpoint { + return { ...checkpoint }; +} + +function copyHandoff(handoff: DurableHandoff): DurableHandoff { + return { ...handoff }; +} diff --git a/packages/db/drizzle/0012_tess_durable_state.sql b/packages/db/drizzle/0012_tess_durable_state.sql new file mode 100644 index 0000000..e89f873 --- /dev/null +++ b/packages/db/drizzle/0012_tess_durable_state.sql @@ -0,0 +1,68 @@ +CREATE TYPE "public"."tess_handoff_status" AS ENUM('pending', 'accepted');--> statement-breakpoint +CREATE TYPE "public"."tess_inbox_status" AS ENUM('pending', 'processing', 'processed');--> statement-breakpoint +CREATE TYPE "public"."tess_outbox_status" AS ENUM('pending', 'processing', 'delivered');--> statement-breakpoint +CREATE TABLE "tess_checkpoints" ( + "session_id" text PRIMARY KEY NOT NULL, + "checkpoint_id" text NOT NULL, + "cursor" text NOT NULL, + "summary" text NOT NULL, + "compaction_epoch" integer NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "tess_checkpoints_checkpoint_id_unique" UNIQUE("checkpoint_id") +); +--> statement-breakpoint +CREATE TABLE "tess_handoffs" ( + "handoff_id" text PRIMARY KEY NOT NULL, + "session_id" text NOT NULL, + "destination" text NOT NULL, + "correlation_id" text NOT NULL, + "checkpoint_id" text NOT NULL, + "status" "tess_handoff_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 +); +--> statement-breakpoint +CREATE TABLE "tess_inbox" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" text NOT NULL, + "idempotency_key" text NOT NULL, + "correlation_id" text NOT NULL, + "content" text NOT NULL, + "status" "tess_inbox_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 +); +--> statement-breakpoint +CREATE TABLE "tess_outbox" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" text NOT NULL, + "idempotency_key" text NOT NULL, + "correlation_id" text NOT NULL, + "kind" text NOT NULL, + "content" text NOT NULL, + "status" "tess_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 +); +--> statement-breakpoint +CREATE TABLE "tess_sessions" ( + "id" text PRIMARY KEY NOT NULL, + "tenant_id" text NOT NULL, + "owner_id" text NOT NULL, + "provider_id" text NOT NULL, + "runtime_session_id" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "tess_checkpoints" ADD CONSTRAINT "tess_checkpoints_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "tess_handoffs" ADD CONSTRAINT "tess_handoffs_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "tess_inbox" ADD CONSTRAINT "tess_inbox_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "tess_outbox" ADD CONSTRAINT "tess_outbox_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "tess_sessions" ADD CONSTRAINT "tess_sessions_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "tess_handoffs_session_status_idx" ON "tess_handoffs" USING btree ("session_id","status");--> statement-breakpoint +CREATE UNIQUE INDEX "tess_inbox_session_idempotency_idx" ON "tess_inbox" USING btree ("session_id","idempotency_key");--> statement-breakpoint +CREATE INDEX "tess_inbox_session_status_created_idx" ON "tess_inbox" USING btree ("session_id","status","created_at");--> statement-breakpoint +CREATE UNIQUE INDEX "tess_outbox_session_idempotency_idx" ON "tess_outbox" USING btree ("session_id","idempotency_key");--> statement-breakpoint +CREATE INDEX "tess_outbox_session_status_created_idx" ON "tess_outbox" USING btree ("session_id","status","created_at"); \ No newline at end of file diff --git a/packages/db/drizzle/0013_tess_checkpoint_history.sql b/packages/db/drizzle/0013_tess_checkpoint_history.sql new file mode 100644 index 0000000..f863ddc --- /dev/null +++ b/packages/db/drizzle/0013_tess_checkpoint_history.sql @@ -0,0 +1,5 @@ +ALTER TABLE "tess_checkpoints" DROP CONSTRAINT "tess_checkpoints_checkpoint_id_unique";--> statement-breakpoint +ALTER TABLE "tess_checkpoints" DROP CONSTRAINT "tess_checkpoints_pkey";--> statement-breakpoint +ALTER TABLE "tess_checkpoints" ADD COLUMN "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "tess_checkpoints_session_idempotency_idx" ON "tess_checkpoints" USING btree ("session_id","checkpoint_id");--> statement-breakpoint +CREATE INDEX "tess_checkpoints_session_epoch_idx" ON "tess_checkpoints" USING btree ("session_id","compaction_epoch"); \ No newline at end of file diff --git a/packages/db/drizzle/0014_tess_outbox_channel_scope.sql b/packages/db/drizzle/0014_tess_outbox_channel_scope.sql new file mode 100644 index 0000000..32e3c73 --- /dev/null +++ b/packages/db/drizzle/0014_tess_outbox_channel_scope.sql @@ -0,0 +1,5 @@ +ALTER TABLE "tess_outbox" ADD COLUMN "channel_id" text; +--> statement-breakpoint +UPDATE "tess_outbox" SET "channel_id" = 'legacy:unknown' WHERE "channel_id" IS NULL; +--> statement-breakpoint +ALTER TABLE "tess_outbox" ALTER COLUMN "channel_id" SET NOT NULL; diff --git a/packages/db/drizzle/meta/0012_snapshot.json b/packages/db/drizzle/meta/0012_snapshot.json new file mode 100644 index 0000000..7ade9e7 --- /dev/null +++ b/packages/db/drizzle/meta/0012_snapshot.json @@ -0,0 +1,4172 @@ +{ + "id": "5d2dbfe9-f5d2-4342-a613-8c73943a6122", + "prevId": "0aa37ae4-5a0b-464b-ba70-121c5d9bbd23", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounts_provider_account_idx": { + "name": "accounts_provider_account_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.admin_tokens": { + "name": "admin_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "admin_tokens_user_id_idx": { + "name": "admin_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "admin_tokens_hash_idx": { + "name": "admin_tokens_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "admin_tokens_user_id_users_id_fk": { + "name": "admin_tokens_user_id_users_id_fk", + "tableFrom": "admin_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_logs": { + "name": "agent_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'hot'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "summarized_at": { + "name": "summarized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_logs_session_tier_idx": { + "name": "agent_logs_session_tier_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_user_id_idx": { + "name": "agent_logs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_tier_created_at_idx": { + "name": "agent_logs_tier_created_at_idx", + "columns": [ + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_logs_user_id_users_id_fk": { + "name": "agent_logs_user_id_users_id_fk", + "tableFrom": "agent_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_tools": { + "name": "allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_project_id_idx": { + "name": "agents_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_owner_id_idx": { + "name": "agents_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_is_system_idx": { + "name": "agents_is_system_idx", + "columns": [ + { + "expression": "is_system", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_project_id_projects_id_fk": { + "name": "agents_project_id_projects_id_fk", + "tableFrom": "agents", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agents_owner_id_users_id_fk": { + "name": "agents_owner_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appreciations": { + "name": "appreciations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "from_user": { + "name": "from_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_user": { + "name": "to_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backlog": { + "name": "backlog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "backlog_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "depends_on": { + "name": "depends_on", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "claim_owner": { + "name": "claim_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_ttl_seconds": { + "name": "claim_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acceptance": { + "name": "acceptance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "backlog_status_priority_idx": { + "name": "backlog_status_priority_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_status_claimed_at_idx": { + "name": "backlog_status_claimed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_idempotency_key_idx": { + "name": "backlog_idempotency_key_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "conversations_user_archived_idx": { + "name": "conversations_user_archived_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_project_id_idx": { + "name": "conversations_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_agent_id_idx": { + "name": "conversations_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "conversations_user_id_users_id_fk": { + "name": "conversations_user_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversations_project_id_projects_id_fk": { + "name": "conversations_project_id_projects_id_fk", + "tableFrom": "conversations", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "conversations_agent_id_agents_id_fk": { + "name": "conversations_agent_id_agents_id_fk", + "tableFrom": "conversations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "events_type_idx": { + "name": "events_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_date_idx": { + "name": "events_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_audit_log": { + "name": "federation_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "verb": { + "name": "verb", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "denied_reason": { + "name": "denied_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "query_hash": { + "name": "query_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bytes_out": { + "name": "bytes_out", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_audit_log_peer_created_at_idx": { + "name": "federation_audit_log_peer_created_at_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_subject_created_at_idx": { + "name": "federation_audit_log_subject_created_at_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_created_at_idx": { + "name": "federation_audit_log_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_audit_log_peer_id_federation_peers_id_fk": { + "name": "federation_audit_log_peer_id_federation_peers_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_subject_user_id_users_id_fk": { + "name": "federation_audit_log_subject_user_id_users_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_grant_id_federation_grants_id_fk": { + "name": "federation_audit_log_grant_id_federation_grants_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_enrollment_tokens": { + "name": "federation_enrollment_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_enrollment_tokens_grant_id_federation_grants_id_fk": { + "name": "federation_enrollment_tokens_grant_id_federation_grants_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_enrollment_tokens_peer_id_federation_peers_id_fk": { + "name": "federation_enrollment_tokens_peer_id_federation_peers_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_grants": { + "name": "federation_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_reason": { + "name": "revoked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_grants_subject_status_idx": { + "name": "federation_grants_subject_status_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_grants_peer_status_idx": { + "name": "federation_grants_peer_status_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_grants_subject_user_id_users_id_fk": { + "name": "federation_grants_subject_user_id_users_id_fk", + "tableFrom": "federation_grants", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_grants_peer_id_federation_peers_id_fk": { + "name": "federation_grants_peer_id_federation_peers_id_fk", + "tableFrom": "federation_grants", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_peers": { + "name": "federation_peers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_serial": { + "name": "cert_serial", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_not_after": { + "name": "cert_not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "client_key_pem": { + "name": "client_key_pem", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "peer_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "endpoint_url": { + "name": "endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_peers_cert_serial_idx": { + "name": "federation_peers_cert_serial_idx", + "columns": [ + { + "expression": "cert_serial", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_peers_state_idx": { + "name": "federation_peers_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_peers_common_name_unique": { + "name": "federation_peers_common_name_unique", + "nullsNotDistinct": false, + "columns": [ + "common_name" + ] + }, + "federation_peers_cert_serial_unique": { + "name": "federation_peers_cert_serial_unique", + "nullsNotDistinct": false, + "columns": [ + "cert_serial" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.insights": { + "name": "insights", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "relevance_score": { + "name": "relevance_score", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decayed_at": { + "name": "decayed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "insights_user_id_idx": { + "name": "insights_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_category_idx": { + "name": "insights_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_relevance_idx": { + "name": "insights_relevance_idx", + "columns": [ + { + "expression": "relevance_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "insights_user_id_users_id_fk": { + "name": "insights_user_id_users_id_fk", + "tableFrom": "insights", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_conversation_id_idx": { + "name": "messages_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mission_tasks": { + "name": "mission_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr": { + "name": "pr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mission_tasks_mission_id_idx": { + "name": "mission_tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_task_id_idx": { + "name": "mission_tasks_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_user_id_idx": { + "name": "mission_tasks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_status_idx": { + "name": "mission_tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mission_tasks_mission_id_missions_id_fk": { + "name": "mission_tasks_mission_id_missions_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mission_tasks_task_id_tasks_id_fk": { + "name": "mission_tasks_task_id_tasks_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mission_tasks_user_id_users_id_fk": { + "name": "mission_tasks_user_id_users_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.missions": { + "name": "missions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planning'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "milestones": { + "name": "milestones", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "missions_project_id_idx": { + "name": "missions_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "missions_user_id_idx": { + "name": "missions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "missions_project_id_projects_id_fk": { + "name": "missions_project_id_projects_id_fk", + "tableFrom": "missions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "missions_user_id_users_id_fk": { + "name": "missions_user_id_users_id_fk", + "tableFrom": "missions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preferences": { + "name": "preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mutable": { + "name": "mutable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "preferences_user_id_idx": { + "name": "preferences_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "preferences_user_key_idx": { + "name": "preferences_user_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "preferences_user_id_users_id_fk": { + "name": "preferences_user_id_users_id_fk", + "tableFrom": "preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_credentials": { + "name": "provider_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_type": { + "name": "credential_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_credentials_user_provider_idx": { + "name": "provider_credentials_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_credentials_user_id_idx": { + "name": "provider_credentials_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_credentials_user_id_users_id_fk": { + "name": "provider_credentials_user_id_users_id_fk", + "tableFrom": "provider_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routing_rules": { + "name": "routing_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routing_rules_scope_priority_idx": { + "name": "routing_rules_scope_priority_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_user_id_idx": { + "name": "routing_rules_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_enabled_idx": { + "name": "routing_rules_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routing_rules_user_id_users_id_fk": { + "name": "routing_rules_user_id_users_id_fk", + "tableFrom": "routing_rules", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_expires_at_idx": { + "name": "sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'custom'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "installed_by": { + "name": "installed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skills_enabled_idx": { + "name": "skills_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skills_installed_by_users_id_fk": { + "name": "skills_installed_by_users_id_fk", + "tableFrom": "skills", + "tableTo": "users", + "columnsFrom": [ + "installed_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_name_unique": { + "name": "skills_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summarization_jobs": { + "name": "summarization_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "logs_processed": { + "name": "logs_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "insights_created": { + "name": "insights_created", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summarization_jobs_status_idx": { + "name": "summarization_jobs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee": { + "name": "assignee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_project_id_idx": { + "name": "tasks_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_mission_id_idx": { + "name": "tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_project_id_projects_id_fk": { + "name": "tasks_project_id_projects_id_fk", + "tableFrom": "tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_mission_id_missions_id_fk": { + "name": "tasks_mission_id_missions_id_fk", + "tableFrom": "tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_members_team_user_idx": { + "name": "team_members_team_user_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_invited_by_users_id_fk": { + "name": "team_members_invited_by_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "teams_manager_id_users_id_fk": { + "name": "teams_manager_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_checkpoints": { + "name": "tess_checkpoints", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compaction_epoch": { + "name": "compaction_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tess_checkpoints_session_id_tess_sessions_id_fk": { + "name": "tess_checkpoints_session_id_tess_sessions_id_fk", + "tableFrom": "tess_checkpoints", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tess_checkpoints_checkpoint_id_unique": { + "name": "tess_checkpoints_checkpoint_id_unique", + "nullsNotDistinct": false, + "columns": [ + "checkpoint_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_handoffs": { + "name": "tess_handoffs", + "schema": "", + "columns": { + "handoff_id": { + "name": "handoff_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_handoff_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_handoffs_session_status_idx": { + "name": "tess_handoffs_session_status_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_handoffs_session_id_tess_sessions_id_fk": { + "name": "tess_handoffs_session_id_tess_sessions_id_fk", + "tableFrom": "tess_handoffs", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_inbox": { + "name": "tess_inbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_inbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_inbox_session_idempotency_idx": { + "name": "tess_inbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_inbox_session_status_created_idx": { + "name": "tess_inbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_inbox_session_id_tess_sessions_id_fk": { + "name": "tess_inbox_session_id_tess_sessions_id_fk", + "tableFrom": "tess_inbox", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_outbox": { + "name": "tess_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_outbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_outbox_session_idempotency_idx": { + "name": "tess_outbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_outbox_session_status_created_idx": { + "name": "tess_outbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_outbox_session_id_tess_sessions_id_fk": { + "name": "tess_outbox_session_id_tess_sessions_id_fk", + "tableFrom": "tess_outbox", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_sessions": { + "name": "tess_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime_session_id": { + "name": "runtime_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tess_sessions_owner_id_users_id_fk": { + "name": "tess_sessions_owner_id_users_id_fk", + "tableFrom": "tess_sessions", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.backlog_status": { + "name": "backlog_status", + "schema": "public", + "values": [ + "ready", + "claimed", + "blocked", + "done" + ] + }, + "public.grant_status": { + "name": "grant_status", + "schema": "public", + "values": [ + "pending", + "active", + "revoked", + "expired" + ] + }, + "public.peer_state": { + "name": "peer_state", + "schema": "public", + "values": [ + "pending", + "active", + "suspended", + "revoked" + ] + }, + "public.tess_handoff_status": { + "name": "tess_handoff_status", + "schema": "public", + "values": [ + "pending", + "accepted" + ] + }, + "public.tess_inbox_status": { + "name": "tess_inbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "processed" + ] + }, + "public.tess_outbox_status": { + "name": "tess_outbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "delivered" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/0013_snapshot.json b/packages/db/drizzle/meta/0013_snapshot.json new file mode 100644 index 0000000..4a124f5 --- /dev/null +++ b/packages/db/drizzle/meta/0013_snapshot.json @@ -0,0 +1,4214 @@ +{ + "id": "0cd8e1a3-a7f6-4c39-ac19-7cc0f9c02164", + "prevId": "5d2dbfe9-f5d2-4342-a613-8c73943a6122", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounts_provider_account_idx": { + "name": "accounts_provider_account_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.admin_tokens": { + "name": "admin_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "admin_tokens_user_id_idx": { + "name": "admin_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "admin_tokens_hash_idx": { + "name": "admin_tokens_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "admin_tokens_user_id_users_id_fk": { + "name": "admin_tokens_user_id_users_id_fk", + "tableFrom": "admin_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_logs": { + "name": "agent_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'hot'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "summarized_at": { + "name": "summarized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_logs_session_tier_idx": { + "name": "agent_logs_session_tier_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_user_id_idx": { + "name": "agent_logs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_tier_created_at_idx": { + "name": "agent_logs_tier_created_at_idx", + "columns": [ + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_logs_user_id_users_id_fk": { + "name": "agent_logs_user_id_users_id_fk", + "tableFrom": "agent_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_tools": { + "name": "allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_project_id_idx": { + "name": "agents_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_owner_id_idx": { + "name": "agents_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_is_system_idx": { + "name": "agents_is_system_idx", + "columns": [ + { + "expression": "is_system", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_project_id_projects_id_fk": { + "name": "agents_project_id_projects_id_fk", + "tableFrom": "agents", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agents_owner_id_users_id_fk": { + "name": "agents_owner_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appreciations": { + "name": "appreciations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "from_user": { + "name": "from_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_user": { + "name": "to_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backlog": { + "name": "backlog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "backlog_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "depends_on": { + "name": "depends_on", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "claim_owner": { + "name": "claim_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_ttl_seconds": { + "name": "claim_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acceptance": { + "name": "acceptance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "backlog_status_priority_idx": { + "name": "backlog_status_priority_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_status_claimed_at_idx": { + "name": "backlog_status_claimed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_idempotency_key_idx": { + "name": "backlog_idempotency_key_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "conversations_user_archived_idx": { + "name": "conversations_user_archived_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_project_id_idx": { + "name": "conversations_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_agent_id_idx": { + "name": "conversations_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "conversations_user_id_users_id_fk": { + "name": "conversations_user_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversations_project_id_projects_id_fk": { + "name": "conversations_project_id_projects_id_fk", + "tableFrom": "conversations", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "conversations_agent_id_agents_id_fk": { + "name": "conversations_agent_id_agents_id_fk", + "tableFrom": "conversations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "events_type_idx": { + "name": "events_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_date_idx": { + "name": "events_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_audit_log": { + "name": "federation_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "verb": { + "name": "verb", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "denied_reason": { + "name": "denied_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "query_hash": { + "name": "query_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bytes_out": { + "name": "bytes_out", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_audit_log_peer_created_at_idx": { + "name": "federation_audit_log_peer_created_at_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_subject_created_at_idx": { + "name": "federation_audit_log_subject_created_at_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_created_at_idx": { + "name": "federation_audit_log_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_audit_log_peer_id_federation_peers_id_fk": { + "name": "federation_audit_log_peer_id_federation_peers_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_subject_user_id_users_id_fk": { + "name": "federation_audit_log_subject_user_id_users_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_grant_id_federation_grants_id_fk": { + "name": "federation_audit_log_grant_id_federation_grants_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_enrollment_tokens": { + "name": "federation_enrollment_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_enrollment_tokens_grant_id_federation_grants_id_fk": { + "name": "federation_enrollment_tokens_grant_id_federation_grants_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_enrollment_tokens_peer_id_federation_peers_id_fk": { + "name": "federation_enrollment_tokens_peer_id_federation_peers_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_grants": { + "name": "federation_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_reason": { + "name": "revoked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_grants_subject_status_idx": { + "name": "federation_grants_subject_status_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_grants_peer_status_idx": { + "name": "federation_grants_peer_status_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_grants_subject_user_id_users_id_fk": { + "name": "federation_grants_subject_user_id_users_id_fk", + "tableFrom": "federation_grants", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_grants_peer_id_federation_peers_id_fk": { + "name": "federation_grants_peer_id_federation_peers_id_fk", + "tableFrom": "federation_grants", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_peers": { + "name": "federation_peers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_serial": { + "name": "cert_serial", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_not_after": { + "name": "cert_not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "client_key_pem": { + "name": "client_key_pem", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "peer_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "endpoint_url": { + "name": "endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_peers_cert_serial_idx": { + "name": "federation_peers_cert_serial_idx", + "columns": [ + { + "expression": "cert_serial", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_peers_state_idx": { + "name": "federation_peers_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_peers_common_name_unique": { + "name": "federation_peers_common_name_unique", + "nullsNotDistinct": false, + "columns": [ + "common_name" + ] + }, + "federation_peers_cert_serial_unique": { + "name": "federation_peers_cert_serial_unique", + "nullsNotDistinct": false, + "columns": [ + "cert_serial" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.insights": { + "name": "insights", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "relevance_score": { + "name": "relevance_score", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decayed_at": { + "name": "decayed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "insights_user_id_idx": { + "name": "insights_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_category_idx": { + "name": "insights_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_relevance_idx": { + "name": "insights_relevance_idx", + "columns": [ + { + "expression": "relevance_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "insights_user_id_users_id_fk": { + "name": "insights_user_id_users_id_fk", + "tableFrom": "insights", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_conversation_id_idx": { + "name": "messages_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mission_tasks": { + "name": "mission_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr": { + "name": "pr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mission_tasks_mission_id_idx": { + "name": "mission_tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_task_id_idx": { + "name": "mission_tasks_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_user_id_idx": { + "name": "mission_tasks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_status_idx": { + "name": "mission_tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mission_tasks_mission_id_missions_id_fk": { + "name": "mission_tasks_mission_id_missions_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mission_tasks_task_id_tasks_id_fk": { + "name": "mission_tasks_task_id_tasks_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mission_tasks_user_id_users_id_fk": { + "name": "mission_tasks_user_id_users_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.missions": { + "name": "missions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planning'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "milestones": { + "name": "milestones", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "missions_project_id_idx": { + "name": "missions_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "missions_user_id_idx": { + "name": "missions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "missions_project_id_projects_id_fk": { + "name": "missions_project_id_projects_id_fk", + "tableFrom": "missions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "missions_user_id_users_id_fk": { + "name": "missions_user_id_users_id_fk", + "tableFrom": "missions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preferences": { + "name": "preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mutable": { + "name": "mutable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "preferences_user_id_idx": { + "name": "preferences_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "preferences_user_key_idx": { + "name": "preferences_user_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "preferences_user_id_users_id_fk": { + "name": "preferences_user_id_users_id_fk", + "tableFrom": "preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_credentials": { + "name": "provider_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_type": { + "name": "credential_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_credentials_user_provider_idx": { + "name": "provider_credentials_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_credentials_user_id_idx": { + "name": "provider_credentials_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_credentials_user_id_users_id_fk": { + "name": "provider_credentials_user_id_users_id_fk", + "tableFrom": "provider_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routing_rules": { + "name": "routing_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routing_rules_scope_priority_idx": { + "name": "routing_rules_scope_priority_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_user_id_idx": { + "name": "routing_rules_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_enabled_idx": { + "name": "routing_rules_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routing_rules_user_id_users_id_fk": { + "name": "routing_rules_user_id_users_id_fk", + "tableFrom": "routing_rules", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_expires_at_idx": { + "name": "sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'custom'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "installed_by": { + "name": "installed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skills_enabled_idx": { + "name": "skills_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skills_installed_by_users_id_fk": { + "name": "skills_installed_by_users_id_fk", + "tableFrom": "skills", + "tableTo": "users", + "columnsFrom": [ + "installed_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_name_unique": { + "name": "skills_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summarization_jobs": { + "name": "summarization_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "logs_processed": { + "name": "logs_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "insights_created": { + "name": "insights_created", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summarization_jobs_status_idx": { + "name": "summarization_jobs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee": { + "name": "assignee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_project_id_idx": { + "name": "tasks_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_mission_id_idx": { + "name": "tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_project_id_projects_id_fk": { + "name": "tasks_project_id_projects_id_fk", + "tableFrom": "tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_mission_id_missions_id_fk": { + "name": "tasks_mission_id_missions_id_fk", + "tableFrom": "tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_members_team_user_idx": { + "name": "team_members_team_user_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_invited_by_users_id_fk": { + "name": "team_members_invited_by_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "teams_manager_id_users_id_fk": { + "name": "teams_manager_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_checkpoints": { + "name": "tess_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compaction_epoch": { + "name": "compaction_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_checkpoints_session_idempotency_idx": { + "name": "tess_checkpoints_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_checkpoints_session_epoch_idx": { + "name": "tess_checkpoints_session_epoch_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "compaction_epoch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_checkpoints_session_id_tess_sessions_id_fk": { + "name": "tess_checkpoints_session_id_tess_sessions_id_fk", + "tableFrom": "tess_checkpoints", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_handoffs": { + "name": "tess_handoffs", + "schema": "", + "columns": { + "handoff_id": { + "name": "handoff_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_handoff_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_handoffs_session_status_idx": { + "name": "tess_handoffs_session_status_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_handoffs_session_id_tess_sessions_id_fk": { + "name": "tess_handoffs_session_id_tess_sessions_id_fk", + "tableFrom": "tess_handoffs", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_inbox": { + "name": "tess_inbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_inbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_inbox_session_idempotency_idx": { + "name": "tess_inbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_inbox_session_status_created_idx": { + "name": "tess_inbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_inbox_session_id_tess_sessions_id_fk": { + "name": "tess_inbox_session_id_tess_sessions_id_fk", + "tableFrom": "tess_inbox", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_outbox": { + "name": "tess_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_outbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_outbox_session_idempotency_idx": { + "name": "tess_outbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_outbox_session_status_created_idx": { + "name": "tess_outbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_outbox_session_id_tess_sessions_id_fk": { + "name": "tess_outbox_session_id_tess_sessions_id_fk", + "tableFrom": "tess_outbox", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_sessions": { + "name": "tess_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime_session_id": { + "name": "runtime_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tess_sessions_owner_id_users_id_fk": { + "name": "tess_sessions_owner_id_users_id_fk", + "tableFrom": "tess_sessions", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.backlog_status": { + "name": "backlog_status", + "schema": "public", + "values": [ + "ready", + "claimed", + "blocked", + "done" + ] + }, + "public.grant_status": { + "name": "grant_status", + "schema": "public", + "values": [ + "pending", + "active", + "revoked", + "expired" + ] + }, + "public.peer_state": { + "name": "peer_state", + "schema": "public", + "values": [ + "pending", + "active", + "suspended", + "revoked" + ] + }, + "public.tess_handoff_status": { + "name": "tess_handoff_status", + "schema": "public", + "values": [ + "pending", + "accepted" + ] + }, + "public.tess_inbox_status": { + "name": "tess_inbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "processed" + ] + }, + "public.tess_outbox_status": { + "name": "tess_outbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "delivered" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/0014_snapshot.json b/packages/db/drizzle/meta/0014_snapshot.json new file mode 100644 index 0000000..249e99a --- /dev/null +++ b/packages/db/drizzle/meta/0014_snapshot.json @@ -0,0 +1,4220 @@ +{ + "id": "48124a40-a224-4d1c-ab8f-b3dab554b1ac", + "prevId": "0cd8e1a3-a7f6-4c39-ac19-7cc0f9c02164", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounts_provider_account_idx": { + "name": "accounts_provider_account_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.admin_tokens": { + "name": "admin_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "admin_tokens_user_id_idx": { + "name": "admin_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "admin_tokens_hash_idx": { + "name": "admin_tokens_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "admin_tokens_user_id_users_id_fk": { + "name": "admin_tokens_user_id_users_id_fk", + "tableFrom": "admin_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_logs": { + "name": "agent_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'hot'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "summarized_at": { + "name": "summarized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_logs_session_tier_idx": { + "name": "agent_logs_session_tier_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_user_id_idx": { + "name": "agent_logs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_tier_created_at_idx": { + "name": "agent_logs_tier_created_at_idx", + "columns": [ + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_logs_user_id_users_id_fk": { + "name": "agent_logs_user_id_users_id_fk", + "tableFrom": "agent_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_tools": { + "name": "allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_project_id_idx": { + "name": "agents_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_owner_id_idx": { + "name": "agents_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_is_system_idx": { + "name": "agents_is_system_idx", + "columns": [ + { + "expression": "is_system", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_project_id_projects_id_fk": { + "name": "agents_project_id_projects_id_fk", + "tableFrom": "agents", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agents_owner_id_users_id_fk": { + "name": "agents_owner_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appreciations": { + "name": "appreciations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "from_user": { + "name": "from_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_user": { + "name": "to_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backlog": { + "name": "backlog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "backlog_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "depends_on": { + "name": "depends_on", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "claim_owner": { + "name": "claim_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_ttl_seconds": { + "name": "claim_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acceptance": { + "name": "acceptance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "backlog_status_priority_idx": { + "name": "backlog_status_priority_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_status_claimed_at_idx": { + "name": "backlog_status_claimed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_idempotency_key_idx": { + "name": "backlog_idempotency_key_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "conversations_user_archived_idx": { + "name": "conversations_user_archived_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_project_id_idx": { + "name": "conversations_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_agent_id_idx": { + "name": "conversations_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "conversations_user_id_users_id_fk": { + "name": "conversations_user_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversations_project_id_projects_id_fk": { + "name": "conversations_project_id_projects_id_fk", + "tableFrom": "conversations", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "conversations_agent_id_agents_id_fk": { + "name": "conversations_agent_id_agents_id_fk", + "tableFrom": "conversations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "events_type_idx": { + "name": "events_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_date_idx": { + "name": "events_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_audit_log": { + "name": "federation_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "verb": { + "name": "verb", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "denied_reason": { + "name": "denied_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "query_hash": { + "name": "query_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bytes_out": { + "name": "bytes_out", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_audit_log_peer_created_at_idx": { + "name": "federation_audit_log_peer_created_at_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_subject_created_at_idx": { + "name": "federation_audit_log_subject_created_at_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_created_at_idx": { + "name": "federation_audit_log_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_audit_log_peer_id_federation_peers_id_fk": { + "name": "federation_audit_log_peer_id_federation_peers_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_subject_user_id_users_id_fk": { + "name": "federation_audit_log_subject_user_id_users_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_grant_id_federation_grants_id_fk": { + "name": "federation_audit_log_grant_id_federation_grants_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_enrollment_tokens": { + "name": "federation_enrollment_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_enrollment_tokens_grant_id_federation_grants_id_fk": { + "name": "federation_enrollment_tokens_grant_id_federation_grants_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_enrollment_tokens_peer_id_federation_peers_id_fk": { + "name": "federation_enrollment_tokens_peer_id_federation_peers_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_grants": { + "name": "federation_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_reason": { + "name": "revoked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_grants_subject_status_idx": { + "name": "federation_grants_subject_status_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_grants_peer_status_idx": { + "name": "federation_grants_peer_status_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_grants_subject_user_id_users_id_fk": { + "name": "federation_grants_subject_user_id_users_id_fk", + "tableFrom": "federation_grants", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_grants_peer_id_federation_peers_id_fk": { + "name": "federation_grants_peer_id_federation_peers_id_fk", + "tableFrom": "federation_grants", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_peers": { + "name": "federation_peers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_serial": { + "name": "cert_serial", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_not_after": { + "name": "cert_not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "client_key_pem": { + "name": "client_key_pem", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "peer_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "endpoint_url": { + "name": "endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_peers_cert_serial_idx": { + "name": "federation_peers_cert_serial_idx", + "columns": [ + { + "expression": "cert_serial", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_peers_state_idx": { + "name": "federation_peers_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_peers_common_name_unique": { + "name": "federation_peers_common_name_unique", + "nullsNotDistinct": false, + "columns": [ + "common_name" + ] + }, + "federation_peers_cert_serial_unique": { + "name": "federation_peers_cert_serial_unique", + "nullsNotDistinct": false, + "columns": [ + "cert_serial" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.insights": { + "name": "insights", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "relevance_score": { + "name": "relevance_score", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decayed_at": { + "name": "decayed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "insights_user_id_idx": { + "name": "insights_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_category_idx": { + "name": "insights_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_relevance_idx": { + "name": "insights_relevance_idx", + "columns": [ + { + "expression": "relevance_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "insights_user_id_users_id_fk": { + "name": "insights_user_id_users_id_fk", + "tableFrom": "insights", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_conversation_id_idx": { + "name": "messages_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mission_tasks": { + "name": "mission_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr": { + "name": "pr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mission_tasks_mission_id_idx": { + "name": "mission_tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_task_id_idx": { + "name": "mission_tasks_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_user_id_idx": { + "name": "mission_tasks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_status_idx": { + "name": "mission_tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mission_tasks_mission_id_missions_id_fk": { + "name": "mission_tasks_mission_id_missions_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mission_tasks_task_id_tasks_id_fk": { + "name": "mission_tasks_task_id_tasks_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mission_tasks_user_id_users_id_fk": { + "name": "mission_tasks_user_id_users_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.missions": { + "name": "missions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planning'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "milestones": { + "name": "milestones", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "missions_project_id_idx": { + "name": "missions_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "missions_user_id_idx": { + "name": "missions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "missions_project_id_projects_id_fk": { + "name": "missions_project_id_projects_id_fk", + "tableFrom": "missions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "missions_user_id_users_id_fk": { + "name": "missions_user_id_users_id_fk", + "tableFrom": "missions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preferences": { + "name": "preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mutable": { + "name": "mutable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "preferences_user_id_idx": { + "name": "preferences_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "preferences_user_key_idx": { + "name": "preferences_user_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "preferences_user_id_users_id_fk": { + "name": "preferences_user_id_users_id_fk", + "tableFrom": "preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_credentials": { + "name": "provider_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_type": { + "name": "credential_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_credentials_user_provider_idx": { + "name": "provider_credentials_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_credentials_user_id_idx": { + "name": "provider_credentials_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_credentials_user_id_users_id_fk": { + "name": "provider_credentials_user_id_users_id_fk", + "tableFrom": "provider_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routing_rules": { + "name": "routing_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routing_rules_scope_priority_idx": { + "name": "routing_rules_scope_priority_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_user_id_idx": { + "name": "routing_rules_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_enabled_idx": { + "name": "routing_rules_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routing_rules_user_id_users_id_fk": { + "name": "routing_rules_user_id_users_id_fk", + "tableFrom": "routing_rules", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_expires_at_idx": { + "name": "sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'custom'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "installed_by": { + "name": "installed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skills_enabled_idx": { + "name": "skills_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skills_installed_by_users_id_fk": { + "name": "skills_installed_by_users_id_fk", + "tableFrom": "skills", + "tableTo": "users", + "columnsFrom": [ + "installed_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_name_unique": { + "name": "skills_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summarization_jobs": { + "name": "summarization_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "logs_processed": { + "name": "logs_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "insights_created": { + "name": "insights_created", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summarization_jobs_status_idx": { + "name": "summarization_jobs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee": { + "name": "assignee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_project_id_idx": { + "name": "tasks_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_mission_id_idx": { + "name": "tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_project_id_projects_id_fk": { + "name": "tasks_project_id_projects_id_fk", + "tableFrom": "tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_mission_id_missions_id_fk": { + "name": "tasks_mission_id_missions_id_fk", + "tableFrom": "tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_members_team_user_idx": { + "name": "team_members_team_user_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_invited_by_users_id_fk": { + "name": "team_members_invited_by_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "teams_manager_id_users_id_fk": { + "name": "teams_manager_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_checkpoints": { + "name": "tess_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compaction_epoch": { + "name": "compaction_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_checkpoints_session_idempotency_idx": { + "name": "tess_checkpoints_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_checkpoints_session_epoch_idx": { + "name": "tess_checkpoints_session_epoch_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "compaction_epoch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_checkpoints_session_id_tess_sessions_id_fk": { + "name": "tess_checkpoints_session_id_tess_sessions_id_fk", + "tableFrom": "tess_checkpoints", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_handoffs": { + "name": "tess_handoffs", + "schema": "", + "columns": { + "handoff_id": { + "name": "handoff_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_handoff_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_handoffs_session_status_idx": { + "name": "tess_handoffs_session_status_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_handoffs_session_id_tess_sessions_id_fk": { + "name": "tess_handoffs_session_id_tess_sessions_id_fk", + "tableFrom": "tess_handoffs", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_inbox": { + "name": "tess_inbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_inbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_inbox_session_idempotency_idx": { + "name": "tess_inbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_inbox_session_status_created_idx": { + "name": "tess_inbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_inbox_session_id_tess_sessions_id_fk": { + "name": "tess_inbox_session_id_tess_sessions_id_fk", + "tableFrom": "tess_inbox", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_outbox": { + "name": "tess_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "tess_outbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tess_outbox_session_idempotency_idx": { + "name": "tess_outbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tess_outbox_session_status_created_idx": { + "name": "tess_outbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tess_outbox_session_id_tess_sessions_id_fk": { + "name": "tess_outbox_session_id_tess_sessions_id_fk", + "tableFrom": "tess_outbox", + "tableTo": "tess_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tess_sessions": { + "name": "tess_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime_session_id": { + "name": "runtime_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tess_sessions_owner_id_users_id_fk": { + "name": "tess_sessions_owner_id_users_id_fk", + "tableFrom": "tess_sessions", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.backlog_status": { + "name": "backlog_status", + "schema": "public", + "values": [ + "ready", + "claimed", + "blocked", + "done" + ] + }, + "public.grant_status": { + "name": "grant_status", + "schema": "public", + "values": [ + "pending", + "active", + "revoked", + "expired" + ] + }, + "public.peer_state": { + "name": "peer_state", + "schema": "public", + "values": [ + "pending", + "active", + "suspended", + "revoked" + ] + }, + "public.tess_handoff_status": { + "name": "tess_handoff_status", + "schema": "public", + "values": [ + "pending", + "accepted" + ] + }, + "public.tess_inbox_status": { + "name": "tess_inbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "processed" + ] + }, + "public.tess_outbox_status": { + "name": "tess_outbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "delivered" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index fb09f9d..b5b1dd1 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -85,6 +85,27 @@ "when": 1782310438919, "tag": "0011_bitter_gateway", "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1783911983447, + "tag": "0012_tess_durable_state", + "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1783913232578, + "tag": "0013_tess_checkpoint_history", + "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1783913398006, + "tag": "0014_tess_outbox_channel_scope", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 7f343ce..c085a7c 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -487,6 +487,113 @@ export const agentLogs = pgTable( ], ); +// ─── Tess durable session state ───────────────────────────────────────────── +// PostgreSQL is canonical for restart-safe Tess session recovery. The state +// machine lives in @mosaicstack/agent; these records are its durable adapter. + +export const tessInboxStatusEnum = pgEnum('tess_inbox_status', [ + 'pending', + 'processing', + 'processed', +]); +export const tessOutboxStatusEnum = pgEnum('tess_outbox_status', [ + 'pending', + 'processing', + 'delivered', +]); +export const tessHandoffStatusEnum = pgEnum('tess_handoff_status', ['pending', 'accepted']); + +export const tessSessions = pgTable('tess_sessions', { + id: text('id').primaryKey(), + tenantId: text('tenant_id').notNull(), + ownerId: text('owner_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + providerId: text('provider_id').notNull(), + runtimeSessionId: text('runtime_session_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), +}); + +export const tessInbox = pgTable( + 'tess_inbox', + { + id: uuid('id').primaryKey().defaultRandom(), + sessionId: text('session_id') + .notNull() + .references(() => tessSessions.id, { onDelete: 'cascade' }), + idempotencyKey: text('idempotency_key').notNull(), + correlationId: text('correlation_id').notNull(), + content: text('content').notNull(), + status: tessInboxStatusEnum('status').notNull().default('pending'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => [ + uniqueIndex('tess_inbox_session_idempotency_idx').on(t.sessionId, t.idempotencyKey), + index('tess_inbox_session_status_created_idx').on(t.sessionId, t.status, t.createdAt), + ], +); + +export const tessOutbox = pgTable( + 'tess_outbox', + { + id: uuid('id').primaryKey().defaultRandom(), + sessionId: text('session_id') + .notNull() + .references(() => tessSessions.id, { onDelete: 'cascade' }), + idempotencyKey: text('idempotency_key').notNull(), + correlationId: text('correlation_id').notNull(), + channelId: text('channel_id').notNull(), + kind: text('kind').notNull(), + content: text('content').notNull(), + status: tessOutboxStatusEnum('status').notNull().default('pending'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => [ + uniqueIndex('tess_outbox_session_idempotency_idx').on(t.sessionId, t.idempotencyKey), + index('tess_outbox_session_status_created_idx').on(t.sessionId, t.status, t.createdAt), + ], +); + +export const tessCheckpoints = pgTable( + 'tess_checkpoints', + { + id: uuid('id').primaryKey().defaultRandom(), + sessionId: text('session_id') + .notNull() + .references(() => tessSessions.id, { onDelete: 'cascade' }), + checkpointId: text('checkpoint_id').notNull(), + cursor: text('cursor').notNull(), + summary: text('summary').notNull(), + compactionEpoch: integer('compaction_epoch').notNull(), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => [ + uniqueIndex('tess_checkpoints_session_idempotency_idx').on(t.sessionId, t.checkpointId), + index('tess_checkpoints_session_epoch_idx').on(t.sessionId, t.compactionEpoch), + ], +); + +export const tessHandoffs = pgTable( + 'tess_handoffs', + { + handoffId: text('handoff_id').primaryKey(), + sessionId: text('session_id') + .notNull() + .references(() => tessSessions.id, { onDelete: 'cascade' }), + destination: text('destination').notNull(), + correlationId: text('correlation_id').notNull(), + checkpointId: text('checkpoint_id').notNull(), + status: tessHandoffStatusEnum('status').notNull().default('pending'), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), + }, + (t) => [index('tess_handoffs_session_status_idx').on(t.sessionId, t.status)], +); + // ─── Skills ───────────────────────────────────────────────────────────────── export const skills = pgTable( -- 2.49.1 From 444988d23bada28d3cc9ce7e588e18e052f058ff Mon Sep 17 00:00:00 2001 From: Jarvis Date: Sun, 12 Jul 2026 23:36:14 -0500 Subject: [PATCH 2/3] fix(tess): harden durable recovery namespaces --- .../runtime-provider-registry.service.test.ts | 3 + .../runtime-provider-registry.service.ts | 8 + .../tess-durable-session.repository.test.ts | 63 +++--- .../agent/tess-durable-session.repository.ts | 187 ++++++++++-------- .../command-authorization.service.spec.ts | 4 +- .../commands/command-authorization.service.ts | 24 ++- .../src/commands/runtime-approval-verifier.ts | 2 +- .../gateway/src/gc/session-gc.service.spec.ts | 2 +- docs/tess/ARCHITECTURE.md | 4 +- .../agent/src/tess-durable-session.test.ts | 1 + packages/agent/src/tess-durable-session.ts | 11 +- .../0012_interaction_durable_state.sql | 71 +++++++ .../db/drizzle/0012_tess_durable_state.sql | 68 ------- .../0013_interaction_checkpoint_history.sql | 5 + .../drizzle/0013_tess_checkpoint_history.sql | 5 - .../0014_interaction_outbox_channel_scope.sql | 5 + .../0014_tess_outbox_channel_scope.sql | 5 - packages/db/drizzle/meta/0012_snapshot.json | 100 +++++----- packages/db/drizzle/meta/0013_snapshot.json | 104 +++++----- packages/db/drizzle/meta/0014_snapshot.json | 104 +++++----- packages/db/drizzle/meta/_journal.json | 6 +- packages/db/src/schema.ts | 58 +++--- 22 files changed, 449 insertions(+), 391 deletions(-) create mode 100644 packages/db/drizzle/0012_interaction_durable_state.sql delete mode 100644 packages/db/drizzle/0012_tess_durable_state.sql create mode 100644 packages/db/drizzle/0013_interaction_checkpoint_history.sql delete mode 100644 packages/db/drizzle/0013_tess_checkpoint_history.sql create mode 100644 packages/db/drizzle/0014_interaction_outbox_channel_scope.sql delete mode 100644 packages/db/drizzle/0014_tess_outbox_channel_scope.sql diff --git a/apps/gateway/src/agent/__tests__/runtime-provider-registry.service.test.ts b/apps/gateway/src/agent/__tests__/runtime-provider-registry.service.test.ts index a521b5d..a70eafe 100644 --- a/apps/gateway/src/agent/__tests__/runtime-provider-registry.service.test.ts +++ b/apps/gateway/src/agent/__tests__/runtime-provider-registry.service.test.ts @@ -22,6 +22,8 @@ import { type RuntimeApprovalVerifier, } from '../runtime-provider-registry.service.js'; +process.env['MOSAIC_AGENT_NAME'] ??= 'test-runtime-agent'; + const OWNER_SCOPE: ActorTenantScope = { userId: 'owner-1', tenantId: 'tenant-1' }; const CONTEXT = { actorScope: OWNER_SCOPE, @@ -243,6 +245,7 @@ describe('RuntimeProviderService security boundary', (): void => { tenantId: OWNER_SCOPE.tenantId, channelId: CONTEXT.channelId, correlationId: CONTEXT.correlationId, + agentName: process.env['MOSAIC_AGENT_NAME'], }); expect(provider.terminateCalls).toBe(1); }); diff --git a/apps/gateway/src/agent/runtime-provider-registry.service.ts b/apps/gateway/src/agent/runtime-provider-registry.service.ts index c56906f..cb16a29 100644 --- a/apps/gateway/src/agent/runtime-provider-registry.service.ts +++ b/apps/gateway/src/agent/runtime-provider-registry.service.ts @@ -64,12 +64,19 @@ export interface RuntimeTerminationAction { tenantId: string; channelId: string; correlationId: string; + agentName: string; } export interface RuntimeApprovalVerifier { consume(approvalRef: string, action: RuntimeTerminationAction): Promise; } +function configuredAgentName(): string { + const agentName = process.env['MOSAIC_AGENT_NAME']?.trim(); + if (!agentName) throw new RuntimeApprovalDeniedError(); + return agentName; +} + class RuntimeApprovalDeniedError extends Error { constructor() { super('Runtime termination approval denied'); @@ -261,6 +268,7 @@ export class RuntimeProviderService { tenantId: scope.tenantId, channelId: scope.channelId, correlationId: scope.correlationId, + agentName: configuredAgentName(), }); if (!approved) { throw new RuntimeApprovalDeniedError(); diff --git a/apps/gateway/src/agent/tess-durable-session.repository.test.ts b/apps/gateway/src/agent/tess-durable-session.repository.test.ts index c9c4336..1cd1d3b 100644 --- a/apps/gateway/src/agent/tess-durable-session.repository.test.ts +++ b/apps/gateway/src/agent/tess-durable-session.repository.test.ts @@ -1,14 +1,15 @@ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { eq, sql, tessInbox } from '@mosaicstack/db'; +import { eq, sql, interactionInbox } from '@mosaicstack/db'; import { DurableSessionCoordinator, type DurableSessionIdentity } from '@mosaicstack/agent'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { createPgliteDb, runPgliteMigrations, type DbHandle } from '@mosaicstack/db'; import { TessDurableSessionRepository } from './tess-durable-session.repository.js'; import { TessDurableSessionService } from './tess-durable-session.service.js'; const IDENTITY: DurableSessionIdentity = { + agentName: 'Nova', sessionId: 'tess-pglite-session', tenantId: 'tenant-pglite', ownerId: 'tess-owner', @@ -18,27 +19,34 @@ const IDENTITY: DurableSessionIdentity = { describe('TessDurableSessionRepository', () => { let dataDir: string | undefined; - let handle: DbHandle | undefined; + let handle: DbHandle; let previousAuthSecret: string | undefined; - beforeEach((): void => { + beforeAll(async (): Promise => { previousAuthSecret = process.env['BETTER_AUTH_SECRET']; process.env['BETTER_AUTH_SECRET'] = 'tess-durable-state-test-sealing-key'; + dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-state-')); + handle = createPgliteDb(dataDir); + await runPgliteMigrations(handle); + await seedOwner(handle); + }, 30_000); + + beforeEach(async (): Promise => { + await handle.db.execute(sql`DELETE FROM interaction_handoffs`); + await handle.db.execute(sql`DELETE FROM interaction_checkpoints`); + await handle.db.execute(sql`DELETE FROM interaction_inbox`); + await handle.db.execute(sql`DELETE FROM interaction_outbox`); + await handle.db.execute(sql`DELETE FROM interaction_sessions`); }); - afterEach(async (): Promise => { - await handle?.close(); + afterAll(async (): Promise => { + await handle.close(); if (dataDir) rmSync(dataDir, { recursive: true, force: true }); if (previousAuthSecret === undefined) delete process.env['BETTER_AUTH_SECRET']; else process.env['BETTER_AUTH_SECRET'] = previousAuthSecret; }); it('survives a full PGlite close/reopen mid-session without duplicate inbox or outbox side effects', async () => { - dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-recovery-')); - handle = createPgliteDb(dataDir); - await runPgliteMigrations(handle); - await seedOwner(handle); - const beforeRestart = new DurableSessionCoordinator( new TessDurableSessionRepository(handle.db), ); @@ -81,7 +89,7 @@ describe('TessDurableSessionRepository', () => { }); await handle.close(); - handle = createPgliteDb(dataDir); + handle = createPgliteDb(dataDir!); const afterRestart = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); const recovered = await afterRestart.recover(IDENTITY.sessionId); @@ -111,11 +119,6 @@ describe('TessDurableSessionRepository', () => { }, 30_000); it('redacts sensitive durable payloads before persistence', async () => { - dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-redaction-')); - handle = createPgliteDb(dataDir); - await runPgliteMigrations(handle); - await seedOwner(handle); - const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); await coordinator.create(IDENTITY); await coordinator.receive({ @@ -142,9 +145,9 @@ describe('TessDurableSessionRepository', () => { const snapshot = await coordinator.snapshot(IDENTITY.sessionId); const [persisted] = await handle.db - .select({ content: tessInbox.content }) - .from(tessInbox) - .where(eq(tessInbox.idempotencyKey, 'redacted-inbox')); + .select({ content: interactionInbox.content }) + .from(interactionInbox) + .where(eq(interactionInbox.idempotencyKey, 'redacted-inbox')); expect(JSON.stringify(snapshot)).not.toContain('super-secret-canary'); expect(JSON.stringify(snapshot)).not.toContain('operator@example.test'); @@ -153,11 +156,6 @@ describe('TessDurableSessionRepository', () => { }, 30_000); it('rejects database inbox and outbox idempotency-key conflicts', async () => { - dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-idempotency-conflict-')); - handle = createPgliteDb(dataDir); - await runPgliteMigrations(handle); - await seedOwner(handle); - const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); await coordinator.create(IDENTITY); await coordinator.receive({ @@ -196,11 +194,6 @@ describe('TessDurableSessionRepository', () => { }, 30_000); it('does not requeue a live outbox claim during a normal scoped dispatch', async () => { - dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-live-claim-')); - handle = createPgliteDb(dataDir); - await runPgliteMigrations(handle); - await seedOwner(handle); - const repository = new TessDurableSessionRepository(handle.db); const coordinator = new DurableSessionCoordinator(repository); const runtimeProviders = { sendMessage: vi.fn().mockResolvedValue(undefined) }; @@ -241,11 +234,6 @@ describe('TessDurableSessionRepository', () => { }, 30_000); it('rejects an outbox correlation mismatch before claiming the pending effect', async () => { - dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-outbox-mismatch-')); - handle = createPgliteDb(dataDir); - await runPgliteMigrations(handle); - await seedOwner(handle); - const repository = new TessDurableSessionRepository(handle.db); const coordinator = new DurableSessionCoordinator(repository); const runtimeProviders = { sendMessage: vi.fn().mockResolvedValue(undefined) }; @@ -279,11 +267,6 @@ describe('TessDurableSessionRepository', () => { }, 30_000); it('dispatches only the outbox record bound to the supplied correlation and channel', async () => { - dataDir = mkdtempSync(join(tmpdir(), 'tess-durable-outbox-scope-')); - handle = createPgliteDb(dataDir); - await runPgliteMigrations(handle); - await seedOwner(handle); - const repository = new TessDurableSessionRepository(handle.db); const coordinator = new DurableSessionCoordinator(repository); const runtimeProviders = { sendMessage: vi.fn().mockResolvedValue(undefined) }; diff --git a/apps/gateway/src/agent/tess-durable-session.repository.ts b/apps/gateway/src/agent/tess-durable-session.repository.ts index 76f07e1..9daf324 100644 --- a/apps/gateway/src/agent/tess-durable-session.repository.ts +++ b/apps/gateway/src/agent/tess-durable-session.repository.ts @@ -1,14 +1,15 @@ +import { createHash } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { and, asc, desc, eq, - tessCheckpoints, - tessHandoffs, - tessInbox, - tessOutbox, - tessSessions, + interactionCheckpoints, + interactionHandoffs, + interactionInbox, + interactionOutbox, + interactionSessions, type Db, } from '@mosaicstack/db'; import { seal, unseal } from '@mosaicstack/auth'; @@ -37,9 +38,10 @@ export class TessDurableSessionRepository implements DurableSessionStore { async create(identity: DurableSessionIdentity): Promise { await this.db - .insert(tessSessions) + .insert(interactionSessions) .values({ id: identity.sessionId, + agentName: identity.agentName, tenantId: identity.tenantId, ownerId: identity.ownerId, providerId: identity.providerId, @@ -60,25 +62,28 @@ export class TessDurableSessionRepository implements DurableSessionStore { const [inbox, outbox, checkpoints, handoffs] = await Promise.all([ this.db .select() - .from(tessInbox) - .where(eq(tessInbox.sessionId, sessionId)) - .orderBy(asc(tessInbox.createdAt)), + .from(interactionInbox) + .where(eq(interactionInbox.sessionId, sessionId)) + .orderBy(asc(interactionInbox.createdAt)), this.db .select() - .from(tessOutbox) - .where(eq(tessOutbox.sessionId, sessionId)) - .orderBy(asc(tessOutbox.createdAt)), + .from(interactionOutbox) + .where(eq(interactionOutbox.sessionId, sessionId)) + .orderBy(asc(interactionOutbox.createdAt)), this.db .select() - .from(tessCheckpoints) - .where(eq(tessCheckpoints.sessionId, sessionId)) - .orderBy(desc(tessCheckpoints.compactionEpoch), desc(tessCheckpoints.createdAt)) + .from(interactionCheckpoints) + .where(eq(interactionCheckpoints.sessionId, sessionId)) + .orderBy( + desc(interactionCheckpoints.compactionEpoch), + desc(interactionCheckpoints.createdAt), + ) .limit(1), this.db .select() - .from(tessHandoffs) - .where(eq(tessHandoffs.sessionId, sessionId)) - .orderBy(asc(tessHandoffs.createdAt)), + .from(interactionHandoffs) + .where(eq(interactionHandoffs.sessionId, sessionId)) + .orderBy(asc(interactionHandoffs.createdAt)), ]); const checkpoint = checkpoints[0]; @@ -92,30 +97,36 @@ export class TessDurableSessionRepository implements DurableSessionStore { } async enqueueInbox(input: DurableInboxInput): Promise> { + const digest = contentDigest(input.content); const record: DurableInboxInput = { ...input, content: redactSensitiveContent(input.content).content, }; const inserted = await this.db - .insert(tessInbox) - .values({ ...record, content: seal(record.content), status: 'pending' }) + .insert(interactionInbox) + .values({ + ...record, + content: seal(record.content), + contentDigest: digest, + status: 'pending', + }) .onConflictDoNothing() - .returning({ status: tessInbox.status }); + .returning({ status: interactionInbox.status }); if (inserted[0]) return { accepted: true, status: inserted[0].status }; const existing = await this.db .select() - .from(tessInbox) + .from(interactionInbox) .where( and( - eq(tessInbox.sessionId, input.sessionId), - eq(tessInbox.idempotencyKey, input.idempotencyKey), + eq(interactionInbox.sessionId, input.sessionId), + eq(interactionInbox.idempotencyKey, input.idempotencyKey), ), ) .limit(1); if (!existing[0]) throw new Error(`Durable Tess inbox enqueue failed: ${input.idempotencyKey}`); const entry = toInbox(existing[0]); - if (!sameInbox(entry, record)) { + if (!sameInbox(entry, record) || existing[0].contentDigest !== digest) { throw new Error(`Durable Tess inbox idempotency conflict: ${input.idempotencyKey}`); } return { accepted: false, status: entry.status }; @@ -125,16 +136,18 @@ export class TessDurableSessionRepository implements DurableSessionStore { for (let attempt = 0; attempt < 3; attempt += 1) { const candidate = await this.db .select() - .from(tessInbox) - .where(and(eq(tessInbox.sessionId, sessionId), eq(tessInbox.status, 'pending'))) - .orderBy(asc(tessInbox.createdAt)) + .from(interactionInbox) + .where( + and(eq(interactionInbox.sessionId, sessionId), eq(interactionInbox.status, 'pending')), + ) + .orderBy(asc(interactionInbox.createdAt)) .limit(1); const entry = candidate[0]; if (!entry) return null; const claimed = await this.db - .update(tessInbox) + .update(interactionInbox) .set({ status: 'processing', updatedAt: new Date() }) - .where(and(eq(tessInbox.id, entry.id), eq(tessInbox.status, 'pending'))) + .where(and(eq(interactionInbox.id, entry.id), eq(interactionInbox.status, 'pending'))) .returning(); if (claimed[0]) return toInbox(claimed[0]); } @@ -143,26 +156,26 @@ export class TessDurableSessionRepository implements DurableSessionStore { async completeInbox(sessionId: string, idempotencyKey: string): Promise { await this.db - .update(tessInbox) + .update(interactionInbox) .set({ status: 'processed', updatedAt: new Date() }) .where( and( - eq(tessInbox.sessionId, sessionId), - eq(tessInbox.idempotencyKey, idempotencyKey), - eq(tessInbox.status, 'processing'), + eq(interactionInbox.sessionId, sessionId), + eq(interactionInbox.idempotencyKey, idempotencyKey), + eq(interactionInbox.status, 'processing'), ), ); } async releaseInbox(sessionId: string, idempotencyKey: string): Promise { await this.db - .update(tessInbox) + .update(interactionInbox) .set({ status: 'pending', updatedAt: new Date() }) .where( and( - eq(tessInbox.sessionId, sessionId), - eq(tessInbox.idempotencyKey, idempotencyKey), - eq(tessInbox.status, 'processing'), + eq(interactionInbox.sessionId, sessionId), + eq(interactionInbox.idempotencyKey, idempotencyKey), + eq(interactionInbox.status, 'processing'), ), ); } @@ -170,31 +183,37 @@ export class TessDurableSessionRepository implements DurableSessionStore { async enqueueOutbox( input: DurableOutboxInput, ): Promise> { + const digest = contentDigest(input.content); const record: DurableOutboxInput = { ...input, content: redactSensitiveContent(input.content).content, }; const inserted = await this.db - .insert(tessOutbox) - .values({ ...record, content: seal(record.content), status: 'pending' }) + .insert(interactionOutbox) + .values({ + ...record, + content: seal(record.content), + contentDigest: digest, + status: 'pending', + }) .onConflictDoNothing() - .returning({ status: tessOutbox.status }); + .returning({ status: interactionOutbox.status }); if (inserted[0]) return { accepted: true, status: inserted[0].status }; const existing = await this.db .select() - .from(tessOutbox) + .from(interactionOutbox) .where( and( - eq(tessOutbox.sessionId, input.sessionId), - eq(tessOutbox.idempotencyKey, input.idempotencyKey), + eq(interactionOutbox.sessionId, input.sessionId), + eq(interactionOutbox.idempotencyKey, input.idempotencyKey), ), ) .limit(1); if (!existing[0]) throw new Error(`Durable Tess outbox enqueue failed: ${input.idempotencyKey}`); const entry = toOutbox(existing[0]); - if (!sameOutbox(entry, record)) { + if (!sameOutbox(entry, record) || existing[0].contentDigest !== digest) { throw new Error(`Durable Tess outbox idempotency conflict: ${input.idempotencyKey}`); } return { accepted: false, status: entry.status }; @@ -204,16 +223,18 @@ export class TessDurableSessionRepository implements DurableSessionStore { for (let attempt = 0; attempt < 3; attempt += 1) { const candidate = await this.db .select() - .from(tessOutbox) - .where(and(eq(tessOutbox.sessionId, sessionId), eq(tessOutbox.status, 'pending'))) - .orderBy(asc(tessOutbox.createdAt)) + .from(interactionOutbox) + .where( + and(eq(interactionOutbox.sessionId, sessionId), eq(interactionOutbox.status, 'pending')), + ) + .orderBy(asc(interactionOutbox.createdAt)) .limit(1); const entry = candidate[0]; if (!entry) return null; const claimed = await this.db - .update(tessOutbox) + .update(interactionOutbox) .set({ status: 'processing', updatedAt: new Date() }) - .where(and(eq(tessOutbox.id, entry.id), eq(tessOutbox.status, 'pending'))) + .where(and(eq(interactionOutbox.id, entry.id), eq(interactionOutbox.status, 'pending'))) .returning(); if (claimed[0]) return toOutbox(claimed[0]); } @@ -225,13 +246,13 @@ export class TessDurableSessionRepository implements DurableSessionStore { idempotencyKey: string, ): Promise { const claimed = await this.db - .update(tessOutbox) + .update(interactionOutbox) .set({ status: 'processing', updatedAt: new Date() }) .where( and( - eq(tessOutbox.sessionId, sessionId), - eq(tessOutbox.idempotencyKey, idempotencyKey), - eq(tessOutbox.status, 'pending'), + eq(interactionOutbox.sessionId, sessionId), + eq(interactionOutbox.idempotencyKey, idempotencyKey), + eq(interactionOutbox.status, 'pending'), ), ) .returning(); @@ -240,26 +261,26 @@ export class TessDurableSessionRepository implements DurableSessionStore { async completeOutbox(sessionId: string, idempotencyKey: string): Promise { await this.db - .update(tessOutbox) + .update(interactionOutbox) .set({ status: 'delivered', updatedAt: new Date() }) .where( and( - eq(tessOutbox.sessionId, sessionId), - eq(tessOutbox.idempotencyKey, idempotencyKey), - eq(tessOutbox.status, 'processing'), + eq(interactionOutbox.sessionId, sessionId), + eq(interactionOutbox.idempotencyKey, idempotencyKey), + eq(interactionOutbox.status, 'processing'), ), ); } async releaseOutbox(sessionId: string, idempotencyKey: string): Promise { await this.db - .update(tessOutbox) + .update(interactionOutbox) .set({ status: 'pending', updatedAt: new Date() }) .where( and( - eq(tessOutbox.sessionId, sessionId), - eq(tessOutbox.idempotencyKey, idempotencyKey), - eq(tessOutbox.status, 'processing'), + eq(interactionOutbox.sessionId, sessionId), + eq(interactionOutbox.idempotencyKey, idempotencyKey), + eq(interactionOutbox.status, 'processing'), ), ); } @@ -271,14 +292,14 @@ export class TessDurableSessionRepository implements DurableSessionStore { summary: redactSensitiveContent(input.summary).content, }; const inserted = await this.db - .insert(tessCheckpoints) + .insert(interactionCheckpoints) .values({ ...checkpoint, cursor: seal(checkpoint.cursor), summary: seal(checkpoint.summary), }) .onConflictDoNothing() - .returning({ checkpointId: tessCheckpoints.checkpointId }); + .returning({ checkpointId: interactionCheckpoints.checkpointId }); if (inserted[0]) return; const existing = await this.findCheckpoint(input.sessionId, input.checkpointId); @@ -290,11 +311,11 @@ export class TessDurableSessionRepository implements DurableSessionStore { async findCheckpoint(sessionId: string, checkpointId: string): Promise { const checkpoints = await this.db .select() - .from(tessCheckpoints) + .from(interactionCheckpoints) .where( and( - eq(tessCheckpoints.sessionId, sessionId), - eq(tessCheckpoints.checkpointId, checkpointId), + eq(interactionCheckpoints.sessionId, sessionId), + eq(interactionCheckpoints.checkpointId, checkpointId), ), ) .limit(1); @@ -308,10 +329,10 @@ export class TessDurableSessionRepository implements DurableSessionStore { throw new Error(`Durable Tess handoff checkpoint is unavailable: ${input.checkpointId}`); } const inserted = await this.db - .insert(tessHandoffs) + .insert(interactionHandoffs) .values({ ...input }) .onConflictDoNothing() - .returning({ handoffId: tessHandoffs.handoffId }); + .returning({ handoffId: interactionHandoffs.handoffId }); if (inserted[0]) return; const existing = await this.findHandoff(input.handoffId); @@ -323,8 +344,8 @@ export class TessDurableSessionRepository implements DurableSessionStore { async findHandoff(handoffId: string): Promise { const handoffs = await this.db .select() - .from(tessHandoffs) - .where(eq(tessHandoffs.handoffId, handoffId)) + .from(interactionHandoffs) + .where(eq(interactionHandoffs.handoffId, handoffId)) .limit(1); const handoff = handoffs[0]; return handoff ? toHandoff(handoff) : null; @@ -335,20 +356,23 @@ export class TessDurableSessionRepository implements DurableSessionStore { // reached an external target before a crash, so it is deliberately not // replayed by generic recovery. await this.db - .update(tessInbox) + .update(interactionInbox) .set({ status: 'pending', updatedAt: new Date() }) - .where(and(eq(tessInbox.sessionId, sessionId), eq(tessInbox.status, 'processing'))); + .where( + and(eq(interactionInbox.sessionId, sessionId), eq(interactionInbox.status, 'processing')), + ); } private async session(sessionId: string): Promise { const sessions = await this.db .select() - .from(tessSessions) - .where(eq(tessSessions.id, sessionId)) + .from(interactionSessions) + .where(eq(interactionSessions.id, sessionId)) .limit(1); const session = sessions[0]; return session ? { + agentName: session.agentName, sessionId: session.id, tenantId: session.tenantId, ownerId: session.ownerId, @@ -359,8 +383,13 @@ export class TessDurableSessionRepository implements DurableSessionStore { } } +function contentDigest(content: string): string { + return createHash('sha256').update(content).digest('hex'); +} + function sameIdentity(left: DurableSessionIdentity, right: DurableSessionIdentity): boolean { return ( + left.agentName === right.agentName && left.sessionId === right.sessionId && left.tenantId === right.tenantId && left.ownerId === right.ownerId && @@ -410,7 +439,7 @@ function sameHandoff(left: DurableHandoff, right: DurableHandoffInput): boolean ); } -function toInbox(row: typeof tessInbox.$inferSelect): DurableInboxEntry { +function toInbox(row: typeof interactionInbox.$inferSelect): DurableInboxEntry { return { sessionId: row.sessionId, idempotencyKey: row.idempotencyKey, @@ -420,7 +449,7 @@ function toInbox(row: typeof tessInbox.$inferSelect): DurableInboxEntry { }; } -function toOutbox(row: typeof tessOutbox.$inferSelect): DurableOutboxEntry { +function toOutbox(row: typeof interactionOutbox.$inferSelect): DurableOutboxEntry { return { sessionId: row.sessionId, idempotencyKey: row.idempotencyKey, @@ -432,7 +461,7 @@ function toOutbox(row: typeof tessOutbox.$inferSelect): DurableOutboxEntry { }; } -function toCheckpoint(row: typeof tessCheckpoints.$inferSelect): DurableCheckpoint { +function toCheckpoint(row: typeof interactionCheckpoints.$inferSelect): DurableCheckpoint { return { sessionId: row.sessionId, checkpointId: row.checkpointId, @@ -442,7 +471,7 @@ function toCheckpoint(row: typeof tessCheckpoints.$inferSelect): DurableCheckpoi }; } -function toHandoff(row: typeof tessHandoffs.$inferSelect): DurableHandoff { +function toHandoff(row: typeof interactionHandoffs.$inferSelect): DurableHandoff { return { sessionId: row.sessionId, handoffId: row.handoffId, diff --git a/apps/gateway/src/commands/command-authorization.service.spec.ts b/apps/gateway/src/commands/command-authorization.service.spec.ts index 831ffbb..315d902 100644 --- a/apps/gateway/src/commands/command-authorization.service.spec.ts +++ b/apps/gateway/src/commands/command-authorization.service.spec.ts @@ -70,11 +70,12 @@ describe('CommandAuthorizationService', () => { tenantId: 'tenant-1', channelId: 'discord:operator', correlationId: 'correlation-malformed-expiry', + agentName: 'Nova', }; const service = createService('admin', entries); const approval = await service.createRuntimeTerminationApproval(action); expect(approval).not.toBeNull(); - const key = `tess:command-approval:${approval!.approvalId}`; + const key = `agent:Nova:command-approval:${approval!.approvalId}`; const stored = entries.get(key); expect(stored).toBeDefined(); entries.set(key, JSON.stringify({ ...JSON.parse(stored!), expiresAt: 'not-a-date' })); @@ -93,6 +94,7 @@ describe('CommandAuthorizationService', () => { tenantId: 'tenant-1', channelId: 'discord:operator', correlationId: 'correlation-1', + agentName: 'Nova', }; const beforeRestart = createService('admin', entries); const approval = await beforeRestart.createRuntimeTerminationApproval(action); diff --git a/apps/gateway/src/commands/command-authorization.service.ts b/apps/gateway/src/commands/command-authorization.service.ts index 4228a18..a9f829e 100644 --- a/apps/gateway/src/commands/command-authorization.service.ts +++ b/apps/gateway/src/commands/command-authorization.service.ts @@ -23,6 +23,8 @@ export interface RuntimeTerminationApprovalAction { tenantId: string; channelId: string; correlationId: string; + /** Provisioned roster identity; isolates approvals between interaction agents. */ + agentName: string; } export interface RuntimeTerminationApproval extends RuntimeTerminationApprovalAction { @@ -92,7 +94,7 @@ export class CommandAuthorizationService { } /** - * Uses the same `tess:command-approval:*` store and one-time deletion rule as + * Uses the same `interaction:command-approval:*` store and one-time deletion rule as * command approvals. This deliberately avoids a parallel approval database. */ async createRuntimeTerminationApproval( @@ -108,7 +110,12 @@ export class CommandAuthorizationService { ...action, expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(), }; - await this.redis.set(this.key(approval.approvalId), JSON.stringify(approval), 'EX', '300'); + await this.redis.set( + this.runtimeKey(action.agentName, approval.approvalId), + JSON.stringify(approval), + 'EX', + '300', + ); return approval; } @@ -116,7 +123,7 @@ export class CommandAuthorizationService { approvalId: string, action: RuntimeTerminationApprovalAction, ): Promise { - const encoded = await this.redis.get(this.key(approvalId)); + const encoded = await this.redis.get(this.runtimeKey(action.agentName, approvalId)); if (!encoded) return false; let approval: unknown; try { @@ -134,7 +141,7 @@ export class CommandAuthorizationService { return false; } if ((await this.resolveRole(approval.actorId)) !== 'admin') return false; - return (await this.redis.del(this.key(approvalId))) === 1; + return (await this.redis.del(this.runtimeKey(action.agentName, approvalId))) === 1; } private async resolveRole(actorId: string): Promise { @@ -196,6 +203,7 @@ export class CommandAuthorizationService { action.tenantId, action.channelId, action.correlationId, + action.agentName, ].every((value: string): boolean => value.trim().length > 0); } @@ -209,6 +217,7 @@ export class CommandAuthorizationService { tenantId: action.tenantId, channelId: action.channelId, correlationId: action.correlationId, + agentName: action.agentName, }), ) .digest('hex'); @@ -244,11 +253,16 @@ export class CommandAuthorizationService { 'sessionId' in value && 'channelId' in value && 'correlationId' in value && + 'agentName' in value && 'expiresAt' in value ); } private key(approvalId: string): string { - return `tess:command-approval:${approvalId}`; + return `interaction:command-approval:${approvalId}`; + } + + private runtimeKey(agentName: string, approvalId: string): string { + return `agent:${encodeURIComponent(agentName)}:command-approval:${approvalId}`; } } diff --git a/apps/gateway/src/commands/runtime-approval-verifier.ts b/apps/gateway/src/commands/runtime-approval-verifier.ts index b16294c..47e0973 100644 --- a/apps/gateway/src/commands/runtime-approval-verifier.ts +++ b/apps/gateway/src/commands/runtime-approval-verifier.ts @@ -7,7 +7,7 @@ import { CommandAuthorizationService } from './command-authorization.service.js' /** * Adapter from the provider registry's exact termination action to the shared, - * Redis-backed `tess:command-approval:*` store. It has no separate approval + * Redis-backed `interaction:command-approval:*` store. It has no separate approval * persistence or replay semantics. */ @Injectable() diff --git a/apps/gateway/src/gc/session-gc.service.spec.ts b/apps/gateway/src/gc/session-gc.service.spec.ts index c6f3948..8c5ab6a 100644 --- a/apps/gateway/src/gc/session-gc.service.spec.ts +++ b/apps/gateway/src/gc/session-gc.service.spec.ts @@ -108,7 +108,7 @@ describe('SessionGCService', () => { } as never; const payload = { command: 'gc', conversationId: 'owned' }; const approval = await authorization.createApproval(command, payload, 'admin-1'); - const approvalKey = `tess:command-approval:${approval!.approvalId}`; + const approvalKey = `interaction:command-approval:${approval!.approvalId}`; const gc = new SessionGCService(redis as never, mockLogService as unknown as LogService); await gc.collect('owned'); diff --git a/docs/tess/ARCHITECTURE.md b/docs/tess/ARCHITECTURE.md index 858945e..31ae36d 100644 --- a/docs/tess/ARCHITECTURE.md +++ b/docs/tess/ARCHITECTURE.md @@ -41,7 +41,7 @@ Every call receives an immutable, server-derived actor/tenant/channel scope and `@mosaicstack/agent` owns the explicit `AgentRuntimeProviderRegistry`; duplicate provider IDs are rejected rather than replaced. Gateway owns `RuntimeProviderService`, which creates a frozen `RuntimeScope` from authenticated `ActorTenantScope` and trusted ingress channel/correlation metadata before every provider call. The service checks the declared provider capability before invoking a side effect and records metadata-only audit events (`providerId`, operation, outcome, actor/tenant/channel, correlation, and resource ID). It never records message bodies, idempotency keys, or approval references. -Termination is fail-closed: a runtime approval verifier consumes a one-time, exact action binding for the provider, session, actor, tenant, channel, and correlation ID before `terminate` reaches a provider. The verifier reuses the Redis-backed `tess:command-approval:*` store and its expiry/delete-on-consume semantics; it has no parallel approval store. This internal service introduces no HTTP endpoint; later Discord, CLI, MCP, and provider adapters consume the same gateway boundary. +Termination is fail-closed: a runtime approval verifier consumes a one-time, exact action binding for the provider, session, actor, tenant, channel, and correlation ID before `terminate` reaches a provider. The verifier reuses the Redis-backed `interaction:command-approval:*` store and its expiry/delete-on-consume semantics; it has no parallel approval store. This internal service introduces no HTTP endpoint; later Discord, CLI, MCP, and provider adapters consume the same gateway boundary. ## Authority Model @@ -61,7 +61,7 @@ Valkey holds the existing short-lived, one-time command-approval records; Postgr ### M2 Durable Recovery `@mosaicstack/agent` owns a transport-neutral state machine and `apps/gateway` provides its -PostgreSQL adapter. `tess_sessions` holds immutable identity; inbox/outbox records use a +PostgreSQL adapter. `interaction_sessions` holds immutable identity; inbox/outbox records use a per-session unique idempotency key and transition `pending → processing → processed|delivered`. Checkpoints are immutable history scoped by session and checkpoint ID: the latest checkpoint supports compaction recovery, while a handoff always resolves the exact checkpoint it references. diff --git a/packages/agent/src/tess-durable-session.test.ts b/packages/agent/src/tess-durable-session.test.ts index dd2c500..64e5ca8 100644 --- a/packages/agent/src/tess-durable-session.test.ts +++ b/packages/agent/src/tess-durable-session.test.ts @@ -6,6 +6,7 @@ import { } from './tess-durable-session.js'; const IDENTITY: DurableSessionIdentity = { + agentName: 'Nova', sessionId: 'tess-session-1', tenantId: 'tenant-1', ownerId: 'owner-1', diff --git a/packages/agent/src/tess-durable-session.ts b/packages/agent/src/tess-durable-session.ts index d259640..039fe06 100644 --- a/packages/agent/src/tess-durable-session.ts +++ b/packages/agent/src/tess-durable-session.ts @@ -1,4 +1,6 @@ export interface DurableSessionIdentity { + /** Provisioned roster identity; isolates durable state between named agents. */ + agentName: string; sessionId: string; tenantId: string; ownerId: string; @@ -220,7 +222,13 @@ export class DurableSessionCoordinator { } private assertIdentity(identity: DurableSessionIdentity): void { - this.assertRecord(identity.sessionId, identity.tenantId, identity.ownerId, identity.providerId); + this.assertRecord( + identity.agentName, + identity.sessionId, + identity.tenantId, + identity.ownerId, + identity.providerId, + ); if (identity.runtimeSessionId.trim().length === 0) { throw new Error('Durable runtime session identity is required'); } @@ -410,6 +418,7 @@ export class InMemoryDurableSessionStore implements DurableSessionStore { function identitiesEqual(left: DurableSessionIdentity, right: DurableSessionIdentity): boolean { return ( + left.agentName === right.agentName && left.sessionId === right.sessionId && left.tenantId === right.tenantId && left.ownerId === right.ownerId && diff --git a/packages/db/drizzle/0012_interaction_durable_state.sql b/packages/db/drizzle/0012_interaction_durable_state.sql new file mode 100644 index 0000000..36d8680 --- /dev/null +++ b/packages/db/drizzle/0012_interaction_durable_state.sql @@ -0,0 +1,71 @@ +CREATE TYPE "public"."interaction_handoff_status" AS ENUM('pending', 'accepted');--> statement-breakpoint +CREATE TYPE "public"."interaction_inbox_status" AS ENUM('pending', 'processing', 'processed');--> statement-breakpoint +CREATE TYPE "public"."interaction_outbox_status" AS ENUM('pending', 'processing', 'delivered');--> statement-breakpoint +CREATE TABLE "interaction_checkpoints" ( + "session_id" text PRIMARY KEY NOT NULL, + "checkpoint_id" text NOT NULL, + "cursor" text NOT NULL, + "summary" text NOT NULL, + "compaction_epoch" integer NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "interaction_checkpoints_checkpoint_id_unique" UNIQUE("checkpoint_id") +); +--> statement-breakpoint +CREATE TABLE "interaction_handoffs" ( + "handoff_id" text PRIMARY KEY NOT NULL, + "session_id" text NOT NULL, + "destination" text NOT NULL, + "correlation_id" text NOT NULL, + "checkpoint_id" text NOT NULL, + "status" "interaction_handoff_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 +); +--> statement-breakpoint +CREATE TABLE "interaction_inbox" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" text NOT NULL, + "idempotency_key" text NOT NULL, + "correlation_id" text NOT NULL, + "content" text NOT NULL, + "content_digest" text NOT NULL, + "status" "interaction_inbox_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 +); +--> statement-breakpoint +CREATE TABLE "interaction_outbox" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "session_id" text NOT NULL, + "idempotency_key" text NOT NULL, + "correlation_id" text NOT NULL, + "kind" text NOT NULL, + "content" text NOT NULL, + "content_digest" text NOT NULL, + "status" "interaction_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 +); +--> statement-breakpoint +CREATE TABLE "interaction_sessions" ( + "id" text PRIMARY KEY NOT NULL, + "agent_name" text NOT NULL, + "tenant_id" text NOT NULL, + "owner_id" text NOT NULL, + "provider_id" text NOT NULL, + "runtime_session_id" text NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "interaction_checkpoints" ADD CONSTRAINT "interaction_checkpoints_session_id_interaction_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."interaction_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "interaction_handoffs" ADD CONSTRAINT "interaction_handoffs_session_id_interaction_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."interaction_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "interaction_inbox" ADD CONSTRAINT "interaction_inbox_session_id_interaction_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."interaction_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "interaction_outbox" ADD CONSTRAINT "interaction_outbox_session_id_interaction_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."interaction_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "interaction_sessions" ADD CONSTRAINT "interaction_sessions_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "interaction_handoffs_session_status_idx" ON "interaction_handoffs" USING btree ("session_id","status");--> statement-breakpoint +CREATE UNIQUE INDEX "interaction_inbox_session_idempotency_idx" ON "interaction_inbox" USING btree ("session_id","idempotency_key");--> statement-breakpoint +CREATE INDEX "interaction_inbox_session_status_created_idx" ON "interaction_inbox" USING btree ("session_id","status","created_at");--> statement-breakpoint +CREATE UNIQUE INDEX "interaction_outbox_session_idempotency_idx" ON "interaction_outbox" USING btree ("session_id","idempotency_key");--> statement-breakpoint +CREATE INDEX "interaction_outbox_session_status_created_idx" ON "interaction_outbox" USING btree ("session_id","status","created_at"); \ No newline at end of file diff --git a/packages/db/drizzle/0012_tess_durable_state.sql b/packages/db/drizzle/0012_tess_durable_state.sql deleted file mode 100644 index e89f873..0000000 --- a/packages/db/drizzle/0012_tess_durable_state.sql +++ /dev/null @@ -1,68 +0,0 @@ -CREATE TYPE "public"."tess_handoff_status" AS ENUM('pending', 'accepted');--> statement-breakpoint -CREATE TYPE "public"."tess_inbox_status" AS ENUM('pending', 'processing', 'processed');--> statement-breakpoint -CREATE TYPE "public"."tess_outbox_status" AS ENUM('pending', 'processing', 'delivered');--> statement-breakpoint -CREATE TABLE "tess_checkpoints" ( - "session_id" text PRIMARY KEY NOT NULL, - "checkpoint_id" text NOT NULL, - "cursor" text NOT NULL, - "summary" text NOT NULL, - "compaction_epoch" integer NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "tess_checkpoints_checkpoint_id_unique" UNIQUE("checkpoint_id") -); ---> statement-breakpoint -CREATE TABLE "tess_handoffs" ( - "handoff_id" text PRIMARY KEY NOT NULL, - "session_id" text NOT NULL, - "destination" text NOT NULL, - "correlation_id" text NOT NULL, - "checkpoint_id" text NOT NULL, - "status" "tess_handoff_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 -); ---> statement-breakpoint -CREATE TABLE "tess_inbox" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "session_id" text NOT NULL, - "idempotency_key" text NOT NULL, - "correlation_id" text NOT NULL, - "content" text NOT NULL, - "status" "tess_inbox_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 -); ---> statement-breakpoint -CREATE TABLE "tess_outbox" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "session_id" text NOT NULL, - "idempotency_key" text NOT NULL, - "correlation_id" text NOT NULL, - "kind" text NOT NULL, - "content" text NOT NULL, - "status" "tess_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 -); ---> statement-breakpoint -CREATE TABLE "tess_sessions" ( - "id" text PRIMARY KEY NOT NULL, - "tenant_id" text NOT NULL, - "owner_id" text NOT NULL, - "provider_id" text NOT NULL, - "runtime_session_id" text NOT NULL, - "created_at" timestamp with time zone DEFAULT now() NOT NULL, - "updated_at" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "tess_checkpoints" ADD CONSTRAINT "tess_checkpoints_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "tess_handoffs" ADD CONSTRAINT "tess_handoffs_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "tess_inbox" ADD CONSTRAINT "tess_inbox_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "tess_outbox" ADD CONSTRAINT "tess_outbox_session_id_tess_sessions_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."tess_sessions"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "tess_sessions" ADD CONSTRAINT "tess_sessions_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "tess_handoffs_session_status_idx" ON "tess_handoffs" USING btree ("session_id","status");--> statement-breakpoint -CREATE UNIQUE INDEX "tess_inbox_session_idempotency_idx" ON "tess_inbox" USING btree ("session_id","idempotency_key");--> statement-breakpoint -CREATE INDEX "tess_inbox_session_status_created_idx" ON "tess_inbox" USING btree ("session_id","status","created_at");--> statement-breakpoint -CREATE UNIQUE INDEX "tess_outbox_session_idempotency_idx" ON "tess_outbox" USING btree ("session_id","idempotency_key");--> statement-breakpoint -CREATE INDEX "tess_outbox_session_status_created_idx" ON "tess_outbox" USING btree ("session_id","status","created_at"); \ No newline at end of file diff --git a/packages/db/drizzle/0013_interaction_checkpoint_history.sql b/packages/db/drizzle/0013_interaction_checkpoint_history.sql new file mode 100644 index 0000000..7fc8573 --- /dev/null +++ b/packages/db/drizzle/0013_interaction_checkpoint_history.sql @@ -0,0 +1,5 @@ +ALTER TABLE "interaction_checkpoints" DROP CONSTRAINT "interaction_checkpoints_checkpoint_id_unique";--> statement-breakpoint +ALTER TABLE "interaction_checkpoints" DROP CONSTRAINT "interaction_checkpoints_pkey";--> statement-breakpoint +ALTER TABLE "interaction_checkpoints" ADD COLUMN "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "interaction_checkpoints_session_idempotency_idx" ON "interaction_checkpoints" USING btree ("session_id","checkpoint_id");--> statement-breakpoint +CREATE INDEX "interaction_checkpoints_session_epoch_idx" ON "interaction_checkpoints" USING btree ("session_id","compaction_epoch"); \ No newline at end of file diff --git a/packages/db/drizzle/0013_tess_checkpoint_history.sql b/packages/db/drizzle/0013_tess_checkpoint_history.sql deleted file mode 100644 index f863ddc..0000000 --- a/packages/db/drizzle/0013_tess_checkpoint_history.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE "tess_checkpoints" DROP CONSTRAINT "tess_checkpoints_checkpoint_id_unique";--> statement-breakpoint -ALTER TABLE "tess_checkpoints" DROP CONSTRAINT "tess_checkpoints_pkey";--> statement-breakpoint -ALTER TABLE "tess_checkpoints" ADD COLUMN "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL;--> statement-breakpoint -CREATE UNIQUE INDEX "tess_checkpoints_session_idempotency_idx" ON "tess_checkpoints" USING btree ("session_id","checkpoint_id");--> statement-breakpoint -CREATE INDEX "tess_checkpoints_session_epoch_idx" ON "tess_checkpoints" USING btree ("session_id","compaction_epoch"); \ No newline at end of file diff --git a/packages/db/drizzle/0014_interaction_outbox_channel_scope.sql b/packages/db/drizzle/0014_interaction_outbox_channel_scope.sql new file mode 100644 index 0000000..af44cf5 --- /dev/null +++ b/packages/db/drizzle/0014_interaction_outbox_channel_scope.sql @@ -0,0 +1,5 @@ +ALTER TABLE "interaction_outbox" ADD COLUMN "channel_id" text; +--> statement-breakpoint +UPDATE "interaction_outbox" SET "channel_id" = 'legacy:unknown' WHERE "channel_id" IS NULL; +--> statement-breakpoint +ALTER TABLE "interaction_outbox" ALTER COLUMN "channel_id" SET NOT NULL; diff --git a/packages/db/drizzle/0014_tess_outbox_channel_scope.sql b/packages/db/drizzle/0014_tess_outbox_channel_scope.sql deleted file mode 100644 index 32e3c73..0000000 --- a/packages/db/drizzle/0014_tess_outbox_channel_scope.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE "tess_outbox" ADD COLUMN "channel_id" text; ---> statement-breakpoint -UPDATE "tess_outbox" SET "channel_id" = 'legacy:unknown' WHERE "channel_id" IS NULL; ---> statement-breakpoint -ALTER TABLE "tess_outbox" ALTER COLUMN "channel_id" SET NOT NULL; diff --git a/packages/db/drizzle/meta/0012_snapshot.json b/packages/db/drizzle/meta/0012_snapshot.json index 7ade9e7..721391c 100644 --- a/packages/db/drizzle/meta/0012_snapshot.json +++ b/packages/db/drizzle/meta/0012_snapshot.json @@ -3354,8 +3354,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_checkpoints": { - "name": "tess_checkpoints", + "public.interaction_checkpoints": { + "name": "interaction_checkpoints", "schema": "", "columns": { "session_id": { @@ -3405,10 +3405,10 @@ }, "indexes": {}, "foreignKeys": { - "tess_checkpoints_session_id_tess_sessions_id_fk": { - "name": "tess_checkpoints_session_id_tess_sessions_id_fk", - "tableFrom": "tess_checkpoints", - "tableTo": "tess_sessions", + "interaction_checkpoints_session_id_interaction_sessions_id_fk": { + "name": "interaction_checkpoints_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_checkpoints", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3421,8 +3421,8 @@ }, "compositePrimaryKeys": {}, "uniqueConstraints": { - "tess_checkpoints_checkpoint_id_unique": { - "name": "tess_checkpoints_checkpoint_id_unique", + "interaction_checkpoints_checkpoint_id_unique": { + "name": "interaction_checkpoints_checkpoint_id_unique", "nullsNotDistinct": false, "columns": [ "checkpoint_id" @@ -3433,8 +3433,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_handoffs": { - "name": "tess_handoffs", + "public.interaction_handoffs": { + "name": "interaction_handoffs", "schema": "", "columns": { "handoff_id": { @@ -3469,7 +3469,7 @@ }, "status": { "name": "status", - "type": "tess_handoff_status", + "type": "interaction_handoff_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3491,8 +3491,8 @@ } }, "indexes": { - "tess_handoffs_session_status_idx": { - "name": "tess_handoffs_session_status_idx", + "interaction_handoffs_session_status_idx": { + "name": "interaction_handoffs_session_status_idx", "columns": [ { "expression": "session_id", @@ -3514,10 +3514,10 @@ } }, "foreignKeys": { - "tess_handoffs_session_id_tess_sessions_id_fk": { - "name": "tess_handoffs_session_id_tess_sessions_id_fk", - "tableFrom": "tess_handoffs", - "tableTo": "tess_sessions", + "interaction_handoffs_session_id_interaction_sessions_id_fk": { + "name": "interaction_handoffs_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_handoffs", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3534,8 +3534,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_inbox": { - "name": "tess_inbox", + "public.interaction_inbox": { + "name": "interaction_inbox", "schema": "", "columns": { "id": { @@ -3571,7 +3571,7 @@ }, "status": { "name": "status", - "type": "tess_inbox_status", + "type": "interaction_inbox_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3593,8 +3593,8 @@ } }, "indexes": { - "tess_inbox_session_idempotency_idx": { - "name": "tess_inbox_session_idempotency_idx", + "interaction_inbox_session_idempotency_idx": { + "name": "interaction_inbox_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3614,8 +3614,8 @@ "method": "btree", "with": {} }, - "tess_inbox_session_status_created_idx": { - "name": "tess_inbox_session_status_created_idx", + "interaction_inbox_session_status_created_idx": { + "name": "interaction_inbox_session_status_created_idx", "columns": [ { "expression": "session_id", @@ -3643,10 +3643,10 @@ } }, "foreignKeys": { - "tess_inbox_session_id_tess_sessions_id_fk": { - "name": "tess_inbox_session_id_tess_sessions_id_fk", - "tableFrom": "tess_inbox", - "tableTo": "tess_sessions", + "interaction_inbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_inbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_inbox", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3663,8 +3663,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_outbox": { - "name": "tess_outbox", + "public.interaction_outbox": { + "name": "interaction_outbox", "schema": "", "columns": { "id": { @@ -3706,7 +3706,7 @@ }, "status": { "name": "status", - "type": "tess_outbox_status", + "type": "interaction_outbox_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3728,8 +3728,8 @@ } }, "indexes": { - "tess_outbox_session_idempotency_idx": { - "name": "tess_outbox_session_idempotency_idx", + "interaction_outbox_session_idempotency_idx": { + "name": "interaction_outbox_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3749,8 +3749,8 @@ "method": "btree", "with": {} }, - "tess_outbox_session_status_created_idx": { - "name": "tess_outbox_session_status_created_idx", + "interaction_outbox_session_status_created_idx": { + "name": "interaction_outbox_session_status_created_idx", "columns": [ { "expression": "session_id", @@ -3778,10 +3778,10 @@ } }, "foreignKeys": { - "tess_outbox_session_id_tess_sessions_id_fk": { - "name": "tess_outbox_session_id_tess_sessions_id_fk", - "tableFrom": "tess_outbox", - "tableTo": "tess_sessions", + "interaction_outbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_outbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_outbox", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3798,8 +3798,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_sessions": { - "name": "tess_sessions", + "public.interaction_sessions": { + "name": "interaction_sessions", "schema": "", "columns": { "id": { @@ -3849,9 +3849,9 @@ }, "indexes": {}, "foreignKeys": { - "tess_sessions_owner_id_users_id_fk": { - "name": "tess_sessions_owner_id_users_id_fk", - "tableFrom": "tess_sessions", + "interaction_sessions_owner_id_users_id_fk": { + "name": "interaction_sessions_owner_id_users_id_fk", + "tableFrom": "interaction_sessions", "tableTo": "users", "columnsFrom": [ "owner_id" @@ -4132,16 +4132,16 @@ "revoked" ] }, - "public.tess_handoff_status": { - "name": "tess_handoff_status", + "public.interaction_handoff_status": { + "name": "interaction_handoff_status", "schema": "public", "values": [ "pending", "accepted" ] }, - "public.tess_inbox_status": { - "name": "tess_inbox_status", + "public.interaction_inbox_status": { + "name": "interaction_inbox_status", "schema": "public", "values": [ "pending", @@ -4149,8 +4149,8 @@ "processed" ] }, - "public.tess_outbox_status": { - "name": "tess_outbox_status", + "public.interaction_outbox_status": { + "name": "interaction_outbox_status", "schema": "public", "values": [ "pending", diff --git a/packages/db/drizzle/meta/0013_snapshot.json b/packages/db/drizzle/meta/0013_snapshot.json index 4a124f5..6c85c86 100644 --- a/packages/db/drizzle/meta/0013_snapshot.json +++ b/packages/db/drizzle/meta/0013_snapshot.json @@ -3354,8 +3354,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_checkpoints": { - "name": "tess_checkpoints", + "public.interaction_checkpoints": { + "name": "interaction_checkpoints", "schema": "", "columns": { "id": { @@ -3411,8 +3411,8 @@ } }, "indexes": { - "tess_checkpoints_session_idempotency_idx": { - "name": "tess_checkpoints_session_idempotency_idx", + "interaction_checkpoints_session_idempotency_idx": { + "name": "interaction_checkpoints_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3432,8 +3432,8 @@ "method": "btree", "with": {} }, - "tess_checkpoints_session_epoch_idx": { - "name": "tess_checkpoints_session_epoch_idx", + "interaction_checkpoints_session_epoch_idx": { + "name": "interaction_checkpoints_session_epoch_idx", "columns": [ { "expression": "session_id", @@ -3455,10 +3455,10 @@ } }, "foreignKeys": { - "tess_checkpoints_session_id_tess_sessions_id_fk": { - "name": "tess_checkpoints_session_id_tess_sessions_id_fk", - "tableFrom": "tess_checkpoints", - "tableTo": "tess_sessions", + "interaction_checkpoints_session_id_interaction_sessions_id_fk": { + "name": "interaction_checkpoints_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_checkpoints", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3475,8 +3475,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_handoffs": { - "name": "tess_handoffs", + "public.interaction_handoffs": { + "name": "interaction_handoffs", "schema": "", "columns": { "handoff_id": { @@ -3511,7 +3511,7 @@ }, "status": { "name": "status", - "type": "tess_handoff_status", + "type": "interaction_handoff_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3533,8 +3533,8 @@ } }, "indexes": { - "tess_handoffs_session_status_idx": { - "name": "tess_handoffs_session_status_idx", + "interaction_handoffs_session_status_idx": { + "name": "interaction_handoffs_session_status_idx", "columns": [ { "expression": "session_id", @@ -3556,10 +3556,10 @@ } }, "foreignKeys": { - "tess_handoffs_session_id_tess_sessions_id_fk": { - "name": "tess_handoffs_session_id_tess_sessions_id_fk", - "tableFrom": "tess_handoffs", - "tableTo": "tess_sessions", + "interaction_handoffs_session_id_interaction_sessions_id_fk": { + "name": "interaction_handoffs_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_handoffs", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3576,8 +3576,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_inbox": { - "name": "tess_inbox", + "public.interaction_inbox": { + "name": "interaction_inbox", "schema": "", "columns": { "id": { @@ -3613,7 +3613,7 @@ }, "status": { "name": "status", - "type": "tess_inbox_status", + "type": "interaction_inbox_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3635,8 +3635,8 @@ } }, "indexes": { - "tess_inbox_session_idempotency_idx": { - "name": "tess_inbox_session_idempotency_idx", + "interaction_inbox_session_idempotency_idx": { + "name": "interaction_inbox_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3656,8 +3656,8 @@ "method": "btree", "with": {} }, - "tess_inbox_session_status_created_idx": { - "name": "tess_inbox_session_status_created_idx", + "interaction_inbox_session_status_created_idx": { + "name": "interaction_inbox_session_status_created_idx", "columns": [ { "expression": "session_id", @@ -3685,10 +3685,10 @@ } }, "foreignKeys": { - "tess_inbox_session_id_tess_sessions_id_fk": { - "name": "tess_inbox_session_id_tess_sessions_id_fk", - "tableFrom": "tess_inbox", - "tableTo": "tess_sessions", + "interaction_inbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_inbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_inbox", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3705,8 +3705,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_outbox": { - "name": "tess_outbox", + "public.interaction_outbox": { + "name": "interaction_outbox", "schema": "", "columns": { "id": { @@ -3748,7 +3748,7 @@ }, "status": { "name": "status", - "type": "tess_outbox_status", + "type": "interaction_outbox_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3770,8 +3770,8 @@ } }, "indexes": { - "tess_outbox_session_idempotency_idx": { - "name": "tess_outbox_session_idempotency_idx", + "interaction_outbox_session_idempotency_idx": { + "name": "interaction_outbox_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3791,8 +3791,8 @@ "method": "btree", "with": {} }, - "tess_outbox_session_status_created_idx": { - "name": "tess_outbox_session_status_created_idx", + "interaction_outbox_session_status_created_idx": { + "name": "interaction_outbox_session_status_created_idx", "columns": [ { "expression": "session_id", @@ -3820,10 +3820,10 @@ } }, "foreignKeys": { - "tess_outbox_session_id_tess_sessions_id_fk": { - "name": "tess_outbox_session_id_tess_sessions_id_fk", - "tableFrom": "tess_outbox", - "tableTo": "tess_sessions", + "interaction_outbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_outbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_outbox", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3840,8 +3840,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_sessions": { - "name": "tess_sessions", + "public.interaction_sessions": { + "name": "interaction_sessions", "schema": "", "columns": { "id": { @@ -3891,9 +3891,9 @@ }, "indexes": {}, "foreignKeys": { - "tess_sessions_owner_id_users_id_fk": { - "name": "tess_sessions_owner_id_users_id_fk", - "tableFrom": "tess_sessions", + "interaction_sessions_owner_id_users_id_fk": { + "name": "interaction_sessions_owner_id_users_id_fk", + "tableFrom": "interaction_sessions", "tableTo": "users", "columnsFrom": [ "owner_id" @@ -4174,16 +4174,16 @@ "revoked" ] }, - "public.tess_handoff_status": { - "name": "tess_handoff_status", + "public.interaction_handoff_status": { + "name": "interaction_handoff_status", "schema": "public", "values": [ "pending", "accepted" ] }, - "public.tess_inbox_status": { - "name": "tess_inbox_status", + "public.interaction_inbox_status": { + "name": "interaction_inbox_status", "schema": "public", "values": [ "pending", @@ -4191,8 +4191,8 @@ "processed" ] }, - "public.tess_outbox_status": { - "name": "tess_outbox_status", + "public.interaction_outbox_status": { + "name": "interaction_outbox_status", "schema": "public", "values": [ "pending", diff --git a/packages/db/drizzle/meta/0014_snapshot.json b/packages/db/drizzle/meta/0014_snapshot.json index 249e99a..b60ca23 100644 --- a/packages/db/drizzle/meta/0014_snapshot.json +++ b/packages/db/drizzle/meta/0014_snapshot.json @@ -3354,8 +3354,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_checkpoints": { - "name": "tess_checkpoints", + "public.interaction_checkpoints": { + "name": "interaction_checkpoints", "schema": "", "columns": { "id": { @@ -3411,8 +3411,8 @@ } }, "indexes": { - "tess_checkpoints_session_idempotency_idx": { - "name": "tess_checkpoints_session_idempotency_idx", + "interaction_checkpoints_session_idempotency_idx": { + "name": "interaction_checkpoints_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3432,8 +3432,8 @@ "method": "btree", "with": {} }, - "tess_checkpoints_session_epoch_idx": { - "name": "tess_checkpoints_session_epoch_idx", + "interaction_checkpoints_session_epoch_idx": { + "name": "interaction_checkpoints_session_epoch_idx", "columns": [ { "expression": "session_id", @@ -3455,10 +3455,10 @@ } }, "foreignKeys": { - "tess_checkpoints_session_id_tess_sessions_id_fk": { - "name": "tess_checkpoints_session_id_tess_sessions_id_fk", - "tableFrom": "tess_checkpoints", - "tableTo": "tess_sessions", + "interaction_checkpoints_session_id_interaction_sessions_id_fk": { + "name": "interaction_checkpoints_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_checkpoints", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3475,8 +3475,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_handoffs": { - "name": "tess_handoffs", + "public.interaction_handoffs": { + "name": "interaction_handoffs", "schema": "", "columns": { "handoff_id": { @@ -3511,7 +3511,7 @@ }, "status": { "name": "status", - "type": "tess_handoff_status", + "type": "interaction_handoff_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3533,8 +3533,8 @@ } }, "indexes": { - "tess_handoffs_session_status_idx": { - "name": "tess_handoffs_session_status_idx", + "interaction_handoffs_session_status_idx": { + "name": "interaction_handoffs_session_status_idx", "columns": [ { "expression": "session_id", @@ -3556,10 +3556,10 @@ } }, "foreignKeys": { - "tess_handoffs_session_id_tess_sessions_id_fk": { - "name": "tess_handoffs_session_id_tess_sessions_id_fk", - "tableFrom": "tess_handoffs", - "tableTo": "tess_sessions", + "interaction_handoffs_session_id_interaction_sessions_id_fk": { + "name": "interaction_handoffs_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_handoffs", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3576,8 +3576,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_inbox": { - "name": "tess_inbox", + "public.interaction_inbox": { + "name": "interaction_inbox", "schema": "", "columns": { "id": { @@ -3613,7 +3613,7 @@ }, "status": { "name": "status", - "type": "tess_inbox_status", + "type": "interaction_inbox_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3635,8 +3635,8 @@ } }, "indexes": { - "tess_inbox_session_idempotency_idx": { - "name": "tess_inbox_session_idempotency_idx", + "interaction_inbox_session_idempotency_idx": { + "name": "interaction_inbox_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3656,8 +3656,8 @@ "method": "btree", "with": {} }, - "tess_inbox_session_status_created_idx": { - "name": "tess_inbox_session_status_created_idx", + "interaction_inbox_session_status_created_idx": { + "name": "interaction_inbox_session_status_created_idx", "columns": [ { "expression": "session_id", @@ -3685,10 +3685,10 @@ } }, "foreignKeys": { - "tess_inbox_session_id_tess_sessions_id_fk": { - "name": "tess_inbox_session_id_tess_sessions_id_fk", - "tableFrom": "tess_inbox", - "tableTo": "tess_sessions", + "interaction_inbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_inbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_inbox", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3705,8 +3705,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_outbox": { - "name": "tess_outbox", + "public.interaction_outbox": { + "name": "interaction_outbox", "schema": "", "columns": { "id": { @@ -3754,7 +3754,7 @@ }, "status": { "name": "status", - "type": "tess_outbox_status", + "type": "interaction_outbox_status", "typeSchema": "public", "primaryKey": false, "notNull": true, @@ -3776,8 +3776,8 @@ } }, "indexes": { - "tess_outbox_session_idempotency_idx": { - "name": "tess_outbox_session_idempotency_idx", + "interaction_outbox_session_idempotency_idx": { + "name": "interaction_outbox_session_idempotency_idx", "columns": [ { "expression": "session_id", @@ -3797,8 +3797,8 @@ "method": "btree", "with": {} }, - "tess_outbox_session_status_created_idx": { - "name": "tess_outbox_session_status_created_idx", + "interaction_outbox_session_status_created_idx": { + "name": "interaction_outbox_session_status_created_idx", "columns": [ { "expression": "session_id", @@ -3826,10 +3826,10 @@ } }, "foreignKeys": { - "tess_outbox_session_id_tess_sessions_id_fk": { - "name": "tess_outbox_session_id_tess_sessions_id_fk", - "tableFrom": "tess_outbox", - "tableTo": "tess_sessions", + "interaction_outbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_outbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_outbox", + "tableTo": "interaction_sessions", "columnsFrom": [ "session_id" ], @@ -3846,8 +3846,8 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.tess_sessions": { - "name": "tess_sessions", + "public.interaction_sessions": { + "name": "interaction_sessions", "schema": "", "columns": { "id": { @@ -3897,9 +3897,9 @@ }, "indexes": {}, "foreignKeys": { - "tess_sessions_owner_id_users_id_fk": { - "name": "tess_sessions_owner_id_users_id_fk", - "tableFrom": "tess_sessions", + "interaction_sessions_owner_id_users_id_fk": { + "name": "interaction_sessions_owner_id_users_id_fk", + "tableFrom": "interaction_sessions", "tableTo": "users", "columnsFrom": [ "owner_id" @@ -4180,16 +4180,16 @@ "revoked" ] }, - "public.tess_handoff_status": { - "name": "tess_handoff_status", + "public.interaction_handoff_status": { + "name": "interaction_handoff_status", "schema": "public", "values": [ "pending", "accepted" ] }, - "public.tess_inbox_status": { - "name": "tess_inbox_status", + "public.interaction_inbox_status": { + "name": "interaction_inbox_status", "schema": "public", "values": [ "pending", @@ -4197,8 +4197,8 @@ "processed" ] }, - "public.tess_outbox_status": { - "name": "tess_outbox_status", + "public.interaction_outbox_status": { + "name": "interaction_outbox_status", "schema": "public", "values": [ "pending", diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index b5b1dd1..5d842b5 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -90,21 +90,21 @@ "idx": 12, "version": "7", "when": 1783911983447, - "tag": "0012_tess_durable_state", + "tag": "0012_interaction_durable_state", "breakpoints": true }, { "idx": 13, "version": "7", "when": 1783913232578, - "tag": "0013_tess_checkpoint_history", + "tag": "0013_interaction_checkpoint_history", "breakpoints": true }, { "idx": 14, "version": "7", "when": 1783913398006, - "tag": "0014_tess_outbox_channel_scope", + "tag": "0014_interaction_outbox_channel_scope", "breakpoints": true } ] diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index c085a7c..abd0ff8 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -491,20 +491,24 @@ export const agentLogs = pgTable( // PostgreSQL is canonical for restart-safe Tess session recovery. The state // machine lives in @mosaicstack/agent; these records are its durable adapter. -export const tessInboxStatusEnum = pgEnum('tess_inbox_status', [ +export const interactionInboxStatusEnum = pgEnum('interaction_inbox_status', [ 'pending', 'processing', 'processed', ]); -export const tessOutboxStatusEnum = pgEnum('tess_outbox_status', [ +export const interactionOutboxStatusEnum = pgEnum('interaction_outbox_status', [ 'pending', 'processing', 'delivered', ]); -export const tessHandoffStatusEnum = pgEnum('tess_handoff_status', ['pending', 'accepted']); +export const interactionHandoffStatusEnum = pgEnum('interaction_handoff_status', [ + 'pending', + 'accepted', +]); -export const tessSessions = pgTable('tess_sessions', { +export const interactionSessions = pgTable('interaction_sessions', { id: text('id').primaryKey(), + agentName: text('agent_name').notNull(), tenantId: text('tenant_id').notNull(), ownerId: text('owner_id') .notNull() @@ -515,55 +519,57 @@ export const tessSessions = pgTable('tess_sessions', { updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); -export const tessInbox = pgTable( - 'tess_inbox', +export const interactionInbox = pgTable( + 'interaction_inbox', { id: uuid('id').primaryKey().defaultRandom(), sessionId: text('session_id') .notNull() - .references(() => tessSessions.id, { onDelete: 'cascade' }), + .references(() => interactionSessions.id, { onDelete: 'cascade' }), idempotencyKey: text('idempotency_key').notNull(), correlationId: text('correlation_id').notNull(), content: text('content').notNull(), - status: tessInboxStatusEnum('status').notNull().default('pending'), + contentDigest: text('content_digest').notNull(), + status: interactionInboxStatusEnum('status').notNull().default('pending'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ - uniqueIndex('tess_inbox_session_idempotency_idx').on(t.sessionId, t.idempotencyKey), - index('tess_inbox_session_status_created_idx').on(t.sessionId, t.status, t.createdAt), + uniqueIndex('interaction_inbox_session_idempotency_idx').on(t.sessionId, t.idempotencyKey), + index('interaction_inbox_session_status_created_idx').on(t.sessionId, t.status, t.createdAt), ], ); -export const tessOutbox = pgTable( - 'tess_outbox', +export const interactionOutbox = pgTable( + 'interaction_outbox', { id: uuid('id').primaryKey().defaultRandom(), sessionId: text('session_id') .notNull() - .references(() => tessSessions.id, { onDelete: 'cascade' }), + .references(() => interactionSessions.id, { onDelete: 'cascade' }), idempotencyKey: text('idempotency_key').notNull(), correlationId: text('correlation_id').notNull(), channelId: text('channel_id').notNull(), kind: text('kind').notNull(), content: text('content').notNull(), - status: tessOutboxStatusEnum('status').notNull().default('pending'), + contentDigest: text('content_digest').notNull(), + status: interactionOutboxStatusEnum('status').notNull().default('pending'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ - uniqueIndex('tess_outbox_session_idempotency_idx').on(t.sessionId, t.idempotencyKey), - index('tess_outbox_session_status_created_idx').on(t.sessionId, t.status, t.createdAt), + uniqueIndex('interaction_outbox_session_idempotency_idx').on(t.sessionId, t.idempotencyKey), + index('interaction_outbox_session_status_created_idx').on(t.sessionId, t.status, t.createdAt), ], ); -export const tessCheckpoints = pgTable( - 'tess_checkpoints', +export const interactionCheckpoints = pgTable( + 'interaction_checkpoints', { id: uuid('id').primaryKey().defaultRandom(), sessionId: text('session_id') .notNull() - .references(() => tessSessions.id, { onDelete: 'cascade' }), + .references(() => interactionSessions.id, { onDelete: 'cascade' }), checkpointId: text('checkpoint_id').notNull(), cursor: text('cursor').notNull(), summary: text('summary').notNull(), @@ -572,26 +578,26 @@ export const tessCheckpoints = pgTable( updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (t) => [ - uniqueIndex('tess_checkpoints_session_idempotency_idx').on(t.sessionId, t.checkpointId), - index('tess_checkpoints_session_epoch_idx').on(t.sessionId, t.compactionEpoch), + uniqueIndex('interaction_checkpoints_session_idempotency_idx').on(t.sessionId, t.checkpointId), + index('interaction_checkpoints_session_epoch_idx').on(t.sessionId, t.compactionEpoch), ], ); -export const tessHandoffs = pgTable( - 'tess_handoffs', +export const interactionHandoffs = pgTable( + 'interaction_handoffs', { handoffId: text('handoff_id').primaryKey(), sessionId: text('session_id') .notNull() - .references(() => tessSessions.id, { onDelete: 'cascade' }), + .references(() => interactionSessions.id, { onDelete: 'cascade' }), destination: text('destination').notNull(), correlationId: text('correlation_id').notNull(), checkpointId: text('checkpoint_id').notNull(), - status: tessHandoffStatusEnum('status').notNull().default('pending'), + status: interactionHandoffStatusEnum('status').notNull().default('pending'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, - (t) => [index('tess_handoffs_session_status_idx').on(t.sessionId, t.status)], + (t) => [index('interaction_handoffs_session_status_idx').on(t.sessionId, t.status)], ); // ─── Skills ───────────────────────────────────────────────────────────────── -- 2.49.1 From cbdc38af954d49016b5fdc4a6dab0497317b6a1c Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 13 Jul 2026 00:00:03 -0500 Subject: [PATCH 3/3] fix(tess): secure checkpoint idempotency --- .../tess-durable-session.repository.test.ts | 101 +- .../agent/tess-durable-session.repository.ts | 54 +- ..._interaction_checkpoint_payload_digest.sql | 3 + packages/db/drizzle/meta/0015_snapshot.json | 4244 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + packages/db/src/schema.ts | 1 + 6 files changed, 4401 insertions(+), 9 deletions(-) create mode 100644 packages/db/drizzle/0015_interaction_checkpoint_payload_digest.sql create mode 100644 packages/db/drizzle/meta/0015_snapshot.json diff --git a/apps/gateway/src/agent/tess-durable-session.repository.test.ts b/apps/gateway/src/agent/tess-durable-session.repository.test.ts index 1cd1d3b..c194499 100644 --- a/apps/gateway/src/agent/tess-durable-session.repository.test.ts +++ b/apps/gateway/src/agent/tess-durable-session.repository.test.ts @@ -1,7 +1,8 @@ import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { eq, sql, interactionInbox } from '@mosaicstack/db'; +import { createHash } from 'node:crypto'; +import { eq, sql, interactionCheckpoints, interactionInbox } from '@mosaicstack/db'; import { DurableSessionCoordinator, type DurableSessionIdentity } from '@mosaicstack/agent'; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import { createPgliteDb, runPgliteMigrations, type DbHandle } from '@mosaicstack/db'; @@ -155,6 +156,104 @@ describe('TessDurableSessionRepository', () => { expect(persisted?.content).not.toContain('[REDACTED]'); }, 30_000); + it('fails closed when the configured idempotency secret is unavailable', async () => { + const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); + await coordinator.create(IDENTITY); + const secret = process.env['BETTER_AUTH_SECRET']; + delete process.env['BETTER_AUTH_SECRET']; + try { + await expect( + coordinator.receive({ + sessionId: IDENTITY.sessionId, + idempotencyKey: 'requires-idempotency-secret', + correlationId: 'correlation-secret', + content: 'sensitive payload', + }), + ).rejects.toThrow(/required for durable idempotency digests/); + } finally { + if (secret === undefined) delete process.env['BETTER_AUTH_SECRET']; + else process.env['BETTER_AUTH_SECRET'] = secret; + } + }, 30_000); + + it('uses keyed pre-redaction digests to reject distinct sensitive checkpoint payloads', async () => { + const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); + await coordinator.create(IDENTITY); + const input = { + sessionId: IDENTITY.sessionId, + checkpointId: 'checkpoint-secret-conflict', + cursor: 'api_key=secret-one', + summary: 'bearer secret-one', + compactionEpoch: 1, + }; + await coordinator.checkpoint(input); + + await expect( + coordinator.checkpoint({ + ...input, + cursor: 'api_key=secret-two', + summary: 'bearer secret-two', + }), + ).rejects.toThrow(/checkpoint identity conflict/); + + const [persisted] = await handle.db + .select({ + digest: interactionCheckpoints.contentDigest, + cursor: interactionCheckpoints.cursor, + }) + .from(interactionCheckpoints) + .where(eq(interactionCheckpoints.checkpointId, input.checkpointId)); + expect(persisted?.cursor).not.toContain('secret-one'); + expect(persisted?.digest).not.toBe( + createHash('sha256') + .update(JSON.stringify([input.cursor, input.summary])) + .digest('hex'), + ); + + await coordinator.checkpoint({ + ...input, + checkpointId: 'checkpoint-delimiter-conflict', + cursor: 'a\u0000b', + summary: 'c', + }); + await expect( + coordinator.checkpoint({ + ...input, + checkpointId: 'checkpoint-delimiter-conflict', + cursor: 'a', + summary: 'b\u0000c', + }), + ).rejects.toThrow(/checkpoint identity conflict/); + }, 30_000); + + it('rejects distinct sensitive inbox and outbox payloads under reused idempotency keys', async () => { + const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); + await coordinator.create(IDENTITY); + const inbox = { + sessionId: IDENTITY.sessionId, + idempotencyKey: 'inbox-secret-conflict', + correlationId: 'correlation-inbox-secret', + content: 'api_key=secret-one', + }; + const outbox = { + sessionId: IDENTITY.sessionId, + idempotencyKey: 'outbox-secret-conflict', + correlationId: 'correlation-outbox-secret', + channelId: 'cli', + kind: 'provider.send', + content: 'api_key=secret-one', + }; + await coordinator.receive(inbox); + await coordinator.enqueueOutbox(outbox); + + await expect(coordinator.receive({ ...inbox, content: 'api_key=secret-two' })).rejects.toThrow( + /idempotency conflict/, + ); + await expect( + coordinator.enqueueOutbox({ ...outbox, content: 'api_key=secret-two' }), + ).rejects.toThrow(/idempotency conflict/); + }, 30_000); + it('rejects database inbox and outbox idempotency-key conflicts', async () => { const coordinator = new DurableSessionCoordinator(new TessDurableSessionRepository(handle.db)); await coordinator.create(IDENTITY); diff --git a/apps/gateway/src/agent/tess-durable-session.repository.ts b/apps/gateway/src/agent/tess-durable-session.repository.ts index 9daf324..8d2f52c 100644 --- a/apps/gateway/src/agent/tess-durable-session.repository.ts +++ b/apps/gateway/src/agent/tess-durable-session.repository.ts @@ -1,4 +1,4 @@ -import { createHash } from 'node:crypto'; +import { createHash, createHmac } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import { and, @@ -126,7 +126,10 @@ export class TessDurableSessionRepository implements DurableSessionStore { .limit(1); if (!existing[0]) throw new Error(`Durable Tess inbox enqueue failed: ${input.idempotencyKey}`); const entry = toInbox(existing[0]); - if (!sameInbox(entry, record) || existing[0].contentDigest !== digest) { + if ( + !sameInbox(entry, record) || + !matchesContentDigest(existing[0].contentDigest, input.content) + ) { throw new Error(`Durable Tess inbox idempotency conflict: ${input.idempotencyKey}`); } return { accepted: false, status: entry.status }; @@ -213,7 +216,10 @@ export class TessDurableSessionRepository implements DurableSessionStore { if (!existing[0]) throw new Error(`Durable Tess outbox enqueue failed: ${input.idempotencyKey}`); const entry = toOutbox(existing[0]); - if (!sameOutbox(entry, record) || existing[0].contentDigest !== digest) { + if ( + !sameOutbox(entry, record) || + !matchesContentDigest(existing[0].contentDigest, input.content) + ) { throw new Error(`Durable Tess outbox idempotency conflict: ${input.idempotencyKey}`); } return { accepted: false, status: entry.status }; @@ -286,6 +292,9 @@ export class TessDurableSessionRepository implements DurableSessionStore { } async checkpoint(input: DurableCheckpointInput): Promise { + // Compute identity before redaction. The persisted digest is keyed so a database + // reader cannot use it as an offline oracle for sensitive cursor/summary values. + const digest = contentDigest(JSON.stringify([input.cursor, input.summary])); const checkpoint: DurableCheckpointInput = { ...input, cursor: redactSensitiveContent(input.cursor).content, @@ -295,6 +304,7 @@ export class TessDurableSessionRepository implements DurableSessionStore { .insert(interactionCheckpoints) .values({ ...checkpoint, + contentDigest: digest, cursor: seal(checkpoint.cursor), summary: seal(checkpoint.summary), }) @@ -302,8 +312,21 @@ export class TessDurableSessionRepository implements DurableSessionStore { .returning({ checkpointId: interactionCheckpoints.checkpointId }); if (inserted[0]) return; - const existing = await this.findCheckpoint(input.sessionId, input.checkpointId); - if (!existing || !sameCheckpoint(existing, checkpoint)) { + const existing = await this.db + .select() + .from(interactionCheckpoints) + .where( + and( + eq(interactionCheckpoints.sessionId, input.sessionId), + eq(interactionCheckpoints.checkpointId, input.checkpointId), + ), + ) + .limit(1); + if ( + !existing[0] || + !sameCheckpoint(toCheckpoint(existing[0]), checkpoint) || + !matchesCheckpointDigest(existing[0].contentDigest, digest) + ) { throw new Error(`Durable Tess checkpoint identity conflict: ${input.checkpointId}`); } } @@ -384,7 +407,24 @@ export class TessDurableSessionRepository implements DurableSessionStore { } function contentDigest(content: string): string { - return createHash('sha256').update(content).digest('hex'); + const secret = process.env['BETTER_AUTH_SECRET']; + if (!secret) { + throw new Error('BETTER_AUTH_SECRET is required for durable idempotency digests'); + } + return `hmac:v1:${createHmac('sha256', secret).update(content).digest('hex')}`; +} + +function matchesContentDigest(stored: string, content: string): boolean { + return ( + stored === contentDigest(content) || + stored === createHash('sha256').update(content).digest('hex') + ); +} + +function matchesCheckpointDigest(stored: string, digest: string): boolean { + // Legacy rows predate any pre-redaction identity and cannot safely prove equality. + // Reject rather than let redaction collapse distinct sensitive checkpoint payloads. + return stored === digest; } function sameIdentity(left: DurableSessionIdentity, right: DurableSessionIdentity): boolean { @@ -422,8 +462,6 @@ function sameCheckpoint(left: DurableCheckpoint, right: DurableCheckpointInput): return ( left.sessionId === right.sessionId && left.checkpointId === right.checkpointId && - left.cursor === right.cursor && - left.summary === right.summary && left.compactionEpoch === right.compactionEpoch ); } diff --git a/packages/db/drizzle/0015_interaction_checkpoint_payload_digest.sql b/packages/db/drizzle/0015_interaction_checkpoint_payload_digest.sql new file mode 100644 index 0000000..f291de1 --- /dev/null +++ b/packages/db/drizzle/0015_interaction_checkpoint_payload_digest.sql @@ -0,0 +1,3 @@ +ALTER TABLE "interaction_checkpoints" ADD COLUMN "content_digest" text;--> statement-breakpoint +UPDATE "interaction_checkpoints" SET "content_digest" = 'legacy' WHERE "content_digest" IS NULL;--> statement-breakpoint +ALTER TABLE "interaction_checkpoints" ALTER COLUMN "content_digest" SET NOT NULL; diff --git a/packages/db/drizzle/meta/0015_snapshot.json b/packages/db/drizzle/meta/0015_snapshot.json new file mode 100644 index 0000000..ccb49fb --- /dev/null +++ b/packages/db/drizzle/meta/0015_snapshot.json @@ -0,0 +1,4244 @@ +{ + "id": "1a0a53b1-2dd8-4ea9-8d59-3e92ccca6c6a", + "prevId": "48124a40-a224-4d1c-ab8f-b3dab554b1ac", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "accounts_provider_account_idx": { + "name": "accounts_provider_account_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.admin_tokens": { + "name": "admin_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "admin_tokens_user_id_idx": { + "name": "admin_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "admin_tokens_hash_idx": { + "name": "admin_tokens_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "admin_tokens_user_id_users_id_fk": { + "name": "admin_tokens_user_id_users_id_fk", + "tableFrom": "admin_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_logs": { + "name": "agent_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'info'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'hot'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "summarized_at": { + "name": "summarized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_logs_session_tier_idx": { + "name": "agent_logs_session_tier_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_user_id_idx": { + "name": "agent_logs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_logs_tier_created_at_idx": { + "name": "agent_logs_tier_created_at_idx", + "columns": [ + { + "expression": "tier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_logs_user_id_users_id_fk": { + "name": "agent_logs_user_id_users_id_fk", + "tableFrom": "agent_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agents": { + "name": "agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_tools": { + "name": "allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agents_project_id_idx": { + "name": "agents_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_owner_id_idx": { + "name": "agents_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agents_is_system_idx": { + "name": "agents_is_system_idx", + "columns": [ + { + "expression": "is_system", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agents_project_id_projects_id_fk": { + "name": "agents_project_id_projects_id_fk", + "tableFrom": "agents", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agents_owner_id_users_id_fk": { + "name": "agents_owner_id_users_id_fk", + "tableFrom": "agents", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appreciations": { + "name": "appreciations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "from_user": { + "name": "from_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "to_user": { + "name": "to_user", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backlog": { + "name": "backlog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "backlog_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "depends_on": { + "name": "depends_on", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "claim_owner": { + "name": "claim_owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_ttl_seconds": { + "name": "claim_ttl_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acceptance": { + "name": "acceptance", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "backlog_status_priority_idx": { + "name": "backlog_status_priority_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_status_claimed_at_idx": { + "name": "backlog_status_claimed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backlog_idempotency_key_idx": { + "name": "backlog_idempotency_key_idx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "conversations_user_archived_idx": { + "name": "conversations_user_archived_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_project_id_idx": { + "name": "conversations_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "conversations_agent_id_idx": { + "name": "conversations_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "conversations_user_id_users_id_fk": { + "name": "conversations_user_id_users_id_fk", + "tableFrom": "conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "conversations_project_id_projects_id_fk": { + "name": "conversations_project_id_projects_id_fk", + "tableFrom": "conversations", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "conversations_agent_id_agents_id_fk": { + "name": "conversations_agent_id_agents_id_fk", + "tableFrom": "conversations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "events_type_idx": { + "name": "events_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "events_date_idx": { + "name": "events_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_audit_log": { + "name": "federation_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "verb": { + "name": "verb", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "denied_reason": { + "name": "denied_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "query_hash": { + "name": "query_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bytes_out": { + "name": "bytes_out", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_audit_log_peer_created_at_idx": { + "name": "federation_audit_log_peer_created_at_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_subject_created_at_idx": { + "name": "federation_audit_log_subject_created_at_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_audit_log_created_at_idx": { + "name": "federation_audit_log_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_audit_log_peer_id_federation_peers_id_fk": { + "name": "federation_audit_log_peer_id_federation_peers_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_subject_user_id_users_id_fk": { + "name": "federation_audit_log_subject_user_id_users_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "federation_audit_log_grant_id_federation_grants_id_fk": { + "name": "federation_audit_log_grant_id_federation_grants_id_fk", + "tableFrom": "federation_audit_log", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_enrollment_tokens": { + "name": "federation_enrollment_tokens", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_enrollment_tokens_grant_id_federation_grants_id_fk": { + "name": "federation_enrollment_tokens_grant_id_federation_grants_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_enrollment_tokens_peer_id_federation_peers_id_fk": { + "name": "federation_enrollment_tokens_peer_id_federation_peers_id_fk", + "tableFrom": "federation_enrollment_tokens", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_grants": { + "name": "federation_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "peer_id": { + "name": "peer_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "grant_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_reason": { + "name": "revoked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_grants_subject_status_idx": { + "name": "federation_grants_subject_status_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_grants_peer_status_idx": { + "name": "federation_grants_peer_status_idx", + "columns": [ + { + "expression": "peer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "federation_grants_subject_user_id_users_id_fk": { + "name": "federation_grants_subject_user_id_users_id_fk", + "tableFrom": "federation_grants", + "tableTo": "users", + "columnsFrom": [ + "subject_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_grants_peer_id_federation_peers_id_fk": { + "name": "federation_grants_peer_id_federation_peers_id_fk", + "tableFrom": "federation_grants", + "tableTo": "federation_peers", + "columnsFrom": [ + "peer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_peers": { + "name": "federation_peers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "common_name": { + "name": "common_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_pem": { + "name": "cert_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_serial": { + "name": "cert_serial", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cert_not_after": { + "name": "cert_not_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "client_key_pem": { + "name": "client_key_pem", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "peer_state", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "endpoint_url": { + "name": "endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "federation_peers_cert_serial_idx": { + "name": "federation_peers_cert_serial_idx", + "columns": [ + { + "expression": "cert_serial", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "federation_peers_state_idx": { + "name": "federation_peers_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_peers_common_name_unique": { + "name": "federation_peers_common_name_unique", + "nullsNotDistinct": false, + "columns": [ + "common_name" + ] + }, + "federation_peers_cert_serial_unique": { + "name": "federation_peers_cert_serial_unique", + "nullsNotDistinct": false, + "columns": [ + "cert_serial" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.insights": { + "name": "insights", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'agent'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "relevance_score": { + "name": "relevance_score", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "decayed_at": { + "name": "decayed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "insights_user_id_idx": { + "name": "insights_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_category_idx": { + "name": "insights_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "insights_relevance_idx": { + "name": "insights_relevance_idx", + "columns": [ + { + "expression": "relevance_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "insights_user_id_users_id_fk": { + "name": "insights_user_id_users_id_fk", + "tableFrom": "insights", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_checkpoints": { + "name": "interaction_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_digest": { + "name": "content_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "compaction_epoch": { + "name": "compaction_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_checkpoints_session_idempotency_idx": { + "name": "interaction_checkpoints_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interaction_checkpoints_session_epoch_idx": { + "name": "interaction_checkpoints_session_epoch_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "compaction_epoch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_checkpoints_session_id_interaction_sessions_id_fk": { + "name": "interaction_checkpoints_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_checkpoints", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_handoffs": { + "name": "interaction_handoffs", + "schema": "", + "columns": { + "handoff_id": { + "name": "handoff_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destination": { + "name": "destination", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "interaction_handoff_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_handoffs_session_status_idx": { + "name": "interaction_handoffs_session_status_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_handoffs_session_id_interaction_sessions_id_fk": { + "name": "interaction_handoffs_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_handoffs", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_inbox": { + "name": "interaction_inbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_digest": { + "name": "content_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "interaction_inbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_inbox_session_idempotency_idx": { + "name": "interaction_inbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interaction_inbox_session_status_created_idx": { + "name": "interaction_inbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_inbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_inbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_inbox", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_outbox": { + "name": "interaction_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_digest": { + "name": "content_digest", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "interaction_outbox_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "interaction_outbox_session_idempotency_idx": { + "name": "interaction_outbox_session_idempotency_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "interaction_outbox_session_status_created_idx": { + "name": "interaction_outbox_session_status_created_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "interaction_outbox_session_id_interaction_sessions_id_fk": { + "name": "interaction_outbox_session_id_interaction_sessions_id_fk", + "tableFrom": "interaction_outbox", + "tableTo": "interaction_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interaction_sessions": { + "name": "interaction_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_name": { + "name": "agent_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "runtime_session_id": { + "name": "runtime_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "interaction_sessions_owner_id_users_id_fk": { + "name": "interaction_sessions_owner_id_users_id_fk", + "tableFrom": "interaction_sessions", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "conversation_id": { + "name": "conversation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "messages_conversation_id_idx": { + "name": "messages_conversation_id_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_conversation_id_conversations_id_fk": { + "name": "messages_conversation_id_conversations_id_fk", + "tableFrom": "messages", + "tableTo": "conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mission_tasks": { + "name": "mission_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr": { + "name": "pr", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mission_tasks_mission_id_idx": { + "name": "mission_tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_task_id_idx": { + "name": "mission_tasks_task_id_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_user_id_idx": { + "name": "mission_tasks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mission_tasks_status_idx": { + "name": "mission_tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mission_tasks_mission_id_missions_id_fk": { + "name": "mission_tasks_mission_id_missions_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mission_tasks_task_id_tasks_id_fk": { + "name": "mission_tasks_task_id_tasks_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mission_tasks_user_id_users_id_fk": { + "name": "mission_tasks_user_id_users_id_fk", + "tableFrom": "mission_tasks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.missions": { + "name": "missions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'planning'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "milestones": { + "name": "milestones", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "missions_project_id_idx": { + "name": "missions_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "missions_user_id_idx": { + "name": "missions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "missions_project_id_projects_id_fk": { + "name": "missions_project_id_projects_id_fk", + "tableFrom": "missions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "missions_user_id_users_id_fk": { + "name": "missions_user_id_users_id_fk", + "tableFrom": "missions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preferences": { + "name": "preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mutable": { + "name": "mutable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "preferences_user_id_idx": { + "name": "preferences_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "preferences_user_key_idx": { + "name": "preferences_user_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "preferences_user_id_users_id_fk": { + "name": "preferences_user_id_users_id_fk", + "tableFrom": "preferences", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "projects_owner_id_users_id_fk": { + "name": "projects_owner_id_users_id_fk", + "tableFrom": "projects", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "projects_team_id_teams_id_fk": { + "name": "projects_team_id_teams_id_fk", + "tableFrom": "projects", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_credentials": { + "name": "provider_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_type": { + "name": "credential_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "provider_credentials_user_provider_idx": { + "name": "provider_credentials_user_provider_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_credentials_user_id_idx": { + "name": "provider_credentials_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_credentials_user_id_users_id_fk": { + "name": "provider_credentials_user_id_users_id_fk", + "tableFrom": "provider_credentials", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.routing_rules": { + "name": "routing_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "routing_rules_scope_priority_idx": { + "name": "routing_rules_scope_priority_idx", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_user_id_idx": { + "name": "routing_rules_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "routing_rules_enabled_idx": { + "name": "routing_rules_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "routing_rules_user_id_users_id_fk": { + "name": "routing_rules_user_id_users_id_fk", + "tableFrom": "routing_rules", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sessions_expires_at_idx": { + "name": "sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'custom'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "installed_by": { + "name": "installed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skills_enabled_idx": { + "name": "skills_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skills_installed_by_users_id_fk": { + "name": "skills_installed_by_users_id_fk", + "tableFrom": "skills", + "tableTo": "users", + "columnsFrom": [ + "installed_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_name_unique": { + "name": "skills_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.summarization_jobs": { + "name": "summarization_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "logs_processed": { + "name": "logs_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "insights_created": { + "name": "insights_created", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "summarization_jobs_status_idx": { + "name": "summarization_jobs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'not-started'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mission_id": { + "name": "mission_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "assignee": { + "name": "assignee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_project_id_idx": { + "name": "tasks_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_mission_id_idx": { + "name": "tasks_mission_id_idx", + "columns": [ + { + "expression": "mission_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_project_id_projects_id_fk": { + "name": "tasks_project_id_projects_id_fk", + "tableFrom": "tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_mission_id_missions_id_fk": { + "name": "tasks_mission_id_missions_id_fk", + "tableFrom": "tasks", + "tableTo": "missions", + "columnsFrom": [ + "mission_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_members": { + "name": "team_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_members_team_user_idx": { + "name": "team_members_team_user_idx", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_invited_by_users_id_fk": { + "name": "team_members_invited_by_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "manager_id": { + "name": "manager_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_owner_id_users_id_fk": { + "name": "teams_owner_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "teams_manager_id_users_id_fk": { + "name": "teams_manager_id_users_id_fk", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "manager_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_slug_unique": { + "name": "teams_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tickets": { + "name": "tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'medium'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tickets_status_idx": { + "name": "tickets_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.backlog_status": { + "name": "backlog_status", + "schema": "public", + "values": [ + "ready", + "claimed", + "blocked", + "done" + ] + }, + "public.grant_status": { + "name": "grant_status", + "schema": "public", + "values": [ + "pending", + "active", + "revoked", + "expired" + ] + }, + "public.interaction_handoff_status": { + "name": "interaction_handoff_status", + "schema": "public", + "values": [ + "pending", + "accepted" + ] + }, + "public.interaction_inbox_status": { + "name": "interaction_inbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "processed" + ] + }, + "public.interaction_outbox_status": { + "name": "interaction_outbox_status", + "schema": "public", + "values": [ + "pending", + "processing", + "delivered" + ] + }, + "public.peer_state": { + "name": "peer_state", + "schema": "public", + "values": [ + "pending", + "active", + "suspended", + "revoked" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 5d842b5..dcdb944 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -106,6 +106,13 @@ "when": 1783913398006, "tag": "0014_interaction_outbox_channel_scope", "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1783942610000, + "tag": "0015_interaction_checkpoint_payload_digest", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index abd0ff8..76809d2 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -571,6 +571,7 @@ export const interactionCheckpoints = pgTable( .notNull() .references(() => interactionSessions.id, { onDelete: 'cascade' }), checkpointId: text('checkpoint_id').notNull(), + contentDigest: text('content_digest').notNull(), cursor: text('cursor').notNull(), summary: text('summary').notNull(), compactionEpoch: integer('compaction_epoch').notNull(), -- 2.49.1