History

Centralize shared enum validators across core and schema

Rhys Sullivan committed 8 months ago

Commit 96afda91 parent 84fddd56 files · +288 −207Browse files

Unified · Split

6 files
  1. executor/apps/web/src/lib/types.ts +43 −22
  2. executor/packages/core/src/types.ts +102 −22
  3. executor/packages/database/convex/schema.ts +64 −106
  4. executor/packages/database/package.json +1 −0
  5. executor/packages/database/src/database/validators.ts +73 −54
  6. executor/packages/database/src/task/status.ts +5 −3
executor/apps/web/src/lib/types.ts+43−22
@@ -1,5 +1,26 @@
11 import type { Id } from "@executor/database/convex/_generated/dataModel";
22 import type {
3+ ApprovalStatus as CoreApprovalStatus,
4+ ArgumentConditionOperator as CoreArgumentConditionOperator,
5+ CredentialProvider as CoreCredentialProvider,
6+ CredentialScope as CoreCredentialScope,
7+ CredentialScopeType as CoreCredentialScopeType,
8+ PolicyApprovalMode as CorePolicyApprovalMode,
9+ PolicyDecision as CorePolicyDecision,
10+ PolicyEffect as CorePolicyEffect,
11+ PolicyMatchType as CorePolicyMatchType,
12+ PolicyScopeType as CorePolicyScopeType,
13+ SourceAuthType as CoreSourceAuthType,
14+ StorageDurability as CoreStorageDurability,
15+ StorageInstanceStatus as CoreStorageInstanceStatus,
16+ StorageProvider as CoreStorageProvider,
17+ StorageScopeType as CoreStorageScopeType,
18+ TaskStatus as CoreTaskStatus,
19+ ToolApprovalMode as CoreToolApprovalMode,
20+ ToolRoleBindingStatus as CoreToolRoleBindingStatus,
21+ ToolRoleSelectorType as CoreToolRoleSelectorType,
22+ ToolSourceScopeType as CoreToolSourceScopeType,
23+ ToolSourceType as CoreToolSourceType,
324 ToolPolicyRecord as CoreToolPolicyRecord,
425 ToolPolicyAssignmentRecord as CoreToolPolicyAssignmentRecord,
526 ToolPolicySetRecord as CoreToolPolicySetRecord,
@@ -8,34 +29,34 @@ import type { · 2 unmodified lines
829
930 // ── Shared types (inlined from @executor/contracts) ──────────────────────────
1031
11−export type TaskStatus = "queued" | "running" | "completed" | "failed" | "timed_out" | "denied";
12−export type ApprovalStatus = "pending" | "approved" | "denied";
13−export type PolicyDecision = "allow" | "require_approval" | "deny";
14−export type PolicyScopeType = "account" | "organization" | "workspace";
15−export type PolicyMatchType = "glob" | "exact";
16−export type PolicyEffect = "allow" | "deny";
17−export type PolicyApprovalMode = "inherit" | "auto" | "required";
18−export type ToolRoleSelectorType = "all" | "source" | "namespace" | "tool_path";
19−export type ToolRoleBindingStatus = "active" | "disabled";
20−export type ArgumentConditionOperator = "equals" | "contains" | "starts_with" | "not_equals";
32+export type TaskStatus = CoreTaskStatus;
33+export type ApprovalStatus = CoreApprovalStatus;
34+export type PolicyDecision = CorePolicyDecision;
35+export type PolicyScopeType = CorePolicyScopeType;
36+export type PolicyMatchType = CorePolicyMatchType;
37+export type PolicyEffect = CorePolicyEffect;
38+export type PolicyApprovalMode = CorePolicyApprovalMode;
39+export type ToolRoleSelectorType = CoreToolRoleSelectorType;
40+export type ToolRoleBindingStatus = CoreToolRoleBindingStatus;
41+export type ArgumentConditionOperator = CoreArgumentConditionOperator;
2142
2243 export interface ArgumentCondition {
2344 key: string;
2445 operator: ArgumentConditionOperator;
2546 value: string;
2647 }
27−export type CredentialScope = "account" | "organization" | "workspace";
28−export type CredentialProvider = "local-convex" | "workos-vault";
29−export type ToolSourceScopeType = "organization" | "workspace";
30−export type CredentialScopeType = "account" | "organization" | "workspace";
31−export type ToolApprovalMode = "auto" | "required";
32−export type ToolSourceType = "mcp" | "openapi" | "graphql";
33−export type StorageScopeType = "scratch" | "account" | "workspace" | "organization";
34−export type StorageDurability = "ephemeral" | "durable";
35−export type StorageInstanceStatus = "active" | "closed" | "deleted";
36−export type StorageProvider = "agentfs-local" | "agentfs-cloudflare";
37−
38−export type SourceAuthType = "none" | "bearer" | "apiKey" | "basic" | "mixed";
48+export type CredentialScope = CoreCredentialScope;
49+export type CredentialProvider = CoreCredentialProvider;
50+export type ToolSourceScopeType = CoreToolSourceScopeType;
51+export type CredentialScopeType = CoreCredentialScopeType;
52+export type ToolApprovalMode = CoreToolApprovalMode;
53+export type ToolSourceType = CoreToolSourceType;
54+export type StorageScopeType = CoreStorageScopeType;
55+export type StorageDurability = CoreStorageDurability;
56+export type StorageInstanceStatus = CoreStorageInstanceStatus;
57+export type StorageProvider = CoreStorageProvider;
58+
59+export type SourceAuthType = CoreSourceAuthType;
3960
4061 export interface SourceAuthProfile {
4162 type: SourceAuthType;
executor/packages/core/src/types.ts+102−22
@@ -2,29 +2,103 @@ · 1 unmodified line
22
33 import type { Id } from "../../database/convex/_generated/dataModel.d.ts";
44
5−export type TaskStatus = "queued" | "running" | "completed" | "failed" | "timed_out" | "denied";
6−export type ApprovalStatus = "pending" | "approved" | "denied";
7−export type ToolCallStatus = "requested" | "pending_approval" | "completed" | "failed" | "denied";
5+export const ACCOUNT_PROVIDERS = ["workos", "anonymous"] as const;
6+export type AccountProvider = (typeof ACCOUNT_PROVIDERS)[number];
7+
8+export const ACCOUNT_STATUSES = ["active", "deleted"] as const;
9+export type AccountStatus = (typeof ACCOUNT_STATUSES)[number];
10+
11+export const ORGANIZATION_STATUSES = ["active", "deleted"] as const;
12+export type OrganizationStatus = (typeof ORGANIZATION_STATUSES)[number];
13+
14+export const ORGANIZATION_ROLES = ["owner", "admin", "member", "billing_admin"] as const;
15+export type OrganizationRole = (typeof ORGANIZATION_ROLES)[number];
16+
17+export const ORGANIZATION_MEMBER_STATUSES = ["active", "pending", "removed"] as const;
18+export type OrganizationMemberStatus = (typeof ORGANIZATION_MEMBER_STATUSES)[number];
19+
20+export const BILLING_SUBSCRIPTION_STATUSES = [
21+ "incomplete",
22+ "incomplete_expired",
23+ "trialing",
24+ "active",
25+ "past_due",
26+ "canceled",
27+ "unpaid",
28+ "paused",
29+] as const;
30+export type BillingSubscriptionStatus = (typeof BILLING_SUBSCRIPTION_STATUSES)[number];
31+
32+export const INVITE_STATUSES = ["pending", "accepted", "expired", "revoked", "failed"] as const;
33+export type InviteStatus = (typeof INVITE_STATUSES)[number];
34+
35+export const TASK_STATUSES = ["queued", "running", "completed", "failed", "timed_out", "denied"] as const;
36+export type TaskStatus = (typeof TASK_STATUSES)[number];
37+
38+export const COMPLETED_TASK_STATUSES = ["completed", "failed", "timed_out", "denied"] as const;
39+export type CompletedTaskStatus = (typeof COMPLETED_TASK_STATUSES)[number];
40+
41+export const APPROVAL_STATUSES = ["pending", "approved", "denied"] as const;
42+export type ApprovalStatus = (typeof APPROVAL_STATUSES)[number];
43+
44+export const TOOL_CALL_STATUSES = ["requested", "pending_approval", "completed", "failed", "denied"] as const;
45+export type ToolCallStatus = (typeof TOOL_CALL_STATUSES)[number];
46+
47+export const TERMINAL_TOOL_CALL_STATUSES = ["completed", "failed", "denied"] as const;
48+export type TerminalToolCallStatus = (typeof TERMINAL_TOOL_CALL_STATUSES)[number];
49+
850 export type PolicyDecision = "allow" | "require_approval" | "deny";
9−export type PolicyScopeType = "account" | "organization" | "workspace";
10−export type PolicyMatchType = "glob" | "exact";
11−export type PolicyEffect = "allow" | "deny";
12−export type PolicyApprovalMode = "inherit" | "auto" | "required";
13−export type PolicyResourceType = "all_tools" | "source" | "namespace" | "tool_path";
14−export type ToolSourceScopeType = "organization" | "workspace";
15−export type CredentialScopeType = "account" | "organization" | "workspace";
51+
52+export const POLICY_SCOPE_TYPES = ["account", "organization", "workspace"] as const;
53+export type PolicyScopeType = (typeof POLICY_SCOPE_TYPES)[number];
54+
55+export const POLICY_MATCH_TYPES = ["glob", "exact"] as const;
56+export type PolicyMatchType = (typeof POLICY_MATCH_TYPES)[number];
57+
58+export const POLICY_EFFECTS = ["allow", "deny"] as const;
59+export type PolicyEffect = (typeof POLICY_EFFECTS)[number];
60+
61+export const POLICY_APPROVAL_MODES = ["inherit", "auto", "required"] as const;
62+export type PolicyApprovalMode = (typeof POLICY_APPROVAL_MODES)[number];
63+
64+export const POLICY_RESOURCE_TYPES = ["all_tools", "source", "namespace", "tool_path"] as const;
65+export type PolicyResourceType = (typeof POLICY_RESOURCE_TYPES)[number];
66+
67+export const TOOL_SOURCE_SCOPE_TYPES = ["organization", "workspace"] as const;
68+export type ToolSourceScopeType = (typeof TOOL_SOURCE_SCOPE_TYPES)[number];
69+
70+export const CREDENTIAL_SCOPE_TYPES = ["account", "organization", "workspace"] as const;
71+export type CredentialScopeType = (typeof CREDENTIAL_SCOPE_TYPES)[number];
1672 export type CredentialScope = CredentialScopeType;
17−export type CredentialProvider = "local-convex" | "workos-vault";
18−export type ToolApprovalMode = "auto" | "required";
19−export type ToolSourceType = "mcp" | "openapi" | "graphql";
20−export type StorageScopeType = "scratch" | "account" | "workspace" | "organization";
21−export type StorageDurability = "ephemeral" | "durable";
22−export type StorageInstanceStatus = "active" | "closed" | "deleted";
23−export type StorageProvider = "agentfs-local" | "agentfs-cloudflare";
73+
74+export const CREDENTIAL_PROVIDERS = ["local-convex", "workos-vault"] as const;
75+export type CredentialProvider = (typeof CREDENTIAL_PROVIDERS)[number];
76+
77+export const TOOL_APPROVAL_MODES = ["auto", "required"] as const;
78+export type ToolApprovalMode = (typeof TOOL_APPROVAL_MODES)[number];
79+
80+export const TOOL_SOURCE_TYPES = ["mcp", "openapi", "graphql"] as const;
81+export type ToolSourceType = (typeof TOOL_SOURCE_TYPES)[number];
82+
83+export const STORAGE_SCOPE_TYPES = ["scratch", "account", "workspace", "organization"] as const;
84+export type StorageScopeType = (typeof STORAGE_SCOPE_TYPES)[number];
85+
86+export const STORAGE_DURABILITIES = ["ephemeral", "durable"] as const;
87+export type StorageDurability = (typeof STORAGE_DURABILITIES)[number];
88+
89+export const STORAGE_INSTANCE_STATUSES = ["active", "closed", "deleted"] as const;
90+export type StorageInstanceStatus = (typeof STORAGE_INSTANCE_STATUSES)[number];
91+
92+export const STORAGE_PROVIDERS = ["agentfs-local", "agentfs-cloudflare"] as const;
93+export type StorageProvider = (typeof STORAGE_PROVIDERS)[number];
94+
95+export const STORAGE_ACCESS_TYPES = ["opened", "provided", "accessed"] as const;
96+export type StorageAccessType = (typeof STORAGE_ACCESS_TYPES)[number];
2497
2598 export type JsonSchema = Record<string, unknown>;
2699
27−export type SourceAuthType = "none" | "bearer" | "apiKey" | "basic" | "mixed";
100+export const SOURCE_AUTH_TYPES = ["none", "bearer", "apiKey", "basic", "mixed"] as const;
101+export type SourceAuthType = (typeof SOURCE_AUTH_TYPES)[number];
28102
29103 export interface SourceAuthProfile {
30104 type: SourceAuthType;
@@ -96,7 +170,8 @@ export interface ToolCallRecord { · 65 unmodified lines
96170 completedAt?: number;
97171 }
98172
99−export type ArgumentConditionOperator = "equals" | "contains" | "starts_with" | "not_equals";
173+export const ARGUMENT_CONDITION_OPERATORS = ["equals", "contains", "starts_with", "not_equals"] as const;
174+export type ArgumentConditionOperator = (typeof ARGUMENT_CONDITION_OPERATORS)[number];
100175
101176 export interface ArgumentCondition {
102177 key: string;
@@ -125,7 +200,11 @@ export interface ToolPolicyRecord { · 22 unmodified lines
125200 updatedAt: number;
126201 }
127202
128−export type ToolRoleSelectorType = "all" | "source" | "namespace" | "tool_path";
203+export const TOOL_ROLE_SELECTOR_TYPES = ["all", "source", "namespace", "tool_path"] as const;
204+export type ToolRoleSelectorType = (typeof TOOL_ROLE_SELECTOR_TYPES)[number];
205+
206+export const TOOL_ROLE_BINDING_STATUSES = ["active", "disabled"] as const;
207+export type ToolRoleBindingStatus = (typeof TOOL_ROLE_BINDING_STATUSES)[number];
129208
130209 export interface ToolPolicySetRecord {
131210 id: string;
@@ -162,7 +241,7 @@ export interface ToolPolicyAssignmentRecord { · 30 unmodified lines
162241 workspaceId?: Id<"workspaces">;
163242 targetAccountId?: Id<"accounts">;
164243 clientId?: string;
165− status: "active" | "disabled";
244+ status: ToolRoleBindingStatus;
166245 expiresAt?: number;
167246 createdAt: number;
168247 updatedAt: number;
@@ -315,7 +394,8 @@ export interface SandboxRuntime { · 146 unmodified lines
315394 ): Promise<SandboxExecutionResult>;
316395 }
317396
318−export type ToolCredentialAuthType = "bearer" | "apiKey" | "basic";
397+export const TOOL_CREDENTIAL_AUTH_TYPES = ["bearer", "apiKey", "basic"] as const;
398+export type ToolCredentialAuthType = (typeof TOOL_CREDENTIAL_AUTH_TYPES)[number];
319399
320400 export interface ToolCredentialSpec {
321401 sourceKey: string;
executor/packages/database/convex/schema.ts+64−106
@@ -1,5 +1,34 @@
11 import { defineSchema, defineTable } from "convex/server";
22 import { v } from "convex/values";
3+import {
4+ argumentConditionValidator,
5+ accountProviderValidator,
6+ accountStatusValidator,
7+ approvalStatusValidator,
8+ billingSubscriptionStatusValidator,
9+ credentialProviderValidator,
10+ credentialScopeTypeValidator,
11+ inviteStatusValidator,
12+ jsonObjectValidator,
13+ orgMemberStatusValidator,
14+ organizationStatusValidator,
15+ orgRoleValidator,
16+ policyApprovalModeValidator,
17+ policyEffectValidator,
18+ policyMatchTypeValidator,
19+ policyScopeTypeValidator,
20+ storageDurabilityValidator,
21+ storageInstanceStatusValidator,
22+ storageProviderValidator,
23+ storageScopeTypeValidator,
24+ taskStatusValidator,
25+ toolApprovalModeValidator,
26+ toolCallStatusValidator,
27+ toolRoleBindingStatusValidator,
28+ toolRoleSelectorTypeValidator,
29+ toolSourceScopeTypeValidator,
30+ toolSourceTypeValidator,
31+} from "../src/database/validators";
332
433 // Convex database schema.
534 //
@@ -10,87 +39,20 @@ import { v } from "convex/values"; · 4 unmodified lines
1039 // systems and in logs; `_id` stays internal to Convex.
1140 // - `accountId` links rows to `accounts` where identity context is needed.
1241 //
13−// The small validators below act like enums for schema fields.
14−// Note: Some of these are duplicated as request validators under `executor/packages/database/convex/database/validators.ts`
15−// and in a few feature modules. Keep the literal sets aligned to avoid drift.
16−
17−const accountProvider = v.union(v.literal("workos"), v.literal("anonymous"));
18−const accountStatus = v.union(v.literal("active"), v.literal("deleted"));
19−const organizationStatus = v.union(v.literal("active"), v.literal("deleted"));
20−const orgRole = v.union(v.literal("owner"), v.literal("admin"), v.literal("member"), v.literal("billing_admin"));
21−const orgMemberStatus = v.union(v.literal("active"), v.literal("pending"), v.literal("removed"));
22−const billingSubscriptionStatus = v.union(
23− v.literal("incomplete"),
24− v.literal("incomplete_expired"),
25− v.literal("trialing"),
26− v.literal("active"),
27− v.literal("past_due"),
28− v.literal("canceled"),
29− v.literal("unpaid"),
30− v.literal("paused"),
31−);
32−const inviteStatus = v.union(
33− v.literal("pending"),
34− v.literal("accepted"),
35− v.literal("expired"),
36− v.literal("revoked"),
37− v.literal("failed"),
38−);
39−const taskStatus = v.union(
40− v.literal("queued"),
41− v.literal("running"),
42− v.literal("completed"),
43− v.literal("failed"),
44− v.literal("timed_out"),
45− v.literal("denied"),
46−);
47−const approvalStatus = v.union(v.literal("pending"), v.literal("approved"), v.literal("denied"));
48−const toolCallStatus = v.union(
49− v.literal("requested"),
50− v.literal("pending_approval"),
51− v.literal("completed"),
52− v.literal("failed"),
53− v.literal("denied"),
54−);
55−const toolApprovalMode = v.union(v.literal("auto"), v.literal("required"));
56−const policyScopeType = v.union(v.literal("account"), v.literal("organization"), v.literal("workspace"));
57−const policyMatchType = v.union(v.literal("glob"), v.literal("exact"));
58−const policyEffect = v.union(v.literal("allow"), v.literal("deny"));
59−const policyApprovalMode = v.union(v.literal("inherit"), v.literal("auto"), v.literal("required"));
60−const toolRoleSelectorType = v.union(
61− v.literal("all"),
62− v.literal("source"),
63− v.literal("namespace"),
64− v.literal("tool_path"),
65−);
66−const toolRoleBindingStatus = v.union(v.literal("active"), v.literal("disabled"));
67−const toolSourceScopeType = v.union(v.literal("organization"), v.literal("workspace"));
68−const credentialScopeType = v.union(v.literal("account"), v.literal("organization"), v.literal("workspace"));
69−const storageScopeType = v.union(v.literal("scratch"), v.literal("account"), v.literal("workspace"), v.literal("organization"));
70−const storageDurability = v.union(v.literal("ephemeral"), v.literal("durable"));
71−const storageInstanceStatus = v.union(v.literal("active"), v.literal("closed"), v.literal("deleted"));
72−const storageProvider = v.union(v.literal("agentfs-local"), v.literal("agentfs-cloudflare"));
73−const credentialProvider = v.union(
74− v.literal("local-convex"),
75− v.literal("workos-vault"),
76−);
77−const toolSourceType = v.union(v.literal("mcp"), v.literal("openapi"), v.literal("graphql"));
78−const jsonObject = v.record(v.string(), v.any());
79−
8042 export default defineSchema({
8143 // User identities (WorkOS-backed or anonymous).
8244 //
8345 // Primary access patterns:
8446 // - Lookup by provider + providerAccountId (WorkOS user id / anon id).
8547 accounts: defineTable({
86− provider: accountProvider,
48+ provider: accountProviderValidator,
8749 providerAccountId: v.string(), // WorkOS user ID or anon_* UUID
8850 email: v.optional(v.string()),
8951 name: v.string(),
9052 firstName: v.optional(v.string()),
9153 lastName: v.optional(v.string()),
9254 avatarUrl: v.optional(v.string()),
93− status: accountStatus,
55+ status: accountStatusValidator,
9456 createdAt: v.number(),
9557 updatedAt: v.number(),
9658 lastLoginAt: v.optional(v.number()),
@@ -128,7 +90,7 @@ export default defineSchema({ · 31 unmodified lines
12890 workosOrgId: v.optional(v.string()), // external WorkOS org ID
12991 slug: v.string(),
13092 name: v.string(),
131− status: organizationStatus,
93+ status: organizationStatusValidator,
13294 createdByAccountId: v.optional(v.id("accounts")),
13395 createdAt: v.number(),
13496 updatedAt: v.number(),
@@ -148,8 +110,8 @@ export default defineSchema({ · 13 unmodified lines
148110 organizationMembers: defineTable({
149111 organizationId: v.id("organizations"),
150112 accountId: v.id("accounts"),
151− role: orgRole,
152− status: orgMemberStatus,
113+ role: orgRoleValidator,
114+ status: orgMemberStatusValidator,
153115 billable: v.boolean(),
154116 invitedByAccountId: v.optional(v.id("accounts")),
155117 joinedAt: v.optional(v.number()),
@@ -171,8 +133,8 @@ export default defineSchema({ · 15 unmodified lines
171133 organizationId: v.id("organizations"),
172134 workspaceId: v.optional(v.id("workspaces")),
173135 email: v.string(),
174− role: orgRole,
175− status: inviteStatus,
136+ role: orgRoleValidator,
137+ status: inviteStatusValidator,
176138 providerInviteId: v.optional(v.string()), // external WorkOS invite ID
177139 invitedByAccountId: v.id("accounts"),
178140 expiresAt: v.number(),
@@ -215,7 +177,7 @@ export default defineSchema({ · 36 unmodified lines
215177 organizationId: v.id("organizations"),
216178 stripeSubscriptionId: v.string(), // external Stripe subscription ID
217179 stripePriceId: v.string(), // external Stripe price ID
218− status: billingSubscriptionStatus,
180+ status: billingSubscriptionStatusValidator,
219181 currentPeriodStart: v.optional(v.number()),
220182 currentPeriodEnd: v.optional(v.number()),
221183 cancelAtPeriodEnd: v.boolean(),
@@ -251,9 +213,9 @@ export default defineSchema({ · 29 unmodified lines
251213 workspaceId: v.id("workspaces"),
252214 accountId: v.optional(v.id("accounts")),
253215 clientId: v.optional(v.string()), // client label: "web", "mcp", etc.
254− status: taskStatus,
216+ status: taskStatusValidator,
255217 timeoutMs: v.number(),
256− metadata: jsonObject,
218+ metadata: jsonObjectValidator,
257219 nextEventSequence: v.optional(v.number()),
258220 error: v.optional(v.string()),
259221 stdout: v.optional(v.string()),
@@ -279,8 +241,8 @@ export default defineSchema({ · 19 unmodified lines
279241 taskId: v.string(), // references tasks.taskId (not tasks._id)
280242 workspaceId: v.id("workspaces"),
281243 toolPath: v.string(),
282− input: jsonObject,
283− status: approvalStatus,
244+ input: jsonObjectValidator,
245+ status: approvalStatusValidator,
284246 reason: v.optional(v.string()),
285247 reviewerId: v.optional(v.string()), // account._id or anon_<uuid>
286248 createdAt: v.number(),
@@ -300,7 +262,7 @@ export default defineSchema({ · 13 unmodified lines
300262 callId: v.string(),
301263 workspaceId: v.id("workspaces"),
302264 toolPath: v.string(),
303− status: toolCallStatus,
265+ status: toolCallStatusValidator,
304266 approvalId: v.optional(v.string()),
305267 error: v.optional(v.string()),
306268 createdAt: v.number(),
@@ -318,7 +280,7 @@ export default defineSchema({ · 11 unmodified lines
318280 taskId: v.string(), // references tasks.taskId (not tasks._id)
319281 eventName: v.string(),
320282 type: v.string(),
321− payload: jsonObject,
283+ payload: jsonObjectValidator,
322284 createdAt: v.number(),
323285 })
324286 .index("by_task_sequence", ["taskId", "sequence"]),
@@ -342,18 +304,14 @@ export default defineSchema({ · 17 unmodified lines
342304 ruleId: v.string(), // domain ID: trule_<uuid>
343305 roleId: v.string(),
344306 organizationId: v.id("organizations"),
345− selectorType: toolRoleSelectorType,
307+ selectorType: toolRoleSelectorTypeValidator,
346308 sourceKey: v.optional(v.string()),
347309 namespacePattern: v.optional(v.string()),
348310 toolPathPattern: v.optional(v.string()),
349− matchType: policyMatchType,
350− effect: policyEffect,
351− approvalMode: policyApprovalMode,
352− argumentConditions: v.optional(v.array(v.object({
353− key: v.string(),
354− operator: v.union(v.literal("equals"), v.literal("contains"), v.literal("starts_with"), v.literal("not_equals")),
355− value: v.string(),
356− }))),
311+ matchType: policyMatchTypeValidator,
312+ effect: policyEffectValidator,
313+ approvalMode: policyApprovalModeValidator,
314+ argumentConditions: v.optional(v.array(argumentConditionValidator)),
357315 priority: v.number(),
358316 createdAt: v.number(),
359317 updatedAt: v.number(),
@@ -367,11 +325,11 @@ export default defineSchema({ · 7 unmodified lines
367325 bindingId: v.string(), // domain ID: trbind_<uuid>
368326 roleId: v.string(),
369327 organizationId: v.id("organizations"),
370− scopeType: policyScopeType,
328+ scopeType: policyScopeTypeValidator,
371329 workspaceId: v.optional(v.id("workspaces")),
372330 targetAccountId: v.optional(v.id("accounts")),
373331 clientId: v.optional(v.string()),
374− status: toolRoleBindingStatus,
332+ status: toolRoleBindingStatusValidator,
375333 expiresAt: v.optional(v.number()),
376334 createdAt: v.number(),
377335 updatedAt: v.number(),
@@ -396,14 +354,14 @@ export default defineSchema({ · 18 unmodified lines
396354 sourceCredentials: defineTable({
397355 bindingId: v.string(), // domain ID: bind_<uuid>
398356 credentialId: v.string(), // domain ID: conn_<uuid>
399− scopeType: credentialScopeType,
357+ scopeType: credentialScopeTypeValidator,
400358 accountId: v.optional(v.id("accounts")),
401359 organizationId: v.id("organizations"),
402360 workspaceId: v.optional(v.id("workspaces")),
403361 sourceKey: v.string(),
404− provider: credentialProvider,
405− secretJson: jsonObject,
406− overridesJson: v.optional(jsonObject),
362+ provider: credentialProviderValidator,
363+ secretJson: jsonObjectValidator,
364+ overridesJson: v.optional(jsonObjectValidator),
407365 boundAuthFingerprint: v.optional(v.string()),
408366 createdAt: v.number(),
409367 updatedAt: v.number(),
@@ -430,13 +388,13 @@ export default defineSchema({ · 20 unmodified lines
430388 // - Enforce name uniqueness per owner scope.
431389 toolSources: defineTable({
432390 sourceId: v.string(), // domain ID: src_<uuid>
433− scopeType: toolSourceScopeType,
391+ scopeType: toolSourceScopeTypeValidator,
434392 organizationId: v.id("organizations"),
435393 workspaceId: v.optional(v.id("workspaces")),
436394 name: v.string(),
437− type: toolSourceType,
395+ type: toolSourceTypeValidator,
438396 configVersion: v.number(),
439− config: jsonObject,
397+ config: jsonObjectValidator,
440398 specHash: v.optional(v.string()),
441399 authFingerprint: v.optional(v.string()),
442400 enabled: v.boolean(),
@@ -527,7 +485,7 @@ export default defineSchema({ · 84 unmodified lines
527485 normalizedPath: v.string(),
528486 aliases: v.array(v.string()),
529487 description: v.string(),
530− approval: toolApprovalMode,
488+ approval: toolApprovalModeValidator,
531489 source: v.optional(v.string()),
532490 searchText: v.string(),
533491 displayInput: v.optional(v.string()),
@@ -577,10 +535,10 @@ export default defineSchema({ · 43 unmodified lines
577535 // Rows are scope-aware and can be shared across account/workspace/org contexts.
578536 storageInstances: defineTable({
579537 instanceId: v.string(),
580− scopeType: storageScopeType,
581− durability: storageDurability,
582− status: storageInstanceStatus,
583− provider: storageProvider,
538+ scopeType: storageScopeTypeValidator,
539+ durability: storageDurabilityValidator,
540+ status: storageInstanceStatusValidator,
541+ provider: storageProviderValidator,
584542 backendKey: v.string(),
585543 organizationId: v.id("organizations"),
586544 workspaceId: v.optional(v.id("workspaces")),
@@ -626,8 +584,8 @@ export default defineSchema({ · 39 unmodified lines
626584 accountLinks: defineTable({
627585 sourceAccountId: v.id("accounts"),
628586 targetAccountId: v.id("accounts"),
629− sourceProvider: accountProvider,
630− targetProvider: accountProvider,
587+ sourceProvider: accountProviderValidator,
588+ targetProvider: accountProviderValidator,
631589 linkReason: v.union(v.literal("anonymous_claim")),
632590 createdAt: v.number(),
633591 updatedAt: v.number(),
executor/packages/database/package.json+1−0
@@ -10,6 +10,7 @@ · 9 unmodified lines
1010 "dependencies": {
1111 "@convex-dev/rate-limiter": "^0.3.2",
1212 "convex": "^1.31.7",
13+ "convex-helpers": "^0.1.111",
1314 "sql.js": "^1.14.0"
1415 },
1516 "main": "./convex/_generated/api.js",
executor/packages/database/src/database/validators.ts+73−54
@@ -1,70 +1,89 @@
11 import { v } from "convex/values";
2+import { literals } from "convex-helpers/validators";
3+import {
4+ ACCOUNT_PROVIDERS,
5+ ACCOUNT_STATUSES,
6+ APPROVAL_STATUSES,
7+ ARGUMENT_CONDITION_OPERATORS,
8+ BILLING_SUBSCRIPTION_STATUSES,
9+ COMPLETED_TASK_STATUSES,
10+ CREDENTIAL_PROVIDERS,
11+ CREDENTIAL_SCOPE_TYPES,
12+ INVITE_STATUSES,
13+ ORGANIZATION_MEMBER_STATUSES,
14+ ORGANIZATION_ROLES,
15+ ORGANIZATION_STATUSES,
16+ POLICY_APPROVAL_MODES,
17+ POLICY_EFFECTS,
18+ POLICY_MATCH_TYPES,
19+ POLICY_RESOURCE_TYPES,
20+ POLICY_SCOPE_TYPES,
21+ STORAGE_ACCESS_TYPES,
22+ STORAGE_DURABILITIES,
23+ STORAGE_INSTANCE_STATUSES,
24+ STORAGE_PROVIDERS,
25+ STORAGE_SCOPE_TYPES,
26+ TASK_STATUSES,
27+ TERMINAL_TOOL_CALL_STATUSES,
28+ TOOL_APPROVAL_MODES,
29+ TOOL_CALL_STATUSES,
30+ TOOL_ROLE_BINDING_STATUSES,
31+ TOOL_ROLE_SELECTOR_TYPES,
32+ TOOL_SOURCE_SCOPE_TYPES,
33+ TOOL_SOURCE_TYPES,
34+} from "../../../core/src/types";
35+
36+// Core enum validators shared by Convex schema and function args.
37+export const accountProviderValidator = literals(...ACCOUNT_PROVIDERS);
38+export const accountStatusValidator = literals(...ACCOUNT_STATUSES);
39+export const organizationStatusValidator = literals(...ORGANIZATION_STATUSES);
40+export const orgRoleValidator = literals(...ORGANIZATION_ROLES);
41+export const orgMemberStatusValidator = literals(...ORGANIZATION_MEMBER_STATUSES);
42+
43+export const billingSubscriptionStatusValidator = literals(...BILLING_SUBSCRIPTION_STATUSES);
44+
45+export const inviteStatusValidator = literals(...INVITE_STATUSES);
46+
47+export const taskStatusValidator = literals(...TASK_STATUSES);
248
349 export const jsonObjectValidator = v.record(v.string(), v.any());
450
5−export const completedTaskStatusValidator = v.union(
6− v.literal("completed"),
7− v.literal("failed"),
8− v.literal("timed_out"),
9− v.literal("denied"),
10−);
11−
12−export const approvalStatusValidator = v.union(v.literal("pending"), v.literal("approved"), v.literal("denied"));
13−
14−export const terminalToolCallStatusValidator = v.union(
15− v.literal("completed"),
16− v.literal("failed"),
17− v.literal("denied"),
18−);
19−
20−export const policyScopeTypeValidator = v.union(v.literal("account"), v.literal("organization"), v.literal("workspace"));
21−export const policyResourceTypeValidator = v.union(
22− v.literal("all_tools"),
23− v.literal("source"),
24− v.literal("namespace"),
25− v.literal("tool_path"),
26−);
27−export const policyMatchTypeValidator = v.union(v.literal("glob"), v.literal("exact"));
28−export const policyEffectValidator = v.union(v.literal("allow"), v.literal("deny"));
29−export const policyApprovalModeValidator = v.union(v.literal("inherit"), v.literal("auto"), v.literal("required"));
30−export const toolRoleSelectorTypeValidator = v.union(
31− v.literal("all"),
32− v.literal("source"),
33− v.literal("namespace"),
34− v.literal("tool_path"),
35−);
36−export const toolRoleBindingStatusValidator = v.union(v.literal("active"), v.literal("disabled"));
37−export const argumentConditionOperatorValidator = v.union(v.literal("equals"), v.literal("contains"), v.literal("starts_with"), v.literal("not_equals"));
51+export const completedTaskStatusValidator = literals(...COMPLETED_TASK_STATUSES);
52+
53+export const approvalStatusValidator = literals(...APPROVAL_STATUSES);
54+
55+export const toolCallStatusValidator = literals(...TOOL_CALL_STATUSES);
56+
57+export const toolApprovalModeValidator = literals(...TOOL_APPROVAL_MODES);
58+
59+export const terminalToolCallStatusValidator = literals(...TERMINAL_TOOL_CALL_STATUSES);
60+
61+export const policyScopeTypeValidator = literals(...POLICY_SCOPE_TYPES);
62+export const policyResourceTypeValidator = literals(...POLICY_RESOURCE_TYPES);
63+export const policyMatchTypeValidator = literals(...POLICY_MATCH_TYPES);
64+export const policyEffectValidator = literals(...POLICY_EFFECTS);
65+export const policyApprovalModeValidator = literals(...POLICY_APPROVAL_MODES);
66+export const toolRoleSelectorTypeValidator = literals(...TOOL_ROLE_SELECTOR_TYPES);
67+export const toolRoleBindingStatusValidator = literals(...TOOL_ROLE_BINDING_STATUSES);
68+export const argumentConditionOperatorValidator = literals(...ARGUMENT_CONDITION_OPERATORS);
3869 export const argumentConditionValidator = v.object({
3970 key: v.string(),
4071 operator: argumentConditionOperatorValidator,
4172 value: v.string(),
4273 });
43−export const toolSourceScopeTypeValidator = v.union(v.literal("organization"), v.literal("workspace"));
44−export const credentialScopeTypeValidator = v.union(v.literal("account"), v.literal("organization"), v.literal("workspace"));
74+export const toolSourceScopeTypeValidator = literals(...TOOL_SOURCE_SCOPE_TYPES);
75+export const credentialScopeTypeValidator = literals(...CREDENTIAL_SCOPE_TYPES);
4576
46−export const credentialProviderValidator = v.union(
47− v.literal("local-convex"),
48− v.literal("workos-vault"),
49−);
77+export const credentialProviderValidator = literals(...CREDENTIAL_PROVIDERS);
5078
51−export const toolSourceTypeValidator = v.union(v.literal("mcp"), v.literal("openapi"), v.literal("graphql"));
79+export const toolSourceTypeValidator = literals(...TOOL_SOURCE_TYPES);
5280
53−export const storageScopeTypeValidator = v.union(
54− v.literal("scratch"),
55− v.literal("account"),
56− v.literal("workspace"),
57− v.literal("organization"),
58−);
81+export const storageScopeTypeValidator = literals(...STORAGE_SCOPE_TYPES);
5982
60−export const storageDurabilityValidator = v.union(v.literal("ephemeral"), v.literal("durable"));
83+export const storageDurabilityValidator = literals(...STORAGE_DURABILITIES);
6184
62−export const storageInstanceStatusValidator = v.union(v.literal("active"), v.literal("closed"), v.literal("deleted"));
85+export const storageInstanceStatusValidator = literals(...STORAGE_INSTANCE_STATUSES);
6386
64−export const storageProviderValidator = v.union(v.literal("agentfs-local"), v.literal("agentfs-cloudflare"));
87+export const storageProviderValidator = literals(...STORAGE_PROVIDERS);
6588
66−export const storageAccessTypeValidator = v.union(
67− v.literal("opened"),
68− v.literal("provided"),
69− v.literal("accessed"),
70−);
89+export const storageAccessTypeValidator = literals(...STORAGE_ACCESS_TYPES);
executor/packages/database/src/task/status.ts+5−3
@@ -1,9 +1,11 @@
1−import type { TaskStatus } from "../../../core/src/types";
1+import { COMPLETED_TASK_STATUSES, type CompletedTaskStatus, type TaskStatus } from "../../../core/src/types";
22
3−type TerminalTaskStatus = Extract<TaskStatus, "completed" | "failed" | "timed_out" | "denied">;
3+type TerminalTaskStatus = CompletedTaskStatus;
4+
5+const TERMINAL_TASK_STATUS_SET = new Set<TaskStatus>(COMPLETED_TASK_STATUSES);
46
57 export function isTerminalTaskStatus(status: TaskStatus): status is TerminalTaskStatus {
6− return status === "completed" || status === "failed" || status === "timed_out" || status === "denied";
8+ return TERMINAL_TASK_STATUS_SET.has(status);
79 }
810
911 export function taskTerminalEventType(status: TerminalTaskStatus): "task.completed" | "task.failed" | "task.timed_out" | "task.denied" {