◆ Flux

The seven design pillars

Every rule in Flux traces back to seven pillars, and each one is a property the language holds by construction — proven of every accepted program, and machine-verified at every compilation where proof needs help. This page states the seven exactly: the guarantee each one makes, the mechanism and error code behind it, and a down-link to the Spec, FVM or FDK section that owns the full rules. The narrative — why the language is shaped this way, one idea at a time — lives in the Guide.

New here? Start with Guide §2 — Why Flux is built this way →

A note on the samples. Flux has no expression-statements, so a bare expression is not a program. Lines marked ✗ on this page are fragments shown to name what a rule rejects and the error it raises; every unmarked line is a legal statement.

Seven pillars supporting one guarantee set Figure — seven pillars carrying one guarantee: describe an expression and the engine evaluates it, provably terminating, never rewriting history, identical everywhere, and disclosing everything it touches.

The pillars at a glance

# Pillar The guarantee Enforced by Owner
1 Total, not Turing-complete every program terminates; per-step cost is a compile-time constant static budget, [ErrTotal] Time & state
2 Causal by construction a value produced for a step never changes — no-repaint past-only delays, [ErrCausal] Time & state
3 Deterministic to the byte same program, same data → the same numbers on every engine pinned routines, invariant I7 Verification
4 Dimensional kinds every stream carries its physical kind; nonsense is rejected the kind lattice, [ErrDim] Kinds
5 Planes with a one-way firewall presentation may read analysis; analysis may never read presentation plane inference, [ErrFirewall] The four planes & the firewall
6 Capability security no ambient authority; effects are default-deny, host-mediated sealed manifests, [ErrCapDenied] host-services
7 Optimizable by construction a pure, typed, total, causal DAG; an optimizer no one has to trust translation validation, I7 The optimizer

Where the term no-repaint appears below it means one thing exactly: a value, once produced for a step, never changes.

1 · Total, not Turing-complete

Every Flux program terminates, and its cost per step is known at compile time. There are no unbounded loops, no unbounded recursion, no unbounded collections. A program is a finite dataflow graph under a global node cap; user defs form an acyclic graph; iteration is only bounded combinators. A bound that does not const-fold is rejected before the program runs with [ErrTotal], the offending ceiling named — never killed mid-run by a timeout.

Iteration exists, bounded and honest about it: window with map/fold covers counted loops, scan covers running state, and loop(max, …) covers “iterate until done” with a declared ceiling.

FLUX
hi20 = highest(close, 20)                        // windowed reducer — the bound is a constant
w    = window(close, 20)                         // the same 20 values as a vec, for map/fold
hi   = scan(close, (prev) -> math.max(prev, close))   // running state with feedback (see pillar 2)

Totality is a deliberate trade, not a missing feature. The model is unbounded over time, bounded per step — the synchronous-dataflow family, in the lineage of Lustre, Esterel and SCADE. What it forgoes is data-dependent unbounded inner computation: a fixed-point solver that runs “until epsilon”, an open-ended search, an interpreter. In a client-side reactive domain that limit is invisible, because an unbounded loop in a tick or render path is a hung tab — a bug, not a use case. The full boundary is drawn in Non-goals (v1); delays, scan, loop and the budget are specified in Time & state.

2 · Causal by construction

A value produced for a step can never change afterwards — not as a discipline the author keeps, but as a theorem of the language. No construct can read the future. Three rules produce it: delays reach only backwards (x[n], n ≥ 0 a constant), resampling reads only closed units (x @ "1d" sees the last closed daily unit), and any feedback cycle must cross a unit delay. By induction over the graph, output[t] = f(inputs[0..t]); a past value has nothing left to depend on, so nothing can move it. This is no-repaint, and it holds for every accepted program.

FLUX
prev  = close[1]                 // yesterday's close — legal, and na on the first bar
gain  = math.max(close - prev, 0)
peek  = close[-1]                // ✗ [ErrCausal] — a negative delay reads the future

The payoff is that the stream you compute live and the stream you replay over history are the same function — the honest backbone of any evaluation. Causality, clocks, scan and the no-repaint theorem are specified in Time & state.

3 · Deterministic to the byte

The same program on the same data produces the same numbers — between the editing interpreter and the compiled WASM, between two runs, and between two machines (ARM ≡ x86). Floating point is deterministic only if every source of variance is pinned, so Flux pins all of them as language policy: scalar f64 with no SIMD and no FP reassociation in the deterministic core, a fixed reduction order, one pinned libm for every transcendental, shared decimal, Unicode, calendar and seeded-PRNG routines, and a canonical bit pattern for na.

FLUX
r = math.log(close / close[1])   // ratio in, dimensionless out — via the pinned libm, same bits everywhere
s = sma(r, 20)                   // one fixed reduction order — the same sum, bit for bit, on every engine

Determinism means bit-identical compute and a deterministic scene — the same numbers and the same draw-list on every machine, not identical pixels across GPUs. The equivalence is not assumed; the interpreter and the compiled module are run on real data and asserted bit-equal at every compilation (invariant I7), and a divergence blocks the artifact. The harness and reproducible builds are specified in Verification; the pinned routines in Compiler & runtime. Randomness lives only on the presentation side of the firewall (pillar 5): rand is a per-frame presentation generator, non-replayable by design, and determinism there is deliberately not promised.

