Language reference (bundle v10)

The complete human-readable reference for MinLang as defined by language bundle v10. This page is a derivative: the canonical authority is the versioned bundle attached to every release —

https://github.com/codeshift-ai-solutions/minlang-releases/releases/latest/download/minlang-language-bundle.md

— and on any conflict, the bundle wins. Bundles are additive: every program valid under v1-v9 remains valid under v10.

Package/module surface (v6+)

Multi-file projects declare package and module identity in minlang.toml, with explicit module path mapping and optional pinned dependencies. minlang.lock records the resolved closure.

  • module/import/export control cross-module visibility.
  • Imports are compile dependencies; they do not create runtime source links.
  • Remote sources are fetch-time locations only and must be hash-pinned before compile.
  • Compile runs from local + cached verified bytes in a closed graph.

Program structure

A program is one .ml file containing top-level declarations, in any order:

Declaration Purpose Required?
entity The data that exists yes (at least one)
constraint A state the system must never enter per rule
action A named mutation (create/set/delete/deal steps only) per mutation
query A view-model screens read per view
screen Declarative flow, copy, and controls optional
trigger Server-only effect/pipeline invocation binding optional
schedule Time-based trigger invocation surface optional
theme Static look-and-feel data optional
asset Declarative asset intent (sprite/audio) optional
test Executable proof of behavior per constraint (triad)

Indentation is by tabs; identifiers for declarations are PascalCase, fields and inputs snake_case (see the style guide).

Entities

entity Deal {
	id: string req
	company: ref(Company)
	title: string
	stage: enum(lead, qualified, won, lost)
	owner: string
	amount: int
	created_at: string
}

Field types:

Type Meaning Web form control
string text text input
int integer number input
bool boolean checkbox
enum(a, b, c) one of the listed members select
ref(Entity) reference to a row of Entity select over that entity’s rows

req marks a field required. Every entity has an id: string req field that identifies its rows; rows are ordered by ascending id.

Constraints

Constraints own all business logic. Actions are not allowed to validate anything (detector D5).

constraint DealCompanyExists {
	on: Deal
	validate: count(Company as c, c == self.company) == 1
	message: 'company does not exist'
}
  • on: — the entity whose writes this constraint guards. It guards every write path: create, every set, all of them.
  • validate: — a boolean predicate over self (the row being written), action inputs, and count(...) joins. Operators: ==, >=, <=, AND, NOT(...).
  • message: — a single-quoted literal. Failure tests must match it byte for byte.

The predicate shapes that pass validation:

  1. Self-bound predicates for invariants on the row itself — required on mutable entities:

    validate: NOT(self.stage == 'qualified' AND self.owner == '')
    

    Prefer the explicit invalid-state form NOT(<invalid state>); OR-escape logic and off-by-one boundaries are rejected (D6).

  2. Count joins for anything cross-entity — there is no dot-walking (self.company.name is rejected, D2):

    validate: count(Company as c, c == self.company AND c.status == 'active') == 1
    

    Compare whole references (c == self.company), never c.id == self.company (D8).

  3. Create-time uniqueness — compares the action’s input variable, not self, and uses the proactive == 0 boundary:

    validate: count(Company as c, c.name == name) == 0
    

    <= 1, >= 1, and self.field in uniqueness checks are all rejected (D3/D4). Because input-bound constraints don’t cover set paths, put uniqueness only on create-only entities — the style guide explains the D3/D4/D7 interplay.

Actions

action CreateDeal(id: string, company: ref(Company), title: string,
                  stage: enum(lead, qualified, won, lost), owner: string,
                  amount: int, created_at: string, actor_id: string) {
	on: Deal
	create(Deal, { id: id, company: company, title: title, stage: stage,
	               owner: owner, amount: amount, created_at: created_at, actor_id: actor_id })
}
  • The signature declares typed inputs. on: names the subject entity.
  • The body is pure mutation — a sequence of steps from exactly four forms:
