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 record
Section titled “The record”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
previs 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;
seqis 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,presenton 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 intorefs/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,seqtheir new position, then pushed; a push the remote refuses is fetched and reconciled again. Receipts travel by refspec both ways. The remote isdna.remotein git config, ororigin. A plain clone has no record until it syncs. - The membrane over the record. From a clone with no organism,
hale dna askappendsintent.requested(the body: outcome, from, to) and a verdict appendsreview.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-waitappends 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.
Event kinds
Section titled “Event kinds”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.
Storage interfaces
Section titled “Storage interfaces”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 organization
Section titled “The organization”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 substrateDna(the record, the gateways, verification, the editing position, the Reviews). A Review is announced as a typedReviewRequestedfact carrying what a deciding position needs. - Authorities are ranked:
board(4,maintaineris 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 classorganization,constitutional,process-policyortopology, requires the Board; a change outside the grant (dispositionescalate) 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 neitherrepo_write,worktree_io,genome_applynor 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 runbuilds and runs the organization with iris attached;hale dna devruns the application under the same host too, rebuilds and restarts it on an apply, recordsexpression.restartedin the host’s name, watches the window and reports on the membrane. The host writesorg.pidandapp.pidunder.hale/dna. Underrunalone a restart request is logged, not answered: expressing an application deployed elsewhere is a deployment gateway’s job.
The organization evolves
Section titled “The organization evolves”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).
The editing position
Section titled “The editing position”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.
Backends by role
Section titled “Backends by role”The assembly names what fills each role; hale check sees the wiring.
- Record —
Journal:GitJournal(the branch),MemJournal(tests). - Membrane — where humans see and decide:
Board/LocalHumanMembraneover the unix sockets on one machine; the record itself across clones (intent.requested,review.verdictrows, relayed by the host); GitHub, mirrored by the host whengit config dna.githubnamesowner/repo: every pending mutation Review becomes a pull request (github.pr; the candidate pushed todna/<id>, the three views as the body), every GitHub review on it becomes areview.verdictrow in the reviewer’s login (authorityboardwhendna.github.boardlists the login, elsereviewer; 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. - Deployment —
Deployment: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.deployedrecords it), andLocalApplyDeployment(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_requestedasks 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 fleet
Section titled “The fleet”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 anartifact; composition cuts the artifact from the seed first, so a candidate is checked as the fleet it would deploy. An instance may name thenodethat expresses it.[dna] fleet = "<name>"inhale.tomlnames which entry of[fleets]the DNA expresses. - The fleet is evidence. Verification runs
hale fleet check --in <worktree> --if-declaredon 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 ismutation.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-classedtopology(mutation.topology) whatever it was asked as, andOrgPolicysends 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 torefs/dna/revisions/<rev>on the record’s remote first, so every node can fetch it. Underhale dna runwith[dna] fleetset, the host answers an application’sexpression.restart_requestedwith a deploy row;hale dna deploy <revision>andhale 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 latestfleet.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 appendsinstance.upasnode/<name>: node, instance, revision, model hash, build digest, pid. An instance that exits appendsinstance.exitedwith 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>.pidis 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.upat the revision (180 s to settle), then for the observation window with noinstance.exitedat the revision among them. All up and none exited:Observation healthywith the model hash the instances reported. One exited:expression.crashednaming the instance and its node,Observation crashedwith 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 …” andObservation build_failed. hale dna fleetrenders 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.
The host is a Hale program
Section titled “The host is a Hale program”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.
The surface
Section titled “The surface”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.