Skip to content

Data Model

Foundry’s data model lives in a single file: convex/schema.ts (~2,800 lines). It defines 81 tables organized across 10 functional domains. Every tenant-scoped table includes an orgId field tied to a Clerk organization.

The core hierarchy drives everything in the platform:

Organization (Clerk)
└── Program
├── Workstreams
│ └── Requirements ←→ Skills
│ └── Tasks
│ └── Subtasks
├── Risks
├── Sprint Gates
└── Agent Executions

Programs represent client engagements or delivery initiatives. They support configurable source/target platform pairs and delivery phases (discovery, build, test, deploy, complete).

Workstreams group related requirements within a program. Requirements can have self-referential dependency graphs and bidirectional links to skills.

Skills are versioned, domain-tagged instruction sets that teach AI agents how to perform specific delivery tasks. skillVersions is immutable; skills.currentVersion is a pointer to the active version.

DomainTable CountKey Tables
Core Delivery11programs, workstreams, requirements, skills, skillVersions, risks, tasks, subtasks, sprints, sprintGates, sprintGateEvaluations
AI & Agent10agentExecutions, executionAuditRecords, playbooks, playbookInstances, taskDecompositions, refinementSuggestions, riskAssessments, sprintPlanningRecommendations, dailyDigestCache, aiHealthScores
Document Analysis4documents, documentAnalyses, discoveryFindings, visualDiscoveryArtifacts
Video Analysis6videoAnalyses, videoFindings, videoFrameExtractions, videoTranscripts, videoActivityLogs, twelveLabsIndexes
Source Control14repositories, installations, commits, pullRequests, events, deployments, issueMappings, reviews, syncState, retryQueue, activityEvents, tokenCache
Atlassian5atlassianConnections, atlassianWebhookEvents, jiraSyncQueue, jiraSyncRecords, confluencePageRecords
Sandbox Execution6sandboxSessions, sandboxConfigs, sandboxQueue, sandboxPresets, sandboxLogs, envVault
Billing9subscriptions, pricingPlans, usageRecords, usagePeriods, billingEvents, aiUsageRecords, aiModelCache, aiProviderConfigs, trialState
Collaboration5users, teamMembers, chatMessages, comments, notifications
Other11auditLog, activityEvents, analysisActivityLogs, presence, integrations, codebaseAnalyses, codebaseAnalysisLogs, codebaseChatMessages, codeSnippets, codebaseGraphNodes, codebaseGraphEdges

Every public query and mutation must call assertOrgAccess() before accessing data. This is the tenancy boundary.

convex/model/access.ts
export async function assertOrgAccess(ctx, orgId: string) {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new ConvexError("Not authenticated");
const user = await ctx.db.query("users")
.withIndex("by_clerk_id", q => q.eq("clerkId", identity.subject))
.unique();
if (!user || !user.orgIds.includes(orgId))
throw new ConvexError("Access denied");
return user;
}
  1. Clerk issues a JWT containing the user’s subject (Clerk user ID).
  2. Convex validates the JWT and makes the identity available via ctx.auth.getUserIdentity().
  3. assertOrgAccess() looks up the user by clerkId and checks that the requested orgId is in their orgIds[] array.
  4. If the check fails, a ConvexError is thrown and the operation is rejected.

Every query pattern has a corresponding index in schema.ts. The .filter() method causes full table scans and is not used in production paths.

// Correct: index-driven query
const requirements = await ctx.db
.query("requirements")
.withIndex("by_program", q => q.eq("programId", programId))
.collect();
// Wrong: full table scan
const requirements = await ctx.db
.query("requirements")
.filter(q => q.eq(q.field("programId"), programId))
.collect();
  • Single-field: by_org on ["orgId"] — present on every tenant-scoped table
  • Compound: ["programId", "batch"], ["repositoryId", "state", "sourceBranch"] — support complex query patterns
  • Composite ordering: ["programId", "page", "userId"] — composite indexes must match query field order

Foundry maintains a dual audit system:

  1. General audit log (auditLog) — records user actions across the platform (create, update, delete operations).
  2. Execution audit records (executionAuditRecords) — captures point-in-time snapshots of task title, skill name, user identity, environment configuration, and outcome at execution time. These records embed names rather than reference IDs, making them immune to future record mutations (link rot prevention).

Every mutation that changes data must call logAuditEvent() to maintain the audit trail.

CategoryConventionExample
TablescamelCase pluralagentExecutions, discoveryFindings
FunctionscamelCase verb-nounrequirements.listByProgram
Indexessnake_case with by_ prefixby_program, by_org_and_status
  • Requirements ←→ Skills: Bidirectional. Requirements reference which skills are needed; skills track which requirements use them.
  • Skills → Skill Versions: skillVersions is an immutable append-only table. skills.currentVersion points to the active version.
  • Tasks → Subtasks: One-to-many. Subtasks are scoped to a single task and generated by AI.
  • Discovery Findings: Polymorphic analysisId field supports both document and video sources in a single table and review UI.
  • Pull Requests → Tasks: Multiple linking methods: branch name, body reference, commit message, AI inference, manual.