Skip to content

DNA

Reference material, synced from the compiler repo’s spec/. The guide is the gentler path in.

DNA is the part of a Hale application that governs how the application changes. It ships as a library inside the toolchain (vendor/dna, the dna/core seed of the hale repository) and as the hale dna commands. This file specifies what the library and the commands promise; the design is GH #521 and its successor #566; docs/src/dna/ is the guide.

The Journal is a git branch, refs/dna/journal, in the governed repository:

  • One commit per event. The commit’s tree holds journal.jsonl, every event so far, one JSON object per line: seq, kind, entity, body, author. The commit’s subject is <kind> <entity>.
  • The commit DAG is the chain. An event’s digest is its commit; its prev is the parent; the first event has no parent. Integrity is git’s: the ref resolves, and its commit count is the event count.
  • Append is compare-and-swap on the ref. A writer builds the next commit on the head it read and updates the ref with that head as the expected old value. A writer that lost the race reloads and, when it was appending at the tail, re-appends at the new tail; seq is the position in the record, never a promise made before the append.
  • Authorship is git’s. The organism’s own events carry its configured author; a human’s facts (a verdict, an intent through the CLI, a host’s crash accounting) carry the git identity of whoever ran the command. Commit signing, where the repository requires it, applies unchanged.
  • Receipts are blobs under refs/dna/receipts/<sha256>, filed by the digest of their content: a verification step’s output, a diff document. Events name receipts by digest.
  • Leases are blobs under refs/dna/lease/<key> (: in a key becomes /), holder, token, expires, present on four lines, compare-and-swapped on the ref. Tokens are monotonic per key.
  • Sync. hale dna sync (and the host, every tick) fetches the remote’s record into refs/dna/remote/journal, reconciles, and pushes. Local ahead: push. Remote ahead: fast-forward. Diverged: the local-only events are re-appended on top of the remote’s head, bodies and authors unchanged, seq their new position, then pushed; a push the remote refuses is fetched and reconciled again. Receipts travel by refspec both ways. The remote is dna.remote in git config, or origin. A plain clone has no record until it syncs.
  • The membrane over the record. From a clone with no organism, hale dna ask appends intent.requested (the body: outcome, from, to) and a verdict appends review.verdict (the body: the verdict as the socket membrane carries it), each in the appender’s git identity; the host beside the organism relays unanswered rows onto the membrane once, and the organism’s answers (intent.offered, task.born, review.settled, review.refused) return the same way. A row is answered when a later row of the answering kind names its entity. hale dna ask --no-wait appends and returns.

.hale/dna/ holds only what is not the record: the membrane sockets, the status projection, worktrees, scratch inputs to the toolchain. Deleting it loses nothing the record holds.

application.attached, structure.observed, responsibility.proposed, law.deferred, intent.requested, intent.offered, intent.refused, review.verdict, task.born, task.<state>, mutation.proposed, mutation.worktree, mutation.located, mutation.candidate, mutation.<disposition>, mutation.applied, mutation.retained, mutation.rolled_back, mutation.rejected, mutation.revise, mutation.refused, mutation.failed, effect.requested, effect.result, evidence.<step>, evidence.magnitude, review.requested, review.settled, review.refused, expression.restart_requested, expression.deployed, expression.restarted, expression.observed, expression.crashed, pressure.raised, pressure.remeasured, appendage.proposed, model.called, github.pr, github.commented, mutation.topology, fleet.deploy, instance.up, instance.exited, review.reasoned (the deciding verdict’s comment — a person’s note or the Leader’s reasoning — right after review.settled; hale dna review <id> renders it as why:). Their bodies are documented in the guide’s reference chapter; the set grows by ordinary change, and a reader that meets an unknown kind must keep walking.

Journal (ordered append with an expected revision, read by index, chain verification), Coordination (leases with fencing tokens) and Receipts (content-addressed store and read) are interfaces in the core. The git-backed implementations are the ones an assembly wires for an organism; the in-memory ones exist for tests.