Step Effect
create(Entity, { field: value, ... }) insert a row
set(field, value) write one field of the subject row
delete Entity clear the whole set of Entity — there is no per-row delete
deal(profile: <name>, seed: <int>) v4: compile-time expansion into deterministic create steps (this bundle defines the golf_opening card profile). No runtime randomness — the seed is a literal and expansion uses a normative shuffle

No conditionals, no validation, no reads. Constraints decide whether the mutation is allowed.

Input classification

Every input is either user-supplied (rendered as a form field) or runtime-injected (filled automatically at the server boundary, never shown in a form). Classification is by name; the runtime-injected names are exactly:

delta_ms, frame_no, rng_draw, current_time, current_date,
actor_id, provider, recorded_at, recorded_by, request_id, created_at

These are ordinary explicit inputs — they don’t bypass determinism, they just document where the value comes from. Tests supply them as literals.

id semantics

  • An id input on a create action is a fresh id: filled at the server boundary (deps.newId()), never shown to the user.
  • An id input on a set action selects the subject row: rendered as a select over the on: entity’s rows.
  • An action with no user-supplied inputs renders as a plain button.

Queries

query PipelineBoard(workspace: ref(Workspace)) -> list<Deal> {
	from Deal
}
  • from Entity returns the entity’s rows. Return types: list<Entity> or a single Entity.
  • A query input whose name matches a field of the source entity filters rows by it; otherwise all rows are returned, sorted by ascending id (placed_at first when that field exists).
  • Screens bind queries via board (below).

Derive queries

A query may compute its result with derive <builtin>(<args>) instead of from — a pure, bundle-defined function of state. No logic, count(...), conditionals, or runtime dependence is allowed in a derive (D21/D26). The v4 builtins:

Builtin Args Returns Purpose
grid_status(game) ref(Game) enum(playing, x_won, o_won, draw) 3×3 mark-board outcome (win line / draw / playing)
card_total(game, owner) ref(Game), enum owner int Golf hand score for a player
round_winner(game) ref(Game) enum(none, player1, player2, tie) Lowest total wins
top_stock_card(game) ref(Game) string (Card id) Deterministic top of the stock pile
top_discard_card(game) ref(Game) string (Card id) Deterministic top of the discard pile
query BoardOutcome(game: ref(Game)) -> enum(playing, x_won, o_won, draw) {
	derive grid_status(game)
}

Outcome math lives in MinLang via these builtins — target emitters are forbidden from re-implementing it.

Screens

Screens are declarative data: which view is active, the copy on it, and which actions its controls dispatch. They never gate, validate, or mutate state.

screen Pipeline {
	when workspace.view == 'pipeline'
	title "Sales pipeline"
	hint "Create deals, move them through stages, and reassign owners."
	board PipelineBoard
	primary { label "Add deal" action CreateDeal }
	button { label "Companies" action OpenCompanies }
}

The full v3/v4/v7 vocabulary (anything else is rejected, D18/D23/D46):

Key Form Notes
when conjunction of <entity>.<field> == '<literal>' terms joined by AND required. Evaluated against the first row in id order of the entity (the singleton convention). No OR, no other comparators, no count(...)
title static string literal page heading
body static string literal paragraph copy
hint static string literal helper copy
status status <entity>.<field> { member "copy" ... } live status line; the field must be an enum, and the copy map must cover every member exactly once
headline same shape as status outcome headline
board board QueryName or board QueryName { layout dual_hands columns N } names a declared query. Default layout grid_3x3 renders as a responsive table on the web; dual_hands (with required columns) renders a card grid
primary primary { label "..." action Name }, or with sequence [...] / trigger Name primary control dispatches declared action/sequence/trigger
button button { label "..." action Name when <entity>.<field> == '<member>' }, or with sequence [...] / trigger Name repeatable secondary controls; when is optional visibility
tap tap stock\|discard\|slot { action Name }, or with sequence [...] / trigger Name repeatable pile/slot input targets (card games)

All copy is static string literals (D19). Titles, bodies, hints, labels, and every enum-copy entry live in the .ml; compilers and emitters must never invent, default, or hardcode user-facing text.

The first screen in declaration order whose when holds is the active one. Multi-screen flow is driven by a small UI-state entity plus navigation actions — see the style guide.

