DNA, the building block
DNA is what the organism is assembled from: the library in vendor/dna, the dna::Dna locus your organization constructs from it, and the hale dna commands…
DNA is what the organism is assembled from: the library in
vendor/dna, the dna::Dna locus your organization constructs from
it, and the hale dna commands. Its one job is to be the same parts
in every organism, so that what differs between two organisms is
only what their projects wrote. The toolchain owns it: you never edit
vendor/dna, and hale dna upgrade writes it again. It fails loudly
when it and the binary disagree: hale dna status names the digest
vendor/dna came from and tells you to upgrade. It never holds
anything of yours: your source lives in dna/, your secrets in the
vault, your history in the record.
The rest of this section describes the organism by its parts. This
page is the view from underneath: where the library comes from, the
locus it gives you, how the parts are carved out of that locus, and
every file hale dna new and hale dna init write.
Where vendor/dna comes from
Section titled “Where vendor/dna comes from”The hale binary carries the DNA source inside it. new, init and
upgrade write the part a project imports into vendor/dna:
in vendor/dna |
what it is | imported as |
|---|---|---|
*.hl |
the core: the dna::Dna locus and every part it assembles |
import "vendor/dna" as dna; |
pond/ |
pond’s Postgres driver and its NATS client and bus adapter | import "vendor/dna/pond/realtime/nats" as nats; |
legs/ |
a leg’s verbs, performers and hands | import "vendor/dna/legs" as legs; |
README.md |
which toolchain and which source wrote it | — |
The version alone does not name the source, because two builds of one version can embed different source. The digest does:
hale dna --embedded-digest # the digest this binary embeds, and nothing elsehale dna --embedded-digest --from-tree <dir> # what the checkout at <dir> would embedhale --version prints its first sixteen digits (embedded dna: ca4012a0cda7226d). Every materialization writes the full digest into
vendor/dna/README.md and .hale/dna/embedded.digest, and pins the
toolchain version in hale.lock:
# vendor/dna — toolchain-owned
The DNA core (`dna/core` of the hale repository) as shipped by hale 0.21.0.Embedded source digest: ca4012a0cda7226dd7b82f28f2b9c6104df253e56658d6e3aaabd4cf0a8fb25a(`hale dna --embedded-digest`; two builds of one version can embed differentsource, so the digest is the provenance and the version is not.)Regenerated by `hale dna init` / `hale dna upgrade`; pinned in `hale.lock` as`[dna] toolchain`. Do not edit: project-owned source lives in `dna/`..hale/dna/embedded.digest has the same two lines as hale --version,
with all 64 digits.
hale dna status opens with the binary’s digest:
embedded dna: ca4012a0cda7226d (hale 0.21.0)When vendor/dna was materialized from another source set, the same
line adds ; vendor/dna was materialized from <digest> — run `hale dna upgrade`. A clone carries no vendor/dna (it is in
.gitignore); a body started in a fresh clone materializes it
first. What upgrade rewrites, and what it leaves alone, is in
Shaping and governing it.
The binary embeds more than vendor/dna: the host that hale dna dev,
run and most verbs execute, the operations the head serves, and the
face. Those are built from the binary into its cache and never copied
into your project. The digest covers all of it.
The host is two programs over one seed. Its body (dna/host) is what
hale dna dev, hale dna run and hale node run until they are
stopped; every other verb — status, review, apply, fill and the
rest — is its verb program’s (dna/host/verb), which answers one verb
and exits. hale dna builds both into the cache the first time any
host verb needs one, and runs the one the verb belongs to in its own
process, so stopping hale dna dev stops the body itself.
The dna::Dna locus
Section titled “The dna::Dna locus”Your organization is a Hale program, dna/org/main.hl, and its main
locus has three params: core, the substrate; leader, the position
that decides inside the grant; and nerves, its connection to the
broker. core is one dna::Dna, and every setting is a constructor
argument:
import "vendor/dna" as dna;import "vendor/dna/pond/realtime/nats" as nats;
main locus Org { params { core: dna::Dna = dna::Dna { // … journal: dna::RoutedJournal { record: dna::GitJournal { repo: "." } }, // … work: dna::WorkSystem { agent: dna::LegRelay { name: "legs" }, agent_reconciler: dna::RelayReplay { } }, // The organization's spend: one policy (models.hl), one owner. budget: dna::Budget { policy: org_budget() }, // … boundary: dna::AutonomyBoundary { child: "refproj", grant: dna::Grant { child: "refproj", classes: "refactor docs", max_magnitude: 4, review: "pre" } }, review_policy: dna::OrgPolicy { }, board: dna::Board { who: "board" }, gateway: dna::MutationGateway { leases: dna::GitLeases { repo: "." }, workspaces: dna::IsolatedWorktrees { repo: ".", root: ".hale/dna/worktrees" }, repo: dna::LocalGit { repo: "." } }, verification: dna::HaleVerification { receipts: dna::GitReceipts { repo: "." }, scratch: ".hale/dna/scratch", repo: ".", seed: "." }, // … editor: dna::SourceEditor { name: "editor", models: editor_models() }, genome_seed: ".", // … catalog: workflows(), // … planned: true };That is refproj’s, from a fresh hale dna new refproj; your project’s
name stands where refproj does. Each field names a concrete locus
behind one of the core’s interfaces:
| field | as generated | what it is |
|---|---|---|
journal |
dna::RoutedJournal over dna::GitJournal |
the record (refs/dna/journal) and, once adopted, the ledger in memory, read as one (Memory and the record) |
work |
dna::WorkSystem with dna::LegRelay |
who performs work: agent work is relayed to a leg (Legs, hands and voice) |
budget |
dna::Budget { policy: org_budget() } |
the one spend policy, from dna/org/models.hl (The spine) |
boundary |
dna::AutonomyBoundary holding a dna::Grant |
what the Leader may decide on its own (the grant) |
review_policy |
dna::OrgPolicy |
which authority a Review requires (who is asked) |
board |
dna::Board { who: "board" } |
the human authority: intent enters through it, escalations leave through it |
gateway |
dna::MutationGateway |
where a change is made: a lease, an isolated worktree, the repository |
verification |
dna::HaleVerification |
the checks a candidate passes, and the receipt store |
editor |
dna::SourceEditor |
the editing position: read, edit, format and check inside one worktree |
genome_seed |
"." |
the seed every change but an organization one edits |
catalog |
workflows() |
the workflow definitions it admits (dna/org/workflows.hl; hale dna definitions lists them) |
planned |
true |
an ask is planned by the Leader before it becomes a change |
A few fields keep their defaults in the generated file, and are worth knowing because you can set them:
| field | default | what it does |
|---|---|---|
deployment |
dna::NoDeployment { } |
an applied candidate asks the host to rebuild and restart (expression.restart_requested); dna::ShellDeployment { command, seed } runs your own command to express and roll back instead (The heart and the body) |
ceiling |
no grant | a grant above the Leader’s, which the Leader’s is read through at every assessment |
initiative |
true |
persistent pressure becomes a proposed change to the organization, not only a note (org growth) |
org_seed |
"dna/org" |
the seed an organization change edits |
The same file wires the Leader and the nerves:
leader: dna::Leader = dna::Leader { name: "leader", models: leader_models(), receipts: dna::GitReceipts { repo: "." }, source: dna::SourceReader { repo: "." }, // … charter: charter(), purpose: purpose() };and binds the facts that enter the organization to the nerves, one
nats::NatsAdapter each:
claims { adopt Org; } placement { nerves: pinned; } // … bindings { dna::ReviewVerdict: nats::NatsAdapter { }; dna::IntentOffered: nats::NatsAdapter { }; dna::ExpressionObserved: nats::NatsAdapter { }; dna::PressureRaised: nats::NatsAdapter { }; dna::ConcernRaised: nats::NatsAdapter { }; dna::PracticeRequested: nats::NatsAdapter { }; dna::HoldRequested: nats::NatsAdapter { }; dna::PositionOpenRequested: nats::NatsAdapter { }; dna::KnowledgeNodeRequested: nats::NatsAdapter { }; dna::KnowledgeBindingRequested: nats::NatsAdapter { }; dna::KnowledgeEdgeRequested: nats::NatsAdapter { }; dna::WorkSubmit: nats::NatsAdapter { }; dna::WorkAllowanceAsk: nats::NatsAdapter { }; }The nerves’ connection, its failure handler and why it stops the
organization are on The nerves. The loop at the end of
run() calls self.core.request_tick(…) on the wall clock in
milliseconds, which is when schedules occur (Schedules).
Because all of this is construction, hale check sees the whole
organization as wiring: which position holds which handle, and which
effects each can reach. That is what the law in dna/org/law.hl is
checked against (The law).
How the parts are carved out of it
Section titled “How the parts are carved out of it”The core’s other loci each live in a file of their own: the Leader and
the source reader in dna/core/org.hl, the editor in
dna/core/editing.hl, the gateway, worktrees and LocalGit in
dna/core/workspace.hl, verification in dna/core/verification.hl,
GitJournal and GitReceipts in dna/core/record.hl, the grant’s
boundary in dna/core/review.hl, the budget in dna/core/budget.hl,
the work system and the leg relay in dna/core/work_system.hl, the
catalog in dna/core/workflow_definition.hl. The Board, the review
policies and the deployments are in dna/core/assembly.hl, beside
dna::Dna itself.
What dna::Dna keeps about the record is split into families: one
locus per kind of fact, each a child of the assembly. None of them is
set in the generated main.hl; the assembly constructs them, and at
birth lends each the record it appends to:
self.evidence.bind(self.journal, self.verification.receipts); self.org_source.bind(self.journal); self.practices.bind(self.journal); self.concerns.bind(self.journal); self.spending.bind(self.journal); self.positions.bind(self.journal, self.verification.receipts); self.cases.bind(self.journal); self.attempts.bind(self.journal); self.organization_source_review_facts.bind(self.org_source.facts); self.scheduler.bind(self.journal); self.knowledge_record.bind(self.journal, self.verification.receipts);A family holds only its own state and writes only its own rows, in the
order the assembly asks for them; it reads everything back from the
record (dna/core/record_rows.hl). What crosses families — a Review
born, the Board told, an execution admitted — stays the assembly’s.
| field | locus | file | what it holds |
|---|---|---|---|
evidence |
EvidenceStore |
dna/core/evidence.hl |
filing evidence by its class, retention holds, redaction, and a model call’s prompt and context filed as receipts; the vault that keeps protected bodies |
scheduler |
Scheduler |
dna/core/scheduler.hl |
the schedules the record declares, how far it has read them, and what is due at a tick |
knowledge_record |
KnowledgeRecord |
dna/core/knowledge_record.hl |
a knowledge proposal’s rows, whether the Board ratified it, what retired it, and a node’s, binding’s or relationship’s change from request to effect |
org_source |
OrganizationSourceRecord |
dna/core/org_source.hl |
a prepared change to the organization’s source: its rows, its exact verdict, the refusals that end it |
practices |
PracticeRecord |
dna/core/practices.hl |
a practice asked of the organization and its rows, and the Reviews this run has ratified through practice-ratify |
concerns |
ConcernRecord |
dna/core/concerns.hl |
concerns raised and routed, pressure, and the two thresholds (concern_threshold, appendage_threshold, both 3) |
spending |
SpendRecord |
dna/core/spending.hl |
every model call’s evidence and cost, the gate a call’s spend passes, and money reserved and settled against a grant |
positions |
PositionRecord |
dna/core/positions.hl |
a holder proposed for a position, or a person joining an organization (hale dna fill) |
steps |
StepRecord |
dna/core/steps.hl |
the organization’s own catalog steps that wait on a verdict, the Leader or memory |
cases |
CaseRecord |
dna/core/cases.hl |
a person’s cases: admitted, held, refused and completed |
attempts |
AttemptRecord |
dna/core/attempts.hl |
an attempt’s reports until the record holds them, edits waiting on a review, and a leg’s lease on an attempt |
You never construct a family yourself. They matter when you read the
core: a question about practices is answered in practices.hl, not by
reading four thousand lines of assembly.hl.
Every file new and init write
Section titled “Every file new and init write”hale dna new <name> makes a directory holding a small application
and then runs init on it; it refuses a directory that is not empty.
hale dna init [app-dir] attaches to an application that exists: it
finds the seed’s main locus and refuses a seed that has none. init
adds nothing to the application: no import, no param, no binding. Run
again, it keeps every file that is there (kept … (already exists))
and does not reseed a record that exists.
| file | written by | owned by | what it is |
|---|---|---|---|
main.hl |
new |
you | the application: a Ping topic, an Echo locus, a main locus |
tests/main_test.hl |
new |
you | a test that imports the application |
hale.toml |
new writes [deps]; init adds the environments |
you | the application’s and the organization’s entrypoints |
hale.lock |
new, init, upgrade |
the toolchain | pins [dna] toolchain |
.gitignore |
new writes it; init adds to it |
you | keeps vendor/, .hale/ and the two secrets files out of git |
vendor/dna/ |
new, init, upgrade |
the toolchain | the core, pond, the legs, and its README.md |
.hale/dna/embedded.digest |
new, init, upgrade |
the toolchain | the toolchain and digest that wrote vendor/dna |
.hale/dna/baseline.topology |
init |
the toolchain | the application’s topology artifact, cut by hale check --dump-topology before anything else reads it; the record is seeded from it |
dna/org/main.hl |
init |
you | the organization (above) |
dna/org/law.hl |
init |
you | the organization’s law (The law) |
dna/org/purpose.hl |
init |
you | the declared purpose (The purpose) |
dna/org/charter.hl |
init |
you | the Leader’s brief (The charter) |
dna/org/models.hl |
init |
you | the model catalog, written from what this machine had (Legs, hands and voice) |
dna/org/work.hl |
init |
you | a leg’s performers and hands (Legs, hands and voice) |
dna/org/workflows.hl |
init; rewritten by upgrade |
the toolchain | the catalog: the baseline and your own (The spine) |
dna/org/own_workflows.hl |
init |
you | your own workflow definitions, never rewritten |
dna/compose.yaml |
init; rewritten by upgrade |
generated | memory’s Postgres, the nerves’ NATS and the senses’ store for hale dna dev (Getting started) |
dna/senses.yml |
init |
you | what the senses’ store scrapes (Senses and reflexes) |
dna/nats.conf |
init; rewritten by upgrade |
generated | the nerves’ users and permissions, with no password in it (The nerves) |
dna/nats.secrets.conf |
init, upgrade |
the vault | the nerves’ passwords; mode 600, never tracked (The skin) |
dna/postgres.secrets |
init, upgrade |
the vault | the compose database’s superuser password; mode 600, never tracked |
Beside the files, init writes these rows into the record, unless it is
run with --no-library (new takes the flag too):
| row | what it is |
|---|---|
graph.node language:hale, graph.node system:dna |
the nodes knowledge is about: the language this toolchain compiles, and the design the organism is an instance of |
graph.node application:<name>, graph.edge written_in:application:<name>|language:hale |
for an attached application: its node, and the edge that says it is written in Hale (a repository with no application gets the two nodes alone) |
a knowledge.proposed row per chapter of the book and per section of the spec, named library/<path> |
the library: each idea bound to language:hale, or to system:dna for the DNA chapters, with the toolchain’s version |
two review.requested rows, group library |
one Board Review per family, library/language@<version> and library/design@<version>: hale dna review library approve ratifies and binds every idea a family names (What a Work is told) |
Outside the files, init also:
- makes the directory a git repository if it is not one: the record is a branch;
- seeds the record,
refs/dna/journal, with receipts underrefs/dna/receipts/(Memory and the record); - sets
dna.unix.memberin the clone’s git config, so the head’s socket knows your uid, anddna.trust = localwhen the record declares no trust; - draws the organism’s own secrets into the vault and makes an empty slot for each one a person supplies (The skin);
- formats
dna/and the application’s seed withhale fmt.
new --profile remote-body --remote <url> [--body <user@host>] also
adds the origin remote and sets dna.body; hale dna profile
reports the combination from those pieces afterwards.
The manifest
Section titled “The manifest”init adds two environments, one per entrypoint, each under its own
law:
# hale dna init: the two entrypoints and where each deploys (`hale check --matrix`).# The organization adopts its law (dna/org/law.hl) itself; the application keeps its own.[claims]no_base = true
[environments.local]source_only = trueentrypoints = ["."]
[environments.org]source_only = trueentrypoints = ["dna/org"]no_base is stated rather than inferred: the application and the
organization share no constitution, and an unstated shared base would
be a rule that looks bound and binds nothing. hale check --matrix .
checks both.
The ignore file and the lock
Section titled “The ignore file and the lock”# the build artifact/refproj# toolchain-managed (vendor is re-materialized; .hale holds sockets, worktrees, scratch; the record is refs/dna/*)/vendor//.hale//dna/nats.secrets.conf/dna/postgres.secretsdep = []
[dna]toolchain = "0.21.0"The first five lines of the ignore file are new’s; init adds each
of /vendor/, /.hale/ and the two secrets files that is not already
there. The secrets writer refuses a file git would track.
What the record starts with
Section titled “What the record starts with”init on an application writes these rows into the record before
anything runs:
| rows | what they say |
|---|---|
application.attached |
the main locus, the artifact with its digest and shape hash, the toolchain |
structure.observed |
one per locus and topic the compiler observed |
responsibility.proposed |
one per locus, not ratified |
graph.node, graph.edge |
what the application’s directory holds, as for a repository below: the application as the root seed, each API surface it serves and each stream over a hub as a contract it meets as server (from hale check --api) |
knowledge.proposed, review.requested |
the declared purpose, each seeded practice, and the holes the graph implies (the board, the application’s dev and reviewer, each with its mandate), each with its own Board Review |
For refproj that is 72 rows: one attachment, three observations
(Echo, Refproj, Pings), two responsibilities, two graph rows (its
purpose and its seed; it serves no API surface), and thirty-two
proposals with their Reviews — the purpose, eleven design (eight
practices and three mandates), seven operating practices, seven using
practices, and six for the holes (three positions, three mandates). Shaping and governing it
has the practices.
A repository instead of one application
Section titled “A repository instead of one application”Run init on a directory with no Hale source of its own that is no
workspace’s seed, and it treats it as a repository rather than one
application: a seed per process, the specs they meet at, a compose file
that runs them. It makes the organization the same way, with three
differences:
- the manifest gains
[environments.org]alone, since there is no application entrypoint; - no topology artifact is cut; instead the record is seeded with what
the repository holds, as one graph: its purpose (the
README.md’s title and first paragraph), its processes (the compose services), its seeds, its contracts underspec/, its documents, its CI jobs as gates, and itsFRICTION.mdentries as witnesses; - it writes
structure.hlindna/org, whoseoperational_roles()names the roles each deployment also gets (fromsupport accounts billing on-call),""by default.initreads it once, when it seeds the record, and keeps one that is already there, so to opt in you write it beforeinit.
An application gets this path too. Its init reads the same graph from
its own directory after the rows it observes, with two additions: the
application is the root seed, and each API surface it serves (or stream
over a hub) is a contract it meets as server, named from the toolchain’s
own export. A surface that a spec/<stem>.md already describes is that
node, not a second one. The surface’s requires roles become the
equipment of the positions, never an organization or an authority.
From that graph init proposes the positions a delivery needs, each
with its own Board Review: a board, a reviewer for every part that
serves or consumes a contract, a dev and a work item wherever a
gate guards something, an operator under every deployment. What a
directory listing cannot say, you write in markdown, and init reads
it as written:
-
A marked list item. A list item that opens with a code span names its kind:
`axiom`for a decision,`derived`,`practice`or`law`for a rule proposed as a practice. Its first bold span is its name, and the files an axiom links to are what it shaped.- `axiom` **Only the api is exposed.** NATS and the nodes are on theprivate network ([`protocol.yaml`](https://github.com/hale-lang/hale/blob/main/docs/src/dna/spec/protocol.yaml)). -
A declaring table. A table with
Served byandConsumed bycolumns says, for each contract it links, which process serves it, who consumes it, and over what (Over); one whose first column isDeploymentwith aRunscolumn names a deployment and what it runs. A name in code is a process or a seed, a link is a file, plain words are someone outside the repository.| Document | Served by | Consumed by | Over ||---|---|---|---|| [`openapi.yaml`](https://github.com/hale-lang/hale/blob/main/docs/src/dna/spec/openapi.yaml) | `api` | callers, `ui`, `node` | HTTP |
What the conventions cannot take stops init with the reason before
anything reaches the record: an axiom with no bold name, a table row
that links no contract, a name in code the repository does not have, a
name with a | in it. Fix the document and run init again; it keeps
the files it made and seeds the record whole. The graph, the positions
it implies and how routing reads them are on
Memory and the record and The head and the face.