feat(routing): add routing_rules schema, condition types, and action types (M4-001/002/003)

- Add routing_rules table to packages/db with scope, priority, conditions (jsonb), and action (jsonb) columns; generate migration 0004
- Define RoutingCondition, RoutingAction, RoutingRule, TaskClassification, and RoutingDecision types in apps/gateway/src/agent/routing/routing.types.ts
- Expand @mosaic/types routing/index.ts to export all M4 types (TaskType, Complexity, Domain, CostTier+local, Capability) alongside existing RoutingCriteria/RoutingResult
- Fix pre-existing type errors in routing.service.ts (local CostTier) and default-rules.ts (count optional chaining, unknown cast)
- Fix pre-existing Prettier violations in agent module and provider files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 19:02:29 -05:00
parent e020b78e3b
commit aba7764043
16 changed files with 3983 additions and 42 deletions

View File

@@ -0,0 +1,17 @@
CREATE TABLE "routing_rules" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
"priority" integer NOT NULL,
"scope" text DEFAULT 'system' NOT NULL,
"user_id" text,
"conditions" jsonb NOT NULL,
"action" jsonb NOT NULL,
"enabled" boolean DEFAULT true 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 "routing_rules" ADD CONSTRAINT "routing_rules_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "routing_rules_scope_priority_idx" ON "routing_rules" USING btree ("scope","priority");--> statement-breakpoint
CREATE INDEX "routing_rules_user_id_idx" ON "routing_rules" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "routing_rules_enabled_idx" ON "routing_rules" USING btree ("enabled");

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,13 @@
"when": 1773887085247,
"tag": "0003_p8003_perf_indexes",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1774224004898,
"tag": "0004_bumpy_miracleman",
"breakpoints": true
}
]
}

View File

@@ -479,6 +479,66 @@ export const skills = pgTable(
(t) => [index('skills_enabled_idx').on(t.enabled)],
);
// ─── Routing Rules ──────────────────────────────────────────────────────────
export const routingRules = pgTable(
'routing_rules',
{
id: uuid('id').primaryKey().defaultRandom(),
/** Human-readable rule name */
name: text('name').notNull(),
/** Lower number = higher priority; unique per scope */
priority: integer('priority').notNull(),
/** 'system' rules apply globally; 'user' rules are scoped to a specific user */
scope: text('scope', { enum: ['system', 'user'] })
.notNull()
.default('system'),
/** Null for system-scoped rules; FK to users.id for user-scoped rules */
userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }),
/** Array of condition objects that must all match for the rule to fire */
conditions: jsonb('conditions').notNull().$type<Record<string, unknown>[]>(),
/** Routing action to take when all conditions are satisfied */
action: jsonb('action').notNull().$type<Record<string, unknown>>(),
/** Whether this rule is active */
enabled: boolean('enabled').notNull().default(true),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
// Lookup by scope + priority for ordered rule evaluation
index('routing_rules_scope_priority_idx').on(t.scope, t.priority),
// User-scoped rules lookup
index('routing_rules_user_id_idx').on(t.userId),
// Filter enabled rules efficiently
index('routing_rules_enabled_idx').on(t.enabled),
],
);
// ─── Provider Credentials ────────────────────────────────────────────────────
export const providerCredentials = pgTable(
'provider_credentials',
{
id: uuid('id').primaryKey().defaultRandom(),
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
provider: text('provider').notNull(),
credentialType: text('credential_type', { enum: ['api_key', 'oauth_token'] }).notNull(),
encryptedValue: text('encrypted_value').notNull(),
refreshToken: text('refresh_token'),
expiresAt: timestamp('expires_at', { withTimezone: true }),
metadata: jsonb('metadata'),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
// Unique constraint: one credential entry per user per provider
uniqueIndex('provider_credentials_user_provider_idx').on(t.userId, t.provider),
index('provider_credentials_user_id_idx').on(t.userId),
],
);
// ─── Summarization Jobs ─────────────────────────────────────────────────────
export const summarizationJobs = pgTable(