4 · Dimensional kinds

Every stream carries a kind — a dimensional type that records what the value is physically, not merely that it is a number. The price axis is an affine space: price is a point, level is a displacement, ratio is a dimensionless scalar, and the operators form an algebra over them. Operations with no physical meaning are rejected at compile time with a dimensional explanation, not a mystery NaN three steps downstream.

FLUX
range = high - low                                //   price − price → level  (point − point = vector)
band  = sma(close, 20) + 2 * stdev(close, 20)     //   price + level → price  (point + vector = point)
rel   = close / open                              //   price ÷ price → ratio  (dimensionless)
bad   = close + rsi(close, 14)                    // ✗ [ErrDim] — point + dimensionless: no affine meaning

Kinds are the keystone the other pillars lean on: presentation is inferred from them (a price overlays the price axis; an osc(0,100) gets its own pane and guide lines), the memory planner sizes buffers from them, the firewall classifies sinks by them, and the optimizer’s cost model reads them. You annotate nothing — kinds flow bottom-up from sources, and the only time you hear from the system is when it saves you. The sorts, lattice, coercion and operator algebra are specified in Kinds.

5 · Planes with a one-way firewall

Computation and presentation are separate planes, and dependence crosses in exactly one direction: presentation may read analysis; analysis may never read presentation. Everything non-deterministic — now(), screen coordinates, hover state, the per-frame generators rand/noise — exists, but only on the presentation side (CANVAS, TRANSITION), where nothing downstream depends on exactness. A violation is [ErrFirewall], at compile time.

FLUX
dot { at:(bar.i, close); r: 4 }       // CANVAS reading an ANALYSIS value — the legal direction
x = ema(now(), 20)                    // ✗ [ErrFirewall] — wall-clock time cannot enter ANALYSIS

The property this buys is structural: an animated, randomized effect can sit pixels away from a signal with no possibility of contaminating it, because the dependency cannot be expressed. This also keeps the render path honest — scene graphics and text both render on WebGPU, text as SDF glyphs crisp at any zoom, so presentation is free to move without dragging analysis with it. The plane model, the exact forbidden values, the live() escape hatch and [ErrFirewall] are owned by The four planes & the firewall.

6 · Capability security

Scripts have no ambient authority. The language has no primitive that does I/O — no fetch, no DOM access, no eval, no file handles — so a script’s only channel to the world is data it hands the host. On the APP plane that channel is explicit: a Cmd carries inert values (a sound name, a storage key, a score), never a token or handle, and the host executes it only if the matching capability was declared and granted. Emitting a command the manifest does not grant is rejected at compile time with [ErrCapDenied] — it never becomes a runtime event to catch.

FLUX
app quiz {
  capabilities: [ chart:read, sfx, storage:own ]   // everything this app may ever touch
  // ... a Cmd like PlaySfx("ding") is data; the host decides whether it runs
}

Capabilities are named namespace:verb declarations in the app descriptor, never first-class values, so they cannot be re-delegated or amplified from inside a script. A sealed manifest is the union of capability needs across the whole dependency closure, intersected with the user’s grant: a dependency’s need surfaces before install, and no dependency can hold authority the user did not grant to the whole. Installing an artifact is therefore informed consent to a short, exact list. The capability catalogue is owned by host-services; the manifest and grant model by The App plane.

The APP plane is sealed in design and additive to the core, and its rollout follows the v1 language.

7 · Optimizable by construction

A Flux program is a pure, typed, total, causal DAG — the form on which sharing, pruning, reordering and specializing are safe by construction, since any two computations of the same pure subgraph are interchangeable. The program’s small, bounded size makes whole-graph searches affordable, so the optimizer can aim for optimal rather than “good enough”.

The optimizer is verified, never trusted. It obeys one law — the optimized program must be bit-identical to the reference semantics — enforced by translation validation: every compilation runs the reference and optimized artifacts on real data and asserts byte-equality. It may be aggressive precisely because a miscompilation cannot ship; it can only fail loudly at the gate.

FLUX
def ema0(s, n) = let a = 2/(n+1) in scan(s, (p) -> a*s + (1-a)*p)
def macd0(s)   = let l = ema0(s, 12) - ema0(s, 26) in { macd: l, signal: ema0(l, 9), hist: l - ema0(l, 9) }

plot macd0(close).macd, macd0(close).signal    // two calls, one shared subgraph — CSE, verified bit-exact

The default is bit-exact, always: dead code is eliminated, constants folded, element-wise chains fused, and the editor’s cost gutter shows the optimized graph, so what you read is what you pay. The correctness law, the tiers and the validation harness are specified in The optimizer.

Sharing a subexpression across co-active scripts (one merged graph for the whole chart), and a labelled relaxed-floating-point opt-in (reassociation, fused multiply-add, never bit-exact and never silent), are later optimizer tiers.

How the pillars compose

The seven are not independent; the guarantee set holds because the loop closes.

Pull any one pillar and the others weaken; together they close, and the closure is the whole claim: describe an expression, and the engine can evaluate it — provably terminating, never rewriting history, identical everywhere, disclosing everything it touches, at a cost known in advance.

See also