Your First .carrot Contract β
The beauty of Carotene is that you don't start by configuring databases, setting up routing libraries, or writing boilerplate middleware. You start by defining the truth of your system.
A .carrot file is not a script; it is an architectural contract. It defines the data, the security boundaries, the generative logic, and the tests all in one unified, highly readable blueprint.
Letβs build the "Hello World" of enterprise applications: a simple Task Management function.
Step 1: Define the Truth (Data & State) β
Carotene contracts define the strict shapes of our data and the mathematical state machines that govern them. While complex projects use domain blocks for organisation, simple projects can start directly with the primitives.
Create a new file called tasks.carrot and define the structure:
// tasks.carrot
// Note: We removed the 'domain' block.
// Without it, primitives are registered to the global namespace.
// 1. The State Machine
// Tasks can only move in one direction.
flow TaskStatus {
Pending -> Completed
}
// 2. The Data Shape
model Task {
id: UUID
ownerId: UUID
title: String
status: TaskStatus
}
// 3. The Storage Layer
store Task {
model: Task
}π‘ Pro Tip: When to use domain
In small projects, you don't need a domain. However, as your project grows and you add more .carrot files, you might encounter naming collisions (e.g., two different models named User). Wrapping your code in a domain MyNamespace { ... } isolates those primitives to that namespace, preventing collisions.
Step 2: Define the Execution (The Sandbox) β
Now we need a function to actually complete a task. We will put this inside a backend block.
Notice how we don't write the implementation. We define the input, ingest the data, set up the Zero-Trust security rules, and use the @(...) operator to let the AI write the tedious logic.
// ... (Data definitions from above) ...
backend {
function CompleteTask(taskId: store.Task.id) {
// 1. Data Ingestion
targetTask = store.Task(taskId)
// 2. Zero-Trust Security (RBAC)
// If the user calling the API doesn't own the task, throw a 403.
requires session.userId == targetTask.ownerId
// 3. State Guardrail
// Prevent completing an already completed task.
if (targetTask.status == Completed) {
rejects with "Task is already completed"
}
// 4. The Generative Operator
// Let the AI write a personalised message based on the task title.
message = @("Generate a short, enthusiastic congratulatory message for finishing: ${targetTask.title}")
// 5. Side-Effects & Storage
triggers targetTask.status.Completed
updates targetTask
return message
}
}Step 3: Prove It (Test-Driven Generation) β
Because we used the @(...) operator, we must mathematically guarantee that the AI generates the correct logic before it compiles. We do this by appending a flat test block at the bottom of our file.
// ... (Data and Backend blocks) ...
test "User can complete a pending task and receive a message" {
// 1. Setup the isolated Sandbox memory
given activeSession = mock session { userId: "user_1" }
given pendingTask = mock store.Task {
id: "task_1",
ownerId: activeSession.userId,
title: "Submit Expense Report",
status: Pending
}
// 2. Execute the action
result = CompleteTask(pendingTask.id)
// 3. Assert the architecture was respected
asserts store.Task(pendingTask.id).status == Completed
// 4. Assert the AI returned the correct type
asserts type(result) == String
}Step 4: Build & Compile β
You have now written a complete, mathematically sound architecture. You defined the database schema, the RBAC security, the state machine, the execution boundaries, and the test assertions.
Now, you hand the blueprint to the Builder. Run the following command in your terminal:
carrot buildWhat happens next is entirely autonomic:
- The compiler reads your
.carrotfile and provisions the routes, the database schemas, and the security middleware. - The AI encounters your
@(...)operator and writes the TypeScript or Go code to generate the congratulatory message. - The Sandbox intercepts the AI's code, spins up an isolated, embedded database in memory, injects your
mockdata, and runs theCompleteTaskfunction. - It verifies the assertions. If the AI hallucinates, it feeds the error back and tries again.
In under three seconds, the terminal will output:
[β] Build Successful. Zero-Trust Sandbox verified.You just built a secure, tested, AI-powered backend in 45 lines of highly readable text.