Screen-first authoring: ml1 design ui reads a source .mlui file (structured --- screen headers) and generates MinLang screen declarations using only the fixed vocabulary above. It validates all referenced actions, effects, and queries against the existing .ml before emitting. The compile gate (ml1 validate) remains the authority. See UI & UX and CLI.

Triggers and schedules (v7)

trigger declarations define reusable server-only remote-IO bindings:

trigger RegenerateDesign {
	effect GenerateImage
	pipeline GenerateDesign
	action SaveDesign
	pending "Regenerating design..."
	error "We could not regenerate that design."
	success "Design regenerated."
}

UI controls can dispatch trigger Name directly, and schedules can invoke the same trigger surface with static timing metadata:

schedule NightlyRefresh {
	trigger RegenerateDesign
	cron "0 3 * * *"
	timezone "UTC"
	enabled config NIGHTLY_REFRESH_ENABLED
}

This keeps remote IO on the server boundary while preserving pure action semantics in MinLang core logic.

Theme

A theme block restyles the compiled app with static data. Sections: palette, font, cell, window.

theme Default {
	palette {
		primary "#1e40af"
		surface "#f8fafc"
		text "#0f172a"
	}
	font {
		family "Inter"
	}
}

For the web target only these keys map (anything else fails the compile, naming the key and the supported set):

  • palette.{accent, border, danger, danger_text, focus_ring, primary, primary_text, success, surface, surface_raised, text, text_muted, warning} → --ml-color-* design tokens
  • font.family → --ml-font-family

Palette values must be #rgb/#rrggbb hex literals. font.size, font.mark_size, and the cell/window sections are game-host-only (Godot target) and fail a web compile.

Contrast is a compile gate: the effective light palette must keep text/surface, text/surface_raised, text_muted/surface, primary_text/primary, and danger_text/danger at ≥ 4.5:1 (WCAG 2.1 AA); violations fail the compile with the measured ratio. More in UI & UX.

Asset

An asset declares intent for a sprite or audio asset as static data; realizing bytes from intent happens out-of-band and is pinned deterministically by the toolchain.

asset PlaceSound {
	kind audio
	intent "short soft wooden click, single tap, ~120ms, mono"
	seed 1337
}
Fields (fixed vocabulary, D14–D17): kind (sprite audio), intent (one static string literal), seed (integer literal), optional style_ref (names a declared theme).

Tests

test UniqueCompanyNameRejectsDuplicate {
	setup: [
		create(Workspace, { id: "w1", view: "pipeline" }),
		create(Company, { id: "c0", name: "Acme Corp" })
	]
	when: CreateCompany on Company { id: "c1", name: "Acme Corp" }
	then: [
		error_raised('a company with this name already exists'),
		entity_count(Company) == 1
	]
}
  • setup: — a list of create(...) steps establishing the starting state. Literals only; tests are fully deterministic (D12).
  • when: — one action invocation: ActionName on Entity { input: literal, ... }.
  • then: — assertions:
    • error_raised('<message>') — the action was rejected with exactly this message (byte-exact match against the constraint’s message:).
    • entity_count(Entity) == N — row count.
    • entity_has(Entity.N, field == "value") — field of the Nth row (1-based, id order).

Every critical constraint requires the test triad (D11): a success test, a failure test with the exact message, and assertions proving the rejected mutation changed nothing. Each compiles to a Vitest file that must pass unmodified.

The validator: 49 detectors

ml1 validate (and the generation contract LLM authors follow) enforces these detectors. D1–D13 cover behavior, D14–D17 presentation (v2), D18–D21 screens/derive (v3), D22–D27 deal/extended screens/scoring derives (v4), D28–D35 full-app declarations (v5), D36–D45 package/module/compile-graph rules (v6), D46–D49 trigger/schedule safety and boundary rules (v7), D50–D65 presentation and persistence rules (v8), D66–D71 multi-user rules (v9), and D73–D81 full-app fidelity rules (v10). Optional-construct detectors only fire when the construct is declared.

