Skip to content

Integration Contracts

Every new Mistflow app ships with a contracts/ directory: a Zod-backed validation layer derived from your Drizzle schema. It exists so that when the schema changes, every touchpoint that depends on the schema changes with it, in one place, not five.

This page explains what drift looks like, what the contract layer does, and how to add it to a project that doesn’t have one yet.

Say you have a habit tracker with a habits table. Somewhere in your app:

  • A server action inserts new habits (name, targetDaysPerWeek, color)
  • A form on the client collects those fields
  • A list view displays habits by joining onto a streaks table

One day the AI adds a reminderTime column to habits. Without a contract layer, it has to find every one of those places by hand and update each one to match. The form schema doesn’t know about the new field. The server action still rejects it. The list view is fine until someone filters by reminder time, at which point it returns the wrong shape.

This is the drift problem: one source of truth in db/schema.ts, three or four places that have to be manually synced to it.

Mistflow scaffolds every new app with a contracts/ directory that re-exports Zod schemas derived from your Drizzle tables using drizzle-zod. A habit tracker’s layout looks roughly like this:

db/
schema.ts # Drizzle tables, the source of truth
contracts/
habit.ts # Zod schemas derived from db/schema.ts
index.ts # Re-exports everything

Inside contracts/habit.ts:

import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
import { z } from 'zod';
import { habits } from '@/db/schema';
export const HabitRow = createSelectSchema(habits);
export const HabitInsert = createInsertSchema(habits, {
name: z.string().min(1).max(80),
targetDaysPerWeek: z.number().int().min(1).max(7),
});
export const HabitUpdate = HabitInsert.partial();
export type Habit = z.infer<typeof HabitRow>;
export type NewHabit = z.infer<typeof HabitInsert>;

Every consumer of the habits shape imports from contracts/ instead of redefining the shape inline:

  • Forms use HabitInsert via zodResolver(HabitInsert) in react-hook-form
  • Server actions call HabitInsert.parse(input) before touching the database
  • API routes validate request bodies with HabitInsert
  • List views type their data as Habit[]

Now when the AI adds reminderTime, it edits db/schema.ts once. drizzle-zod picks up the new column, Zod’s type inference updates, TypeScript catches every place that was assuming the old shape, and the form/server-action/API-route all converge to the new shape in one pass.

methodologies/mistflow-stack.md is the context document every Mistflow build loads into the editor. It has a section on contracts that tells the AI:

  1. Never write inline Zod schemas for persisted entities. Use contracts/.
  2. When adding a column, edit db/schema.ts first. The contract and every consumer follow from that.
  3. Before writing a server action, import the insert schema from contracts/. Don’t hand-roll validation.
  4. If the schema change adds a required field, update the form’s default values. TypeScript will usually catch this but not always.

mist_implement also surfaces the existing contracts as part of its context.crud payload, so the editor sees what’s already defined before it generates new code.

Projects created before MCP 0.5.0 don’t have a contracts/ directory. Retrofit with:

Terminal window
mist contracts init

This scans db/schema.ts, writes a contract file per table, and adds the appropriate imports. Run it from the project root.

To check that the contracts still match the schema (for example, after a manual schema edit):

Contract drift is surfaced by mist_project with action: "get" — the response flags schema/contract mismatches so your editor can regenerate or add entities before continuing.