Workflows & Lifecycles (flow) ​
You have defined your shapes (model), your databases (store), and your volatile memory (state). The final piece of data management is defining how that data changes over time.
In traditional development, the "lifecycle" of an entity is usually governed by scattered if/else statements across your codebase. If a developer (or an AI agent) forgets one of these checks, a user might accidentally cancel an order that has already shipped.
Carotene solves this with the flow primitive. A flow is a strict, compiler-enforced State Machine mapped directly to your data. It defines the available states, the valid paths between them, and the Named Eventsthat trigger those transitions.
Defining a Flow (States & Events) ​
Instead of writing a standard Enum, you define a flow. You map the valid transitions from one state to another, and you group them under an Event using the on block.
Crucially, Events govern the action, not the destination. A single event can handle completely different starting and ending states, such as a "Move Next" action on a Kanban board.
domain ProjectManagement {
// 1. The Flow explicitly names the transition events
flow TaskLifecycle {
start Todo
// A single event moving different states to their respective next steps
on MoveNext {
Todo -> InProgress
InProgress -> InReview
InReview -> Completed
}
// Multiple states sharing the same destination via a single event
on Archive {
Todo -> Archived
InProgress -> Archived
InReview -> Archived
Completed -> Archived
}
}
backend {
store {
model Task {
id: UUID { primary, generated: true }
title: String
// 2. Assign the Flow as the property type
status: TaskLifecycle
}
}
}
}The Compiler Advantage: From this simple block, the compiler implicitly knows the only valid states are [Todo, InProgress, InReview, Completed, Archived]. You get strict linting and type safety without ever having to write a redundant Enum.
The Execution Bridge: Explicit Signatures & Verbs ​
To actually move the data from Todo to InProgress, you do not manually mutate a string inside your business logic. Instead, you declare the state change at the architectural level using the triggers verb inside a function.
Because Carotene strictly avoids "magic" framework behavior, the function signature dictates exactly what is sent over the network, and the explicit verb list dictates the chronological execution sandbox.
backend {
// 1. Explicit Network Payload: The frontend will only send the ID
function AdvanceTask(taskId: store.Task.id) {
// 2. Explicit Fetch: Grants the AI permission to read the task into memory
reads store.Task
// 3. Explicit State Transition: Mutates the state machine in memory
triggers store.Task.status.MoveNext
// 4. Explicit Database Write: Commits the new state to the database
updates store.Task
}
}By explicitly requiring this chain of verbs, the AI execution sandbox is fully prepared and constrained. The AI fetches the task from the database, is structurally forced to use the compiled state machine method (e.g., await task.transitionTo('MoveNext')), and finally executes the update query to save it.
Precondition Inference (Zero-Runtime Errors) ​
Because the compiler knows exactly what Event AdvanceTask triggers, it knows that this function is only valid if the task is currently Todo, InProgress, or InReview.
This unlocks Carotene's most powerful safety feature: Precondition Inference. Carotene automatically generates guardrails across your entire full-stack application to prevent illegal state changes before they ever run.
1. The Frontend UI Guard (canExecute) ​
When Carotene generates your frontend SDK, it generates a synchronous validation method for every function.
Because the AdvanceTask function only takes an ID over the network, the generated canExecute method requires the frontend to pass in the local state if it wants to perform local validation. The AI agent uses this method when generating your UI to automatically wire up safe interfaces without hallucinating custom validation logic:
// AI-Generated React Component using the Carotene SDK
<button
// The AI passes the current task state into the validation helper.
// If the task is 'Completed' or 'Archived', the button instantly disables.
disabled={!backend.AdvanceTask.canExecute(currentTask.status)}
onClick={() => backend.AdvanceTask(currentTask.id)}
>
Advance Task
</button>This check evaluates the state machine preconditions locally in the browser, providing instant UI feedback without wasting a network request.
2. The Backend Ingress Shield ​
If a malicious user bypasses the frontend and hits the API directly to advance an Archived task, Carotene's generated backend middleware intercepts the request. It queries the database using the provided ID, sees the entity violates the MoveNext precondition, and rejects it with a 400 Bad Requestbefore the business logic is ever executed.
3. The AI Execution Sandbox ​
Inside the execution sandbox, the AI is structurally prevented from assigning invalid states. It does not need to write defensive if (task.status === 'Archived') throw Error boilerplate. It simply executes the trigger, completely guaranteed by the compiler that if the code is running, the state is mathematically valid.