Skip to content

Language Syntax ​

The .carrot file is a strictly typed, declarative architectural blueprint. It is designed to be highly readable for humans, mathematically parsable by the Carotene compiler, and semantically perfect for the LLM generation engine.

This page serves as the complete syntax reference for the Carotene language.

1. Top-Level Declarations ​

Every .carrot file must contain at least one target architecture block (backend / frontend) or data primitive.

domain (Optional Namespace) ​

Acts as an organisational namespace for grouping related models, stores, and state machines.

  • If omitted: All primitives are registered to the Global Namespace. Use this for small projects or simple prototypes.
  • If used: All internal primitives are scoped to the domain name. Use this to prevent naming collisions in large monorepos.

Note: domain does not dictate your folder structure or deployment architecture; it is purely a compile-time naming convention.

dart
// Global Namespace (No domain block)
model User { ... }

// Scoped Namespace
domain Commerce {
  model Order { ... }
}

backend / frontend ​

Defines a physical microservice or application target. The name you provide here dictates the folder name generated dynamically in your monorepo.

  • backend: Internal service mesh APIs.
  • frontend: User-facing web or mobile applications.
dart
backend CoreAPI {
  // Functions go here
}

frontend AdminDashboard {
  // Views go here
}

2. Namespace Resolution & Referencing ​

Carotene uses strict lexical scoping to resolve references between frontends, backends, and data stores. You reference primitives using dot-notation paths.

To keep your code clean, Carotene supports Relative Pathing. You can omit parent names if you are referencing something within the same scope.

Absolute vs. Relative Paths ​

Imagine the following architecture:

dart
domain Commerce {
  backend OrderAPI {
    store { model Order { id: UUID } }
    function Process() { ... }
  }
  
  frontend Web { ... }
}
  • Absolute Path: If you are outside the domain entirely, you must use the full path: Commerce.OrderAPI.store.Order.
  • Same Domain: If you are inside the Web frontend, you can drop the domain name: calls OrderAPI.Process.
  • Same Backend: If you are inside another function in OrderAPI, you drop the backend name: reads store.Order.

Unnamed Blocks ​

If a block does not have a custom name, you simply use the keyword itself in the path.

dart
// Unnamed backend and unnamed store
backend {
  store { model User { id: UUID } }
}

frontend {
  // Uses the literal keywords 'backend' and 'store'
  view Profile(user: backend.store.User) { ... }
}

Property Access ​

You can dot all the way down to specific model properties, which is highly recommended for function arguments to ensure perfect type alignment.

dart
// Binds the argument directly to the UUID type of the Order's ID
function GetOrder(orderId: store.Order.id) {
  reads store.Order
  implements { ... }
}

3. Decorators (Execution Ownership) ​

Decorators define who is responsible for implementing a block of code. They can be applied to any structural block (domain, backend, frontend, function, or loop).

  • @ai (Implicit Default): The Carotene compiler will actively manage the src/ files for this block. It will invoke the LLM to synthesise the @("...") generative operators and overwrite the file if the contract description or signature changes.
  • @manual: The compiler will generate the initial empty stub and strict native interfaces, but it will never invoke the AI or overwrite the src/ file. Ownership is permanently yielded to the human developer.

Inheritance and Scoping ​

Decorators cascade downwards. The most specific decorator wins.

dart
// 1. The entire backend is manual
@manual
backend PaymentAPI {
  
  // Inherits @manual. Hand-written by a developer.
  function ProcessRefund() {
    implements {
      tax = @("Calculate the 5% processing fee")
    }
  }
  
  // 2. Specific Override: This single function delegates back to the AI
  @ai
  function FormatCurrency(amount: Float) {
    implements {
      formatted = @("Format as USD")
    }
  }
}

4. Data & State Primitives ​

These blocks define the strict shapes and permitted lifecycles of your data. They can be declared globally or inside a domain.

model ​

Defines a data structure. Models are purely abstract and are not inherently connected to a database.

  • Supported Primitives: String, Int, Float, Boolean, UUID, Date, JSON.
  • Optional Fields: Suffix with ?.
dart
model User {
  id: UUID
  email: String
  age: Int?
}

flow ​

Defines a deterministic finite state machine.

dart
flow OrderState {
  Pending -> Paid
  Paid -> Shipped
  Paid -> Refunded
}

store ​

Binds a model to the physical database, exposing it to the Carotene runtime.

dart
store Customer {
  model: User
}

5. Execution Primitives ​

These blocks define the actual executable APIs and autonomous workflows. Both are available in backend and frontend blocks; the compiler infers the runtime infrastructure (e.g., CRON vs. requestAnimationFrame) based on the context.

function (Event-Driven) ​

Defines an event-driven workflow. Arguments must be strictly typed, often referencing store or model fields directly.

dart
function ProcessRefund(orderId: store.Order.id) {
  reads store.Order
  implements {
    // Logic goes here
  }
}

The Fast-Path Shorthand (=>) ​

For simple, deterministic execution logic, you can use the expression-bodied shorthand => to eliminate boilerplate. Functions that use this shorthand are statically analyzed and do not require explicit Zero-Trust permission verbs.

dart
// UI Component
view Button(label: String, onClick: Function) = @("Render a button.")

// Deterministic Function
function UpdateName(val: String) => state.User.name = val

socket (Real-Time Bidirectional) ​