The organism is an organization written in Hale (GH #566 F2): a program at dna/org that hale dna init generates and hale dna run runs. The application it oversees is not modified by init and contains none of it; it is observed like any Hale binary. The manifest declares two environments, the application’s and the organization’s ([claims] no_base = true; each adopts its own law).

  • Positions are loci; routing is the bus. Board (the human authority: intent enters through it, escalations and reports leave through it, it owns every grant), Leader (a model-backed position holding the project’s grant: it decides the Reviews inside the grant by reading the source diff and the semantic diff, and its verdict is a model call with evidence), and the substrate Dna (the record, the gateways, verification, the editing position, the Reviews). A Review is announced as a typed ReviewRequested fact carrying what a deciding position needs.
  • Authorities are ranked: board (4, maintainer is its older name), leader (3), supervisor (2), reviewer (1). A claimed authority satisfies a requirement of its rank or below; an unknown name satisfies only itself.
  • Who decides. OrgPolicy: a change that touches law, widens effects or crosses ownership, or is of class organization, constitutional, process-policy or topology, requires the Board; a change outside the grant (disposition escalate) requires the Board; everything else inside the grant requires the Leader.
  • The foundational law (dna/org/law.hl, generated, extendable, never weakened): nothing applies except through the substrate (forbid reaches(positions, effects(genome_apply)) avoiding substrate); the editing position reaches neither repo_write, worktree_io, genome_apply nor the Knowledge; the Leader’s verdict reaches the genome only through the substrate; credentials are sealed. The claim engine follows bus edges, so a position that could reach an effect through a published fact is a build failure with the path as its witness.
  • Hosts. hale dna run builds and runs the organization with iris attached; hale dna dev runs the application under the same host too, rebuilds and restarts it on an apply, records expression.restarted in the host’s name, watches the window and reports on the membrane. The host writes org.pid and app.pid under .hale/dna. Under run alone a restart request is logged, not answered: expressing an application deployed elsewhere is a deployment gateway’s job.

A change of class organization edits the organization’s own seed (Mutation.seed, dna/org), through the same pipeline as a change to the application: a worktree, the editing position confined to that seed, verification of that seed (its own base artifact cut at the same moment), the semantic diff of the organization (positions are loci, routes are subscriptions and publications, capabilities are effect classes), a Review that is the Board’s. Applying it restarts the organization (the restart request names the seed; the host answers one for dna/org by rebuilding and restarting the organization itself, which records expression.restarted at birth); the window then judges the new organization.

Growth is initiative, not reflex: when one source raises pressure appendage_threshold times, the assembly journals appendage.proposed and — with initiative on — proposes a growth Mutation of the organization’s seed (appendage.candidate names it). Nothing is grown until the Board approves the candidate commit.

hale dna board is the Board’s queue: the Reviews only it can settle, the proposals, the last report. hale dna report appends report.filed, in the name of whoever asked, summarizing the record since the previous report (proposed, reviewed, applied, retained, rolled back, rejected, escalated, pressure, proposals, model calls and cost, settlements). hale dna pressure lists the pressure raised and answered; hale dna pressure raise <source> <what> publishes one signal on the membrane’s fourth topic, PressureRaised (dna.pressure.raised).

SourceEditor.perform plans the files an objective is about — every listed file the objective names, else the quick tier’s plan from the listing, one file per line; only a listed file is ever a target — edits each (one model call per file, the request naming the file as target), formats and checks the seed, and, when the check fails, tries again with the diagnostics in the prompt, up to max_tries in all (default 3). Each try is an attempt id (<work>/a<n>), so every try’s model calls are evidence in the record. The result names the files changed and the tries taken; a proposal that does not check within the bound is a failed Attempt with the last diagnostics.

The assembly names what fills each role; hale check sees the wiring.

  • RecordJournal: GitJournal (the branch), MemJournal (tests).
  • Membrane — where humans see and decide: Board / LocalHumanMembrane over the unix sockets on one machine; the record itself across clones (intent.requested, review.verdict rows, relayed by the host); GitHub, mirrored by the host when git config dna.github names owner/repo: every pending mutation Review becomes a pull request (github.pr; the candidate pushed to dna/<id>, the three views as the body), every GitHub review on it becomes a review.verdict row in the reviewer’s login (authority board when dna.github.board lists the login, else reviewer; each review once, keyed by login, commit and state), every settlement goes back as a comment (github.commented) and an approval pushes the genome. GitHub is a projection of the record and the record wins: a review whose head moved is refused here and shows as refused there.
  • DeploymentDeployment: NoDeployment (a host expresses: hale dna dev), ShellDeployment { command, seed } (the command owns expressing and judging: express <candidate> <seed> returning 0 means up and healthy, rollback <base> <seed> restores; the exit code is the observation, expression.deployed records it), and LocalApplyDeployment (tests). With a gateway wired, an approval expresses and judges in the organization’s own handler and no host is asked; without one, expression.restart_requested asks the host.
  • Observation — for a shell gateway, the command’s exit; for a host, the window it watches; for a fleet, what its nodes report (below).

The genome’s topology is the fleet plan Hale already checks (spec/verification.md, “Fleet composition”), and the DNA expresses one arrangement of it:

  • The plan describes the workspace’s own services. Plan schema 1.2 lets an instance name a seed (relative to the plan) instead of an artifact; composition cuts the artifact from the seed first, so a candidate is checked as the fleet it would deploy. An instance may name the node that expresses it. [dna] fleet = "<name>" in hale.toml names which entry of [fleets] the DNA expresses.
  • The fleet is evidence. Verification runs hale fleet check --in <worktree> --if-declared on every candidate (evidence.fleet; fleet_clean, true when the workspace declares no fleet). A change to one service that breaks a claim the fleet makes over all of them is mutation.deny (“candidate breaks the fleet”) with the witness in the receipt. A candidate whose diff names a plan (*.plan.json) or the manifest (hale.toml) changes the fleet’s shape: it is re-classed topology (mutation.topology) whatever it was asked as, and OrgPolicy sends it to the Board.
  • A deploy is a row. fleet.deploy (entity: the Mutation, or the short revision for an operator’s deploy) carries the plan name, the revision, the seed the change edits ("" for the whole genome), the instances touched — every instance with a node whose seed is that directory — and the reason (apply, rollback, or the operator’s). The revision is pushed to refs/dna/revisions/<rev> on the record’s remote first, so every node can fetch it. Under hale dna run with [dna] fleet set, the host answers an application’s expression.restart_requested with a deploy row; hale dna deploy <revision> and hale dna rollback <mutation> write the same row by hand. A rollback’s row asks for the base; nothing is watched.
  • A node expresses. hale node <name> [--repo <clone>] [--fleet <name>] [--tick <ms>] runs in a clone of the governed repository. Every tick it syncs the record and reads the latest fleet.deploy; when that names a revision it does not express, it fetches the revision, checks it out, and for each instance the plan assigns to the node that the deploy touches (or that is not up) cuts the artifact, builds the seed, restarts the process (cwd the clone, LOTUS_OBS=1, HALE_DNA_NODE, HALE_DNA_INSTANCE, HALE_DNA_EXPRESSION) and appends instance.up as node/<name>: node, instance, revision, model hash, build digest, pid. An instance that exits appends instance.exited with its code. The expression identity per instance is therefore reported by the node that expresses it and joined to the plan by instance id. The node decides nothing. .hale/node/<name>/<instance>.pid is the only local state.
  • The window is over every touched instance. After an apply’s deploy row the host waits for every touched instance’s instance.up at the revision (180 s to settle), then for the observation window with no instance.exited at the revision among them. All up and none exited: Observation healthy with the model hash the instances reported. One exited: expression.crashed naming the instance and its node, Observation crashed with the same detail, and the organization rolls back — a rollback deploy row touching the same instances, which every node answers. Never up: expression.crashed “never expressed by …” and Observation build_failed.
  • hale dna fleet renders what the fleet expresses from the record: every instance of the plan, its node, whether it is up, the revision and model hash it last came up at, and the last deploy.

Everything hale dna does beside the compiler that is DNA behaviour rather than manifest or scaffolding is dna/host, a Hale seed embedded in the toolchain beside the core, the membrane client and the surface, built once into the toolchain cache. hale dna <verb> resolves the project from the manifest (the root, the application’s seed, the fleet and its plan under [dna]) and execs the host: host <verb> <root> <seed> <fleet> <plan> …, with HALE_BIN (the toolchain), HALE_DNA_MEMBRANE (the membrane client’s binary) and HALE_DNA_TOOLCHAIN in the environment. The host owns the projections (status, history, review, board, report, pressure, fleet), the writers (ask, a verdict, pressure raise, sync, deploy, rollback, github sync), the supervision (run, dev) and the node agent (hale node). It reads the record through the core’s GitJournal, appends in a person’s or a node’s name with the ref compare-and-swapped, and starts every child — the organization, the application, iris, an instance — through sh, detached, with its pid, its exit code and its log as files under .hale/dna/ or .hale/node/<name>/, echoing the log to the terminal a tick at a time. The compiler keeps init / new / upgrade (they embed the sources), hale fleet check and the plan schema, and the exec shims. The host decides nothing.

hale dna ui [project] [--port N] serves the DNA surface in a browser from the record alone: a Hale program (dna/ui, embedded in the toolchain like the core, the host and the membrane client, built once into the toolchain cache) that answers every request by running one offline verb of hale dna in the project root and returning what it printed — the status projection (/api/status), the Board’s queue (/api/board), the pending Reviews and one Review’s three views (/api/reviews, /api/review/<id>), the fleet (/api/fleet), the history (/api/history[/<entity>]), pressure (/api/pressure). A verdict (POST /api/verdict), an intent (POST /api/ask) and a pressure signal (POST /api/pressure) are the CLI’s own verbs sent and not waited for: onto the membrane when one is bound here, into the record otherwise, in the name the form gives. A path segment reaching the CLI is cleaned of separators and leading dashes, so a request cannot name a file or a flag. The surface reads nothing itself and decides nothing; with or without an organization up, it shows what the CLI shows. hale dna review <id> <verdict> --no-wait is the same non-blocking verdict from the terminal. Iris stays the observer: attached to the organization’s process it renders the org as the live topology it is.

What is deliberately not here yet: a fleet-level semantic diff (a Review’s diff is the edited seed’s; the deploy row names the instances it reaches), pressure raised from services’ typed metrics (hale dna pressure raise is the spelling; nothing raises it for a node), and the organization as an instance of its own plan.