aontu

aontu documentation

Rendered from docs/index.md in the engine repository, where a correction belongs, and where the test suite executes every example on this page.

aontu is a JSON structure unifier: a small language (a purpose-specific dialect inspired by CUE) and an engine that merges partial structures into one consistent result, or reports exactly where they conflict. The same source can describe data, the schema that constrains it, and the defaults that fill it in: all in one notation, all combined by a single operation: unification.

This repository ships two implementations kept in parity:

  • TypeScript in ts/: the canonical implementation, published to npm as aontu.
  • Go in go/: a port (github.com/aontu-lang/aontu/go) that mirrors the core semantics.

Both are checked against one language-agnostic test suite in test/spec/, and every example in these pages is executed by that machinery too: the outputs come from the engine, not from the author’s memory.

How this documentation is organised

The documentation is split by what you are trying to do when you open it. Reach for the part that matches your need:

If you want to…Read
Learn aontu from zero by building something, step by stepTutorials
Accomplish a specific task you already have in mindHow-to guides
Look up exact syntax, semantics, options, or API surfaceLanguage reference · API reference, with three supplements below
Understand unification itself: meet, top, bottom, the latticeUnification
Understand how and why the engine works the way it doesExplanation
See whole systems defined, each with its checks runnableUse cases

There are four tutorials, each building one thing and showing the output of every step: a config that is schema, defaults, and data at once; the graph layer of identity, relations, and reachability; sharing a model as a versioned package; and computing source files from a model. The index says what each one assumes.

The how-to guides are one page per task, grouped six ways: run, embed and integrate; templates, defaults and composition; schemas and constraints; query, explain and change; validate and evolve; modules and multi-file.

Three capabilities have doorways of their own:

  • Declare and check relations. Entities carry identity, the edges between them are declared in the model, and the engine checks both. The recipe is check relations; the live version is use-cases/12-relations; the normative rules are under Declared relations.
  • Write a recursive schema. A schema can name itself, so trees and nested structures validate to any depth. The recipe is define a recursive schema; the live version is use-cases/13-recursive-schema; the semantics are under Recursive references.
  • Generate code from a model. The field names, types and optionality a Go struct or a TypeScript interface needs are already in the model, and the unifier computes the file: a rule set over the records, match for the type mapping, a backtick string to carry the target text, and a component tree of files and lines that a generator runtime writes to disk and holds against its golden. The recipe is generate code from a model; the live version, with three targets in one document and a check that both ports build identical trees, is use-cases/15-code-generation. The rules for the tree itself are the Generation reference.

Six reference sections sit beside the two above. Each is a surface that cuts across the language reference rather than a part of it, which is what the language reference, organised by topic, cannot show at once:

  • Generation reference. The component tree: every component node, the props it carries, the children it admits, and what aontu render and aontu trace do with a tree.
  • Functions reference. The call surface of every built-in: arity, argument modes, accepted kinds and result words, as one table and as slices through it.
  • Error reference. Every registered error code, by class, with what raises it and what a report carries.
  • Packages reference. The files the package system keeps, every field pkg.aontu declares, the name rules, the caps, what an archive may hold, and every refusal code.
  • Grammar reference. The published grammar rule by rule, the spellings the parser accepts beyond it, and what holds the four grammar files to the engine.
  • Agent and editor reference. Every door a machine comes in by, the one answer shape they share, what none of them does, and the posture each takes towards includes.

Tooling:

For agents:

Contract:

  • The trust contract. Hermeticity, termination, determinism, and sandboxing: what a host may rely on when evaluating an aontu document, and where each guarantee is conditional.

For contributors:

  • The style guide. How these pages are written: Diátaxis placement, the voice, the banned-phrase list, and the snippet directives under which every example runs.

Why the split?

The four kinds of document answer four different questions and are kept separate on purpose. A tutorial holds your hand and is allowed to omit detail; a how-to assumes you know the basics and just need the recipe; a reference is exhaustive and dry so you can trust it as the source of truth; an explanation is discursive and is the only place that argues about trade-offs. Mixing them (a reference that teaches, a tutorial that digresses into design rationale) serves none of those needs well, so each lives in its own file.

The rule applies to the toolkit as much as to the language, which is why a verb can appear in all four kinds without any of them repeating another: met once, in passing, while a tutorial builds something; given as a recipe for one goal in a how-to guide; specified exhaustively (every flag, every exit code) in the API reference; and argued for, never merely listed, in the explanation. The use cases stand alongside as whole worked systems, each holding a check.sh that CI runs.

A 30-second taste

# A schema, a default, and data — unified into one result.
port: *8080|integer
host: string
host: "localhost"

Unifying the three lines above yields:

{ "host": "localhost", "port": 8080 }

The port is constrained to be an integer, defaults to 8080, and, because nothing overrode the default, 8080 is what comes out. host is constrained to a string and pinned to "localhost". Conflicting facts (a second port: "high", say, or a port: 1.5) are refused with a precise error rather than silently resolved: the preferred branch keeps the kind it names, which is argued in the explanation.

Try it without writing a file: both implementations ship an aontu command that evaluates a file, reads stdin, or starts a REPL:

$ echo 'port: *8080 | integer' | aontu
{
  "port": 8080
}

Start with build a config that checks itself, the first of four tutorials.