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("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"]), gitConnections: defineTable({ connectedAt: v.number(), credentialCiphertext: v.string(), credentialIv: v.string(), credentialKind: v.union(v.literal("oauth"), v.literal("token")), organizationId: v.id("organizations"), provider: v.union(v.literal("github"), v.literal("gitea")), serverUrl: v.string(), updatedAt: v.number(), username: v.optional(v.string()), }) .index("by_organizationId", ["organizationId"]) .index("by_organizationId_and_provider_and_serverUrl", [ "organizationId", "provider", "serverUrl", ]), projects: defineTable({ createdAt: v.number(), defaultBranch: v.optional(v.string()), description: v.optional(v.string()), gitConnectionId: v.optional(v.id("gitConnections")), 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()), designApprovalVersion: 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.saved"), v.literal("definition.revised"), v.literal("definition.approved"), v.literal("definition.invalidated"), v.literal("question.created"), v.literal("question.answered"), v.literal("question.withdrawn"), v.literal("design.requested"), v.literal("design.saved"), v.literal("design.revised"), v.literal("design.approved"), v.literal("design.invalidated"), 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("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", ]), workApprovals: defineTable({ approvedAt: v.number(), approvedBy: v.string(), definitionVersion: v.number(), designVersion: v.optional(v.number()), kind: v.union(v.literal("definition"), v.literal("design")), status: v.union(v.literal("active"), v.literal("invalidated")), workId: v.id("works"), }) .index("by_workId_and_kind", ["workId", "kind"]) .index("by_workId_and_kind_and_definitionVersion_and_designVersion", [ "workId", "kind", "definitionVersion", "designVersion", ]), 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(), environmentId: v.optional(v.string()), executionKind: v.optional( v.union(v.literal("simulated"), v.literal("real")) ), scenario: v.union( v.literal("success"), v.literal("transient-failure-then-success"), v.literal("needs-input"), v.literal("permanent-failure"), v.literal("cancelled") ), 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", ]), });