import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; /* eslint-disable sort-keys */ const attemptClassification = v.union( v.literal("Succeeded"), v.literal("RetryableFailure"), v.literal("NeedsInput"), v.literal("Blocked"), v.literal("VerificationFailed"), v.literal("BudgetExhausted"), v.literal("Cancelled"), v.literal("PermanentFailure") ); const workStatus = v.union( v.literal("proposed"), v.literal("defining"), v.literal("awaiting-definition-approval"), v.literal("designing"), v.literal("awaiting-design-approval"), v.literal("ready"), v.literal("executing"), v.literal("verifying"), v.literal("needs-input"), v.literal("blocked"), v.literal("completed"), v.literal("failed"), v.literal("cancelled") ); export default defineSchema({ organizations: defineTable({ createdAt: v.number(), createdBy: v.string(), kind: v.union(v.literal("personal"), v.literal("team")), name: v.string(), }).index("by_createdBy_and_kind", ["createdBy", "kind"]), organizationMembers: defineTable({ createdAt: v.number(), organizationId: v.id("organizations"), role: v.union(v.literal("owner"), v.literal("member")), userId: v.string(), }) .index("by_userId", ["userId"]) .index("by_organizationId", ["organizationId"]) .index("by_organizationId_and_userId", ["organizationId", "userId"]), gitProviderAccounts: defineTable({ createdAt: v.number(), externalAccountId: v.string(), externalEmail: v.optional(v.string()), externalUsername: v.string(), organizationId: v.id("organizations"), provider: v.union(v.literal("github"), v.literal("gitea")), serverUrl: v.string(), status: v.union( v.literal("pending-auth"), v.literal("active"), v.literal("reauth-required"), v.literal("revoked"), v.literal("unavailable") ), updatedAt: v.number(), userId: v.string(), }) .index("by_userId_and_provider_and_serverUrl", [ "userId", "provider", "serverUrl", ]) .index("by_organizationId", ["organizationId"]), gitConnections: defineTable({ connectedAt: v.number(), creatorUserId: v.optional(v.string()), credentialCiphertext: v.string(), credentialIv: v.string(), credentialKind: v.union(v.literal("oauth"), v.literal("token")), gitProviderAccountId: v.optional(v.id("gitProviderAccounts")), grantedScopesJson: v.optional(v.string()), lastError: v.optional(v.string()), lastVerifiedAt: v.optional(v.number()), organizationId: v.id("organizations"), provider: v.union(v.literal("github"), v.literal("gitea")), reauthRequiredAt: v.optional(v.number()), serverUrl: v.string(), state: v.optional( v.union( v.literal("pending-auth"), v.literal("active"), v.literal("reauth-required"), v.literal("revoked"), v.literal("unavailable") ) ), updatedAt: v.number(), username: v.optional(v.string()), }) .index("by_organizationId", ["organizationId"]) .index("by_gitProviderAccountId", ["gitProviderAccountId"]), gitProviderOrganizations: defineTable({ createdAt: v.number(), displayName: v.optional(v.string()), externalId: v.string(), externalSlug: v.string(), gitProviderAccountId: v.id("gitProviderAccounts"), organizationId: v.id("organizations"), serverUrl: v.string(), updatedAt: v.number(), verificationState: v.union( v.literal("unverified"), v.literal("verified"), v.literal("stale") ), visibility: v.union(v.literal("public"), v.literal("private")), }) .index("by_gitProviderAccountId", ["gitProviderAccountId"]) .index("by_organizationId", ["organizationId"]), gitRepositories: defineTable({ cloneUrl: v.string(), createdAt: v.number(), defaultBranch: v.string(), fullName: v.string(), gitProviderAccountId: v.id("gitProviderAccounts"), gitProviderOrganizationId: v.optional(v.id("gitProviderOrganizations")), lfsCapability: v.union( v.literal("unknown"), v.literal("supported"), v.literal("unsupported") ), name: v.string(), organizationId: v.id("organizations"), owner: v.string(), permissionsAdmin: v.boolean(), permissionsMaintain: v.boolean(), permissionsPull: v.boolean(), permissionsPush: v.boolean(), permissionsTriage: v.boolean(), privacy: v.union(v.literal("public"), v.literal("private")), provider: v.union(v.literal("github"), v.literal("gitea")), providerRepositoryId: v.string(), serverUrl: v.string(), sourceMigrationId: v.optional(v.id("gitMigrations")), updatedAt: v.number(), webUrl: v.string(), webhookState: v.union( v.literal("none"), v.literal("pending"), v.literal("active"), v.literal("failed") ), }) .index("by_provider_and_serverUrl_and_externalRepositoryId", [ "provider", "serverUrl", "providerRepositoryId", ]) .index("by_organizationId", ["organizationId"]), gitMigrations: defineTable({ createdAt: v.number(), failureReason: v.optional(v.string()), githubConnectionId: v.id("gitConnections"), idempotencyKey: v.string(), includeLfs: v.boolean(), organizationId: v.id("organizations"), puterConnectionId: v.id("gitConnections"), resultingRepositoryId: v.optional(v.id("gitRepositories")), sourceIsPrivate: v.boolean(), sourceRepositoryUrl: v.string(), status: v.union( v.literal("queued"), v.literal("running"), v.literal("succeeded"), v.literal("failed"), v.literal("cancelled") ), targetOwner: v.string(), targetRepositoryName: v.string(), updatedAt: v.number(), }) .index("by_organizationId", ["organizationId"]) .index("by_idempotencyKey", ["idempotencyKey"]), gitWebhookDeliveries: defineTable({ createdAt: v.number(), deliveryId: v.string(), error: v.optional(v.string()), event: v.string(), externalRepositoryId: v.optional(v.string()), payloadHash: v.string(), processingState: v.union( v.literal("received"), v.literal("processing"), v.literal("processed"), v.literal("ignored"), v.literal("failed"), v.literal("duplicate") ), provider: v.union(v.literal("github"), v.literal("gitea")), repositoryRef: v.optional(v.id("gitRepositories")), updatedAt: v.number(), }) .index("by_provider_and_deliveryId", ["provider", "deliveryId"]) .index("by_processingState", ["processingState"]), projects: defineTable({ createdAt: v.number(), defaultBranch: v.optional(v.string()), description: v.optional(v.string()), gitConnectionId: v.optional(v.id("gitConnections")), gitRepositoryId: v.optional(v.id("gitRepositories")), instructions: v.optional(v.string()), name: v.string(), normalizedSourceUrl: v.string(), organizationId: v.id("organizations"), repositoryPath: v.string(), sourceHost: v.string(), sourceUrl: v.string(), updatedAt: v.number(), }) .index("by_organizationId_and_createdAt", ["organizationId", "createdAt"]) .index("by_organizationId_and_normalizedSourceUrl", [ "organizationId", "normalizedSourceUrl", ]), projectContextDocuments: defineTable({ content: v.string(), createdAt: v.number(), kind: v.union( v.literal("readme"), v.literal("agents"), v.literal("product"), v.literal("business"), v.literal("design"), v.literal("tech") ), origin: v.literal("repository"), path: v.string(), projectId: v.id("projects"), sourceUrl: v.string(), updatedAt: v.number(), }).index("by_projectId_and_path", ["projectId", "path"]), conversations: defineTable({ createdAt: v.number(), organizationId: v.id("organizations"), }).index("by_organizationId", ["organizationId"]), conversationTurns: defineTable({ attemptNumber: v.optional(v.number()), clientRequestId: v.string(), completedAt: v.optional(v.number()), conversationId: v.id("conversations"), createdAt: v.number(), error: v.optional(v.string()), leaseExpiresAt: v.optional(v.number()), leaseOwner: v.optional(v.string()), status: v.union( v.literal("queued"), v.literal("dispatching"), v.literal("running"), v.literal("completed"), v.literal("failed"), v.literal("aborted") ), submissionId: v.optional(v.string()), }) .index("by_conversationId_and_clientRequestId", [ "conversationId", "clientRequestId", ]) .index("by_status_and_leaseExpiresAt", ["status", "leaseExpiresAt"]) .index("by_submissionId", ["submissionId"]), conversationMessages: defineTable({ content: v.string(), conversationId: v.id("conversations"), createdAt: v.number(), ordinal: v.number(), role: v.union(v.literal("user"), v.literal("assistant")), turnId: v.id("conversationTurns"), }) .index("by_conversationId_and_ordinal", ["conversationId", "ordinal"]) .index("by_turnId_and_role", ["turnId", "role"]), conversationAttachments: defineTable({ createdAt: v.number(), filename: v.optional(v.string()), messageId: v.id("conversationMessages"), mimeType: v.string(), storageId: v.id("_storage"), }).index("by_messageId", ["messageId"]), signals: defineTable({ conversationId: v.id("conversations"), createdAt: v.number(), desiredOutcome: v.string(), organizationId: v.id("organizations"), processedByAgentInstanceId: v.string(), processedByAgentName: v.string(), projectId: v.id("projects"), sourceKey: v.string(), summary: v.string(), title: v.string(), }) .index("by_organization_and_createdAt", ["organizationId", "createdAt"]) .index("by_organization_and_sourceKey", ["organizationId", "sourceKey"]) .index("by_project_and_createdAt", ["projectId", "createdAt"]), signalConstraints: defineTable({ ordinal: v.number(), signalId: v.id("signals"), value: v.string(), }).index("by_signalId_and_ordinal", ["signalId", "ordinal"]), signalSources: defineTable({ messageId: v.id("conversationMessages"), ordinal: v.number(), rawTextSnapshot: v.string(), signalId: v.id("signals"), sourceCreatedAt: v.number(), }) .index("by_signalId_and_ordinal", ["signalId", "ordinal"]) .index("by_messageId", ["messageId"]), works: defineTable({ createdAt: v.number(), definitionApprovalVersion: v.optional(v.number()), definitionVersion: v.optional(v.number()), designVersion: v.optional(v.number()), objective: v.string(), organizationId: v.id("organizations"), projectId: v.id("projects"), status: workStatus, title: v.string(), updatedAt: v.number(), }) .index("by_project_and_createdAt", ["projectId", "createdAt"]) .index("by_organization_and_createdAt", ["organizationId", "createdAt"]), signalWorkAttachments: defineTable({ createdAt: v.number(), signalId: v.id("signals"), workId: v.id("works"), }) .index("by_signal", ["signalId"]) .index("by_work", ["workId"]) .index("by_signal_and_work", ["signalId", "workId"]), workEvents: defineTable({ createdAt: v.number(), idempotencyKey: v.string(), kind: v.union( v.literal("work.proposed"), v.literal("signal.attached"), v.literal("definition.requested"), v.literal("definition.approved"), v.literal("definition.saved"), v.literal("definition.revised"), v.literal("question.created"), v.literal("question.answered"), v.literal("question.withdrawn"), v.literal("design.saved"), v.literal("design.approved"), v.literal("design.revised"), v.literal("planner.failed"), v.literal("slice.started"), v.literal("slice.completed"), v.literal("slice.ready"), v.literal("run.started"), v.literal("run.completed"), v.literal("run.cancelled"), v.literal("attempt.claimed"), v.literal("attempt.event"), v.literal("attempt.completed"), v.literal("attempt.reconciled"), v.literal("resolver.decided"), v.literal("verification.started"), v.literal("verification.completed"), v.literal("verification.failed"), v.literal("artifact.recorded"), v.literal("delivery.recorded"), v.literal("delivery.updated") ), payloadJson: v.optional(v.string()), referenceId: v.optional(v.string()), signalId: v.optional(v.id("signals")), workId: v.id("works"), }) .index("by_work_and_createdAt", ["workId", "createdAt"]) .index("by_work_and_idempotencyKey", ["workId", "idempotencyKey"]), workDefinitions: defineTable({ createdAt: v.number(), createdBy: v.string(), payloadJson: v.string(), risk: v.union(v.literal("low"), v.literal("medium"), v.literal("high")), status: v.union( v.literal("proposed"), v.literal("current"), v.literal("superseded") ), version: v.number(), workId: v.id("works"), }) .index("by_work_and_version", ["workId", "version"]) .index("by_work_and_status", ["workId", "status"]), workQuestions: defineTable({ alternativesJson: v.string(), answer: v.optional(v.string()), createdAt: v.number(), definitionVersion: v.number(), impact: v.union(v.literal("low"), v.literal("medium"), v.literal("high")), prompt: v.string(), questionId: v.string(), recommendation: v.optional(v.string()), status: v.union( v.literal("open"), v.literal("answered"), v.literal("withdrawn") ), workId: v.id("works"), }) .index("by_workId_and_definitionVersion", ["workId", "definitionVersion"]) .index("by_workId_and_definitionVersion_and_questionId", [ "workId", "definitionVersion", "questionId", ]), designPackets: defineTable({ createdAt: v.number(), createdBy: v.string(), definitionVersion: v.number(), payloadJson: v.string(), status: v.union( v.literal("proposed"), v.literal("current"), v.literal("superseded") ), version: v.number(), workId: v.id("works"), }).index("by_work_and_version", ["workId", "version"]), workSlices: defineTable({ createdAt: v.optional(v.number()), designVersion: v.number(), objective: v.string(), observableBehavior: v.string(), ordinal: v.number(), payloadJson: v.string(), sliceId: v.string(), status: v.union( v.literal("planned"), v.literal("ready"), v.literal("running"), v.literal("completed"), v.literal("blocked") ), title: v.string(), workId: v.id("works"), }) .index("by_workId_and_designVersion", ["workId", "designVersion"]) .index("by_workId_and_designVersion_and_sliceId", [ "workId", "designVersion", "sliceId", ]), workRuns: defineTable({ baseRevision: v.optional(v.string()), candidateRevision: v.optional(v.string()), createdAt: v.number(), designVersion: v.optional(v.number()), endedAt: v.optional(v.number()), kitId: v.string(), kitVersion: v.string(), scenario: v.optional(v.string()), environmentId: v.optional(v.string()), sliceId: v.optional(v.string()), sliceRowId: v.optional(v.id("workSlices")), startedAt: v.optional(v.number()), status: v.union( v.literal("ready"), v.literal("running"), v.literal("terminal"), v.literal("cancelled") ), terminalClassification: v.optional(attemptClassification), terminalSummary: v.optional(v.string()), workflowId: v.optional(v.string()), workId: v.id("works"), }).index("by_work_and_createdAt", ["workId", "createdAt"]), workAttempts: defineTable({ classification: v.optional(attemptClassification), failureReason: v.optional( v.union( v.literal("Authentication"), v.literal("Cancelled"), v.literal("HarnessFailed"), v.literal("InvalidInput"), v.literal("ProviderUnavailable"), v.literal("RepositoryFailed"), v.literal("Timeout") ) ), endedAt: v.optional(v.number()), leaseExpiresAt: v.optional(v.number()), leaseOwner: v.optional(v.string()), number: v.number(), runId: v.id("workRuns"), startedAt: v.optional(v.number()), status: v.union( v.literal("queued"), v.literal("claimed"), v.literal("running"), v.literal("terminal") ), summary: v.optional(v.string()), workId: v.id("works"), workspaceKey: v.optional(v.string()), }) .index("by_runId_and_number", ["runId", "number"]) .index("by_status_and_leaseExpiresAt", ["status", "leaseExpiresAt"]), workAttemptEvents: defineTable({ attemptId: v.id("workAttempts"), kind: v.string(), message: v.string(), metadataJson: v.string(), occurredAt: v.number(), sequence: v.number(), }).index("by_attempt_and_sequence", ["attemptId", "sequence"]), resolverDecisions: defineTable({ attemptId: v.id("workAttempts"), attemptNumber: v.number(), classification: attemptClassification, createdAt: v.number(), decision: v.union(v.literal("retry"), v.literal("terminal")), resultingWorkStatus: v.optional(workStatus), runId: v.id("workRuns"), summary: v.string(), workId: v.id("works"), }) .index("by_runId_and_attemptNumber", ["runId", "attemptNumber"]) .index("by_attemptId", ["attemptId"]), workArtifacts: defineTable({ attemptId: v.optional(v.id("workAttempts")), contentHash: v.optional(v.string()), createdAt: v.number(), designVersion: v.optional(v.number()), environmentId: v.optional(v.string()), idempotencyKey: v.string(), kind: v.union( v.literal("definition"), v.literal("design"), v.literal("diff"), v.literal("test-report"), v.literal("verification-report"), v.literal("runtime-log"), v.literal("screenshot"), v.literal("video"), v.literal("commit"), v.literal("branch"), v.literal("pull-request"), v.literal("preview"), v.literal("deployment"), v.literal("other") ), metadataJson: v.string(), organizationId: v.id("organizations"), producer: v.string(), projectId: v.id("projects"), provenanceJson: v.string(), runId: v.optional(v.id("workRuns")), sliceId: v.optional(v.string()), sourceRevision: v.optional(v.string()), title: v.string(), uri: v.optional(v.string()), verificationStatus: v.union( v.literal("unverified"), v.literal("verified"), v.literal("rejected") ), workId: v.id("works"), }) .index("by_workId_and_createdAt", ["workId", "createdAt"]) .index("by_workId_and_idempotencyKey", ["workId", "idempotencyKey"]) .index("by_runId_and_createdAt", ["runId", "createdAt"]), workDeliveries: defineTable({ artifactId: v.optional(v.id("workArtifacts")), createdAt: v.number(), externalId: v.string(), idempotencyKey: v.string(), kind: v.union( v.literal("branch"), v.literal("commit"), v.literal("pull-request"), v.literal("preview"), v.literal("deployment") ), metadataJson: v.string(), organizationId: v.id("organizations"), projectId: v.id("projects"), provider: v.string(), sourceRevision: v.string(), status: v.union( v.literal("recorded"), v.literal("ready"), v.literal("approved"), v.literal("delivered"), v.literal("failed"), v.literal("cancelled") ), target: v.string(), updatedAt: v.number(), url: v.optional(v.string()), workId: v.id("works"), }) .index("by_workId_and_createdAt", ["workId", "createdAt"]) .index("by_workId_and_idempotencyKey", ["workId", "idempotencyKey"]), // ----------------------------------------------------------------- // Flue persistence stores (schema/format version 4). // ----------------------------------------------------------------- // One-row key/value metadata table, keyed with `schema_version` for the // persisted-store versioning obligation. flueMeta: defineTable({ key: v.string(), value: v.string(), }).index("by_key", ["key"]), // Durable agent-submission lifecycle: one row per submission. Settlement // obligations live on the same row so reserve/finalize are atomic with the // status transition they gate. flueSubmissions: defineTable({ submissionId: v.string(), sessionKey: v.string(), sequence: v.number(), kind: v.union(v.literal("dispatch"), v.literal("direct")), // Client-supplied canonical input/chunks encoded by Flue helpers; exact // string compared on idempotent replay. inputJson: v.string(), chunksJson: v.string(), traceCarrierJson: v.optional(v.string()), // Admit-time canonical record (Flue ConversationCreatedRecord) for the // default session of the default harness. recordJson: v.optional(v.string()), status: v.union( v.literal("queued"), v.literal("running"), v.literal("terminalizing"), v.literal("settled") ), acceptedAt: v.number(), canonicalReadyAt: v.optional(v.number()), attemptId: v.optional(v.string()), inputAppliedAt: v.optional(v.number()), recoveryRequestedAt: v.optional(v.number()), abortRequestedAt: v.optional(v.number()), startedAt: v.optional(v.number()), error: v.optional(v.string()), attemptCount: v.number(), maxRetry: v.number(), timeoutAt: v.number(), ownerId: v.optional(v.string()), leaseExpiresAt: v.number(), // Settlement obligation (reserved/finalized on this row). settlementRecordId: v.optional(v.string()), settlementRecordJson: v.optional(v.string()), settledOutcome: v.optional( v.union(v.literal("completed"), v.literal("failed"), v.literal("aborted")) ), updatedAt: v.number(), }) .index("by_submissionId", ["submissionId"]) .index("by_sessionKey_and_sequence", ["sessionKey", "sequence"]) .index("by_status_and_sequence", ["status", "sequence"]) .index("by_status_and_leaseExpiresAt", ["status", "leaseExpiresAt"]) .index("by_status_and_settlementRecordId", [ "status", "settlementRecordId", ]), // Durable evidence that a submission attempt started and has not yet // settled. Append-once by (submissionId, attemptId). flueAttemptMarkers: defineTable({ attemptId: v.string(), createdAt: v.number(), submissionId: v.string(), }) .index("by_submissionId_and_attemptId", ["submissionId", "attemptId"]) .index("by_submissionId", ["submissionId"]), // Conversation-stream metadata: one row per stream path. flueConversationStreams: defineTable({ closed: v.boolean(), createdAt: v.number(), identityJson: v.string(), incarnation: v.string(), nextOffset: v.number(), nextProducerSequence: v.number(), path: v.string(), producerEpoch: v.number(), producerId: v.optional(v.string()), }).index("by_path", ["path"]), // Conversation-stream batches: one row per appended batch. Offset is // 0-based; `seq` is the row's position in the stream. flueConversationBatches: defineTable({ appendedAt: v.number(), attemptId: v.optional(v.string()), path: v.string(), producerEpoch: v.number(), producerId: v.string(), producerSequence: v.number(), recordsJson: v.string(), seq: v.number(), submissionId: v.optional(v.string()), }) .index("by_path_and_seq", ["path", "seq"]) .index("by_path_producer_epoch_producerSequence", [ "path", "producerId", "producerEpoch", "producerSequence", ]), // Event-stream metadata: one row per stream path. flueEventStreams: defineTable({ closed: v.boolean(), createdAt: v.number(), nextSeq: v.number(), path: v.string(), }).index("by_path", ["path"]), // Event-stream entries: one row per appended event. `onceKey` carries the // idempotency key for `appendEventOnce` and is null for plain appends. flueEventEntries: defineTable({ appendedAt: v.number(), dataJson: v.string(), onceKey: v.optional(v.string()), path: v.string(), seq: v.number(), }) .index("by_path_and_seq", ["path", "seq"]) .index("by_path_and_onceKey", ["path", "onceKey"]), // Workflow run records. flueRuns: defineTable({ durationMs: v.optional(v.number()), endedAt: v.optional(v.string()), errorJson: v.optional(v.string()), inputJson: v.optional(v.string()), isError: v.optional(v.boolean()), resultJson: v.optional(v.string()), runId: v.string(), startedAt: v.string(), status: v.union( v.literal("active"), v.literal("completed"), v.literal("errored") ), traceCarrierJson: v.optional(v.string()), workflowName: v.string(), }) .index("by_runId", ["runId"]) .index("by_startedAt_and_runId", ["startedAt", "runId"]) .index("by_workflowName_and_startedAt_and_runId", [ "workflowName", "startedAt", "runId", ]) .index("by_status_startedAt_runId", ["status", "startedAt", "runId"]) .index("by_status_workflowName_startedAt_runId", [ "status", "workflowName", "startedAt", "runId", ]), // Immutable attachment bytes. Identity is (streamPath, attachmentId); reads // are additionally scoped by conversationId. flueAttachments: defineTable({ attachmentId: v.string(), bytes: v.bytes(), conversationId: v.string(), createdAt: v.number(), digest: v.string(), filename: v.optional(v.string()), mimeType: v.string(), size: v.number(), streamPath: v.string(), }) .index("by_streamPath", ["streamPath"]) .index("by_streamPath_and_attachmentId", ["streamPath", "attachmentId"]) .index("by_streamPath_and_conversationId_and_attachmentId", [ "streamPath", "conversationId", "attachmentId", ]), });