ID Rejects Why
D1 now(), today(), random(), current_user anywhere Total determinism; runtime context must arrive as explicit inputs
D2 Dot-walking (self.company.name) Cross-entity logic must be explicit count(...) joins
D3 Weak uniqueness boundaries (<= 1, >= 1, self.field) Duplicates must be blocked before the conflicting write
D4 self.field in create-time input-driven invariants Create-time checks must bind the action input, not the not-yet-written row
D5 Business logic inside actions Constraints own rules; actions stay pure mutation
D6 Weak constraint shape (OR escapes, off-by-one, delayed enforcement) Rules must model the invalid state explicitly and block the first bad transition
D7 Invariants enforceable only on create Every mutation path must be guarded — updates can’t bypass rules
D8 Mismatched reference comparisons (c.id == self.company) Referential integrity: compare whole refs of matching type
D9 Undeclared entities / hidden side effects in actions Mutations stay inside the declared domain scope
D10 Contradictory, unsatisfiable constraint sets A program with no valid state is a bug, not a program
D11 Missing test-triad members for a critical constraint Every rule needs success + exact-message failure + rejection-leaves-state-unchanged proof
D12 Non-determinism in tests/setup Tests must reproduce byte for byte
D13 Coverage claims not backed by triads No false confidence
D14 Unknown theme/asset keys Presentation vocabulary is fixed and portable
D15 Non-literal presentation values Look and feel is static data, not computation
D16 style_ref to an undeclared theme No dangling references
D17 Runtime/RNG/time/identity dependence in presentation Presentation can’t bypass determinism
D18 Unknown screen keys Screen vocabulary is fixed
D19 Non-literal screen copy All user-facing text lives in the .ml, never in the compiler
D20 Malformed screen bindings (when shape, non-exhaustive copy maps, unknown query/action names) Screens bind only declared things, exhaustively
D21 Invalid derive (unknown builtin, wrong args/return, embedded logic) Outcome derivation is a pure builtin
D22 Invalid deal step (unknown profile, non-literal seed) Dealing is compile-time and deterministic
D23 Unknown extended screen keys (beyond v3 + button/tap and v7 trigger dispatch) Extended screen vocabulary is fixed too
D24 Invalid button/tap/sequence/trigger dispatch shape Controls dispatch only declared action/sequence/trigger bindings
D25 Invalid board layout (unknown kind, missing columns for dual_hands) Layout metadata must be complete
D26 Derive builtin outside the v4 set, or arg/return mismatch Closed builtin set per bundle
D27 deal touching random()/now() or non-literal profile/seed Deal is not runtime RNG
D28-D35 Invalid full-app declarations (secret/config/service/pipeline/prompt/file/rich-screen/shadowing) Full-app web additions stay typed, deterministic, and boundary-safe
D36 Duplicate package/module identity Closed graph identity must be unique
D37 Unresolved import Every imported module must resolve in the compile graph
D38 Private cross-module access Cross-module symbols must be exported and imported explicitly
D39 Ambiguous unqualified symbol Name resolution must be deterministic
D40 Import cycle Disallowed cycles are rejected
D41 Incompatible bundle dependency Dependency bundle pins must be authority-compatible
D42 Unresolved compile dependency Compile inputs must exist as verified bytes
D43 Lockfile/source hash mismatch Lockfile closure must match actual verified bytes
D44 Remote capsule hash mismatch Pinned hashes must match fetched bytes
D45 Unsigned/untrusted/policy-denied package Trust/license/capability policy gates package acceptance
D46 Invalid trigger declaration / unresolved UI trigger Trigger declarations and trigger dispatch references must be valid and declared
D47 Trigger compatibility mismatch Trigger effect/pipeline/action contracts and injected input origins must align
D48 Invalid schedule declaration Schedule trigger/config/cron/timezone bindings must be valid and static
D49 Trigger/schedule server-boundary leak Remote IO must stay inside server-only trigger/pipeline handlers

When the validator and any document disagree, the stricter rule from the bundle applies. Download the canonical bundle: minlang-language-bundle.md.


This site uses Just the Docs, a documentation theme for Jekyll.