Defines a continuous, bidirectional handler. The implements block runs once per received message, and returning a value automatically emits it back to the client's open stream.

dart
socket HandleChat(msg: String) -> String {
  reads store.Message
  implements {
    @("Process the message and return the response")
  }
}

loop (Continuous) ​

Defines a continuous, autonomous background workflow.

dart
loop MatchmakingWorker {
  config {
    stream: { listensTo: state.PlayerQueue }
  }
  reads state.PlayerQueue
  updates store.Match

  implements {
    @("Match players together")
  }
}

6. Security & Permission Primitives ​

These blocks define the Zero-Trust boundaries of your system.

gateway ​

Defines an ingress boundary for external traffic or programmable controls. Gateways must be defined inside a backend or frontend block. They act as routers, mapping external API URLs (and parameters) directly to functions defined in your architecture.

Gateways support protocol-specific routing blocks (such as rest { } for HTTP methods and paths, or rpc { }) to map incoming external endpoints explicitly to your internal functions.

dart
backend CoreAPI {
  function ProcessStripeWebhook(payload: JSON) { ... }
  
  gateway ExternalAPI {
    rest {
      POST "/webhooks/stripe" -> ProcessStripeWebhook()
    }
  }
}

integration ​

Defines an external software API, SDK, or hardware device.

  • Scope: Must be defined inside a backend or frontend block to prevent cross-boundary secret leakage.
  • Usage: Exposes external methods that can be triggered using the calls verb.

requires (RBAC) ​

Immediately evaluates a condition. If it returns false, the function immediately terminates with an HTTP 401 or 403.

dart
requires session.isAuthenticated == true
requires session.role == "Admin"

The Zero-Trust Verbs ​

Explicitly declare what the function or loop is allowed to touch. If a verb is omitted, the Sandbox will physically block the action.

  • reads: Allows querying a specific store.
  • updates: Allows mutating an existing record in a store.
  • creates: Allows inserting a new record into a store.
  • deletes: Allows removing a record from a store.
  • calls: Allows outbound network requests to a defined integration.
dart
reads store.Customer
updates store.Order
calls integration.Stripe

7. Internal Logic & Variables ​

Inside a function or loop, you can write deterministic logic to handle fast-paths, assignments, and validation.

Variable Assignment ​

Standard assignment. Types are inferred automatically by the compiler.

dart
customer = store.Customer.find(customerId)
fee = 5.0

Conditionals (if / else) ​

Standard control flow.

dart
if (customer.tier == "Gold") {
  fee = 0.0
} else {
  fee = 5.0
}

error ​

Defines a strongly typed error primitive. Can be used for declarative error handling across boundaries.

dart
error InsufficientFunds {
  shortfall: Float
}

throws ​

Immediately terminates the function and returns an error payload. Used for business logic validation.

dart
if (order.ageInDays > 30) {
  throws "Refund window has expired"
}

if (wallet.balance < cost) {
  throws error.InsufficientFunds({ shortfall: cost - wallet.balance })
}

catches ​

Used to catch strongly typed errors thrown by the backend or an integration.

dart
backend.Process(id) catches InsufficientFunds (err) {
  implements {
    @("Handle the insufficient funds error by displaying a message")
  }
}

The Generative Operator (@) ​

The "Code Hole". Instructs the compiler to securely pass the bounded context to the configured LLM to synthesize the underlying implementation.

dart
// Assignment generation
discount = @("Calculate the prorated volume discount")

// Fire-and-forget generation
@("Format the customer's address and map it to the Shipping target")

triggers ​

Executes a state machine transition defined in a flow. If the transition is illegal, it throws an error.

dart
triggers targetOrder.status.Refunded

return ​

Outputs the final payload of the function to the client.

dart
return discount

8. Testing Primitives ​

The test block is a flat structure used to mathematically prove the AI's logic inside the Deterministic Sandbox. Tests can be placed anywhere in a .carrot file.

test ​

Defines the test scenario.

dart
test "Calculates the correct discount" {
  // Test code goes here
}

config ​

Configures the Sandbox environment for the specific test block.

  • sandbox options: EmbeddedWASM (Default, ~2ms), DockerTransactional (Heavy, real-world DB features).
  • time: Freezes the sandbox clock to a specific string.
dart
config {
  sandbox: EmbeddedWASM
  time: "2026-01-01"
}

given / mock ​

Seeds the isolated sandbox or intercepts external boundaries.

  • Use given... = mock store... to seed the database and bind a local variable simultaneously.
  • Use mock integration... -> value to intercept a network boundary and return a fake Future.
  • Use throws to simulate network errors.
dart
// Database Mocking & Binding
given user = mock store.User { id: "1", role: "Admin" }

// Network Mocking (Success)
mock integration.Stripe.Charge -> "txn_123"

// Network Mocking (Failure)
mock integration.SendGrid.Send -> throws "Timeout"

asserts ​

Evaluates a mathematical truth at the end of the test. If false, the AI is prompted to fix the code.

dart
// Asserting variable output
asserts result == 100.0

// Asserting Sandbox database state
asserts store.User.find("1").status == Active

// Asserting network side-effects
asserts integration.Stripe.Charge.wasCalledWith(100.0)
asserts integration.Stripe.Refund.wasNotCalled()

// Asserting errors
asserts ProcessRefund(order.id) throws "Refund window has expired"