Held together by the compiler

This chapter is a standard, and partly a goal. It says how a DNA project uses the whole toolchain, one way for each concern, so that the compiler can hold a…

This chapter is a standard, and partly a goal. It says how a DNA project uses the whole toolchain, one way for each concern, so that the compiler can hold a distributed system together. Every rule says what it is, why, what it replaces, how it is enforced, and where DNA itself stands today. A rule marked goal is not yet followed by DNA’s own source; the list of those is the migration this chapter commits to.

Most of Hale’s features are opt-in and orthogonal: a program can use topics without surfaces, claims without effects, units without either. That is right for a language, and wrong for an organization. A system stays coherent only if every part makes the same choices, so that the compiler can see the whole of it: which processes exist, what crosses between them, what each part may reach, what a value means. This chapter makes those choices once.

An organism is a distributed system: the record and its memory, a head, nodes, legs, an application with its heart, a broker for the nerves, a vault, the senses. The aim is that everything which joins those parts is a declaration the compiler checks, not a convention people remember.

The rule behind the rules

Section titled “The rule behind the rules”

Anything that crosses a boundary is declared, and anything declared is checked. A boundary is a process, a part, an authority, a unit of measure or a secret. The rest of the chapter applies that rule to each concern.

Structure: one tree, one plan

Section titled “Structure: one tree, one plan”

A program is one locus tree. Each part of an organism is a locus with its children as params; work that comes and goes is a child born by accept and ended by dissolve; failure goes to the owner’s on_failure (spec/semantics.md § Locus instantiation).

Every process is an instance of a fleet plan, and every deployment is derived from it. The organism’s programs (the host, the organization, the head, the application, the reflexes) are instances of one plan, their routes the topics they bind; hale fleet check composes them. A deployment backend renders the plan for the platform it runs on (GH #1477).

Placement is declared on the main locus. Pools, pinning and replicas are placement { }, never threads or process flags.

Boundaries: declared, typed, generated

Section titled “Boundaries: declared, typed, generated”

Every call between processes is an api surface. A program serves a surface with api::serve over the transport the deployment chooses (unix::Rpc, http::Rpc, mcp::Rpc), with roles on its rows; callers use clients generated from its description (hale api client), checked current in CI.

Every event between parts is a declared topic, bound through an adapter. A topic names its payload type and subject; a main locus binds it (bindings { T: Adapter }), and keyed subscriptions say whose events a locus hears.

A surface’s machine forms are generated and committed, and a test holds them current.

A handler’s failure is declared. A surface handler or an interface method that can fail says fallible(E), so a failure is the transport’s error outcome, never a success carrying a code.

Data: one meaning per value

Section titled “Data: one meaning per value”

Every row of the record is a declared type. A row kind is a type with json tags; it is written and read through its generated codec, and the routing table names types, not strings.

A closed set is an enum. Instead of: a String with its allowed values in a comment. Today: goal (one hand-written enum).

Money is Decimal with its currency; time is Time and Duration; any other measure is a quantity in its unit. An integer quantity (quantity Int in …) is the choice where performance demands it, never a bare Int (spec/units.md).

Lists are collections, not delimited strings. A list that crosses a boundary is a bounded[T; N] where its size is known, or a collection locus where it is not.

Authority: law, effects and secrets

Section titled “Authority: law, effects and secrets”

Every program carries a constitution, over a base every environment shares. hale.toml names a base ([claims] base = "…") and each environment’s constitution; the organism’s own law (dna/org/law.hl) is the base.

Every place a program touches the operating system names its purpose. require attributed(all syscall) in the base: each function that performs a syscall directly carries a user-declared effect class (spec/verification.md § Claims).

A projection is a pure function. Every projection of the record is @no_syscall (and @deterministic where it is), so replay gives the same answer.

A secret is held only by a sealed locus. Through std::secret, revealed only in the statement that writes it to a wire.

Authority belongs to principals, and a backend’s policy is a projection of it (runs_under, The habitat, as designed). Today: not built.

Resources: bounded by design

Section titled “Resources: bounded by design”

Placement and capacity are declared, and a growing allocation says what bounds it. A hot path declares its budget (@budget), a buffer its form (@form(ring_buffer), a capacity slot). A growth the memory-bound survey reports is answered at its cause: fixed where the allocation need not accumulate (the work of a long-lived loop moved into a method, whose per-call scratch frees it; a field mutated in place; a loop that ends on its condition rather than while true), or marked on its function. @bounded_by(<input>) states the input that bounds a real growth: the journal a load reads whole (@bounded_by(self.j)), one document’s members (@bounded_by(document)), the command line (@bounded_by(argv)). A test is a run-to-exit driver that the survey judges long-lived, because it starts loci with handlers; its growth is bounded by its own script: a helper names what it reads whole (@bounded_by(repo)), a poll its own limit (@bounded_by(tries)), and a capture of what the run publishes, or a read only a test’s loop repeats, the script (@bounded_by(script)). @unbounded means the growth is not yet understood, and the comment above it names the issue that tracks it (// GH #…). A decorator that silences nothing is removed: scripts/unbounded-unused.sh lists every one in dna/, and lists none.

A program says when it is healthy and how full it is. The runtime serves liveness, readiness and capacity from what it already knows, with the application’s own readings added, and the deployment backend maps them to the platform’s probes and scaling (GH #1477). Today: not built.

Quality: the toolchain is the gate

Section titled “Quality: the toolchain is the gate”

Every candidate is judged by the toolchain, and the judgment is read as data. Verification runs hale fmt --check, hale check, hale verify, hale test, hale fleet check and hale model diff on every change and reads their JSON output.

hale verify is a required gate on the organism’s source. It fails on any finding, the survey’s advisories included, so a growth is answered when it is written, not found in production.

A surface is tested through its fixture transport. std::api::test::Rpc drives the surface as a caller would; a test never builds a call context by hand. Today: followed by the todo application; goal for the head’s tests.

Model calls in tests are scripted or replayed, never live. FakeModel (rules, answers, turns) for mechanics, RecordedModel tapes for replay; a live model is a deliberate evaluation, not a test.

Observability: one channel

Section titled “Observability: one channel”

Readings are declared, and the organism and the platform read the same ones. A series is declared beside the code that moves it, served by std::metrics, and is the same endpoint a deployment’s probes read.

Layout

Section titled “Layout”

What follows from it

Section titled “What follows from it”

Followed, these rules make a change to the organism checkable before it runs: a new topic that no instance binds, a surface field whose type changed under a caller, a function that reaches a secret or the network without its purpose, a deployment that no longer matches its program, a money value passed where a duration was meant. Each is a build error with a path through the source, not an incident.

The goal rules above are DNA’s own migration, in the order they tighten the system most: typed rows; the organism as a fleet plan, with its broker permissions derived from its bindings; the head’s reads as surfaces with generated clients; a typed heart; law and attributed effects on every program; verification reading data; units for money and time; enums; secrets provisioned on the sealed side.