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.

An organism has three memories, and every row kind belongs to exactly one (GH #646).

Memory Holds Lives in Written by
Structure what the organism is: positions, routes, workflow definitions and grants as authored, the law, the app, the models catalog the codebase the editor, under review
Record how the organism changed and was allowed to: mutations and their reviews, practices proposed and ratified, authority contracted or revoked, connections, provisioning, people memory: Postgres, record_rows in the record’s schema dna_<identity>, one signed row per event, insert-only (GH #1590) the Board, the organism’s pipeline, a head from its clone
Ledger what the organism did today: intents, tasks, assignments, decisions, bills and receipts, money reserved and settled, schedules declared and their occurrences, concerns, handoffs, effect claims, liveness, the application’s readings memory: Postgres, in the record’s own schema dna_<identity> every writer under its own role — the nodes as the spine’s, each head as its own — through memory’s insert function, the gate (GH #1026)

Each kind’s placement is memory_of in the core (routing.hl): the case, intent, task, decision, completion, exception, schedule, receipt, handoff, pressure, instance, effect, report, lease, claim, reading (an application’s events, #987) and reflex (what a reflex made the body do, #988) families are the Ledger’s, as are concern.requested / .raised / .refused, grant.reserved / .released / .fenced / .reservation_refused (money; grant.refused — a grant born wider than its ceiling — is authority and the record’s), spend.reserved / .settled / .compensated, body.claimed / .released / .credential_*, model.called, knowledge.consulted, optimize.refused, budget.exhausted, and a workflow execution’s own facts (workflow.admitted / .refused / .ask_refused / .settled, step.registered / .activated / .completed / .failed, work.settled, attempt.admitted / .outcome), and a leg’s rows on an attempt (attempt.claimed, .outcome_requested / .outcome_refused, .allowance_requested / .allowance_granted / .allowance_refused, .unresolved, .released). Every other kind, and any kind a build does not know, is the record’s. The placement is fixed, part of the schema: it is not data in the record and is not versioned, since every record has its Ledger from its first row (GH #1590). There is no dual-write: a kind has one home, and a writer that cannot reach it fails rather than writing elsewhere.

RoutedJournal is the Journal an organism and the host stand on: the record’s journal and the Ledger’s read as one merged sequence (a row’s seq as read is its merged position; rows name each other by id), appended to by kind, as the table routes it. The Ledger is MemoryLedger (dna/core/memory_spine.hl) over PqLedger (Postgres, the record’s dna_<identity> schema, one insert conditioned on the tail, the claim kinds effect.requested, spend.reserved and task.born unique by (kind, entity)), opened on first use as the role its process’s environment names (Memory, below). There is no service in front of memory (GH #985): a part that reads or writes it opens Postgres itself, and the role’s grants are the trust boundary. The organization’s Ledger carries the body lease and the epoch the host handed it (HALE_DNA_LEASE, HALE_DNA_LEASE_TOKEN): an append under a lease that is not held, is held at another token, or has expired lands nothing (fenced: …).

The Ledger is born with the record (GH #1590): memory’s migration makes both, and nothing adopts or abandons a Ledger. Readers of history (history, the projections, the export) read both memories as one. hale dna run refuses to start without HALE_DNA_MEMORY_DSN_SPINE (#646 decision 3: a body without its memory admits nothing); dev derives the spine’s DSN from memory that is up and migrated and hands it to the host, applying no schema (GH #1653). hale dna ledger rows prints the Ledger’s rows as JSON lines (seq, kind, entity, body, author, writer — the role that wrote it, so a head writing as host is told apart from the body — prev, digest), read under the head’s role or the spine’s.

Writers write directly; the store is the gate (GH #1026). A head — the CLI, hale dna ui, the API — writes a Ledger row itself, straight into memory under its own role, and a node writes the organism’s rows under the spine’s; nothing sits in front of memory and no process admits anything. Every Ledger row lands through memory’s insert function, ledger_append(expected, kind, entity, body, author, lease, epoch, request), a definer function run as the schema’s owner, and the calling role is who wrote it (session_user, kept in the row’s writer column beside lease and epoch). It refuses, returning the reason the verb prints:

  1. a kind that is not the Ledger’s (ledger_kind, the placement above, in the schema): it is appended to the record instead;
  2. a person the record retired (org_retired, below) — checked before the author, since a retirement also ends the person’s memberships, and the refusal says so;
  3. an author the calling role does not hold. memory_principals, owner-only, maps each role to whom it writes as: * any author and no person checks (the spine’s role, and the schema’s owner); @ any person (the head’s role over a record with one owner); an owner’s name, that owner’s members — the persons who hold its organization node (each owner’s head role over a shared record, The owners’ roles, below); host for any head;
  4. a task.transfer_accepted for a Task not offered to an owner, or in the name of someone who is not the member of the owner it is offered to;
  5. task.done, task.failed, completion.linked and completion.excepted in any name but the Task’s assignee’s;
  6. a row naming a lease — the body’s, which its organism carries — whose claim is not live at that token in claims right now: fenced, with whose it is and at what epoch;
  7. a revision a decision was read at that the Ledger has moved past (stale_revision; a task.transfer_accepted lands at the tail whatever its expected, GH #1052, since check 4 reads the transfer against the Ledger as it stands), and a claim kind whose entity is already taken (claimed).

Checks 3 to 5 are not the role’s own business, so they apply to every role but a *. Appends are serialized on the schema, and the row’s digest extends the chain as event_digest does. A write carries a request id minted when it is made (ledger_requests): a write retried after an answer that was lost is answered as it landed, once. A head’s verb says it is done when the row lands, and says why when it is refused; nothing is pending and there is nothing to wait on. A head with no memory named writes nothing, naming the checkpoint the organism’s operations live behind since — never into git instead. A write decided at a revision needs the Ledger’s revision read, and without memory its verb refuses (the ledger's revision was not read). Nothing on a head is authoritative, and no head executes work offline (#646 decision 2). An operational row never goes into the record: memory’s append function refuses a Ledger kind there (GH #1590).

The org chart in memory (GH #1026, #1123). The checks above read what the spine projects, and the org chart is the graph’s (The repository’s graph, below): org_members(person, owner) is a view over the graph’s live holds edges whose position side is an organization node — a person holding organization:<o> is o’s member, and owner is the organization’s name — so it moves with the graph’s own projection and nothing rewrites it; org_retired(person, seq) is the record’s person.retired rows, projected in the same per-row transaction as the graph (and emptied with it on a rebuild). The head reads both, so a verb that checks before writing — a retired person, a transfer’s owner — says so in its own words; the store’s check is the gate, the head’s the message.

The owners’ roles (GH #1026). Over a shared record each owner’s heads write the Ledger as that owner’s role, <schema>_head_<owner>, which the migration creates for every owner the owners’ keys name (HALE_DNA_OWNER_KEYS, <owner>=<key> …, in the owner’s environment at migration) and prints as HALE_DNA_MEMORY_DSN_HEAD_<OWNER>=<dsn>; the plain head role of a shared record reads and writes as no one. A role is who a head is: memory holds no signature bytes, and the keys stay the record’s until the vault holds them (#989). The host is a principal: it writes its own rows as host, under the spine’s role or an owner’s, and its body.* rows carry owner in their body when the record names one, so a row by host still says whose body it was. Who a person is remains the head’s to establish — its issuer under dna.principal = oidc.

Admission, as a reader checks it (GH #1026). A row of the record counts only when dna::row_admissible(record, row, trust) says so: under dna.trust = signed its commit carries a signature the record’s trust verifies (git’s keyring or allowed-signers file), and an unsigned row, or one signed with a key the record does not know, is refused; under local trust every writer is trusted. The projection runs it on every row it applies and moves its stamp past a refused row without projecting anything of it. A Ledger row needs no reader’s check: the insert function refused it before it landed.

Evidence and the export across two memories (stage 4, #653). Receipts are placed by what they evidence: evidence.* (a verification step’s output, a diff document — evidence of a mutation) and review.reasoned are the record’s; receipt.* — a bill filed, its class, its disclosure, its reads, its holds, its redaction — are the Ledger’s, whatever the class, and the body itself is where it always was (a git blob for an internal body, memory’s sealed store for a protected one; only the digest is ever a row). A redaction is a Ledger row (a head’s redaction of a protected body is a row of the record, which the spine reads to erase it): sync applies redactions and candidate drops over both memories read as one, so the treatment holds across the split. The books export is a supported ledger query: it reads hale dna ledger rows beside the record’s rows and never SQL of its own. Recovery between the two memories needs no transaction spanning them: every step that touches both writes its intent in the memory that owns the decision, acts, and writes its outcome, and the restart scans the merged journal — a task settles on its mutation’s outcome, a settled review births its task once, an effect claim is resolved by evidence or gated as unknown, a retirement’s reassignments are reapplied, a filed body without its row is filed again — exactly as they did over one journal, because the routed journal is that journal.

Handoffs between records (stage 5, #662; over the nerves, GH #1590). A handoff crosses between records as a signed envelope through an Exchange, the core’s contract (dna/core/exchange.hl): card(url, record) reads the card the other record publishes, deliver(url, peer, id, line) puts a signed envelope in its inbox, idempotent by the envelope’s id (<handoff id>.<kind>; the handoff id is its origin, peer, kind and subject, so a delivery repeated after an interruption is one envelope), and received() is every envelope this record’s inbox holds. NatsExchange (dna/host/connections.hl) is the nerves’ and MemExchange the fixtures’. The contract #646 set holds: durable delivery — an envelope is stored by the receiver’s exchange stream, which keeps it until the receiver has drained it (no age limit), before deliver returns; one delivered again by its id within a week is stored once, and the receiver keeps one in memory whenever it arrived; a delivery that cannot reach the peer records nothing and says to deliver again; authorship — every envelope is signed with the sending record’s own key, and a receiver admits one only under a connection that pinned that key; the disclosure scope is the connection’s classes, checked before anything is delivered and again before anything is admitted; settlement only on the receiver’s acceptance — the origin’s task moves to transfer_accepted when the receiver’s accepted envelope reaches it, never when a delivery returned.

Coordination in memory (stage 2, #651). Every lease is a row of memory’s claims table, swapped by its token: MemoryLeases is Coordination over current(key) and put(key, lease, expected), and a put lands only when the stored token is the one expected (0 for an absent lease). The body lease is the row body (owner/<owner> over a shared record): the fence renews it every third of its life with every call bounded, a stale token — a host that lost the lease to another — lands nothing, and hale dna body reads it there. A head takes and fences a lease under its own role. Effect claims are unique rows of the Ledger ((kind, entity)), so a contended-claim row does not arise. Memory lost after activation: the renewal fails, the fence keeps the lease it last proved until the margin before its expiry and then stops the organism, and status says THE LEDGER IS UNREACHABLE and that nothing is admitted until it answers.

Memory is Postgres, and only Postgres (GH #985). Its stores are the core’s — dna/core/memory_store.hl (the graph, Pq), memory_ledger.hl (PqLedger, PqLeaseStore), memory_protected.hl (PqProtected), memory_embed.hl, memory_schema.hl (the schema, the roles, the grants and the functions) and memory_spine.hl (Memory, MemoryLedger, MemoryLeases, MemoryKnowledge, MemoryVault) — and they open Postgres through pond’s driver, pinned under dna/core/pond and vendored beside the core at vendor/dna/pond. The projection tail, MemoryProjection, is not in the core: it lives in dna/operations/memory_tail.hl, beside the command codecs it decodes admitted facts with, and runs on the host’s tick (The spine, below).

  • Memory is per record. Each record has its own schema, dna_<identity> (the identity lower-cased, any other character _, at most 56 characters of it), and two roles of its own, dna_<identity>_spine and dna_<identity>_head, cut to fit Postgres’s 63-byte names: one role granted on every record’s schema would read every other record’s evidence on the same server. A role’s password is the vault’s postgres-<role> (The organism’s secrets), drawn when the role is declared and read at use; the migration sets each role’s password from the vault (ALTER ROLE when it exists), so the vault stays the one source. A role’s DSN carries no password: it names the entry, postgres://<role>@host:port/db?sslmode=…&vault=postgres-<role>, and the connection reveals it into its SCRAM exchange’s @secret parameter, in the one statement that uses it (GH #989). The owner’s DSN of the organism’s memory allocation in the local habitat names postgres-owner-<id> the same way (The local habitat holds the organism’s infrastructure, below). An operator’s DSN with a password in it is presented as given.
  • Three DSNs. HALE_DNA_MEMORY_DSN_OWNER is the schema owner’s, used only by the habitat’s acts that migrate (GH #1653): birth, hale habitat migrate [organism], hale dna memory migrate [dir], hale dna upgrade when it is set, and a provisioned body’s provisioning script, which runs hale habitat migrate . once with it. Without it, those acts bring up and migrate the organism’s memory allocation in the local habitat. A migration prints two lines, HALE_DNA_MEMORY_DSN_SPINE=<dsn> and HALE_DNA_MEMORY_DSN_HEAD=<dsn>: the owner’s host and database, each role’s name and credentials. The host that runs the organism is handed HALE_DNA_MEMORY_DSN_SPINE alone, and the organization inherits it: dev derives it from memory that is up and migrated, applying no schema (memory-migrate --derive), and hale dna run takes it from its own environment; both take HALE_DNA_MEMORY_DSN_OWNER and HALE_DNA_MEMORY_DSN_HEAD out of the host’s. With no memory up, dev says so, names hale habitat migrate <organism>, and starts the host without memory. A head — the CLI, hale dna ui, the API — opens memory with HALE_DNA_MEMORY_DSN_HEAD; a process given the spine’s DSN uses that instead, and none holds both. The head’s DSN a migration answers is kept in the clone, .hale/dna/memory.env, beside the line that says where it was derived, HALE_DNA_MEMORY_FROM=owner or HALE_DNA_MEMORY_FROM=habitat <project>/memory-<id>, and every hale dna verb in that clone given neither role’s DSN runs with it. The file holds the head role’s DSN alone (never the owner’s or the spine’s), is written mode 600 and never printed, and is written only where git ignores it (a graft and upgrade keep /.hale/ in .gitignore); a verb refuses it when it is readable by group or others, naming chmod 600. A kept DSN derived from anything but the operator’s database or the organism’s own memory allocation (another habitat’s, a database an older toolchain migrated, a file with no origin line) is removed and refused, naming `hale habitat migrate `: no verb re-derives it, since none migrates as a side effect.
  • The migration runs in one transaction under an advisory lock on the schema: the vector and pgcrypto extensions, the schema and every store’s tables, the record’s claim on the schema (knowledge_meta.record; a schema that names another record is refused), the projection protocol, the receipt functions and the receipt key, both roles and their grants, and memory_meta.schema_version. It is idempotent. It refuses a knowledge store left in public from before stores were scoped (drop its tables or the database; the projection rebuilds from the record), and a schema a newer toolchain migrated.
  • The version fence. The schema version is 8 (GH #1026: 2 made the lease table claims and the graph’s projection one transaction per record row — migrating a version-1 memory renames the table in place, its rows kept; 3 adds the Ledger’s gate, ledger_append, with the org chart it checks and the roles’ principals; GH #1085: 4 adds the repository’s graph, graph_nodes, graph_edges and graph_members; GH #946: 5 adds hats, hats by digest, which moves no projection; GH #1123: 6 makes org_members a view over the graph’s holds edges, where it was a table the spine rewrote from the retired owners map, and moves no projection either; GH #987: 7 adds the reading family to the placement the insert function reads, which moves none; GH #988: 8 adds the reflex family the same way. Each protocol move empties the graph once for the projectors to rebuild). A store’s open selects the record’s schema and refuses one at another version, or at none, naming both and hale habitat migrate <organism> (with HALE_DNA_MEMORY_DSN_OWNER for a Postgres of the operator’s); it also refuses a schema whose claim names another record (it is not read). The host checks the version on the spine’s DSN before it starts anything, and refuses to start (exit 2) on a mismatch.
  • Grants. A head’s role — the head’s, or an owner’s — has SELECT on the ledger, knowledge, org-chart and meta tables (memory_meta, ledger_rows, ledger_meta, ledger_requests, claims, knowledge_*, graph_*, org_members (a view), org_retired), INSERT and UPDATE on claims only, to take and fence a claim, and EXECUTE on receipt_file, receipt_read and ledger_append: a head writes the Ledger through its gate and no table directly. The spine’s role reads and writes its schema’s tables (SELECT, INSERT, UPDATE, DELETE, TRUNCATE) except the owner-only memory_keys, memory_principals, protected_receipts and protected_redactions, and has EXECUTE on every function; it has no DDL. Every Ledger row goes through ledger_append.
  • Connections are bounded. Each process holds one memory handle (Memory: the Ledger and the leases), opened on first use and closed when the process ends; everything in a process that reads the Ledger or a lease takes that handle rather than building its own, and each Pq store closes its connection when it dissolves. A host holds a fixed handful of connections however long it runs.

The spine (GH #1026). The spine is not a process: it is the program any number of identical nodes run, and a node holds nothing — its state is the stores, its code is the genome. On each tick (HOST_TICK, 1 s) every host that runs a body applies the record into the graph and the org chart (MemoryProjection) and erases the protected bodies the record redacted (MemoryVault.complete_redactions). Nothing coordinates the nodes; the stores serialize them — the projection a row at a time by compare-and-swap, an erasure by digest (it is idempotent). There is no spine lease and nothing is admitted: a writer writes, and memory’s insert function is the gate (Writers write directly, above). Every row a node projects is shared already: the record is memory’s, read by every clone and body alike (GH #1590). A host with no HALE_DNA_MEMORY_DSN_SPINE says so and runs with no memory: nothing is projected. What failed on a tick is said once, when it changes, and tried again on the next.

Claims by id (GH #1026). Every non-idempotent act a node takes is claimed by id, with an expiry, in memory before it is taken, so any number of nodes may run one organism over one record with no coordinator. The table is claims(key, holder, token, until, present); the body lease is one of its rows. MemoryLeases.claim(key, holder, ttl) takes a key that is free, expired or already this holder’s, with one conditional write — of two holders racing for a key one lands and the other finds it taken — and answers with the claim as it stands. Expiry is the database’s clock, never a node’s: the write computes until from Postgres’s now() and compares against it, so nodes whose clocks disagree agree on when a claim ends. A renewal by its holder keeps the token; any other take raises it. release_claim(key, holder) gives it back on completion, its token kept so a late renewal is refused. A holder that dies leaves its claim to expire and another finishes the act. Without memory (no DSN) there is no one to race and a claim is granted. The substrate names its node by HALE_DNA_NODE (the host sets it to the body’s holder, user@host:<clone>; otherwise this machine and process) and claims before it spends: an ask is claimed as plan/<intent>, and a human case the leader is asked to plan as plan/case:<case>, for claim_ttl (300 s) before the leader is asked, and, once claimed, the record is read again, so an ask the claim’s previous holder admitted meanwhile is answered with that admission and the claim given back; a node that finds the claim taken answers planning elsewhere: <holder> holds plan/<intent> and writes nothing, and the ask comes back to it with the host’s relay until it is answered or the claim expires (a case is held and driven again by redrive; memory that cannot be reached is said as such, not planned: memory could not be reached to claim …). The claim is given back once the plan’s admission is recorded (the case handed). The optimize pass takes no claim: it is a scheduled occurrence, one execution under its key (Schedules). A claim a node acts on is a claim.taken <key> {holder, token, until} row and its return a claim.released <key> {holder} row (the Ledger’s), so the record shows who took what, and a second node’s take after the first one’s expiry; a claim taken without memory is not recorded.

The genome pull (GH #1026). Nodes converge on the genome by pulling; there is no push deploy for the organism’s own nodes (the heart’s deploy is another thing). At start a host fetches the forge’s default branch (the record’s remote, its HEAD), moves its genome forward to it when it descends from what is checked out, and checks and builds it — the application’s artifact, the organization and, under dev, the application, each through the build cache. A genome that does not check or build is a node.build_failed <holder> {sha, why, by, owner} row; the node returns its checkout to the last genome that built (.hale/dna/genome.built), builds that, and serves it. A node that is up records node.started <holder> {sha, by, owner}. On its tick, every HALE_DNA_GENOME_POLL seconds (300 by default; 0 never), it fetches again, into refs/hale/remote/genome (the genome’s; never FETCH_HEAD, which any other fetch in the clone rewrites); when the forge’s head is neither what it runs nor a sha that did not build here, it stops cleanly — the organization and the expression drain on SIGTERM, the body lease is given back (body.released {why: "genome"}) — and exits with NODE_RESTART (75); its unit (Restart=always) starts it again, and it builds the new genome at start. It never replaces itself in place. A forge head that did not build here is said once and waited out. Each poll writes .hale/dna/genome.json: sha, failed, polls. hale dna status carries a genome: line: the sha each node runs, from its last node.started, and a newer one that did not build since.

The record is memory’s (GH #1590): the table record_rows in the record’s schema dna_<identity>, read and written by every clone and body alike, so nothing of it travels by git and there is nothing to sync.

  • One row per event: seq, kind, entity, body, author, the writer’s role (writer, the session’s), prev, digest, sig.
  • The identity is the first row’s digest. hale habitat graft composes record.genesis {genome, nonce} (the genome’s root commit, and a nonce so two projects made alike are two records), pins its digest in hale.toml ([dna] record), provisions memory (the record’s Ledger with it), and appends it at 0, then record.key {key} (the public half of the record’s key). A clone whose history does not hold the genome a record was born on does not open it (the hale.toml naming it was copied from another project).
  • The rows are read through the toolchain. hale dna record-rows [project] prints the record’s rows in the line format (one JSON object per line: seq, kind, entity, body, author), oldest first, and exits 1 with the reason when the record cannot be read. It prints the record’s rows alone; the Ledger’s are memory’s, and hale dna history walks both. The DNA’s tests and scripts read the record through it and never through its storage.
  • The chain is a prev-digest chain. A row’s digest is sha256(prev|kind|entity|body), its prev the digest before it (genesis for the first); a reader verifies the chain it loads.
  • Append is one function, insert-only. Every writer appends through record_append (one row) or record_append_rows (a batch made visible together, every row checked before any lands), SECURITY DEFINER functions that take the schema’s advisory lock, refuse a Ledger kind, and land the row only on the prev and, when asked, the revision the writer read; no role may update, delete or truncate the record’s rows, or the Ledger’s. A writer that lost the race at the tail reads again and goes round with the new tail, within a bound only a wedged memory reaches; an exact append is one attempt.
  • Every row is signed as its writer signs. The organism’s parts (dna, spine, host, a node, a leg …) sign with the record’s key (Ed25519, in the vault as dna-exchange-<schema>, never leaving the credential) over hale-dna-row + NUL + the digest; a person signs with their own ssh key (ssh-keygen -Y sign -n hale-dna-record, the key git’s user.signingkey names under gpg.format ssh), and is unsigned with none configured. Under dna.trust = signed a person’s row is admitted when the key the record enrolled for them verifies it (hale dna signers: signer.enrolled {who, key} and signer.revoked {who} rows, each counted only when signed by someone the record already admits; the first enrolment is the bootstrap).
  • A record an earlier toolchain kept on git (refs/dna/journal) is not carried forward: nothing reads it, and a graft and hale dna upgrade refuse a clone that holds one, saying to start a fresh record (delete the old refs and the [dna] record line, then graft again).
  • Receipts are kept in memory by the digest of their content: a verification step’s output, a diff document. Events name receipts by digest. A receipt has a data class (GH #606): a public or internal body is kept unsealed (receipt_keep(digest, class, body, at), which refuses a digest that is not the body’s and one memory erased under a redaction); a customer or confidential body (protected_class) is sealed. It is kept in memory alone, sealed there — by the organism through Dna.file_evidence(text, class, by), by a head through hale dna receipt file --class, both through MemoryVault — and the record gets receipt.classified <digest> {class, by, store}. Memory seals it, and no process holds the key (GH #985). The receipt key lives in memory, in memory_keys, written once at migration from the owner’s HALE_DNA_RECEIPT_KEY (sixteen characters at least); a different key at a later migration is refused, because bodies sealed under the first would no longer open. Three SECURITY DEFINER functions, run as the schema’s owner, are the whole surface of the sealed table protected_receipts: receipt_file(digest, class, body, at) seals a body with pgcrypto (OpenPGP, AES-256) under the key, and refuses a class that is not protected, a digest that is not the body’s, and a digest memory erased under a redaction, whoever files it; receipt_read(digest) opens one; receipt_erase(digest, at) deletes the body and keeps its digest in protected_redactions. Heads and the spine may execute receipt_file and receipt_read; only the spine may execute receipt_erase. Memory with no receipt key keeps no protected evidence and says so. A dump of the database carries the key (memory_keys) and the ciphertext together, so a dump is as sensitive as the evidence itself; #989 revisits where the key is held. A query that fails drops the connection, dials again and tries once more, so a database restart does not leave every protected read and erase failing (#637). Without memory, the body is withheld: receipt.withheld <digest> {class, by, why}, and nothing holds it. The record keeps the digest and the class either way, and only the sealed table holds a protected body. Reading one is an act in the reader’s name for a purpose (hale dna receipt show <digest> --purpose <p> [--as <who>]): it is refused unless a receipt.disclosed <digest> {recipient, purpose, by} row names both, a refusal appends receipt.read_refused, and an answer is given only once its receipt.read row is in the record — a read the record cannot hold discloses nothing. A body kept while the record refuses its receipt.classified row is reported so, to be filed again. Dna.evidence_class(digest) is the class a request carries when it puts the body in a prompt, so a hosted model refuses it. This is the third trust profile #606 names: the readers of a clone hold digests and classes, never protected bodies. Under local trust a reader’s name is attribution, as --as is; a verified principal is #612’s. Retention (part 2). receipt.held <digest> {by, why} stands until receipt.hold_released, and refuses redaction while it stands. A redaction (Dna.redact_evidence(digest, by, why, policy), hale dna receipt redact <digest> --why --policy) appends receipt.redacted <digest> {by, why, policy, class, store} and then removes the body: an unsealed body is erased from memory at once (Receipts.erase); a protected body is erased from memory through receipt_erase, by the spine — a head’s redaction of a body memory keeps records receipt.redacted (store: memory) and the spine erases the body on its tick, as it erases every body the record redacted and memory still holds; a withheld one had no body. The redaction is in the record before a byte is erased, appended exactly at the revision the hold was read at: a redaction the record refuses erases nothing, a hold that arrived in between refuses it, and a redaction recorded before an erase that failed is completed on a later tick or by redacting again. A body the record says was redacted is not filed again — Dna.file_evidence answers its digest and keeps nothing, and hale dna receipt file and receipt_file refuse it — and hale dna receipt redact appends an unsealed receipt’s redaction only at the head it read the hold at and, once the organism has adopted the ledger, only at the ledger revision it read the hold at (the request’s expected), refusing “the record moved” otherwise (#636). A store that cannot read the body (its read failed, as opposed to finding none) records nothing and reports nothing erased: redact again once it answers (protected_erase_step). The record keeps the digest, so provenance survives and a reader learns the body is gone — a read of a redacted digest is refused … was redacted: by … under …, and hale dna history says so under a redacted prompt. A copy disclosed outside the record cannot be recalled. The projection retires an idea whose receipt was redacted instead of stopping.
  • Leases are rows of memory (MemoryLeases over the leases table): holder, token, expires, present, put by token compare-and-swap. Tokens are monotonic per key.
  • Effects are requested before they run, and the claim is exclusive (GH #604 rule 3). effect.requested <key> is appended before a dispatch and effect.result <key> once after; a retry finds the request and does not dispatch again. Two writers may both append a request for one key; only the writer whose row is the first request dispatches, the other’s row stays as a contended claim and it does not act: every gate (worktree.open, commit, apply, rollback) refuses in flight while another request holds the claim with no result yet (#635). A request with no result when the organism restarts is resolved by evidence where evidence exists (an apply:<candidate> whose genome is at the candidate is ok; a worktree.open with no worktree is failed) and marked unknown where it does not; the gate refuses an unknown effect until a person resolves it — hale dna effect resolve <key> --outcome ok|failed, one row in their name, the last result for the key being its status.
  • There is nothing to sync (GH #1590). Every clone and body reads and writes the one record in memory, so hale dna sync (and the host’s tick) only applies what the record says of this clone — a dropped candidate’s branch deleted here. A fact’s identity in the record is its content and its place among rows of the same content, never its seq (a node raising one concern three times writes three identical rows, each a fact): a host relays and handles by that identity, so a fact is not relayed twice and a repeated one is not relayed once. Another record’s envelopes arrive on the nerves. Candidates are branches of the genome and travel as branches do (below); the genome’s remote is dna.remote in git config, or origin. A clone of the genome reaches the record through memory (its [dna] record pin names the schema, and hale dna memory migrate or upgrade with the owner’s DSN keeps the head’s DSN in the clone).
  • Candidates are kept, as branches of the genome (GH #1590). The gateway keeps a candidate when it commits it as the branch dna/candidate/<mutation> (refs/heads/dna/candidate/<mutation>, dna::candidate_ref), and the record names its commit (mutation.candidate <mutation>, the sha), so a refused or revised change stays readable as a diff after its worktree is gone: hale dna candidates lists the branches with their Review’s state, hale dna candidates <mutation> shows one from its Review’s base, and hale dna candidates drop <mutation> --why <why> appends candidate.dropped <mutation> {by, why} and deletes the branch here; every clone deletes its own when it reads the row. A candidate is code, and travels as any branch does: a person who pushes one to a forge deletes it there as any branch is deleted. The commits stay in each object store until git collects them. The readers know branches alone; nothing reads or moves a candidate an earlier toolchain kept under the record’s refs.
  • An apply expresses the candidate, tree and all. An approval applies exactly the reviewed candidate, or nothing: the candidate’s worktree head is the pinned digest, the genome’s head is the base the review was against, and the genome has nothing uncommitted — tracked changes anywhere, and every input of the candidate’s build that the tree does not hold. The graph is the candidate’s, read by the compiler in the candidate’s worktree (hale inputs <seed>: the seed’s .hl files and those of every directory they import, transitively; the hale.toml of each such directory and every C source it declares under [ffi]), because a reviewed change that adds an import makes files an input the moment it lands; any file of those kinds the genome holds untracked in one of the graph’s directories, or that the graph names outright, is dirt whether git ignores it or quotes it. What no build reads — a binary, a log, another program’s source — is not dirt. A graph that cannot be inspected is dirt: an inspection that failed authorises nothing. A dirty genome is mutation.refused before any effect row exists, the work untouched; the gateway checks it once more around the git call and reads the head back, so an apply that reports success is the candidate. Approving again an approved and unapplied candidate re-runs the whole gate (mutation.apply_retried, then review.settled again so a waiting hale dna review hears it): the Review readmits only the same approval in full — digest, standing (a required position, or with none the authority), independence, cited evidence, the word approve — and refuses any other verdict; a Review approved and never applied is rehydrated settled, so the road survives a restart.
  • A person’s job (GH #596 W; GH #604 rules 4 and 5). A plan of kind person hands the Task on: the Work is done as far as the organism is concerned and the record keeps task.handed <task> — work, assignee (the person the plan named), by, narrative; nothing is mutated. The person reports it done with hale dna task done <id> [--as <who>] [--note …], a task.done row in their name and nothing else. Completion is checked at admission: a handed Task with an assignee is closed only in the assignee’s name; anyone else is refused and told to reassign. hale dna task reassign <id> --to <who> appends task.reassigned (from, to, by) and the Task stays handed. hale dna retire <who> [--to <successor>] stops new work reaching a person: every handed Task they hold is transferred as its own task.reassigned row and person.retired <who> records it (by, to, transferred). From then no new work reaches them: a job the leader plans for them is handed to that successor, with retired_assignee on the task.handed row — followed on through each successor who retired in turn to the first person still working, unassigned when the chain ends in a retirement that named none or comes back on itself — and a Task is never reassigned to them, nor are they named anyone’s successor; refused while they hold work and no successor is named — pending work is accounted for, never dropped. Refused for a Task that is not handed: one the organism is working, or has settled, is not a person’s to close. A completion meets its acceptance condition. When the plan names the obligation a person’s job discharges, the organism binds the acceptance practice in force at hand-off: task.handed carries acceptance (the digest of the practice acceptance/<obligation> that is ratified and not retired, “” when none) and evidence_required (its text says evidence: required). A later change of practice never changes a case already handed. Such a Task closes only with --evidence <digest>, a receipt the record holds, appended before task.done as completion.linked (task, evidence, by, practice), or with --exception <why> --authorized-by <who>, appended as completion.excepted (task, why, authorized_by, by, practice) — only when <who> has authorized it in their own name first: hale dna task authorize <id> --exception <why> [--as <who>] appends exception.authorized <task> {task, why, by, practice}, refused for the Task’s assignee and, when the bound practice names who may (exceptions by: <names>), for anyone else, and refused outright when the bound practice cannot be read here (absent from the clone, unreadable, redacted) — an unreadable practice is not one that names no one; naming someone is never an authorization. The completion carries the exception exactly as it was authorized under the bound practice: a different --exception is refused, and needs an authorization of its own. A note alone is refused, and so is an exception without an authorizer, a self-authorized one, or both at once. Either may accompany any person’s completion. task.done keeps its body; the projection shows a done Task’s evidence or exception, and a person’s completion with neither as human-reported, because a note never verifies anything.
  • A person proposes a practice (GH #602). hale dna practice propose <name> --text <text> [--because <what prompted it>] [--supersedes <digest>] [--as <who>] carries a PracticeRequest (request_id, name, text, by, because, supersedes) as a practice.requested row, which a node relays onto the topic dna.practice.requested (The nerves). The organization proposes it as knowledge of kind practice, author org, bound to org, with the ordinary Board Review k:<digest> (decided, it is ratified by an execution of practice-ratify: GH #995, The workflow catalog), and journals practice.proposed <request_id> (name, digest, review_id, by, because, supersedes); a request without a name or a text is practice.refused. Nothing is in force until the Board ratifies it. --supersedes takes the digest whole or as any prefix of it, as hale dna practice abbreviates it (sha256:3122d), and the CLI resolves it before it requests anything (dna::superseded_digest): the row carries the full digest of the one practice in force it begins, and a prefix that begins no practice’s digest, several practices’ digests, or the digest of one not in force is refused there, by name, never accepted and then refused at ratification. Roles are authorities, not identities. The person who proposed a practice may ratify it: the proposal is in their name (by), the verdict carries the Board’s authority, and the record keeps both, so one person holding several roles — the Board, and the hands doing the work the practice describes — proposes as one and decides as the other. The Review’s independence rule refuses the candidate’s author, which for a practice is the organization (org), never the proposer; and a governance proposal requiring the Board’s authority (a fill, an opening, a graph proposal) admits its author’s verdict too, so the Board ratifies what it asked for. A change to the work never does, whatever authority it requires. This is the design, not an artifact; an organization that wants the proposer barred from ratifying their own proposal needs a review policy that says so, and none ships. The text arrives byte for byte, newlines included: the host escapes an argument’s own backslashes and newlines in its argument list and unescapes every value it reads. hale dna practice lists every named practice with its state (in force, awaiting the Board, declined, retired), its first line, who proposed it and why, and requests not yet heard.
  • Cross-record handoff (GH #615; over the nerves, GH #1590). A replica of one record is inside the horizon: sync carries all of it, to equally trusted readers. A handoff crosses a horizon into a separate record, and a connection is its only path; nothing selective leaves a record by sync.
    • The card. Every record publishes a card on its nerves: {identity, genome, key} — its identity, its genome’s root commit and the ed25519 public key its envelopes are signed with — signed by that key, the one message of exchange.<org>.card in its exchange stream (DNA_<ID>_EXCHANGE over exchange.<org>.>). hale dna nerves migrate creates the stream, its inbox consumer (exchange, over exchange.<org>.in.>) and the card, purging the one before. The key’s seed is the SHA-256 of the vault’s dna-exchange-<org>, drawn by the graft and upgrade like the organism’s other secrets; it signs through std::secret::Credential.sign_ed25519, so neither the secret nor the seed leaves the vault’s credential. The record is the signer (a record-to-record act); the person who acted is named in the envelope.
    • Connecting. hale dna connect <nerves-url> --record <identity> --name <name> --as <position> --purpose <purpose> --classes <internal,customer,confidential> [--by <who>] reads the other record’s card at its nerves (a direct get: connect needs the peer’s card, published by its nerves, and no process of it running), checks the card’s signature and that it is <identity>’s (the identity its genome pins, [dna] record), refuses this record’s own, and appends connection.proposed (name, url, peer, peer_key, peer_genome, position, purpose, classes, by, review_id, exchange: "nerves") with a Board Review c-<name>-<n>. The connection is in force once a board verdict approves that Review from someone other than its proposer — the host then settles the Review in the approver’s name — and until hale dna disconnect <name> --why <why> appends connection.closed. A connection proposed before the nerves exchange (exchange: "git", no key pinned) is not in force: it is proposed again.
    • The inbox key. The other record’s server holds a user exchange (nats-<org>-exchange in the vault, the nerves allocation’s nats.conf) that may publish into exchange.<org>.in.> and read the card, and nothing else. Its operator hands it to a connected record’s operator, who keeps it under the same vault name; a sender whose vault lacks it connects without credentials, which an open server alone admits. The sender is proven by the envelope’s signature, not by the key.
    • Handing off. hale dna handoff <name> task <id> | receipt <digest> [--note …] [--as <who>] signs one envelope (kind: received) and publishes it to exchange.<peer org>.in.<this org>.received with Nats-Msg-Id its id and the first 32 hex digits of the sha256 of its signature (so a retry of the same signed envelope is stored once, and no other holder of the inbox key can take its place by publishing first under an id it could predict); once the other record’s stream has stored it, this record appends handoff.published (with peer_row, <stream>#<seq>, and the signed envelope) and, for a Task, task.transfer_requested. The envelope’s body carries handoff (h and twelve hex digits of the origin’s identity, kind and subject), origin_record, origin_author, origin_row, lineage (the subject’s rows here, as kind#seq), purpose, position, via, kind, subject, class, fact, note. A handoff retried after a crash completes what the first try left (#637): the envelope is delivered once by its id, and a published Task missing its task.transfer_requested gets it. Only a handed Task crosses. A receipt crosses as its digest and class; its body never leaves this record. A fact whose class the connection does not carry is refused at the edge as handoff.refused, with nothing published.
    • The bounded inbox. The exchange stream is a work queue (retention: workqueue): an envelope leaves it once the receiver’s drain has acknowledged it, kept in memory or refused, and the card (read by direct get, never consumed) stays. What waits undrained is bounded, and the stream refuses what overflows it rather than dropping what it holds (discard: new): each sender’s subject holds at most 10 000 waiting envelopes of a kind (max_msgs_per_subject, discard_new_per_subject) and the stream 256 MiB, so a connected record cannot flood another’s disk while it is not reading, and a receiver that drains takes any number. A handoff refused as full records nothing here and says so.
    • Receiving. The receiving record’s verbs (hale dna handoff, handoff accept, handoff sync) first drain its inbox into memory: each envelope addressed to this record, from the sender its subject names, whose signature holds under the key it carries, is kept once by its sender, its id and the key that signed it in exchange_rows through exchange_keep (insert-only; memory schema 9), and acknowledged; anything else is acknowledged and refused. An envelope claiming a record this one has a live connection to, signed with any key but the one that connection pinned, is acknowledged and dropped unkept, and the drain says so on stderr; one from a record not yet connected is kept beside any other claiming the same id, so a forgery never takes the place of the sender’s own envelope, and only the one under the key a connection pins is admitted. The drain reaches its own nerves as the spine (HALE_DNA_NATS_URL_SPINE, else the owner’s server, else the organism’s nerves allocation if it is up) and says so when it reached none. hale dna handoff lists a received handoff as admitted only under a connection in force back to its origin record that pinned the key the envelope is signed with and carries its class; hale dna handoff accept <id> [--as <who>], only for an admitted one, appends handoff.accepted to its own record (with the envelope’s origin, lineage, purpose and fact) and delivers an accepted envelope into the origin’s inbox; accepting it again delivers that envelope again. hale dna handoff sync admits, for every connection in force, each acceptance the other record sent — under the pinned key — once, as handoff.accepted_by_peer and, for a Task, task.transfer_accepted, each row checked on its own, so a crash between the two is completed at the next sync rather than suppressing it (#637). The Task settles only then, the rule retirement follows (GH #604 rule 5). A closed connection is not read, so history stays in both records and nothing further is admitted.
  • An attributed external decision (GH #616). A decision made by someone who does not run Hale — a manager, a client, an accountant — enters as a fact reported by a position inside the horizon, never as that party’s verdict. hale dna task decide <id> --decided-by <party> --via <channel> --evidence <digest> [--note …] [--as <reporter>] appends one decision.reported row on the Task in the reporter’s name: reporter, decider, channel, evidence, scope (the Task), obligation, practice, policy (the practice’s digest), accepted, why, note. It is refused with nothing appended when the Task is not handed, when the reporter is not its assignee (GH #604 rule 4), when the decider is the reporter, when a field is missing, or when the evidence is not a receipt the record holds. hale dna receipt file <path> [--class internal|customer|confidential] files one: internal text kept in memory with a receipt.filed row (by, name, bytes, class, store), a protected class sealed in memory alone (receipt.classified). Whether a report settles the Task is the obligation’s acceptance policy: a practice named acceptance/<obligation>, ratified by the Board and not retired, whose text says reported decisions: allowed. The obligation is the class of obligation a person’s job discharges; the leader’s plan names it (obligation:) and task.handed carries it. The policy is the practice bound when the Task was handed (task.handed.acceptance), so a later change of practice changes no case already handed — only a Task handed before practices were bound reads the one in force; it is read at admission and its answer kept in the row, so a later change of practice never rewrites what was admitted. Accepted, the Task is decided, and the projection shows the report as such: who reported, who decided, through what, on what evidence. Otherwise the Task stays handed, hale dna board lists it under tasks waiting with the reason, and its assignee closes it with hale dna task done. Without an obligation class or a practice in force, a report never suffices.
  • Schedules (GH #610, GH #1143). When a workflow runs and who convenes it — nothing else. A schedule points at a definition of the catalog (The workflow catalog), never at intent text: Schedule { id, definition, args, every_ms | cron, convener, paused, last_occurrence }, plus the state the record rebuilds (from, task, fired, last_minute). There is no ask and no requires, no schedule.fired or schedule.admitted row, and no claim before an ask: an occurrence is an execution, and its key is its idempotence. Declared by ratification. A ratified practice whose receipt carries schedule (a JSON object as text), or whose text names a cadence and a step among its terms (A practice’s terms, below), declares it in its ratify step, and so does each practice of a family (a pack) the Board ratifies whole; the ratification stands when the declaration is refused — the step is done, ratified by <who>; its schedule was refused (<why>), and the refusal is its own schedule.refused row naming the hole. hale dna schedule declare <id> (--every <n>ms|s|m|h|d | --cron <expr>) --definition <id> --convener <position> [--args <json>] [--as <who>] asks it of the organization through the store: a schedule.requested row the host relays, answered once under the request’s id by schedule.answered {schedule, declared, why, by} (the CLI takes a bare position name as position:<name>). Declaring through the store is the Board’s, or the convener’s own holder’s: with memory naming who holds the Board, a requester who holds neither it nor the convener is answered declared: false with why, and nothing is declared; while nobody holds the Board, or no memory is named, the verified requester declares, as a Review’s fallback decides by its required authority. A practice’s terms. A practice’s text may carry terms, words <key>=<value> (the value the rest of the word; practice_term, dna/core/practices.hl): the parameters of what the practice runs. Only a document of kind practice runs anything: an idea or a mandate whose text shows terms (an example, say) declares nothing. A practice whose terms name every= (an interval, 500ms, 30s, 15m, 1h, 1d) and step=<definition> declares, ratified, the schedule whose id is its name, of that definition, every that interval, convened by the position it is bound to (its target, position:<p>; a practice bound to org names no convener and is refused). Either way, by its receipt’s schedule or by its terms, an occurrence’s inputs are {practice, name} (the ratified digest and the practice’s name) and whatever the receipt’s args add, so the step reads its parameters from the practice itself: an amendment the Board ratifies changes the terms, and the schedule is declared again with them. Upstream watches (below) is the first practice that runs a step this way. A declaration is schedule.declared <id> {every_ms, cron, definition, args, convener, from}, written when new or changed — declaring the same schedule unchanged writes nothing — and from is the occurrence it was declared at (a cron with no match in the week before counts from the declaration’s own time): nothing at or before it is due. It is refused, a schedule.refused <id> {why} row and the reason, when it has no id or one with @ or a space; names neither an interval nor a cron, or both; names a malformed cron (five fields, minute hour day-of-month month day-of-week; *, a, a-b, */n, lists; ranges checked — at declaration, never when it would first fire); names a convener that is not position:<name>, or one that cannot reach the definition’s first store; names a definition not in the catalog, one whose expansion the catalog would refuse, or one with a step the organization does not perform; or carries args that are not a JSON object naming every input the definition takes. Reach: the organism’s own positions (leader, editor) reach every store its workflows write; a position of the graph reaches it through who holds it — a person, or an organization’s members — as memory’s routing says, and in this slice being held is the whole of reach: no per-position grant on a store is read; with no memory named, or memory not answering, a graph convener is refused, nothing assumed. A position nobody holds is a hole: the refusal says “nobody holds position:

    : it convenes nothing until someone does (hale dna fill <p> <holder>)” and the row carries hole: position:<p>, the hole #1091’s path fills. Declaring also writes the schedule into the graph (The repository’s graph): a definition:<id> node, once, and a convenes(position, definition) edge whose body carries the cadence (every 1d, every 90s, cron <expr>) and schedule of every schedule declared for that pair (; - and space-joined). A re-declaration for another pair writes the pair it left again, and retires its edge (graph.retired) when no schedule is left on it. Occurrences. The org program’s loop ticks with the wall clock in milliseconds (request_tick(std::time::nanos(std::time::current()) / 1000000), never a monotonic clock), queued through the organization’s keyed TickRequested handler. Synchronous tick is for that handler or isolated callers with no incoming bus work; a live loop uses request_tick so incoming work cannot enter during journal refresh. An occurrence’s time is its cadence’s: an interval’s step from the epoch (floor(now / every_ms) * every_ms), or a cron’s latest matching minute in UTC (day-of-month and day-of-week both restricted: either; looked for over the last week), a cron evaluated once per minute. A tick in an occurrence’s period admits it as an execution of the definition’s newest revision through its first store, with the ask id sched:<id>@<occurrence time>, args as its inputs, and the convener as its position: the admission is the convener’s owner’s (to, GH #664; the organism’s own positions are the organization’s), and workflow.admitted carries it as convener. On a shared record a body whose organization does not own the convener leaves the schedule to the one that does, writing nothing. That key is the idempotence: a schedule’s state is rebuilt from the record (from its declaration’s from, the admissions under its key, its skipped, refused and missed rows, and its pauses), so a restart, or a second ask of the same occurrence, is one execution. The occurrences whose period passed with no tick — the organism was down — are one schedule.missed <id> {first, last, count, why} row per run of them, and nothing else in this slice. A refused admission is schedule.refused <id> {occurrence, why}. Concurrency is the definition’s (WorkflowDef.concurrency): under skip, the default, an occurrence while the last execution is open (admitted, not yet workflow.settled) is a schedule.skipped <id> {occurrence, task} row, never silent; under overlap it is admitted beside it. Amendments. hale dna schedule pause <id> / resume <id> append schedule.paused {by} / schedule.resumed {by, at} (at in wall milliseconds) in the caller’s name from any clone; the organism reads them at its tick (a git journal is re-read from its ref at most every 5s). A paused schedule is not due: its occurrences pass unfired and unmissed, and a resume goes on from the next one — a resume read at a restart counts misses from its at. hale dna schedule lists each as the record has it — <id> [live|paused] every <n>ms|cron `<expr>` (UTC) — <definition>, convened by <position> · occurred N, skipped N, missed N · last <task>, occurred counting the admissions under its key. Not in this slice, by name: terms (an edge’s until firing a definition), turnarounds (a row’s age firing one), calendars and offsets beyond UTC, per-person availability, derived cadence holes (ingest proposing that a process has no cadence), on_miss: late and a queue concurrency, declaring a refused schedule again once its hole is filled, and who fires on a multi-node spine — the tick is the body’s that holds the lease, and a memory compare-and-swap on the occurrence key is owed when spine nodes multiply.

  • The optimize pass (GH #596 O). The pass is an execution of optimize-walk (GH #995: The workflow catalog) and nothing else. Its cadence is a schedule (above) that the seeded operating practice operating/optimize-cadence declares once the Board ratifies it — {"id": "optimize", "every_ms": 86400000, "definition": "optimize-walk", "args": "{}", "convener": "position:leader"} — convened by the leader, who walks the machinery, so it needs no one seated to be declared; a different cadence is an amendment the Board ratifies. The tick is the body’s that holds the lease, and an occurrence asked twice is one pass. Its one step, walk, first asks the budget — the pass is model-backed work, and none is routed on an exhausted window: optimize.refused <org> (“the pass waits for the next occurrence”) and the step fails saying so — then reads the record’s structural signals — asks planned and how many took the defaults, concerns raised, grant contractions, verdicts refused, mutations rolled back — and asks the leader (OptimizeRequested, keyed by org_id, once per incarnation) to walk the machinery, not the work. The leader answers with one small proposal or none (OrgReviewed), and the substrate journals org.reviewed <org> either way, with the signals it read and the execution it answers (task, carried on OptimizeRequested and OrgReviewed, so passes that overlap never answer each other), which settles its walk step: “if the state is clean, say so”. A proposal enters as an ask in the leader’s name and takes the whole road — planned, proposed, reviewed by the Board as an organization change.
  • Grants layer by containment (GH #596). The substrate may hold a ceiling: the grant above the child’s — the organism’s, the Board’s to widen. A child’s boundary reads its grant through the ceiling as it is now at every assessment: the classes both allow, the smaller magnitude, the stricter review (intersect_grants). Grants contract on their own (two failures in a row halve the ceiling), so a child born within a ceiling of 100 is bound by 50 the moment its parent contracts, and the record says so (grant.contracted <parent> {boundary, from, to, epoch, binds, note}, appended before the contraction takes effect). An organism born over the record restores the last contraction of its ceiling — its epoch and pre review whatever the authored ceiling now says, and the stricter of the recorded and authored magnitudes — before anything is admitted, as it restores a revocation. A contraction whose row the record refuses still binds live, and is appended again before every reserve and admits; until the record holds it, nothing is admitted. A child born wider than its ceiling is the early error: grant.refused <child> names what is wider, and the ceiling binds it from birth; that row is authority, the record’s, and is not the row a refused spend writes. Law layers by adoption: every position lives under the org program’s main, which adopts the organism’s law, so a department’s law can only add to it.
  • Grants delegate resources (GH #605). A Grant also carries amounts (per-operation ceilings, "USD 500.00 EUR 200"; a currency not named may not be spent), window_amounts and window (day, week or none), counterparties and routes (space-separated; none named, none allowed), expires (unix seconds; 0 never) and an authority_epoch. Every field defaults to none, so a grant written before them spends nothing. They contain like the rest: the currencies both grant at the smaller ceilings, the smaller window limit, the parties and routes both name, the earlier expiry (never is later than any, so a child that never expires under a ceiling that does is wider in time), and the sum of the two epochs (intersect_grants); resource_gap names what is wider, and the birth refusal carries it. permits(grant, spend, now) checks a Spend { id, amount, currency, counterparty, route } predicate by predicate and names the field that fails (expires, operation, currency, amount, counterparty, route). Dna.reserve(spend) admits a spend against the effective grant and appends spend.reserved <op> {child, amount, currency, counterparty, route, ceiling, epoch, at, funder, account} (GH #668: the allocation is the row’s entity, a claim kind the Ledger’s unique constraint reserves once, and the row names who pays — a grant’s funder, <owner>/<account>, inherited from the ceiling when the child names none; a child naming another funder than its ceiling’s is born wider, funder … not granted above); a reservation from before this, grant.reserved <child> {op, …}, is read the same way. The child’s own window counts its reservations and the ceiling’s window counts every child’s under it. The remainder is read at a revision and the row is appended with Journal.append_exact at that revision — a writer that moved the record in between makes it stale, never re-appended at the tail — so two children cannot each spend the same remainder. A refusal is grant.reservation_refused <child> naming the field — money, the Ledger’s, never grant.refused. Dna.settle_spend(op, spent) appends spend.settled <op> {child, attempt, spent}, one per attempt: every attempt’s actual consumption is retained and the window counts their sum from then on (a second settlement is attempt 2, not a refusal). Under contention a settlement is once: the row is appended with append_exact at the revision the last settlement was read at, and one another writer landed in between is read as the answer, never doubled. Money that came back is a compensation someone authorized, Dna.compensate_spend(op, amount, by) appending spend.compensated <op> {child, attempt, amount, by} — never an implied rollback. A purchase two children (or two owners of a shared record) fund is two reservations, each its own: one may settle and the other not, and the partial success is visible as such; no atomicity across them is assumed. A contraction advances the epoch, and so does Dna.revoke_grant(by) (the parent’s only; nothing is left granted). A revocation is recorded before it takes effect, grant.revoked <child> {by, parent, epoch} — one the record refuses did not happen — and an organism born over the record restores it before anything is admitted, so a restart never restores revoked authority. Dna.admits(admission) refuses an admission made under an older epoch, or whose grant has expired by the time it is carried out (grant.fenced). The generated law adds money_only_through_the_substrate: forbid reaches(positions, effects(money)) avoiding substrate, with effect money; declared in the core; an organization’s existing dna/org/law.hl is project-owned and gains the clause by hand. The money budget is distinct from the model budget.
  • The principal source (GH #612, #989). Who acts where a verdict, an intent or a task completion enters comes from git config dna.principal. A CLI verb on a clone is its operator’s, as --as and the git identity say. A head’s principal path is the only way in: a peer on the head’s socket is the local account the kernel vouches for, mapped by dna.unix.member; every HTTP caller is an identity provider’s subject under oidc. Trusted-local — a head’s session under its own identity (the launch token, hale dna ui with no principal source) — is a fixture’s mode alone: a head serves it only under HALE_DNA_TRUSTED_LOCAL=1, and refuses to start without either it or dna.principal = oidc. Local mode is OIDC too: hale dna head (or, from a checkout, dna/face/start.sh) starts the stub provider (dna/oidc: the discovery document, a JWKS, an authorization endpoint that signs in local-sub, and a token endpoint that exchanges a code or mints for a subject, every ID token ES256 under its published test key) and signs the person running it in as local-sub, mapped to $USER. Under oidc a head serves nothing without a verified token: /api/* answers 401, / redirects to /auth/login. A session comes from OpenID Connect’s authorization-code flow against dna.oidc.issuer (https, or plain http to 127.0.0.1 or localhost exactly, or to exactly the address the habitat declares, HALE_DNA_HABITAT_ADDRESS: a habitat on another machine of the LAN is named by its DNS and runs no TLS, spec/habitat.md § A habitat on another machine — the URL’s host is parsed, userinfo refused, never matched by prefix, and no other host is admitted): the head discovers the issuer’s endpoints, refuses a discovery document whose authorization_endpoint or token_endpoint is not https (plain http only when the issuer itself is on this machine or the habitat’s declared address; discovery_refusal, #635), sends the browser to its authorize endpoint with a random state and nonce (std::os::getrandom), and exchanges the returned code at the token endpoint itself with the client’s secret (dna.oidc.client, its secret the vault’s oidc-client-<client>, revealed on the one line that builds the token request, dna.oidc.redirect as the callback, else the head’s own /auth/callback). Every ID token is verified: its ES256 signature under a key of the issuer’s JWKS — the head reads the discovery document and its jwks_uri once, at start, and does not start without them; an RS256 token is refused, the standard library verifying ES256 only (es256_refusal) — then claims_refusal checks the issuer, the client among the audience, the expiry and the nonce. A caller that is not a browser presents its ID token as Authorization: Bearer, checked the same way without a nonce (bearer_refusal). A command reaches the head’s surface (HeadCommands) over its http::Rpc exposure (GH #1135, #1417; spec/api.md § The HTTP transport), POST /call/<member>, on the port the head’s launcher gives it (HALE_DNA_COMMANDS_PORT, never derived from the reads’ port) or, none given, the port the system picks, which the API child reports to the head that started it (HALE_DNA_API_PORTS); until it has, a relayed command is commands_unavailable, as a POSTed line of the wire under the caller’s bearer; no program forwards a line. The head’s bearer source (dna/api/bearer.hl, OidcBearer) verifies the token as above and names the principal oidc:<subject>; the binding’s roles map that subject to its person by dna.oidc.member as they map a socket peer by dna.unix.member, the gates are the same, and the receipt says principal_mode: oidc, the person, and the positions they hold (principal_positions). A fixture’s trusted-local head names its launch token the head’s own account (LaunchBearer, uid:<n>), the account its local session acts as. The face (dna/api/project_service) is itself a client of the issuer under HALE_DNA_OIDC_ISSUER, _CLIENT and _MEMBER (the <subject>=<person> who signs in there), configures a project it attaches for that issuer, and hands the session’s ID token to the project’s API child as the bearer — on the child’s reads, and on POST …/commands, which it proxies to the child’s binding transport (a GET there is 405: a lookup is the CommandLookup call) — and the child verifies it itself. A child serving its own shell relays that shell’s POST …/commands the same way (its ID token under OIDC, the launch token in a trusted-local session). Either relay keeps the CSRF guard a cookie session needs — the exact Origin, X-Hale-Command: 1, JSON — before the session becomes a bearer; a caller’s own bearer is not asked. An OIDC child is ready when it refuses an unauthenticated read carrying the digest of the proof the face gave it (X-Hale-Child), so a process squatting on the child’s port is never handed a token. An issuer on the loopback (or on the habitat’s declared address) is anyone’s who can bind its port, so its key is pinned: a head under a loopback issuer does not start without dna.oidc.key (the issuer’s SubjectPublicKeyInfo) and trusts that key alone. In local mode the stub signs under a key made for the launch, its client secret is the vault’s oidc-client-<client>, which the stub and the head each read there (never the environment or argv), the face pins the key into a project it attaches (restarting a child that pinned another), only the browser that opened the URL the face prints (its 0600 launch token) may start or finish a sign-in, and the face answers only as 127.0.0.1 or localhost on its port. The subject — never an email alone — maps to a member through a reviewed mapping, git config --add dna.oidc.member "<subject>=<name>"; an unmapped subject gets no session. A service is a principal too: a program with no person behind it (an application reading a project’s ratified practices) asks the issuer with the OAuth client_credentials grant under its own client id and secret — the stub (dna/oidc) answers for the ids services names (HALE_DNA_OIDC_SERVICES to dna/oidc/serve), each secret the vault’s entry oidc-service-<id> (std::secret’s vault: source; the entry is the secret’s exact bytes, no trailing newline), read at every ask and refused when there is none — with an ES256 token whose subject is the client id and whose audience is the issuer’s relying party. A token verifies only in that shape (ES256, aud the head’s client): an identity provider whose client_credentials tokens are RS256, or name another audience, is not supported. The record maps it by git config --local --add dna.oidc.service "<subject>=<service>". A subject is a person or a service, never both: the face refuses a bearer whose subject the record maps as a member (a person signs in, and a service’s token cannot be told from an ID token), and the child refuses a subject mapped both ways. The face accepts the token as Authorization: Bearer on the project’s API routes only (/api/hale/v1/applications…; on its own routes a bearer is 401), verifies it as it verifies an ID token, refuses a subject the attached project maps as no service (403), hands it to the child as the bearer (its commands reach the binding’s gates like a person’s), and never gives it a session or a cookie. The child reads as the principal service:<service>, never a person’s name, so a read a person alone may make (a Review’s exact candidate) is refused it; its only POST is a command, and a command or a lookup is a person’s: the handler refuses an issuer’s subject that maps to no person (forbidden), whatever its row requires, and never records it as the account the head runs under. A service holds no position, so no row’s role is held by it. A sign-in’s state is used once, expires in ten minutes, and is bound to the browser that started it by an HttpOnly; SameSite=Lax dna_signin cookie: a callback carrying the state from any other browser is refused and leaves the sign-in for the browser that started it (so a callback URL handed to someone else cannot sign them in); a session is a random 256-bit id in an HttpOnly; SameSite=Lax cookie (Secure when the callback is https) and lasts eight hours or until /auth/logout. With a session, a verdict acts as the member (--as, and --authority board when dna.oidc.board names them, reviewer otherwise) and an intent is asked by them (hale dna task create --as), the row’s author too; the form’s own as field is ignored. The head never acts in its own name. It speaks plain HTTP: TLS is a reverse proxy in front of it. Rows arriving by sync remain admitted under dna.trust (GH #604 rule 6); a synced row claiming a rank is not a sign-in.
  • The nerves: the record’s requests reach the organism (GH #986). A request is a row first, always. hale dna task create appends intent.requested (the body: outcome, from, to), a verdict appends review.verdict (the body: the verdict), concern raise appends concern.requested, pressure raise appends pressure.requested and practice propose appends practice.requested, each in the appender’s name and signed as they sign, from any clone. A one-shot verb never publishes. A node — the host under hale dna run or dev, which is its program’s main locus for as long as it runs — relays every request row it holds that the record has not answered onto the nerves on its tick, admitting each under dna.trust (GH #604 rule 6), and again every 30 s while it stays unanswered. Beside a live organism a verb waits for the answer in the record; from a clone with none it syncs and says where the answer will be. A request is answered by its own answer (GH #689): an intent, a verdict’s review and a practice request have an entity of their own, and a concern — whose entity is its source, shared by every concern from it — carries a request id that the organization writes into the concern.raised answering it. That row is one object — the concern’s words, its severity, which occurrence it is, and the request it answers — so the id is a member of the row rather than a shape inside its text: a worker who writes observed [request c1] in a log has written text, not metadata, and the host compares the whole id, so an answer to c10 is not an answer to c1. A row written before that rule carries no such field and is matched by counting, as this host did before. One request is one concern: a request delivered twice — the host relays a row again while it looks unanswered — writes one concern.raised, and the count that turns concerns into a knowledge proposal counts distinct requests, never redeliveries. Two requests raising the same words are two concerns; one id used for a second, different concern — other words, or the same words at another severity — is refused once, in concern.refused, which is that request’s answer and carries its id. Pressure is answered the same way (GH #986): a pressure.requested row carries a request id, and the pressure.raised answering it is an object — the signal’s words, count (which time its source has raised pressure) and request; a pressure row from before carries <what> x<n> and is read as that. A publish is confirmed by its answer in the record, never by the transport (GH #682): a row still unanswered 30 s after it was relayed is relayed again, whatever the transport said. The organism admits an intent once by its id — an intent offered again after its Task was born answers with that Task and journals nothing — a repeated verdict at a settled Review is answered as already settled, and a practice, a concern or a pressure request is answered once by its id. The host’s own observation report is a row too: observation.requested <mutation> (mutation_id, outcome, model_hash, detail), relayed as ExpressionObserved until the organism answers it with expression.observed; a node inside its observation window relays while it waits for that answer. A refusal the host writes for an unverified row answers the request it refuses: for a request with an id of its own (a concern’s, a pressure signal’s) it is one object, why and request. local, the default, trusts every writer to the record — the operator’s profile; signed admits a person’s row only when it carries a signature by the key the record enrolled for them (hale dna signers, The record), and an organism part’s only under the record’s key, and a row that does not is refused in the record in the host’s name (intent.refused, review.refused, concern.refused: “unverified writer”) and never relayed. Signing is one mechanism for this edge, not the boundary itself: a hosted head is admitted through a reviewed mapping of principals to authority and acts as the user, never as itself. The organism’s answers (intent.offered, task.born, review.settled, review.refused) return the same way. The body writes intent.offered, so the asker is not its author: the row’s body names who asked — <outcome> (from alice), an optimizer — and a head’s ask carries the person the head identified. A row is answered when a later row of the answering kind names its entity. hale dna task create --no-wait appends and returns, on either path, once the row is appended (the record is memory’s: there is nothing to sync); without --no-wait, an ask or a verdict made where no body anywhere holds the record’s lease is refused before anything is written. “Beside a live organism” is the record’s body lease: live, and held by this clone’s body.

The nerves (GH #986) are NATS JetStream, through pond’s client and std::bus adapter, vendored under dna/core/pond/realtime/nats.

  • Bindings where the publishes are. The host binds every DNA fact topic it publishes (IntentOffered, ReviewVerdict, ExpressionObserved, PressureRaised, ConcernRaised, PracticeRequested, HoldRequested, PositionOpenRequested, GraphReviewRequested, ScheduleRequested, KnowledgeNodeRequested, KnowledgeBindingRequested, KnowledgeEdgeRequested, WorkSubmit, WorkAllowanceAsk, RowLanded) to nats::NatsAdapter, and owns the connection, a nats::NatsConn placed pinned; its relay is a NodeRelay child whose publishes go out through those bindings. The generated organization reads them through its own pinned connection: a message that arrives is handed to every topic of its subject the organization subscribes, whatever its bindings list (which the graft writes for most of them, for its own publishes). The payload is the bus’s own encoding: both ends are Hale programs declaring the same topic, and nothing converts it. A one-shot verb instantiates no main locus, so it binds nothing and connects to nothing.
  • Subjects are the topics’ declared subjects under the organization’s token — the one memory names its schema with (dna_<record id>): <org>.dna.<family>.<event> (dna_4f….dna.intent.offered); an application’s own events are <org>.app.<app>.<event> (The heart’s events), and what a node tells the heads is <org>.head.<event> (Rows landing). The connection puts the token on what leaves and takes it off what arrives, so a topic keeps its declared subject.
  • One stream per organization, DNA_<ID> over <org>.>, on disk, kept a week (limits retention: what was said is kept and read from a position, not deleted once read). Every publish is acknowledged by the stream. The organization reads through its durable pull consumer spine (from the stream’s start when it is first made, from where it left off after that), filtered to <org>.dna.>, so a fact published while it was down or restarting reaches it when it is back. Over a shared record each owner is a space of its own: every owner runs a body and an organization (GH #664), and an owner’s host publishes under <org>.<owner>. and its organization reads through the durable spine_<owner>, filtered to <org>.<owner>.dna.> — so no organization pulls a fact another owner’s host published, and refuses it, which the relay would take as the answer. The host names its owner from the clone (dna.owner, the name of its organization node) and hands it to the organization (HALE_DNA_OWNER); an owner’s name in a subject or a durable keeps its letters, digits, - and _, anything else _.
  • Credentials, one per family, each a user on the server: owner creates the stream and is held by no process that runs the organism; spine publishes and reads <org>.dna.> and an owner’s <org>.<owner>.dna.> (the host and the organization), reads <org>.app.> through the durable heart (the host), and publishes <org>.head.> and an owner’s <org>.<owner>.head.>; head subscribes and publishes nothing; the application the record attaches holds a user of its own, app-<name> (The application’s account), which publishes on <org>.app.<name>.> alone and reads nothing of DNA’s. Birth, hale habitat migrate [organism] and hale dna nerves migrate [dir] create or update the stream with the owner’s URL (HALE_DNA_NATS_URL_OWNER, or the organism’s nerves allocation in the local habitat, brought up) and print HALE_DNA_NATS_ORG, one HALE_DNA_NATS_URL_<ROLE> per role — nats://host:port alone — and HALE_DNA_NATS_VAULT_REFLEXES, and for the attached application HALE_DNA_NATS_USER_APP and HALE_DNA_NATS_VAULT_APP: the users and vault names a program that is not the host presents (GH #1653). hale dna nerves urls [dir] prints the same lines for nerves that are up, bringing nothing up and creating no stream (nerves-migrate --derive), and names hale habitat migrate <organism> when they are not; hale dna dev derives them the same way before it starts the host and hands the host the spine’s URL and the token, never the owner’s nor the head’s; hale dna run takes the two from its environment and has the owner’s and the head’s removed from it. hale dna nerves drop [dir] deletes the stream, and everything it held, with the owner’s URL — beside dropping memory’s schema, when an organization is torn down.
  • The application’s account (GH #989). The application the record attaches is named by its project: the project’s name as a subject token (lower-case letters and digits, anything else _, at most 32), recorded as name on the application.attached row when it lands. A row recorded before that field resolves by the project’s directory name: a record is data already written, not an API kept compatible. An application publishes under its project’s name. Its user is app-<name>, its password the vault’s nats-<org>-app-<name> and the nerves allocation’s $NATS_APP_<NAME>_PASSWORD, and it may publish on <org>.app.<name>.> and nowhere else; no other application’s subjects, nor the reflexes’. It is provisioned when application.attached lands (hale habitat graft lands it, for an application and for a repository alike, then provisions the organism’s secrets) and on every upgrade that finds it missing. hale dna application remove [project] [--as <who>] writes application.detached {name, by} to the record, the membership’s source, and revokes the account: its user leaves the nerves allocation’s nats.conf, its password the include and its entry the vault, and the nerves-<id> allocation is restarted when it is up, so a connection it holds is closed and none opens again. An application named reflexes holds no account: those subjects are the reflexes’ alone.
  • The nerves’ passwords (GH #989) are the vault’s, one per role and seed, nats-<org>-<role> (The organism’s secrets). A part that connects holds std::secret::Credential { vault: … } beside its user, and pond’s client reveals it on the one line that writes CONNECT — never a field, a return value, a URL or the environment. No URL carries a password: userinfo in a server’s URL is dropped. A role whose entry the vault lacks is refused (nerves migrate, the host, the reflexes); nothing connects with a default.
    • The server’s files live beside the nerves allocation in the local habitat (allocations/<id>/, mode 700; GH #1653), never in a checkout. nats.conf holds no secret: it includes ./nats.secrets.conf and names each user’s password as its variable ($NATS_<ROLE>_PASSWORD, and the attached application’s $NATS_APP_<NAME>_PASSWORD). nats.secrets.conf, written by the bootstrap from the same draw as the vault’s entries, is the one file that holds a password: mode 600, mounted with nats.conf into the nerves-<id> service.
    • Rotation. A graft and upgrade draw every role’s password anew; a nerves-<id> allocation that is up is restarted to read them, and a restart that fails is said. A server of the operator’s (HALE_DNA_NATS_URL_OWNER) takes the conf and the secrets file again after each rotation.
  • Liveness. At start the host waits for the organization to read its facts: its durable consumer has a pull outstanding. It says so (the organization reads its facts from the nerves (DNA_<ID>)) or that it did not within 20 s; the facts wait in the stream either way. A restarted or rolled-back organization is confirmed the same way. Without nerves (HALE_DNA_NATS_URL_SPINE or HALE_DNA_NATS_ORG unset) the host runs and says the organization hears nothing the record asks.
  • A lost connection. The connection keeps every publish until the stream acknowledges it, writes it again after a reconnect, and a publish unacknowledged past its window violates the connection’s delivery closure. The violation collapses it to its owner, which does not restart the connection in place: the node starts again as a fresh process, its connection and its organization’s both new. The host appends violation.recorded (adapter_undeliverable, subject nerves, owner its holder, detail why), stops its organization and the expression, gives the body lease back (body.released, why: nerves) and exits NODE_RESTART (75, the genome pull’s code: one restart code for the organism), and its unit (Restart=always, which restarts on any exit) starts it again. The organization’s program exits 75 on its own connection’s collapse, and the host that supervises it ends with it. The row first is what makes a loss recoverable: the next node relays every request still unanswered.
  • The heart’s events (GH #987). An application’s one hookup to DNA is its events. It declares each as a topic of its own, under its own subject app.<app>.<event>, <app> its project’s name (The application’s account), binds it to pond’s NatsAdapter with a codec that writes one JSON object, and publishes it through a pinned NatsConn as its own user (HALE_DNA_NATS_USER_APP), presenting the vault’s entry HALE_DNA_NATS_VAULT_APP names, under the organization’s prefix, acknowledged by the stream. It imports nothing of DNA. The event names itself: its body is one JSON object as std::json::valid_object admits it (well-formed UTF-8, unique unescaped keys, at most 64 top-level members, nesting at most 64 deep) with a string id of the application’s choosing (letters, digits and -_.:, at most 128 bytes, as the application’s and the event’s names are). The node’s host reads <org>.app.> through the durable heart on its own connection, at most 64 unacknowledged at once. There is one durable for the organization, whichever owner’s host pulls, so each event goes to one host, and the prefix is the organization’s alone. The connection hands each event over as it arrived and does not acknowledge it (pond’s NatsConn.untyped), since no part of the organism declares an application’s events. The host lands it as reading.recorded, entity <app>/<event>/<id>, body app, event, id, subject and payload (the event’s JSON, at most 64 KiB), before anything acts on it. A reading is a signal, never a fact: it acts on nothing itself. What the spine does with one is a row of its own: an application’s concern becomes concern.requested (The application side), a reflex’s firing is read with the others for its target (The senses and the reflexes). What the organism does to the application goes the other way, through the API the application exposes.
    • Duplicates. A second arrival of an entity already recorded, whether a redelivery or a replay, is refused as a duplicate and lands nothing. Whether it is recorded is decided at the revisions the row is appended at, so two hosts land it once.
    • Not a reading. A message that names no application and event, or whose body is not such an object, is not a reading: the host says why and records nothing.
    • Acknowledgement. The host acknowledges the durable’s message once its row has landed, or once it has been refused as a duplicate or as not a reading. One whose row could not be written stays unacknowledged and comes again, so a host that stops between the hand-off and the row loses nothing — within the stream’s retention: an event not landed within a week (NERVES_MAX_AGE_SECS) ages out of the stream unrecorded. One whose write keeps failing comes again every ack wait (30 s) and holds one of the durable’s 64 places.
  • Rows landing. For every row its view of the organism gains, a node publishes head.row.landed (RowLanded: the row’s seq, kind and entity; under an owner’s prefix over a shared record), at most 256 on one tick; its first look only takes its place. It is the heads’ family, outside dna., so no organization’s durable pulls it; the stream keeps it like everything under <org>., and the head’s push is what it is for.
  • Stopping. A node is its program’s main locus, and SIGTERM drains it (GH #1039): its supervision reads draining, stops the organization (which drains the same way), gives the lease back and ends. A node that ends on its own, as a genome that does not build ends it, gives the lease back the same way (body.released {why: "exit <code>"}) once what it started is gone, so a supervisor that starts it again does not leave a lease behind for each start; a lease it no longer holds is not touched (the release is a compare-and-swap on its token). A seed build that fails says why: its exit status (or the signal that ended it) and the last lines of its stderr. hale dna run and dev give the node a 30 s grace for it (LOTUS_DRAIN_GRACE_MS, unless the environment already sets one). The core imports no NATS, because a program holding pond’s connection drains on SIGTERM: an API that imports the core keeps the default action. The head holds its own connection and drains: its watcher stops and the server stops accepting, at once. A request it is still relaying (a read proxied to its API child, a command relayed to its commands port) ends at the drain as well: the relay waits a slice at a time and, once the drain has begun, answers 503, retryable (head_api_unavailable, commands_unavailable), instead of waiting out the relay’s deadline past the drain’s grace.
  • One transport: the nerves (GH #986). Every fact between the organism’s parts travels over the nerves, and an application’s concern is one of its own events (The heart’s events; The application side). An application — an instance a node starts, the expression dev starts — inherits the application’s server, its own user and the vault name of that user’s credential, and the organization’s token (HALE_DNA_NATS_URL_APP, HALE_DNA_NATS_USER_APP, HALE_DNA_NATS_VAULT_APP, HALE_DNA_NATS_ORG; dev derives them as nerves urls does, a node is started with them, and a repository’s compose services are handed them with the path of the password’s vault entry: The body runs a repository’s compose services), and none of the organism’s other credentials: the spine’s, the owner’s, the head’s and the reflexes’ NATS URLs and memory DSNs are taken out of what it inherits. A node materializes vendor/dna in its clone for its toolchain, so an instance can import pond’s NATS client from it. Local mode is the organism’s nerves allocation in the local habitat.

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

Every kind names its memory. The memory column is the routing table (memory_of in routing.hl); a kind no build knows is the record’s.

kind memory what it is
application.attached record the application the organism oversees: its entrypoint, its artifact, the toolchain, and its name, which its broker account carries
application.detached record the attached application removed (hale dna application remove): its name and who; its broker account is revoked
codebase.attached record a foreign codebase the organism works on, attached with no application (hale dna codebase attach): name, repo (a url or a path), build and test (opaque command strings), branch (the default branch, main unless given), toolchain (go, hale or none, the default) and who; a later attach of the name states it anew
codebase.detached record the codebase removed (hale dna codebase detach): its name and who
task.delivered ledger a codebase task’s verdict, delivered (entity the task): via review (merged: review, leg_commit, merge_commit, edited, edit_receipt, comment_receipts: the pull request’s comments, each kept as a receipt, space-separated) or brief (its Review approved: review_id, by)
task.refused ledger a codebase task’s verdict, refused (entity the task): via review (closed without merging; comment_receipts as above) or brief (its Review not approved), with why
structure.observed record the compiler’s model of one of its parts, at the graft
graph.node record a node of the repository’s graph, entity <kind>:<name> (The repository’s graph)
graph.edge record a hyperedge of it, entity its id: kind, members {role, node} in order, via, outside
graph.retired record the node or edge the entity names leaves the graph; a ratified graph proposal’s receipt may carry one too (a re-ingest’s retirement), with the same effect
graph.requested record hale dna ingest asks the running organization to open the Review it filed (request_id, review_id, by), relayed as GraphReviewRequested (dna.graph.requested)
graph.proposed / graph.refused record the organization opened that Review (routed, its signers told), or could not; graph.refused <row id> is also memory’s refusal of a graph row it would not project, in the record or in a ratified proposal (kind, why, row, by: memory)
graph.ingested record hale dna ingest read the repository at a commit (entity the commit; added, changed, retired, the review_id of the one proposal it filed, "" when nothing differed)
hold.requested record someone asks the organization to propose a holder for a position — a person or an organization — or a member for an organization (hale dna fill, or Commands::fill, whose row carries the command’s fields after the CLI’s)
batch.proposed record a batch of changes hale dna apply (or vacate, or pack apply) proposed as one family Review in the group changes (digest, review_id, by, steps, pack for a pack: <name>@<version> sha256:<digest> <source>, and planned_on for one planned on pending proposals, <review id> <digest> a line; through Commands::apply_batch, the command’s fields after those); its graph.requested, under the same id, asks the organization to open the Review
position.requested record someone asks the organization to open a position at run time, with its mandate (hale dna position open: position, text, mandate, under, by; Commands::open_position adds the command’s fields after them), relayed as PositionOpenRequested (dna.position.requested)
position.proposed / position.refused record the organization proposed the position’s node and its mandate as one family Review, group positions (digest, review_id), or why not (a name the graph already states, no mandate, an under that names no node the graph states: the CLI resolves a bare --under <name> to the seed, work part, position or process the graph states by that name)
hold.proposed / hold.refused record the organization proposed it to the Board (digest, review_id), or why not; hold.refused <hold id> is also memory’s refusal of a hold it would not project (why, row, by: memory)
responsibility.proposed record a one-line responsibility inferred for a part, not yet ratified
law.deferred record a clause the graft could not certify
intent.requested ledger an ask from a clone with no organization running
intent.offered / intent.refused ledger the outcome an ask was admitted for, or the refusal
intent.unrecovered ledger an intent offered before a restart that no admission names; never re-offered
review.verdict record a verdict appended in the reviewer’s name, from a clone or a forge
task.born ledger the work an intent or a settled review made
task.handed ledger handed to a person: assignee, obligation, acceptance
case.admitted ledger a human Work’s case, its own handed Task (card 16): parent Task, Work and attempt, objective, assignee, obligation, acceptance, required evidence, its origin (owner), disposition (to) and performer (version 2, card 17) — before its task.born / task.handed, which say what it recorded; its completion (task.done, decision.reported) is the attempt’s outcome (card 17)
task.reassigned ledger the assignment moved to someone else
task.<state> ledger a case’s states, to done or failed; an admitted execution’s root is settled by workflow.settled, its workflow’s row (card 18)
mutation.requested record which exact Work and attempt asked for the Mutation, with the request as asked; written before mutation.proposed (card 14); for a leg’s candidate also the leg (leg), its commit (leg_commit) and the receipt that is its patch (patch, GH #1156)
mutation.proposed record a change proposed for a Task: class, objective, target, base
mutation.worktree record the sandbox opened for it, and removed
mutation.located record the files found, and the grant they were found under
mutation.candidate record the commit the editor produced
mutation.<disposition> record what the autonomy boundary decided: review, stage, escalate, release, deny
mutation.topology record the diff names a fleet seed’s source or the manifest: re-classed for the Board
mutation.applied record the candidate applied to the genome
mutation.apply_retried record the apply ran again on a settled review
mutation.retained record kept after its observation window
mutation.rolled_back / mutation.rejected / mutation.revise record the genome back at the base, refused after review, or sent back for another pass
mutation.refused / mutation.failed record not applied (the candidate moved, the gate said no), or the change did not survive its own verification
effect.requested / effect.result ledger the exclusive claim on an effect key, and its outcome
effect.relayed ledger an attempt a leg relay answered pending: a leg’s to claim at the head, once per attempt
evidence.<step> record a verification step’s output, kept by the digest the row names
evidence.magnitude record the measured magnitude of the change
review.requested record the Review: question, authority, candidate, disposition, evidence, diffs
review.routed record what a routed Review requires: definition, signers (<position>=<holder>,…), gates, fallback; written when it opens and again when its holders change (the latest is the requirement)
review.signed record an approval a routed Review admitted that settled nothing: by, awaiting
review.settled / review.refused record the verdict that decided it, or why one was not admitted
review.reasoned record 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:)
candidate.dropped record a kept candidate is no longer kept, here and at every clone’s sync
knowledge.proposed / .ratified / .declined / .refused record a practice through its review
knowledge.retired record a practice superseded by a later version
library.family.refused record a library family approved while a member was refused (its predecessor was replaced); names the refused members
knowledge.consulted ledger what a piece of work looked up today
grant.contracted record authority narrowed, and what it leaves
grant.revoked record the parent took the authority back
grant.refused record a grant born wider than its ceiling: authority
grant.reservation_refused ledger a spend the window would not admit, naming the field: money
grant.reserved / grant.released ledger a spend admitted against the window, and the reservation settled at what was spent
grant.fenced ledger an admission refused because the grant’s epoch moved since
budget.exhausted ledger the window’s model allowance is spent
model.called ledger a model call and its evidence
model.rule record the model mapping’s rule for a selector (default, position:<name>, contract:<Name>, task:<id>): the size it needs and the sizes an override may move it to (size, permit, by); the latest row per selector is the rule (dna/core/model_map.hl)
model.category record which model fills a category <mode>.<size> today, whether it returns tool calls and its price (model, tool_calls, input_micros_per_1k, output_micros_per_1k, by); the latest row per category stands
optimize.refused ledger the organization’s pass over itself did not run
node.started / node.build_failed record a node runs a genome, by its sha; a genome did not check or build, and the node stayed on the last that did (sha, why)
violation.recorded record a closure the organism absorbed (GH #989), recorded by the part that owns it, which goes on: {kind, subject, owner, detail}. adapter_undeliverable: a node’s connection to the nerves collapsed, a publish the stream did not acknowledge in its window (subject nerves, owner the node’s holder); the node stops and exits 75 for its unit to start it again. lease_unsettled: a leg’s lease expired with no outcome (subject the attempt, owner spine), once per lease, before the attempt is asked again. pulse_stopped: the heart’s pull on its durable refused while the connection held (subject heart, owner the spine’s holder), once per outage; the durable is made again. The board shows the latest five
observation.requested / observation.refused record the host’s observation report as a row (mutation_id, outcome, model_hash, detail), relayed until expression.observed answers it; refused, unrelayed, when the row is not verified under signed trust (GH #986)
claim.taken / claim.released ledger a node took a claim by id before acting — plan/<intent>, plan/case:<case> — and gave it back: holder, and for a take its token and until
attempt.claimed ledger a leg’s claim on an admitted, outstanding attempt, taken at the head (GH #946): the lease (holder, token, until), the principal that took it (principal_mode, principal_name: whose outcome the lease admits), the attempt’s task, work and performer kind, and the command that took it
attempt.outcome_requested ledger the outcome a leg handed back under its lease: the disposition, the result (result, result_ref), the receipts it filed (evidence_ref), the calls it made (calls), the hat it wore (hat_digest, hat_head, hat_watermark, prompt_digest, renderer) and the command (request); relayed until attempt.outcome or attempt.outcome_refused answers it
attempt.outcome_refused ledger why the owner would not settle a leg’s outcome (why, holder, token, request)
attempt.allowance_requested ledger a leg’s ask for its attempt’s spend, under its lease (holder, token, request, at, and the command); relayed until attempt.allowance_granted or attempt.allowance_refused answers it (GH #1131)
attempt.allowance_granted ledger the one gate admitted a leg’s spend: what the attempt may cost (allowance_micros, -1 when nothing bounds it), the ceiling, the window’s spend, the lease and the request
attempt.allowance_refused ledger the one gate admitted no spend (why): on a leg’s attempt (naming the request), the editor’s attempt, or review:<id> for the leader’s Review
attempt.released ledger a leg gave its lease back without an outcome (holder, token, why, and the command); the attempt is another leg’s to claim (GH #946)
friction.filed record what got in a position’s way (position, attempt_id, text, and the command), filed on the attempt it was met on, else on the position; nobody admits it, it is a fact (GH #946)
org.reviewed record that pass’s own answer
person.retired record someone left, and who took their work
body.claimed / body.released ledger who is running this record, by the lease’s token
body.provisioned record a machine made able to run it
body.credential_missing / body.credential_present ledger whether the model’s key is in the vault where the body runs
secret.rotated record a credential set or rotated — the name and the place, never the value
schedule.requested / schedule.answered ledger a schedule asked of the organization (hale dna schedule declare), relayed by the host; its answer under the request’s id: declared, why
schedule.declared / schedule.refused ledger a schedule a ratified practice or a request declares (every_ms, cron, definition, args, convener, from); or one that would not be declared (why, and hole for a convener nobody holds), or an occurrence whose admission was refused (occurrence, why)
schedule.skipped / schedule.missed ledger an occurrence not admitted while the last execution is open (occurrence, task); occurrences whose period passed with no tick (first, last, count, why)
schedule.paused / schedule.resumed ledger paused and resumed by hand, in your name
receipt.classified / receipt.withheld ledger a protected body memory keeps sealed, or one withheld because no memory could keep it
receipt.disclosed ledger a reader authorized, for a purpose
receipt.read / receipt.read_refused ledger a read in the reader’s name, or its refusal
receipt.held / receipt.hold_released ledger a hold that refuses redaction, and its release
receipt.redacted ledger the body removed, the digest kept
concern.requested / concern.raised ledger a concern from a part about the part above it; the request carries its own request id; the answer is one object (what, severity, occurrence, request, and since GH #995 the parent it is routed to and the concern-escalate execution that raised it, task), so a concern’s words are never read as metadata, and one request is one concern, however often it is delivered
concern.refused ledger one the organization would not admit
practice.read record what the hat read once a proposal was ratified, at which head (digest, target, ratified_at, hat, head, included, task): practice-ratify’s hat step (GH #995)
concern.proposed record three raises became a proposal — at concern-escalate’s raise step once the source crossed the threshold, including the execution a birth admits for a source over it, unproposed
pressure.requested / pressure.raised ledger a signal from a source; the request carries its own request id and a node relays it until answered (GH #986); the answer is one object (what, count, request) and is written once per request — a row from before is <what> x<n>
pressure.remeasured ledger the declared fitness signals, measured again after the change
appendage.proposed record an organ the organization proposes for itself
expression.restart_requested record the organization asks for a change to be expressed
expression.restarted / expression.deployed record the shape and build the new expression reports, or what a deployment gateway expressed and judged
expression.observed / expression.crashed record the observation window’s outcome; crashed names the instance and node on a fleet
fleet.deploy record a genome revision expressed through the fleet’s nodes
instance.up / instance.exited ledger a node’s report on one instance of the fleet
github.pr / github.commented record the pull request a Review opened, and the settlement commented back
gate.observed record a gate’s check run at the candidate as the forge reported it: gate, sha, conclusion, url, forge, run; written only by the host’s forge sync, once per run (GH #1161)

hale dna codebase attach <name> --repo <url|path> --build <cmd> --test <cmd> [--toolchain go|hale|none] [--knowledge <dir>] [--branch <name>] [--as <who>] writes codebase.attached (above); --knowledge (default docs/integrations/, a directory inside the repository) declares where the codebase keeps an watched source’s architecture note, <watch>.md, on its default branch (The task a move becomes); detach <name> writes codebase.detached; list (the default) reads the record’s attached codebases. No application row is written and no broker account exists: a codebase is a repository the organism works on, not a heart it senses. The name is lowercase letters, digits, - and _. build and test are opaque strings, run with sh in a worktree and nowhere else; empty means no step. A path --repo is stored absolute. --toolchain declares the toolchain hand a leg works the codebase with (anything but go, hale or none is refused); none leaves build and test as the opaque commands, and hale is recorded for the hale hand to select by.

A genome’s manifest may list its codebases, [[dna.codebase]] in its hale.toml with the same fields (name, repo, branch, build, test, toolchain); the habitat attaches each one at the organism’s graft as this verb does, once (spec/habitat.md, Nurseries and saplings).

A go codebase’s leg holds a Go hand (legs::GoToolchain, beside Toolchain in dna/core/legs/hands.hl, behind the GoHand interface and Hands.go) with three tools added to the hat’s tool_grant: go_vet (go vet ./...), go_test (go test over a package pattern, default ./...; a pattern that starts with - or holds other than path characters is refused) and gofmt_check (gofmt -l .; any listed file is the failure, empty is clean). Each runs in the leg’s worktree of the codebase; its output is bounded and returned as the tool’s result, and the call is a row of the tool-calls receipt like any other. A machine with no go or gofmt on PATH answers that the Go toolchain is not installed there, rather than failing obscurely. For a go codebase verify runs, after build, gofmt -l, go vet and go test ./..., the first to fail refusing as gofmt failed, go vet failed or go test failed; propose runs the same three first and pushes nothing when one fails (not pushed: …).

A leg working a codebase (legs::CodebaseWork, dna/core/legs/codebase.hl) takes its worktree from the codebase’s repository, never the organism’s own: a scratch clone at branch, on a new branch leg/<codebase>/<task>. Its verify step is the codebase’s build and then its test in that worktree (the first to fail refuses, with its output). It proposes by committing, pushing the branch to the codebase’s repository (origin) and opening a review through its ForgeHand: GhForge { repo, base } names the codebase’s repository and default branch (gh pr create --repo --base --head), and ForgeBridge puts the core’s Forge behind the hand, so a fixture runs it over MemForge. The forge decides; a leg never merges.

A task names a codebase with hale dna task create --to position:<name> --codebase <name> <outcome…> (the codebase must be attached, and the ask routed to a position): the intent row, the IntentOffer and the Intent carry codebase, and ask-position’s admission inputs record it. The Work’s hat then carries codebase, the attached row as a JSON object (name, repo, build, test, branch, toolchain; absent for a task naming none, so such a hat’s digest is unchanged). A leg’s hand and submit --from-worktree on a hat that names one work a clone of the codebase’s repository on leg/<codebase>/<task>, not the project’s checkout: the check and test hands run the codebase’s build and test, the validators are the two commands, and submit pushes the branch there, opens the review through the leg’s forge hand and files the outcome as result_ref: review:<ref>. The owner settles such an outcome done on that reference and makes no Mutation of its own genome. The language hands (Go) that make the commands typed are a later item.

A watched upstream (an external service the organism depends on) is a practice, never a manifest table or a record family of its own. The standard pack’s template standard/upstream-watched/<name> is proposed once per upstream (hale dna pack apply standard --set integration=<name> --set position=<owner> --set source=<source> --set kind=<kind> --set every=<cadence> [--set codebase=<name>]), ratified by the Board with the rest of its batch, and bound to the owning position (its target position:<owner>). The batch also proposes an idea, standard/upstream-source/<name>, the source as knowledge, an external source with its URL, bound to the same position. The practice’s text names its terms (A practice’s terms):

  • source=: http://, https:// or file:// for a page or a feed; for an OpenAPI document, the surface hale api import wrote and the genome commits (a path inside it), whose source is the one it was imported from (from openapi "…");
  • kind=: changelog, docs, feed or openapi;
  • every=: the cadence, as a schedule takes it;
  • codebase=: optional, an attached codebase whose client of the source a move is a task for (The task a move becomes); the pack’s default none is no codebase;
  • step=source-check: the step its occurrences run.

Ratified, the practice declares the schedule its text names, its id the practice’s name, of source-check, convened by its position: a position nobody holds is schedule.refused with the hole, and the same terms again write nothing (Schedules). Each occurrence’s inputs are {practice, name}, and its check step (dna/core/source_check.hl) reads its parameters from the practice’s receipt; a document that is not a practice is refused (result refused). A practice that is not in force (never ratified, or retired) checks nothing. One whose terms do not stand (no position, a kind outside the four, a source of the wrong shape) fails its occurrence saying why, its result refused.

The step reads the source and normalizes it:

  • changelog and docs: a body whose first character is < is HTML, read as text with its markup stripped, a line per block element, and the volatile parts left out (head, script, style, noscript, template, svg, iframe, object and comments); any other body is its lines. Each line has its common entities read, its white space collapsed and its ends trimmed, and blank lines are dropped.
  • feed: the entries (RSS item, Atom entry), a line each, title | link (the guid or id without a link); the feed’s own fields and dates are not read.
  • openapi: the source document itself, canonical (hale api drift <surface> --document, run in the genome: the parsed OpenAPI document, JSON or YAML alike, as JSON with its keys sorted), with the drift … lines of hale api drift <surface> as its summary. The document, not the drift lines, is what is digested: a drift line names a row and a slot, so a second change to a row that already drifted reads the same. An exit of --document other than 0, or of the drift check other than 0 or 1, is a failed read.

A fetch is one GET (std::http::get, 30 seconds, no redirects followed: a status outside 2xx fails) or a file read, at most 4 MiB, in UTF-8. The digest is the normalized text’s sha256, and the text is kept as a receipt under it. The baseline is the receipt the previous occurrence named: each occurrence’s outcome is the record of what it saw, and the step reads the outcomes of the practice’s earlier occurrences (by its name, so an amended practice keeps its baseline). An outcome done names the snapshot it saw as its result (sha256:…); one failed with the result unreached could not read the source. Against the baseline:

  • none: the outcome names the snapshot, and nothing else is written;
  • the same digest: the same, nothing else;
  • another digest: the unified diff of the baseline’s snapshot and this text, labelled <practice name> <from12> / <to12>, cut at a line past 16 KiB with a line saying so, is kept as a receipt; a reading.recorded lands, entity sources/<watch>.changed/<task> (<watch> the last segment of the practice’s name), app sources, event and subject <watch>.changed, its payload {id, practice, name, kind, source, position, task, from, to, drift (openapi), receipt, diff}; an API’s receipt leads with its drift lines, then the diff of the canonical document; and the organization admits a concern (on_concern) from <position>/<watch>, so it is routed to the position, its request the reading’s entity and its words naming the reading and the receipt.

A read that fails is the occurrence’s own failure, result unreached. The third in a row (SOURCE_UNREACHABLE_AFTER) also lands one reading.recorded sources/<watch>.unreachable/<task> (payload {…, why, failures}) and no concern; the failures before and after it land nothing, and an occurrence that reads the source again ends the run. A step performed again (a restart before its outcome) that finds a reading its task landed answers from that reading, without reading the source again: a move’s outcome names the snapshot the reading moved to, and its concern, asked again under the same request, names the reading’s receipt, so a source that moved on since is the next occurrence’s move. The step keeps no rows of its own: what each occurrence saw is its outcome, and the snapshots and diffs are receipts.

The concern goes the way every concern goes (concern-escalate): raised and routed, and proposed as knowledge at the threshold. Of itself a concern is not a task; a practice that names an attached codebase= files one for its move (below).

A watched upstream whose practice names an attached codebase (a codebase=<name> term, beside source= and kind=) is that codebase’s client of an external service. Two things follow from a move, by the organization, none by a person filing anything, and both are composed of shapes that exist: the practice, a knowledge node, an ask. No row kind and no record family is added.

The note. The codebase’s knowledge directory (--knowledge) holds <watch>.md on its default branch, the source’s architecture note. At each move the organization reads it (git show <branch>:<dir><watch>.md in the repository, in a scratch clone when it is not on this machine) and proposes it as a knowledge node of kind concept, named integration/<watch>, authored by org and bound to the practice’s position: the Board ratifies it as any knowledge (knowledge.proposed, its Review). A proposal is by the digest of its document, so a note that did not change is the proposal already in the record, and a note that changed is a fresh proposal on the same node. A codebase with no such file notes nothing.

The task. A move (the check that lands reading.recorded sources/<watch>.changed/<task> and its concern, whether the check is fresh or asked again) files, besides, one ask under position:<position> naming the codebase, id source-<watch>-<reading's task> (an ask is admitted once by its id): the ask-position workflow, Patch output, and in the admission’s inputs an integration object {integration (the watch), practice (its name), codebase, source (its kind), reading, diff_receipt, diff, note_path, note_digest, note}, derived from the reading, the concern’s receipt and the practice that convened the check (the diff and the note inline cut at a line past 8000 bytes, the whole of each in the receipt and the codebase). The hat carries it (hat.integration, in the digest), and a prompt rendering names the move, its diff and the note and the two ways to answer. A leg holding the position claims it through the ordinary claim path (attempt.claimed; hale dna work next --as position:<name>).

The answer. The leg reads the client through the codebase’s worktree and answers one of two ways. A change opens a pull request at the codebase’s forge (the Go hand and CodebaseWork.propose, A codebase attached with no application); the review’s body ends in a leg-commit: <sha> line naming the commit the leg left the branch at, and the attempt settles done with result_ref: review:<ref>. A first line beginning NO IMPACT over an unchanged worktree is a no-impact brief: the attempt settles done with result_ref: brief:<digest> and the brief as its result, no validator judging a tree nothing changed, and the organization puts it to a Review of its own, not the forge’s: a knowledge proposal (kind idea) bound to the position, its provenance naming the task (no row of its own). Neither touches anything but the codebase’s repository and the record: no Mutation of the genome is asked. Either answer’s receipts are the architecture note and the reading’s diff in full, one manifest of sources ({"sources": [{kind note|diff|file, path, digest}…]}: the note, the diff by its receipt, and every file read through the read hand with the digest of what it read), and the tool calls (a read row names its path).

The verdict. The forge decides a review’s fate and the organization learns it through the Forge interface (dna/core/forge.hl): review_state (open, merged\t<commit>, closed, unknown), review_body and review_comments (who\tkey\ttext, newlines as \n), beside the verdict, check and comment methods. The host’s forge sync asks it for every codebase review not yet decided, per codebase (the allocated Forgejo’s API for a codebase whose repository is there, by its url; GitHub’s gh pr view for a github forge, the repository taken from the codebase’s url; the file forge’s <n>.merged, <n>.said for fixtures), through codebase_intake (dna/core/codebase_reviews.hl), which returns the rows and writes none:

  • merged: task.delivered {via: review, leg_commit, merge_commit, edited, edit_receipt, comment_receipts}. A review the team edited before merging is still delivered; edited is whether git diff <leg_commit> <merge_commit> in the codebase is not empty, and that diff is the edit_receipt: what the organism learns from;
  • closed unmerged: task.refused {via: review, why, comment_receipts};
  • each comment on the review, once by its key, is a receipt on the task: comment_receipts on the verdict row, in the forge’s order. A comment has no row kind of its own.

A no-impact brief’s Review is the verdict: approved, task.delivered {via: brief}; otherwise task.refused {via: brief}, written when the Review settles (brief_verdict).

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.

A recursive workflow can be written as data (dna/core/workflow_definition.hl; the execution contract it serves is dna/WORKFLOW-CONTRACT.md). A WorkflowCatalog holds definitions: define(id, revision, stores, title) declares a workflow whose ordered steps each write the one store its word names — stores holds one word per step, record, forge, genome, heart, graph, nerves, memory, vault or host, so the step count is the word count (GH #995). A step is where a fact is written: reading, or waiting on another writer, is part of the step whose fact it serves, never a step of its own; leaf(workflow, revision, step, key, work, attempts) adds a required member carrying the content of one Work request and its retry allowance; child(workflow, revision, step, key, child, child_revision) adds a required member that invokes another definition. Each returns "" or why it was refused (a revision defined twice, a member for an undefined workflow). Definitions refer to each other by id and revision, in two flat collections.

Before anything is built, expand checks every definition reachable from the root once, counting each one’s Works and nesting height from its children’s and saturating at the limits: a catalog whose expansion would be enormous is refused from those counts, in one pass over the definitions, without materializing a node of it. The check also holds for the root, which is depth 1, and for the limits themselves (a limit below 1 admits nothing and is refused).

expand(root_task, id, revision, limits) binds one execution: the whole finite tree as BoundNodes (tasks, steps, works) with their ids — a child Task is <parent>.s<i>.<key>, a Workflow <task>/wf<revision>, a Step <workflow>/s<i>, a Work <step>/<key> — each Work carrying its authored request content and allowance (bound_request makes its WorkRequest), and each Task its definition, parent, spawning step, depth and definition path. It refuses the whole expansion, binding nothing and naming what broke, when a definition is missing (by id and revision), a workflow has no steps, a step has no members, a member names a step outside its workflow’s steps, a step declares no store (-), two (record+forge: declares two stores (record, forge)) or a word that is no store, a step writes or reads a part that is not built yet (named with its issue: the heart, GH #987; the vault, GH #989; a host the organization provisions, which only hale dna body provision reaches today), a member key is not [a-z0-9-]+ or repeats in its step, a member is neither a leaf nor a child, a workflow invokes itself directly or through its children, or an AdmissionLimits value is exceeded: max_depth (8), max_steps (32), max_members (32), max_attempts (8, the largest allowance a leaf may bind) and max_works (256), the defaults in parentheses, supplied by the caller and returned with the Expansion. Two invocations of one definition are two executions with their own ids; a later revision changes nothing an earlier revision expands to. A definition id is [a-z0-9][a-z0-9-]*, checked when it is defined and when it is read, so an id can never carry the delimiters the definition path is written with. encode() writes every definition and member as one JSON document (format: dna.workflow-definitions/3: each workflow’s steps, its stores, and how it occurs on a schedule — concurrency and takes) and decode(text) reads one back, refusing a document that does not close, another format, an id outside the grammar, an already defined revision, a document that defines one revision twice, a workflow whose stores do not name one per step, or a concurrency that is not skip or overlap (an absent one is skip), and adding nothing then. latest(id) is the newest revision the catalog holds: what a new Task binds.

How a definition occurs when a schedule points at it (GH #1143, Schedules) is the definition’s: occurs(id, revision, concurrency, takes) sets WorkflowDef.concurrency — skip (the default: an occurrence while an execution of it is open is skipped) or overlap (admitted beside it) — and takes, the input names, space-separated, that a schedule’s args must carry; it refuses another concurrency, or a definition the catalog does not hold. The baseline’s ask-edit@1 and ask-person@1 take objective. hale dna definitions prints occurs: <concurrency> while open[, takes <inputs>] under a definition only when it says more than the default.

encode_bound() writes one bound execution as a recipe (format: dna.workflow-recipe/1; an admission nests it as an object of its own, never escaped into a string) carrying the node count it was written with, and decode_bound(text) reads one back. It refuses another format, a missing node array, a count that disagrees with what the document carries, a node without an identity or of no known kind, a node whose number was written as text or whose text was written as a number, and a document that does not close: every object, array and string closed by its own delimiter, with nothing after the value. That is a closure scan rather than a JSON grammar validator — it does not check escape sequences or number syntax — which is what reading back one encoder’s own output needs; a document from outside the system wants a real parser in front of it. A whole final node also ends in }, so the last character says nothing about the document. Every refusal binds nothing: half a recipe is not a smaller execution. Nothing yet executes a bound expansion.

DNA ships a catalog of definitions, each a chain in which every step writes one store (dna::baseline_definitions(), in dna/core/workflow_definition.hl). A project’s dna/org/workflows.hl is generated — written by hale habitat graft and rewritten to the current shape by every hale dna upgrade, whatever an earlier toolchain wrote — and returns it plus the project’s own (fn workflows() -> dna::WorkflowCatalog), which live in dna/org/own_workflows.hl (fn own_workflows(catalog): project-owned, written only where there is none, never rewritten; what it answers when a definition is refused is kept on the catalog, refused, which hale dna definitions leads with and fails on, never a smaller catalog; an upgrade that rewrites a workflows.hl holding definitions of its own names each line it dropped, for the project to move into own_workflows.hl), and the generated organization admits from it (catalog: workflows() on its dna::Dna; a body given no catalog takes the baseline at birth). The baseline is the vendored toolchain’s, so an upgrade supersedes it by revision: a Task born under a revision finishes under it — its recipe was bound at admission — and a new Task binds the newest (latest).

id steps (the store each writes)
ask-triage offered · classify · plan (record each)
change-deliver candidate (record) · review (forge) · verdict (record) · apply (genome) · deploy (heart) · settle (record)
practice-ratify ratify · hat (record each)
deploy-observe deploy (heart) · settle (record)
concern-escalate raise (record)
optimize-walk walk (record)
source-check check (record)
secret-rotate rotate (vault) · recorded (record)
body-provision provision · start (host) · lease (memory) · observed (record)
ask-edit, ask-person, ask-judge, ask-position the ask’s one leaf: an edit prepared for review, a person’s job, a judgment handed back as an Assessment, or a change made by the position it is routed to (record)

Every step writes a fact of its own. What #995 listed as steps that write none is folded into the step whose fact it serves: practice-ratify’s verdict is the Review’s row, so ratify waits for it and writes the ratification or the decline; memory’s projection writes the idea and its binding, so hat waits for it and records the row it was fenced to; the request and the Review are written by whoever proposes (a person’s request, a concern, the record’s birth); a concern’s routing is a field of its concern.raised row and its proposal is written in the same step at the threshold; the optimize pass’s walk reads what its org.reviewed row records; deploy-observe’s pulse is what settle waits for, and its rollback is the failure edge, not a step: the happy path writes nothing to the genome.

A step the organization performs itself is one leaf that requires: "organism". The engine relays its attempt to the assembly serving the scope (OrganismRelay, kind organism: StepRequested, answered pending), which performs the step and reports the attempt’s outcome (Dna.on_step_requested). A step ensures its rows — performed again after a redelivery it writes none twice — and one that waits (a verdict, the leader’s word, memory’s projection) is kept and performed again when what it waits on may have arrived: a verdict, the leader’s answer, the tick. The organization performs the steps this toolchain knows, by definition and key: practice-ratify/ratify, practice-ratify/hat, concern-escalate/raise, optimize-walk/walk, source-check/check. A definition with an organism step it does not know — a path not moved onto the catalog yet (ask-triage, whose ask is still admitted as ask-edit or ask-person after the leader plans it), or a revision from a newer toolchain — is refused at admission naming the step, never failed when it runs, as a definition on a part not built is refused naming the part; both are workflow.refused rows and nothing else. The organization performs a step only of a definition admitted directly, so one that invokes a definition with an organism step anywhere below it is refused at admission too. A ratification the catalog refuses is answered once per incarnation — one workflow.refused row — and asked again after a restart, so a catalog fixed in between ratifies it, and nothing retries at every reconciliation. hale dna definitions lists what an admission would refuse each definition for.

  • practice-ratify carries every knowledge proposal the Board decided: when a verdict settles a proposal’s Review, the organization admits one execution for it (ratify:<digest>), whoever proposed it — a person (hale dna practice propose, proposed by the organization as before), a concern, the record’s birth (the purpose, the seeded practices, a repository’s holes and practices), a holder asked for. ratify writes knowledge.ratified (and knowledge.retired for what it supersedes, or knowledge.refused) under an approval, and knowledge.declined otherwise, which fails the chain; it is done only when the whole consequence landed, so a retirement the record refused is written again. hat fails the chain, saying so, when no memory is named (nothing would come to wait for); otherwise it waits for memory’s projection to hold the idea ratified, then reads the target’s package at memory’s head and writes practice.read <digest> (target, ratified_at, hat, head, included, task): what the hat read, at which head. No verdict path writes a ratification of its own — a Review’s verdict or a decision by command (review.command_decided) alike; a settled verdict whose ratification never ran is admitted the same way at the next reconciliation.
  • concern-escalate is a concern: hale dna concerns, and the organization’s own, are admitted as concern:<source>:<request> (a redelivery of the request is the same execution; the same request with other words is concern.refused, as before). Its one step, raise, writes concern.raised <source> (what, severity, occurrence, request, the parent it is routed to, task) and, once the source crossed the threshold, proposes it as knowledge by the rule that always did. A source over the threshold with no proposal and no execution running for it is escalated once at birth by an execution that raises nothing more (its raises are the record’s).
  • optimize-walk is the optimize pass, and occurs on the schedule operating/optimize-cadence declares (The optimize pass): walk asks the budget first — an exhausted one is optimize.refused and fails the step, and the pass waits for the next occurrence — then reads the machinery’s signals, asks the leader once per incarnation and is done at its org.reviewed row, which names the execution (task).
  • source-check is one check of an upstream a practice watches, and occurs on the schedule the practice declares (Upstream watches); it takes practice and name, and its parameters are the practice’s text. check reads the source and lands what Upstream watches says, or finds the reading it landed when it is performed again.

The ledger is the runtime. Every step is a few rows of the day’s work (step.*, attempt.*, work.settled, effect.*), each one row of the Ledger in memory beside the record (GH #1590).

The declared purpose is a proposal like any other. hale habitat graft proposes it in the record’s one seed call (host verb graph-ingest, a repository’s or an application’s: kind purpose, author org, for org, provenance: declared) with the Board’s Review under the group purpose, which hale dna review lists first and hale dna review purpose approve decides; the generated organization holds no Review of its own for it.

The graph read again (hale dna ingest [project] [--at <rev>]). The graft reads the repository once; a later commit is read again with the same ingest, the commit (HEAD by default) checked out beside the record under .hale/dna/ingest/ with the repository’s own directory name and removed after. The ingest also states a tool node per operation of every surface the repository serves (tool:<Surface>::<op>: its contract, the roles it requires, and its effect class, uncertain while a description says nothing of an operation’s effects). What differs from the graph the record states (its rows and its ratified graph proposals, latest body per id), over the kinds an ingest owns (purpose, axiom, process, seed, contract, noun, deployment, gate, document, witness, tool; meets, names, refers, constrains, runs, gates, witnesses, and an unfold between two of them), is filed as ONE graph proposal in the group ingest: added and changed nodes and edges with their bodies, then what is gone as graph.retired rows, edges before nodes. A graph.ingested row names the commit either way; the same difference at the same commit is proposed once, and nothing differing proposes nothing. The host files the proposal and its Review, and a graph.requested row, relayed like any request, has the running organization route and open that Review (graph.proposed), so the Board’s verdict settles it there; an organization started later finds it in the record. The proposal’s author is hale dna ingest, and a verdict the record holds before the organization opens the Review is weighed when it opens, in record order: the first decisive verdict decides, as it would have live. An attached application’s own re-ingest is not built yet.

hale dna definitions [project] [--json] lists the catalog dna/org/workflows.hl returns, built beside a one-line main as the model catalog’s probe is: every definition with its revision and title, each step with its key and store, what a newer revision supersedes, and what a definition is refused at admission for; --json is the catalog’s own document. The head serves the same catalog to the face’s definitions views (ops::ProjectWorkflowCatalog: what hale dna definitions --json reads, re-read when either file or the vendored catalog changes; the baseline alone for a project with no workflows.hl; unavailable, with the reason, when it does not build).

What a workflow execution leaves behind is written as a fact of the day’s work (dna/core/workflow_events.hl). Seven kinds carry an execution: workflow.admitted names the Task, the definition and revision, the inputs and their receipt, the bound recipe, the limits that were applied and what the expansion came to; step.registered names a step’s whole required set before any of it is dispatched; attempt.admitted carries one attempt’s id and the WorkRequest it was admitted with; attempt.outcome its disposition, result and evidence; and work.settled, step.completed / step.failed and workflow.settled settle each level to the one above it, a child workflow naming the parent Task and spawning step it answers. An admission also names the engine (wf1), and every fact a version. A decoder refuses a version it does not know, another engine’s admission or refusal, a fact that does not name the entity it is about, a fact that says nothing where a number belongs (an absent revision is not revision 0), and a fact whose own identities contradict each other: an attempt id that is not its Work and number, an attempt carrying a request for another Work or another attempt, a Work settling under a Step it is not part of or on another Work’s attempt, a step failing on a member it never had, a child naming its parent but not the step that spawned it, an admission deeper or wider than the limits it says were applied. A fact is read whole or not at all. The identities an attempt admission carries are written once and derived from the fact, so its id, its number and its embedded request cannot disagree.

step.activated and workflow.refused have codecs of their own, like the rest; a routing row is not a durable shape. A fact a transition committed carries the scope, key and proposal id it was committed under, appended with it, so the committed proposal can be reconstructed from the journal after a restart and compared against a later reuse of that id.

These kinds are the ledger’s, like every other operational kind.

An attempt’s id is its Work and its number (<work>/a<n>, numbered from 0 as §4 has it: the first attempt is a0 and a retry is a1), so a retry is a new id and a re-sent admission is the same one. A transition’s proposal id (§9 of dna/WORKFLOW-CONTRACT.md) is what the transition does, not a counter or a clock: the same id carrying the same scope, key, kind, entity and body is that transition proposed again, which the committer answers from its journal; the same id carrying anything else is a conflict, and transition_conflict names which part differs.

The state an execution is in is read from those facts and nothing else (dna/core/workflow_projection.hl). replay(projection, kind, entity, body) applies one row and answers one of three things: the fact was applied, it is a replay of what the projection already holds, or it is refused. Applying a fact asks nothing of the world: the entry point carries @no_syscall, which the compiler checks through everything it reaches, and the projection holds no journal, gateway or model to call. The contract guards syscall-class effects; having nothing to call is what rules out the rest.

The admitted recipe is the authority. An admission carries the whole bound tree under its Task, and every later fact is checked against it: a Step is one the recipe binds for that Task at that index; a registration names exactly the members the recipe bound under that Step, each by key, kind and entity, none missing and none invented; a Work’s retry allowance is the one bound for it; a child is admitted only as the Task its parent bound under that key, from a step that is registered and active, and answers only as that Task. A child’s admission carries the subtree its parent bound under it, exactly: the same nodes, each the same in every field, none added, none left out, none changed. It activates a binding; it never redefines the execution its parent admitted. An attempt asks for what the Work is: its request’s content — objective, context, bindings, output contract, data class, requires, target, cost ceiling — is the bound Work’s, on the first attempt and on every retry; who performs it is the application’s choice. A fact the recipe does not know is refused, however well-formed.

Every transition has its basis. A Work settles done only on a current attempt that recorded done; it settles failed only on a failed attempt with no allowance left, or once its Step has failed or its Task is cancelled, under which a member records its outcome and stops (the drain policy); it settles cancelled only under a cancelled Task (the fence). A retry is the next attempt, admitted only after the last one recorded a failure and only within the allowance. A Step activates only after the step before it completed, completes only when every member it registered has settled done, and fails only on a member that failed. A Task settles done only when every Step the recipe binds for it completed, and failed only after a Step failed and every responsibility it admitted has settled: the root settles last. Cancellation stops further admission in the cancelled Task and in everything under it — no attempt, no step, no child is admitted below a cancelled ancestor — while what was already admitted still records its outcome and settles under the fence. The walk to the root goes as deep as the caller’s max_depth admitted — nothing here has a ceiling of its own — and an ancestry that does not resolve refuses admission rather than permitting it. A failed ancestor is not a cancelled one: under the drain policy a member keeps its responsibility until its own outcome.

A fact is one row. The row’s entity is the fact’s own identity, and a step.completed row carries a completion. A fact whose identity is already recorded is a replay when it is the same bytes and a conflict otherwise, checked before the state, so a row from history is recognized as such after the state has moved past it. The join is by identity, never by counting: two answers from one member are one member. A Work’s result is the output of the attempt it settled on, readable only once it has settled; what an outstanding or superseded attempt said is never the Work’s result.

An application admits a defined workflow through the assembly (Dna.admit_workflow(WorkflowAsk)): which definition and revision, with what inputs, under an identity of the caller’s. The durable fact precedes everything. The id is minted, the definition expanded under the assembly’s admission_limits (its catalog holds what the application defined), and the admission — engine, bound recipe, limits, counts, the ask’s identity — appended with exact compare-and-append before anything could run; only then the legacy task.born summary, so the tooling of the day lists the Task. A refusal, by the catalog or by the record, is a workflow.refused row under the id it minted and nothing else: no summary, no child, no work request. Dna.run_workflow(WorkflowAsk) is the admission followed by the execution’s start (card 18, below).

The ask’s id is the admission’s identity. Asked again — relayed after a lost answer, retried — it is one execution, found in the record by that id. One decision, one revision: the record is read once, at a revision the body captures; whether the ask already landed and whether a candidate id is already an execution’s or a refusal’s are decided against that reading; and the append is exact at that same revision. An exact append protects the revision it was given, not a decision taken at an older one, so when the record has moved — stale, or the store’s unique claim on the id — the whole decision is taken again from a fresh reading. A taken id is skipped before any append. Two bodies contending for one Task id cannot both take it, and one ask admitted by another body between this body’s reading and its append is found on the next reading, not admitted twice.

Only the owner of the position admits (GH #664): the same rule ask applies, applied before an id is minted, so a body over a shared record that names no owner admits no workflow either — it answers the asker and writes nothing, the ask being the owner’s to admit. A refusal, whether the catalog’s or the store’s, is written as the versioned workflow.refused and the write is checked: when the record will not take even that, the answer says the refusal went unrecorded rather than presenting it as recorded. A refusal bound to a Task id carries the admission’s own guarantee — it is appended exactly at the revision that found the id free, and when the record has moved it is read again: the ask may have landed from another body, in which case that execution is the answer, and the id may be another execution’s now, in which case the refusal is not a Task’s at all. A refusal that reaches no Task — the id taken meanwhile, every id it would mint already taken, the record moved under it too often — is a fact of its own kind, workflow.ask_refused, under the ask’s own identity and a decision ordinal (<ask>#<n>), which can share no identity with any Task under any owner’s name; the projection holds it beside the executions, never as one. The same ask refused again for the same reason is the same decision, answered without another row; refused for another reason, it is the next ordinal. That decision, too, is one reading of the record: the decisions already taken on the ask are counted at a captured revision, the ordinal chosen against that count, and the append is exact at that revision, so another body deciding on the same ask meanwhile moves the record and the decision is taken again — its decision for the same reason becomes this body’s answer, and for another reason takes the ordinal ahead of this body’s. Nothing is appended as a changed body under a recorded identity, and a refusal never attaches to an execution another body admitted. A restart counts admitted and refused ids among those minted, so an id is never minted twice, and an admitted Task is never resumed as legacy edit work, with or without its summary row: the admission is the one positive discriminator, and its recovery belongs to the engine’s own cards.

The durable Work owner admits an attempt (attempt.admitted) and then asks for it to run through AttemptExecutor (dna/core/ workflow_execution.hl), which runs one admitted attempt once. Nothing runs before the admission is in the record as admitted: the attempt’s identity, its performer kind and every field of its request are checked against the recorded admission. An attempt whose outcome is recorded is not run again; the recorded outcome is the answer, claim or no claim. The decision to run is one reading of the record: refreshed, read at a captured revision — the admission, whether the attempt is settled, whether its execution is claimed — and the claim appended exactly at that revision, with the effect claim of the existing effect records, keyed by the attempt. When the record has moved the decision is taken again: an outcome recorded meanwhile is the answer and the performer is not called; a claim taken meanwhile is attached to; only a claim that lands is followed by a performer call. A request that arrives while the attempt is running attaches to its pending outcome instead of starting a second performer, and a claim the record refuses runs nothing. The performer the admission names runs once. Every reply, the one that comes back from the call and the one that comes later, is judged first for whose it is: it must be about this attempt (a reply that echoes an attempt_id must echo this one) and from that performer (the identity the kind selects), and a reply that is neither is not this attempt’s pending answer either — it is refused before anything is made of it, and the attempt stays outstanding under its claim. A reply that is this attempt’s and not terminal leaves it outstanding and records nothing; a terminal one must say something the outcome contract carries — done, failed, declined or timeout — or it is refused without a row and without closing the claim. The later reply is settled through the same path, under the same rules. An accepted outcome is persisted as attempt.outcome with an exact append before anything is answered, and a reply for an attempt whose outcome is already recorded changes nothing; an outcome the record will not take is reported unrecorded, never as done, and nothing advances on it.

The executor retries nothing (one retry owner: the Work’s lifecycle) and advances nothing (the Work settles on the outcome in a later card). It makes no claim that external effects happen once: a performer that acted and died before its outcome was saved is a later card’s. The WorkSystem retries nothing of its own (card 18): it routes each admitted attempt to the performer of the kind the admission names.

The spine’s whole API to a leg is three things (GH #946): the hat as a read, dna.attempt.claim, and dna.attempt.outcome. A leg holds nothing between tasks and has no database role; the owner still admits and settles. Once the organism has adopted the ledger the attempt kinds live there: the head reads them and writes a leg’s rows there under its role (HALE_DNA_MEMORY_DSN_HEAD; memory’s insert function is the gate, keyed by the request), and refuses the commands and the hat only when no memory is named to it.

  • The hat (GET …/dna/context?id=<work>; dna/operations/context.hl) is one Work’s context as structure, never a prompt: the position’s identity — the graph’s position:<name> id, from the performer kind the attempt admitted last names, or the kind the request selects before one is, or, for a Work whose requirement names position:<name> (ask-position), that position — and its charter (the record’s graph.node text for that id, "" until the graph names it); read under OIDC, it is the lease holder’s — the person the record maps the bearer to, whose attempt.claimed row is the Work’s current, unexpired, unreleased lease; any other caller is refused 403 lease_required; the practices ratified for the Work’s targets (targets: its path, its codebase’s languages, system:dna for an organization change, the position — The hat’s target set), from memory under the reader’s role, resolved to text with their ids; the knowledge bindings; the output contract and the hands it gives (dna/core/contracts.hl): the tools a performer may use, each {name, description, input_schema, effect, role, family} from the toolchain’s built-ins (dna/core/tools.hl), the tool grant (their names) and the validators among them, which the harness runs before handing the outcome back (a Patch gives read edit check test fmt patch, validated by check test fmt; an Assessment the read-only record_status record_history org_chart; a contract not yet described, none); the model the mapping resolves for the Work (dna/core/model_map.hl: the mode its hands make (think with no hands, tools with hands and no workspace hand, do with a workspace hand: an Assessment is a tools Work; a tools or do category is filled only by a model that returns tool calls), the size of its most specific model.rule, a task’s override only inside the permitted sizes, the model the model.category row names, the rule that decided it, or why no model fills the category); the data class; the history of the execution the Work belongs to, as facts (the head replays only that execution’s facts, its root task’s id and every id under it, child tasks included, and refuses context_read_limit past 512 facts, 256 bound nodes, 512 KiB of bodies or a 64 KiB body; the record’s length is no bound); the record head and memory’s projection watermark it was rendered at (-1 without memory, and practices_status says so); and the digest, sha256 over the canonical body with the digest itself left out. Rendered twice at one head it is one digest; a row that moves the head moves it. Two attempts of one task may wear different hats, correctly: what is forbidden is unrecorded variation, so the hat reads no clock, no environment value and no random id, and replay renders from the recorded hat, never the live graph. Rendering is a leg’s, and the renderer’s version is evidence of its own. The practices are structure — {id, name, text, kind, author} from one snapshot of memory, the watermark read in the same transaction as the ranked bound — never rendered lines split again; and every hat is composed by one function (hat_compose), whether built by the owner for an edit it asks (the attempt carries the hat’s digest as its context_digest; the practices reach the editor as its brief, the ask stays the ask) or by a head for a leg, is kept in memory by its digest (hats, insert if absent).
  • The tool loop (dna/core/legs/tool_loop.hl). A model performer whose hat carries hands and whose model.mode is tools or do offers the hands to the model as tools (ModelRequest.tools, the chat API’s function shape, with the turns since the prompt in ModelRequest.messages) and runs every tool_calls entry the reply asks for (ModelResult.tool_calls, each {id, name, arguments}) on the leg’s side, sending each result back as a tool message, until a reply carries no tool call. A do Work works in a scratch clone of the leg’s repository at HEAD; its answer stands once the hat’s validators pass on every touched directory holding .hl source (a changed file’s own directory: a nested seed is its own), a refusal being one more turn; a Patch is then one commit on the base, handed back as result_ref: commit:<sha> with the commit’s patch as the first receipt. A hand outside the hat’s tool_grant, or a path outside the worktree or into its .git (a .. or .git component in any case or spelling, a control character, or a symbolic link anywhere on the way), is refused as the call’s answer; a changed file git can only name quoted is refused by validate rather than passed unchecked. record_history reads the execution its id belongs to (/dna/workflows?id=<task>). A RecordedModel keys a loop’s request on its tools and turns too and keeps the tool calls it returned, so a replay follows the same turns. Each model call is a calls entry (its response digest covers its tool calls) and each tool call a row of one receipt, {"tool_calls": [{turn, name, arguments_digest, ok, output_digest, output_bytes, error}]}. The loop is bounded by max_turns (16) and the attempt’s allowance.
  • A person’s session runs the same hands one call at a time: hale dna work hand --attempt <a> --name <hand> --args <json> (and hale mcp’s hale_dna_hand) reads the attempt’s hat from the head and runs the named hand through the same bench, a file hand in a clone of the repository kept per attempt under .hale/dna/work/ with its base recorded beside it; submit --from-worktree runs the hat’s validators over the touched directories, refuses on a failure, and otherwise submits one commit on that base as result_ref: commit:<sha> with its patch as the first receipt, then removes the kept clone.
  • The claim is taken at the head. The filter names the performer kind and identity, the capabilities the leg has, the data classes it may see, the owners it works for and a TTL. The identity is a position (position:<name>, #<n> for one worker of several) the record knows, and one the principal’s person holds (GH #1162): by the record’s live holds edges — a graph.edge row, or one a ratified graph proposal carries (hale dna fill) — the edge, its position and its holder not retired, memory not having refused it (hold.refused), the person not retired; or, under a record that declares dna.trust = local, any position. The socket’s roles read the same edges (HoldsReader): a person holds a position in their own name, or as a member of an organization holding it (GH #1123, as the route reads a firm-held position); a membership alone is no seat, so it grants neither position nor any other role. Otherwise the claim is refused naming the person and the position, and so is a renewal once the person no longer holds it (a release stays open) and friction filed as a position the person does not hold. Without local trust the organization’s own kind positions (position:agent, leader, …) are held only by a holds edge naming them. The head picks the first admitted attempt of that kind that is outstanding — asked to run (effect.requested attempt:<id>), no outcome — was relayed to legs (effect.relayed attempt:<id>: the owner records it when a leg relay answered the attempt pending, and only then — an edit the organism’s editor holds, or a case a person does, is outstanding the same way and is never a leg’s, GH #1158; a row the record would not take is recorded when the next ask redelivers the attempt, and a leg is refused it until then), fits the filter, and is not held by another leg under a live lease; takes memory’s claim attempt:<id> for the performer with the TTL under the head’s role (GH #1026: the store decides between two legs racing; without memory nothing races); and appends attempt.claimed naming the lease and the principal that took it. The claim is not fenced on the record head the leg read — memory’s conditional insert is the race, so a leg a row behind is not turned away with stale_subject — and a claim memory took that ends in no row is given back. A leg that names no data class is handed nothing: no class is no class. The receipt returns the task and the lease as a value. Nothing fitting, or everything held, is a refusal: a receipt with the reason — which names the class or the owner that stood in the way — and no row, since legs ask often and an empty answer is not a fact.
  • The outcome comes back under the lease, with the hat the leg wore (its digest, the head and watermark it was rendered at, the digest of what was rendered, the renderer’s version — the five together or none). The head refuses — a receipt with the reason, no row — an attempt never admitted or never asked to run, one already settled or already handed back (duplicate: one outcome under one lease), a lease that is not this holder’s at this token now, by the recorded claim and, with memory, by memory’s (stale), and an outcome from a principal other than the one that took the lease; files the receipts (a protected body in memory alone, the rest as git receipts); and appends attempt.outcome_requested. A node relays the row onto the nerves (WorkSubmit, dna.work.submit) until the record answers it. The owner checks the same lease, journals the calls the leg made as model.called rows on the attempt id — tokens per task hold out of process — and settles the attempt through the path every reply takes: attempt.outcome by exact append, naming the request it answers and carrying result_ref and the hat, the Work and the task after it; a submission it will not settle — an attempt never relayed to a leg (no effect.relayed), a stale lease, a second outcome after settlement — is an attempt.outcome_refused row naming why and the request, which answers it, so the relay stops. A Patch Work’s outcome (the request’s output_contract is Patch, disposition done) is a candidate change, and the owner makes it one (GH #1156): the outcome names the leg’s commit (result_ref: commit:<sha>) and files that commit’s own patch among its receipts (git format-patch, whose From <sha> line names it; the legs’ git hand exports it, hands.git.patch). The owner mints a Mutation bound to the attempt (mutation.requested carrying the binding, the leg, its commit and the receipt), opens its gateway worktree at the genome’s head, applies the patch there — only under the seed the Task’s class edits, every path counted (a rename’s source too), and never the organization’s own seed unless the class is organization — and commits, verifies and opens the Review exactly as for the editor’s candidate, the leg its author. The patch is the candidate: what it carries is what is verified and reviewed, and verification runs the candidate’s own tests on the owner, as it does the editor’s. The attempt settles done on the prepared candidate (<m>: candidate prepared (<disposition>) from commit:<sha>), or failed with the Mutation’s failure — no commit named, no receipt that is its patch, a patch that does not apply at the head, one that edits outside the seed, a candidate denied, a Review that could not open — so the Work’s allowance decides what follows. A submission relayed again finds the Mutation bound to the attempt and makes no second; one found still in flight (the owner restarted while making it) is failed as interrupted. The outcome names the bound Mutation’s commit, whichever lease handed it back. A candidate whose seed holds no Hale source at the base or at the candidate is not denied for a check with nothing to judge (hale_source false in its evidence) and is never staged or released by a grant: it is reviewed before anything applies. A LegRelay performer answers pending for the kinds a program hands to legs, so the attempt waits for one instead of running in process (stage 1 of the legs; the in-process performers go as each stage lands), and RelayReplay is its reconciler: an owner that restarts with a leg’s attempt in flight leaves it to its claim — nothing is asked of it while the lease is live — and asks it again once the lease expires with no outcome (effect.redelivered, once per lease), when a leg claims it anew.
  • The spend (GH #1131). Before its first model call a leg asks the spine, which holds the one budget (Models), for its attempt’s spend: dna.attempt.allowance under its lease (the attempt, the holder, the token), which the head admits as it admits a renewal — the same checks — and writes as attempt.allowance_requested (holder, token, request, at, and the command). A node relays it onto the nerves (WorkAllowanceAsk, dna.work.allowance) until the record answers it. The owner checks the lease as of the head’s admission (this holder, this token, live, the attempt relayed to a leg and unsettled) and asks the one gate with the Work’s own cost ceiling: attempt.allowance_granted (allowance_micros, ceiling_micros, spent_micros, window_micros, window, holder, token, request, at) or attempt.allowance_refused (why, …), naming the request. The ask reads back requested until then, granted with allowance_micros, or refused with the reason. The model performer asks before its first call and waits up to 60 s for the answer; refused, or unanswered, it makes no call and hands back declined, naming why; granted, it makes no call — a retry included — whose cost is known before it is sent and is past what is left of the allowance (ModelBackend.price_micros: a price per call, a recorded answer; the attempt is declined), nor any once its calls have spent it. A cost learned only from the answer (a price per token, a harness’s summary) can cross the allowance: that call is made whole, so a leg passes its grant by at most one call. At settle the owner takes an outcome whose calls cost more than the lease was granted — anything, when it asked for nothing — as the attempt failed, naming the overrun (no Patch Work’s candidate is made from it), so the Work’s allowance of attempts bounds the repeats; the calls are journaled all the same, for what was spent is spent.
  • The verbs (hale dna work, GH #946 slice 4; dna/core/legs, vendored as vendor/dna/legs) are a leg as API clients: login asks the head (GET /.well-known/hale-dna, unauthenticated under OIDC: principal, issuer, client) who signs its people in, and where that is the local stub (a loopback issuer, client dna-local) mints an ID token for the subject the record’s dna.oidc.member maps to --as <person>, the client secret read from the vault entry oidc-client-dna-local and never printed, and prints export HALE_DNA_ID_TOKEN=…; for any other issuer it prints the sign-in URL and exits 1; next is dna.attempt.claim for a position (--as position:<name>, the graph’s id, never a free string; a claim matches attempts by performer kind, --kind, and a position the graph states that names no kind of its own claims as an agent and also takes the Works routed to it, ask-position; a refusal names the kinds outstanding and the flag), brief the hat read — or rendered in the leg, text, prompt or agent, recording the hat digest, the digest of what was rendered and the renderer’s version (legs-render/2) — renew is dna.attempt.renew (the lease extended, the token kept: another attempt.claimed row), allowance is dna.attempt.allowance read back until the spine answers it (--wait, 60 s: what an external harness asks before its first model call), submit is dna.attempt.outcome, settle that command read back, release is dna.attempt.release (attempt.released; the attempt is another leg’s to claim), friction is dna.friction.file (friction.filed, a fact nobody admits), and hand runs one of the hat’s hands on this machine (A person’s session, above). Each prints one JSON object and holds nothing afterwards; submit and release are keyed on the lease, so run twice they are one act; next mints a fresh id per call (a claim by its holder renews); renew is counted by the caller. A leg’s identity is a position the record knows — position:<name> from the graph (a graph.node row, or a position a ratified graph proposal states: a hole hale habitat graft proposed, a position hale dna position open opened; read through the record the command is admitted over, and ended by a reviewed retirement in a later ratified proposal) or the organization’s own, / allowed in the name as the graph spells a process’s positions (position:api/dev), with #<n> for one worker of several — never free text, at the head as at the verb. While an outcome under a lease awaits the owner the lease is neither renewed nor given back, and the attempt awaits no other leg; without memory the record numbers the leases (the next claim row is the next token). Exit codes: 0 admitted, 1 refused (the receipt printed), unreachable, or a run that ends unsettled or unresolved; 2 usage, or a catalog with a performer declaring no effect class. run is one cycle through the project’s performers (dna/org/work.hl, generated by the graft): a person’s leg renders the brief and leaves the outcome to submit; a deterministic performer wins for the work kinds it takes and settles; the model performer (legs::ModelPerformer) is the catalog’s router behind the performer interface, out of process, one task per run: the brief rendered as a prompt, the answer the result, and every call the router answered handed back as evidence (the adapter that answered, backend, reported model, tokens, cost, wall time under the prompt and context digests), which the owner journals as model.called rows on the attempt through the encoder its in-process calls take; a rate-limited call is backed off inside the attempt (bounded, doubling, the lease held) and each wait is a row of evidence of its own, so the attempt’s cost in time is in the record beside its cost in tokens; the lease is renewed before each wait. The model is sent the render alone (the hat is structure), and the hat’s digest is the context digest on every row. --performer names the performer instead of the catalog’s choice; a performer never answers a kind it does not take, forced or not. Every performer declares an effect class — effect_free (answered, touched nothing), idempotent (may be run again to the same end) or uncertain (an agent’s seat with tools: may have acted once already) — with no default: a catalog with a performer declaring none is refused by the verbs that claim or hand back (exit 2) and read by the rest. The class rides on the claim (effect_class), which the head admits only if the Work admits the class — the class decides what a failed settle becomes, never what a performer may do, so a Work that is answered admits every class and an edit admits no effect_free; the hat says so in effects — is recorded on attempt.claimed (a renewal carries it on), must be named again by an outcome under the lease, and is evidence on the outcome (effect_class on attempt.outcome_requested). A settle that fails (the outcome cannot be handed back or read back, was refused, or the owner refused it) on an effect_free or idempotent performer is unsettled: the lease is given back when still the leg’s, and the loop runs the slot again after a rest, performing anew. On an uncertain performer nothing is retried by a program: the leg marks the attempt unresolved at the head — an outcome of disposition unresolved, its calls as evidence, admitted past the lease’s end — which lands attempt.unresolved and effect.result unknown on the attempt; friction names the attempt, the holder, the lease and the resolution; the leg answers unresolved (exit 1) and the loop stops that slot. A marked attempt awaits no leg (its holder included) until a person resolves it (hale dna effect resolve attempt:<id> --outcome ok|failed), on which the owner settles it. An uncertain claim whose lease lapses with no outcome is never asked again either: the owner records effect.result unknown itself; such a claim’s lease is an hour by default. The model leg judges a refusal by whether the backend may have acted (ModelResult.made, false only for a refusal before anything was sent or run): behind an uncertain performer one that may have is unresolved, never retried or failed over; behind the other two a failed outcome; a rate-limited call is asked again only when it was never made. loop --parallel N is a worker: N supervised child processes, each run as its own holder position:<name>#<n> (two workers of one position never share a lease), each answer one JSON line as the child ends, started again at once after a task and after a per-slot doubling backoff when idle (nothing claimed, a person’s, given back); --once runs each once. The leg is its program’s main locus: SIGTERM/SIGINT drains the loop, which ends its children (TERM, then KILL after a grace) and reaps them before it ends; --drain (a marker beside the leg) ends a running loop once its children have. Nothing is held between tasks; a child ended mid-task leaves a lease that expires. A project initialised with no backend configured gets NoModel: its agent Works are a person’s. The catalog and the tape (models.hl, tape.hl) stay in the core while the editor and leader call them in process. A performer is handed the brief and the hands as interfaces — git in scratch, the forge (it decides, a leg never merges; the organism’s forge allocation answers “open a review”, AllocatedForge, and no hand names a forge by kind), the toolchain; deploy and the heart’s API refuse, naming #987 — and answers a performance (a result struct until #732). The verb is the project’s own program, built beside the vendored seed under .hale/dna/legs by the host; hale mcp exposes it as hale_dna_work, and a Work’s hands as hale_dna_hand. The commands go over the head’s api socket (GH #1104 piece 5): each verb a call on a member of the head’s surface — Commands::claim, Commands::renew, Commands::outcome, Commands::release_attempt, Commands::file_friction, requiring position; Commands::lookup for settle — with the receipt on the value channel as the record wrote it; the reads (the record head, the hat, the socket’s path in /capabilities) stay HTTP. The principal is the peer’s credentials mapped to a person by the record (dna.unix.member; a record that declares dna.trust = local, as hale habitat graft declares for the one it makes, is that person’s — they hold every position — and one that declares nothing, or another trust, leaves it to the graph’s holds edges); the lease’s holder is the position the leg works as (--as, #<n> for a worker, --worker <n>), never a free flag; a peer whose person holds no position is told so at attach, and a verb outside the caller’s slice is unknown, exit 1. The leg takes the socket from --socket, HALE_DNA_SOCKET, or the head’s capabilities.

A command is one act per request id: sent again under the same id, a command that landed is found by it, before anything else is checked, and answered with its receipt. A head answers snapshot_changed when its record moved while it read it, and command_busy when the record moved through every read its admission allows itself. Neither admits the command, though a pass may have left what the command’s own retry takes back or finds (a claim is given back; a renewal extends the same lease; receipts are filed by their digest). So a leg asks the same command again, 200ms apart, under the same id, until ten seconds have passed, whatever the command, and reads the hat again the same way. A record several legs and the owner write moves often. command_outcome_unknown is not asked again: the leg reads the command back by its id. A hat it still cannot read gives the lease back.

One active step and its leaf Works are resident loci (dna/core/workflow_runtime.hl), in the shape the lifetime proof established: a StepRun is an accepted child no parent releases, born from its owner’s handler, alive after its run() and every handler return, advancing from its own handlers keyed by its id, ending with terminate;. It owns its WorkRuns the same way, one per leaf, born from the handler that heard the activation land. Neither holds a journal: each proposes every transition to the one WorkflowRuntime over the bus — TransitionProposed, keyed by scope — holds one proposal at a time, and acts only on that proposal’s answer, and only on ok. A refusal dispatches nothing.

WorkflowRuntime is the committer, and the executor, over one journal. A proposal is decided at one reading: the runtime refreshes, applies what the record holds beyond what its projection has seen and remembers each committed proposal’s id and revision, and that reading’s revision is the one the decision is taken at and the one the append is exact at — never a second look. A proposal already committed is answered again from what was committed, if it is the same proposal; the same id carrying another transition is a conflict, refused with nothing appended. Otherwise the transition is validated against the projection without touching it (a dry run of card 06’s rules), appended exactly at that revision, and applied for real once the append has landed. When the record has moved under the decision — another runtime committed the same transition, or anything else — the append is stale and the decision is taken again against the new state: the same transition committed meanwhile is answered from its row, and one row stands. An append the record refuses answers a refusal and moves nothing, so the same transition proposed again is decided against the state as it is. A proposal the state refuses lands nothing: the record is not the place a transition is found invalid. An answer says who refused (basis: state, record or conflict), because the residents treat them differently. The body of a proposal carries the reference the row will be committed under, and it must be the proposal’s own scope, key and id, whole, or the proposal is refused as a conflict with itself before the state sees it; a committed proposal is rebuilt from its row and compared field by field with one sent again — the same, whole, is a replay answered from the row; anything else under that id, another key included, is a conflict. A Work asks the runtime to run its admitted attempt by id (AttemptRequested); the runtime reads the admission from the record and runs it through the one-attempt path above, and every reply — the one that came back from the call and the one that came later, through settle — reaches the Work the same way (AttemptReplied, keyed by the Work), so an immediate and a delayed reply are one reducer and one completion path.

The barrier holds as the contract has it. A StepRun registers its whole member set (step.registered), activates (step.activated), and only then births its leaves; a member answers once, by its key (MemberSettled, keyed by the step): a member that answered again and an answer from no member of the step change nothing. Two required leaves complete the step in either order; one immediate reply and one delayed one leave it waiting, alive, with the outstanding Work reachable. A member that failed fails the step at once (step.failed, naming the member) and a step whose every member settled done completes it (step.completed); either publishes one terminal outcome (StepSettled, keyed by the step) and settles the step. A member is its key and the Work bound under it: a settlement naming a required key for another Work is no member’s and changes nothing. After that a sibling’s later reply is recorded — its attempt’s outcome and its Work’s settlement land as facts — and reopens nothing; the StepRun stays until every member it dispatched has settled, then reclaims itself, and a WorkRun reclaims itself once its settlement is answered. A leaf with allowance left is retried before it fails: each attempt is admitted as a proposal of its own, and a retry the record will not take — the step failed meanwhile — settles the Work failed on the attempt that failed, the drain. A refusal by the record is no outcome: a Work or a Step whose append the record refused holds its transition, tells nobody it settled, reclaims nothing, and proposes the same transition again when the record resumes (RecordResumed, published by whoever knows the record is writable again) or, for a Work, when its attempt’s reply reaches it again; a member has answered its step only once its settlement is in the record. A Work whose first admission the state refused has nothing admitted, no claim and no effect to await: under a Step that has durably settled it retires (MemberRetired, keyed by the step), which is not a settlement — it answers nothing — and the step counts it toward its drain and reclaims itself once every admitted responsibility settled; a first admission the record refused is proposed again when the step settles, so the state says whether it can still be admitted. One the state refuses while its step runs stays, unattempted and reachable, until it is fenced. Nothing here orders steps, spawns child workflows or survives a restart; those are later cards.

One admitted execution is a WorkflowRun, resident like its steps: it holds every leaf of the execution (copied at birth), births step 0, and births step i+1 only from the handler that hears step i has drained — StepDrained, keyed by the task, which a StepRun publishes as it leaves, once every member it admitted has settled or retired — behind that step’s committed completion. No ordering lives in the workflow that the record does not enforce: the projection refuses a step’s activation while the step before has not completed, and the activation’s own identity (<step>@activated) is one row at the committer, so a duplicate completion cannot start the next step twice; a drain message for a step the workflow has advanced past, or for none it has active, changes nothing. When the last step drained completed the workflow proposes workflow.settled done; a failed step drains first, then the workflow proposes workflow.settled failed, and later steps are never born. Both are proposals like any other: decided at one reading, held when the record refuses them, proposed again on RecordResumed.

A cancellation is asked of a workflow by task (WorkflowCancelRequested) and is a proposal too: workflow.settled cancelled, which the state takes at any point before the execution settled and refuses after. Once it landed the workflow says so (WorkflowSettled, keyed by the task) and births nothing further; its active step hears it and fences what it dispatched (StepFenced, keyed by the step) — a step with no outcome yet is cancelled (no row of its own; the task’s row is its basis), and a step that had failed and is draining keeps its failure: the fence is not a second outcome. The fence is the one message a Work acts on for a cancellation (a cancelled step settlement is for the owner; a Work ignores it), so a Work that retires or settles on it acts, and ends, exactly once. Its Works hear the fence: an admitted attempt still awaiting its reply settles cancelled (the projection allows that settlement only under a cancelled task); a first admission the record refused is proposed again and, refused by the state, retires; one the state had refused retires. The fence itself is in the record, not in a notification: the executor reads the execution’s durable cancellation — the task’s workflow.settled, or an ancestor’s, walked through the admissions — at the same reading its execution claim is exact at, and an attempt not yet claimed under a cancelled execution does not run, however the notification lagged, while one claimed before the cancellation still records its outcome. Nothing external is undone — a reply that comes later is recorded as the attempt’s outcome and reopens nothing. The workflow leaves once the fenced step has drained. An activation the record refuses dispatches nothing: the step holds it, no leaf is born, and the record’s resumption lands it once. Nothing here spawns child workflows or survives a restart; those are later cards.

One admitted Task is a TaskRun, resident like the rest: it holds the bound recipe, decoded into its own catalog at birth, and from it the leaves and children of every step, and births the one WorkflowRun that runs them. The executions owner (Executions, the Task owner the contract names, one per scope) accepts every TaskRun and never releases one: a root asked of it (ExecutionAsked) with its admission already in the record, and a child asked by its parent Task. One execution is one Task: a Task asked twice is born once. The same engine runs every level; there is no other path for a child.

A step that dispatches a child member asks its own Task (ChildRequested, keyed by the parent task), which holds the recipe the child is bound in; the Task cuts the subtree the recipe bound under that child, exactly, and asks the owner to admit and run it (ChildAdmitRequested, keyed by scope). A request that names a child the recipe does not bind under that step and key is ignored. The child proposes its own admission first — workflow.admitted with the subtree, its parent, its spawning step and its member key, which the projection accepts only for what the parent bound, from a step that is registered, active and unsettled — and runs only once that landed; an admission the record refused is held and proposed again when the record resumes, or when the spawning step settles or is fenced — so the state, not the child, says whether it can still be admitted, and under a failed step it retires on that answer; one the state refused retires under the settled or fenced spawning step, as a leaf does. A fence that reaches a child while its admission answer is out is kept, with its reason, and applied when the answer comes: an admission that landed starts and is cancelled at once through its own settlement. A spawning step that settles or is fenced while the answer is out owes the child one re-decision, taken on that answer if it is a refusal by the record: the admission is proposed again once, and the state says — neither ordering of the two messages is assumed, and a record that keeps refusing is not spun against. A child that settled tells the step that spawned it once (ChildSettled, keyed by that step) after its row landed, and the step counts the exact child bound under the key: a settlement for another Task under the key, for a key that is no child of the step, or delivered again changes nothing; a failed child fails the step at once, as a failed leaf does. The Task leaves once its workflow has (WorkflowLeft), and tells the step that spawned it so (ChildLeft, keyed by that step): the step’s drain waits for a child that settled to have left, not for its settlement, so no ancestor reclaims itself ahead of a descendant still holding something — a Work whose settlement the record refused, say. A leave from a child that has not answered is nobody’s: a leave follows a settlement. A step fenced by its Task’s cancellation fences a running child the same way: the child’s Task asks its own workflow to cancel, which settles cancelled through the same proposal, so the fence reaches every level below and each level drains and reclaims from the leaves upward.

Restore is not a mode. An execution asked of the executions owner over the record a crash left — or fresh: the same ask — first asks the runtime what the record holds about it (ExecutionStateAsked, answered from the projection): one the record has settled is over — its settlement is announced and the workflow leaves, birthing nothing — except a cancelled one whose admitted Works have not settled: cancelled is not cancelled-and-drained, and the current step is born fenced, so those Works settle cancelled, the step drains, and the workflow leaves behind it. A cancellation heard while the record is being asked waits for the answer: an execution the record has settled is not cancelled, and one still open is cancelled first — and then, as whenever a cancellation lands with no step active, the record is asked again what it still holds admitted and unsettled, and that is drained through the current step, born fenced, before the workflow leaves. One still open proposes its transitions exactly as a fresh one does, and the committer answers what the record already holds as a replay, so every Task, Step, Work and Attempt id a restored incarnation uses is the record’s, never minted again — a completed record rebooted gains no row and runs nothing, and a cancelled one ends the restored execution before anything is born.

The one thing a restart adds is redelivery, and the runtime derives it from its own incarnation, never from a request: a request for an attempt names the attempt and the Work it is an attempt of, and the runtime knows which attempts it claimed itself and which it already redelivered. Every request is decided against the record at one reading: an attempt whose outcome is recorded is answered from it and never runs again, and a claim the dead incarnation left open under it is closed with it, durably, before the Work is answered — on every path that answers from a recorded outcome: a request that finds it, one that runs into it on its own reading after seeing the attempt unclaimed, one that attaches to a running attempt and finds it, and a reply — and a close the record refuses leaves the Work holding its responsibility, told so, until a later request or reply closes it: a Work whose request was refused without an outcome asks again when the record resumes; one never claimed runs through the ordinary path, which claims first; one claimed by this incarnation, or redelivered by it, is running, and the request attaches, so a duplicate dispatch starts no second invocation; one claimed and never answered by a claimant this incarnation is not was claimed by an incarnation nobody will hear from again — unless its effect resulted unknown (a restart the existing recovery reconciled no further), which stays visibly unresolved, nothing running on it until card 12c’s reconciliation says what happened. Otherwise its dispatch is issued again with the same attempt id and no new claim — a transport redelivery, not a new attempt — and that decision is a row (effect.redelivered) appended exactly at the reading that saw the claim, no outcome and no fence, so an outcome or a cancellation landing meanwhile makes it stale and the decision is taken again. A reply from the old process for the still-current attempt is accepted and recorded like any reply, and whichever reply arrives second changes nothing. The admission’s performer kind is durable: the restored incarnation runs the attempt on that kind. What an invocation that died did before it died is a later card’s, as is cross-process delivery.

A restart rebuilds an execution from the record alone. The executions owner is asked with nothing but the task’s id and which performer kind runs each leaf (ExecutionAsked); it asks the runtime for the admission as the record holds it (AdmissionAsked), and the runtime answers from the row, read whole, and from its projection’s word that the row was admitted (AdmissionAnswered): the definition and the bound revision, the recipe, the limits. The Task is born from that answer, so an execution restored after a restart runs what it was admitted with — its bound recipe — whatever the catalog offers now; a definition changed to a new revision between shutdown and restart binds new admissions and touches no old execution. A task the record never admitted, one admitted with a recipe that cannot be read, or one whose admission the projection refused is not resumed: the owner says so (ExecutionRefused, with why) and nothing is born or invented for it. From there the residents re-propose through the transition code live execution uses, and the record decides what replays and what runs: a step the record completed is not reborn and the next step starts once; a child the record settled announces its settlement and leaves, and its parent’s step completes on it; a member still waiting is recovered where the record left it — its admitted attempt redelivered, under card 12c’s reconciliation — and completed members do not rerun; the grandchild settles, then the child, then the root. Only unfinished responsibilities exist in memory after a restart, and every id and every required-member set is the record’s.

The admission recovered is the one the projection accepted — the body it recorded when it applied the row — never the last row in the record that names the task: a later row the projection refused (a re-admission under another revision, a row whose body names another task) is not the execution, and a task the projection holds no admission of is refused with the projection’s reason for the last row it refused, from a dry replay of that row. A child of another Task is not recovered by asking for it: the answer says whose child it is, under which step and key, and the child comes back through its parent, in its bound place. The workflow identity an execution runs under is the one its recipe bound, read from the record and never re-derived; the projection refuses an admission whose recipe binds the task under any workflow but <task>/wf<revision> (§4 of the contract), so the recipe and the identity never say different things. Every question the owner puts to the record carries an identity of its own (AdmissionAsked.ask_id), held outstanding until answered; an answer is taken only to a question this owner holds outstanding, for the task it asked about, and only once — an answer nobody asked for, however positive, and an answer under a question already answered, are strays and birth nothing. A leaf asks the record what it holds about it before proposing anything (WorkStateAsked / WorkStateAnswered): a settled Work has answered and leaves; a Work with an admitted attempt resumes that attempt under its number, id and performer kind as the record has them — the kinds a restart is asked with bind only attempts not yet admitted — and a Work never attempted admits its first attempt under the asked kind. The state question carries the Work’s scope and an identity of its own (WorkStateAsked.ask_id), held outstanding until answered; the answer carries both back, and the Work takes only the answer to its one outstanding question — its scope, its identity — once: a same-named Work in another scope’s record (ids are minted within a record) is not this Work, and a stale or unsolicited answer is no state. A fence heard while the state question is outstanding is remembered and decided on after the answer: an admitted attempt is re-proposed and, admitted under the fence, settles cancelled; a Work never attempted proposes its first admission, which the state refuses under the cancelled Task, and retires. No settlement ever names no attempt.

Workflow execution: uncertain external effects

Section titled “Workflow execution: uncertain external effects”

An attempt’s performer may have acted before the outcome was saved: the incarnation claimed the attempt, invoked the adapter, and died before the outcome row landed. Recovery reconciles before it decides on a redelivery. The work system carries an effect adapter’s reconciliation beside each of its performers, one per kind as the performers are (Reconciler, default NoEvidence): the adapter of the admitted kind is asked, for the identity that kind selects, about an attempt whose claim an incarnation that died left open, and it says whether it can tell, whether it acted, what it recorded when it did, or that acting again is documented safe. Its answer names the kind and identity it speaks for, and evidence that does not name the admitted performer’s is no evidence about the attempt — another adapter’s attestation that nothing acted, or its documented idempotence, does not authorize replaying this performer. An adapter that acted hands back the outcome it recorded, as it recorded it — judged for whose it is exactly as any reply is, the performer it names and the attempt it names if it names one, never relabelled — and that is the attempt’s outcome, recorded and its claim closed; nothing is performed twice. An adapter that attests it never acted, or whose effect is documented idempotent, has the dispatch issued again (effect.redelivered, saying which). An adapter that cannot say leaves the claim resulted unknown, durably and visibly (effect.result unknown (reconciled after a restart: …), the existing rule-3 mark, appended exactly at the reading and under the fence; the record moving under that append — an outcome, a resolution, a takeover, or something unrelated — has the decision taken again at the new reading, so a resolution that landed meanwhile is the outcome and an unrelated move just lands the unknown row after it), and the attempt waits — asked again it still waits, nothing is replayed blind — until a person resolves it (hale dna effect resolve <key> --outcome ok|failed); then the resolution is the attempt’s outcome, recorded so the Work settles on it. The assembly’s own restart rule, which results the other effect families by evidence or marks them unknown, leaves an attempt’s claim to the runtime’s adapter. One flat leaf; no exactly-once external execution is promised and no compensation is invented: what is promised is that recovery never performs an effect twice on its own say-so, and never pretends to know what it cannot.

Workflow execution: retries under a restart, and the fence

Section titled “Workflow execution: retries under a restart, and the fence”

A retry is the next numbered attempt of a Work, and it is admitted only after the record holds the outcome that permits it — a failed attempt with allowance left, the allowance the recipe bound — as card 06’s rules have it; its request and performer kind are the admission’s, durable, so nothing is planned again for it. Under a restart the retry is the record’s: a reboot proposes attempt zero, is answered from its recorded failure, proposes attempt one, which the record already holds and replays, and asks for it — redelivered if the incarnation that claimed it died — and never mints attempt two. A late success for attempt zero is answered from its recorded failure and changes nothing; the Work’s current attempt is attempt one. An admission the record refuses invokes no performer and spends no retry: the Work holds it, with its number, and proposes it again when the record resumes.

The runtime runs under a lease that is rows of its own record (lease.taken, carrying the fencing token — a per-key epoch allocated under the exact append that takes it — the holder and the expiry; lease.renewed, which moves the expiry and keeps the token; lease.released; each a JSON body, so a holder reads back as it was written and an empty holder is refused): a key, a holder, the token the holder was granted, and the clock it judges the lease by. The token is the row’s own, never its position: a routed reader that starts fresh reads the record’s rows before the ledger’s and finds the same lease a reader that was live found, and the lease is the take with the highest token, renewed and released by rows that name it. Ownership is read at the revision a write is exact at, so the fence is atomic with the write by construction: a takeover is a row, it moves the revision, and a stale holder’s exact append fails and is decided again — at the new revision the record names the new holder, and the write is fenced. That holds for every write the runtime makes: a transition it commits, a claim, a redelivery, an outcome it records, a claim it closes. A holder whose lease expired — unclaimed, re-taken by another, or re-taken under its own name by a restarted incarnation with a new token — commits nothing, however live it feels, and answers fenced, which a resident holds as it holds a refusal by the record and proposes again once the record resumes under a live token; a request that only attaches to a running attempt writes nothing and is not fenced. Two takes of a free lease at once land one: the other is stale, decided again and refused. The leases (MemoryLeases, and MemLeases for fixtures) stay for what they hold — a body, a mutation — where the operation they fence is not an append to this record. A runtime with no lease key is unfenced: a standalone runtime over a memory record.

An edit leaf of a workflow execution is performed by the assembly — its editor, its gateway, its record — never by a router performer. The engine reaches it through a fifth performer kind, edit: the EditRelay publishes the attempt (EditRequested, keyed by the runtime’s scope, which is the organism’s id) and answers pending; the assembly performs or waits, and reports the outcome for that exact attempt (AttemptReported), which the runtime settles as it settles a late reply — judged, recorded, answered to its Work and only its Work; a report whose outcome names another attempt than the report does, or none, is nobody’s and settles nothing. The Work settles as any Work does, its step counts it, and the workflow settles the root: no Work of an admitted execution settles its Task, and a Mutation’s review outcome never settles it either — one root terminal writer.

Two output contracts, never reinterpreted as each other. Patch — the default for an edit leaf — is a candidate prepared for review: the attempt is performed once, the Mutation it produces is bound to it (card 14), and the Work is done when the candidate is in review, escalated or released, failed when preparation failed before a candidate; a later verdict changes nothing about it. Applied (or Approved) is the candidate approved and applied: the Work performs nothing and waits on one exact candidate: the Work its request names in target, as <step>/<key> within the workflow (s0/e) or a whole Work id, resolved to the Mutation bound (card 14) to the attempt that Work’s settlement accepted — a first attempt that failed to prepare and was retried is not it. A Work naming none fails at once, with why, since the dependency is explicit and never a Task-wide or Work-wide guess; one whose producer settled other than done fails, there being no candidate. The producer’s settlement and its candidate’s review and application are prerequisites that land in either order, and each landing is an occasion to look again: a waiting attempt is evaluated afresh — by the same resolution — when any Work of the engine ends (RunLives), when a review settles, when the record resumes, on redrive, and whenever the assembly is asked; it is answered once, when its state is an outcome. The same resolution serves an ask again after a restart, which waits once. It is answered from that candidate’s durable state alone: rejected or sent back is failed; approved and applied (or retained) is done; approved and rolled back is failed; approved but not applied — the apply gate refused, an application pending or unresolved — is no outcome yet, and the Work keeps waiting, to be answered when the application lands, on a first approval or on an approval again. An attempt asked again — a redelivery after a restart — is answered from the Mutation bound to it and edits nothing twice, so the edit kind’s reconciliation is a replay. A request row the record refuses starts nothing: the attempt is held, and redrive puts it again. A published report is not a durable one: the assembly keeps every report until the record is seen to hold the attempt’s outcome, and puts a kept one again when the record resumes or on redrive — the validated outcome re-persisted, never the edit performed again.

A human leaf of a workflow execution is a case for a person. The engine reaches the assembly through the human performer kind: the HumanRelay publishes the attempt (CaseRequested, keyed by the runtime’s scope, the organism’s id) and answers pending, and the attempt stays out until the person reports the case done. The assembly admits the case as its own handed Task, <task>.s<i>.<key> — the Work’s step index and member key under its Task, the child-Task form of §4 — stable across redeliveries and naming its exact parent Work and attempt by construction. The admission (case.admitted, versioned and self-sufficient: parent Task, Work and attempt, objective, assignee, obligation, acceptance practice, required evidence, who said so) lands first, exact at the revision read, and only then the rows the tooling of the day reads — task.born <case>, then task.handed <case> (or task.transfer_requested, for another owner’s person) — so hale dna task done <case> --as <who> addresses that case as it addresses a job today, and the same gate applies. Assignee, obligation, acceptance practice and required evidence bind as a job’s do: in an organism the leader plans for, the leader says who and under what obligation; without one the case is unassigned. A retired assignee’s successor, a transfer to another owner, and reassignment keep their semantics.

What the leader proposed — whom, under what obligation — is one thing; what binds is decided from the reading the admission is exact at: who is admissible now (a retired person’s successor), which acceptance practice is in force now, whose the assignee is. A reading gone stale under the append repeats the decision, not only the write, so a retirement that landed between sends the case to the successor, never to the person retired. The admission names its origin (owner) and its disposition (to: the owner whose person takes it, empty for the origin’s own), and the compatibility rows say what the admission recorded, never what the body writing them is.

The hand-off settles nothing: the parent Work stays pending, and the root is neither closed nor marked by it. Two human Works in one step are two cases, each completable on its own. An attempt asked again — a redelivery after a restart, a later attempt of the same Work — finds the case by its stable identity and admits no second, and asks the leader nothing: the terms bound at admission stand, and a case already awaiting the leader’s word is not asked for twice. A restart that fell between the admission and either compatibility row completes them from the admission, in that order, and never plans, resumes or pends a case as edit work: a case is a person’s, never an executable root, never legacy edit work. Only the recorded owner completes them: a transfer to another owner is theirs to hand once a member of theirs accepts it (task.transfer_accepted, as a job’s today), and another owner’s restart neither hands it nor completes the offer. An admission the record refuses holds the attempt for redrive; a compatibility row the record refuses holds the case, completed when the record resumes or on redrive without a restart, and the hand-off is announced only once it is durable.

The solo path: the generated org chart sets solo_without_model on its substrate. While the organism has no model (has_model: no model.category row names one, model_mapped, and the editor’s router permits no call, its hosted backends keyless and its local server, which LocalModel probes on this host, not running; a canned or recorded backend is a model), a Work the routing would hand to a leg (an agent’s: a judgment, a Work routed to a position) or to the editor is a human leaf instead, a case for a person, and the case is handed unassigned with no word asked of the leader. The organism’s own steps (an organism Work: applying a ratified change, raising a concern) need no model and stay the organism’s. Nothing waits on a model and nothing times out into a leg; mapping a category or giving the catalog a model gives the next execution its legs back. A substrate a program wires itself (solo_without_model false, the default) routes as before. A chart generated before the flag routes as before, and hale dna upgrade does not add it: DNA migrates nothing, and such an organism takes the solo path when it is born again (hale habitat graft).

A case is completed by its person through the tooling of the day — hale dna task done <case> --as <who>, with the evidence its bound acceptance condition wants (completion.linked) or an exception someone else authorized (completion.excepted), or a decision someone else made, reported by the assignee and accepted under the bound practice (decision.reported, GH #616) — and that accepted fact is the outcome of the attempt the case was admitted for (card 17). The assembly reads the completion from the record (case_completion, completion.hl) and reports it to the runtime of its scope as it reports an edit leaf’s outcome: judged, recorded as attempt.outcome and answered to its Work by the runtime’s own barrier, so the Work settles done, the step counts it, and a completion the record already holds an outcome for changes nothing. The report is kept until the record is seen to hold the outcome, as an edit’s is.

The face reaches the same through two rows of the head’s surface, each in the caller’s name (any person of the record), answering {ok, code, why, task_id, state, assignee}: Commands::take_task ({task_id}: a handed Task that names no assignee becomes the caller’s, a task.reassigned row from "") and Commands::complete_task ({task_id, text}: a task.done row whose body is by <who>: <text>, refused evidence_required where the bound practice wants evidence, and by memory’s gate for anyone but the assignee).

The completion is judged against what the admission bound — the assignee (moved by task.reassigned), whether evidence is required, which practice — never against a practice in force later: a practice relaxed after the hand-off relaxes nothing, while a case handed after the relaxation closes under the new one; evidence linked under another practice is none, and a decision accepted under another policy than the bound one, or under none, or naming another case or none as its scope, is none. An exception applies to the person on the completion row: recorded in their name and authorized by someone other than them — a case reassigned to its exception’s authorizer does not close under their own authorization. A completion the bound condition refuses — not the assignee’s, no evidence linked under the bound practice where it is required, an exception that is not the closer’s or that the closer authorized, a decision reported by someone other than the assignee — connects nothing and is noted once; a later valid one connects. The first valid completion is the outcome; a second is a duplicate and settles nothing twice. One case’s completion advances nothing of another’s.

It is observed wherever the assembly reads the record: live, at the organism’s tick, which examines the cases whenever the record has rows past the revision they were last examined at — by that revision, never by whether the tick’s own refresh moved, since another handler (a redelivery’s) reads the record without examining every case; when the record resumes; on redrive; at a restart of the assembly (which reads what the record holds); and at a restart of the execution, whose redelivery asks for the attempt again and is answered with the outcome the record holds. A case keeps its parent step waiting through a restart of the execution: nothing settles it but its completion. Only the recorded owner connects a completion — the Work runs in the origin’s execution. The tooling’s own gate (hale dna task done, evidence, exceptions, reported decisions) is unchanged.

What the sections above promise is held, as one oracle, by dna/tests/workflow_conformance_test.hl over the canonical three-level example (dna/WORKFLOW-CONTRACT.md §6), in every supported mode, with a unix listen binding declared so every publish is queued as in a bound organism. A review traces each promise to it; what it does not cover is not promised.

Delivery. A leaf’s attempt is admitted, claimed and delivered to its performer once per claim. A performer answers at once or later; a later reply names the attempt and the performer, and reaches the runtime whenever it comes — after the grandchild’s step, before the root’s own leaf, in any order. A reply for an attempt whose outcome is recorded is answered from the record and writes nothing, whether it repeats the recorded reply or arrives for an attempt the Work has retried past. A reply from another identity than the admitted performer’s is nobody’s and is refused. A reply for an attempt the record never admitted or never claimed is nobody’s.

Failure. A leaf that fails its allowance fails its step; the step fails its execution; a child’s failure fails the step that invoked it, up to the root. Nothing later activates and nothing further is requested. A leaf still out under a failed step keeps its responsibility: its outcome is recorded when it comes, it reopens nothing, and the execution above it settles failed only after it settled (the drain policy, contract §6).

Cancellation. A root’s cancellation fences everything below it: what was admitted records its outcome; nothing further is admitted or claimed at any depth; the tree drains and reclaims from the leaves up; a late reply for a fenced attempt is still recorded, as the attempt’s outcome, and reopens nothing. A root’s own residents learn of a cancellation committed by another hand through a restart or through the fence on their next transition, not by being told.

Restart. The record is the execution. An incarnation booted over it — in process, a new runtime over the same record, a new routed record over the same two memories, or a new process — rebuilds only what is unfinished, under the original ids: no execution is admitted again, no step registers or activates twice, no completed member runs again. An attempt claimed by the incarnation that died is reconciled before anything is delivered: a durable adapter that recorded the effect answers from its store and the attempt is not performed again; a performer that truthfully has not acted is delivered the attempt again, once, under the same id, and the record says so (effect.redelivered). The completed record rebooted gains no row and asks no adapter. Every fact of an execution is routed to the ledger (one memory), so a reconstruction of the routed record keeps their order.

Fencing. Under a lease, a holder whose token is not live commits nothing: its replies are refused and written nowhere; with the lease back, the same replies go through and the run completes as every other.

Reclamation. A settled execution dissolves every resident of its tree, leaves first; repeated completed runs leave nothing alive. What grows is the record.

Logical exactly-once, apart from external-effect reconciliation. One effect.requested per attempt, ever; one attempt.outcome; one work.settled on that attempt; one settlement per execution. Whether the external effect happened once is the adapter’s to say: an adapter whose store holds the invocation is not asked again; one that cannot say leaves the claim resulted unknown and the attempt waits for a person (card 12c); one documented idempotent is replayed. The record distinguishes the cases: a redelivered attempt has its effect.redelivered row and, in the adapter’s store, two invocations under one id.

The process boundary. dna/tests/conformance/runner.hl is the public assembly (Dna.run_workflow over the record and the Ledger in memory) with a durable service adapter whose store is a file; the fixture builds it with the hale under test, runs it as a separate process, kills it with SIGKILL the moment C1’s attempt is admitted, the moment it is claimed, and the moment its adapter has recorded the effect but before the outcome landed, and runs it again over the same record each time: the assembly resumes the admitted root, the attempt keeps its one admission and one claim, the redelivery is recorded once, the store is the outcome, D runs once, the root settles last. Run once more over the completed record, it runs nothing.

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 memory implementations (RecordJournal, MemoryLeases, MemoryReceipts, GH #1590) are the ones an assembly wires for an organism; the in-process ones exist for tests. Protected bodies go through MemoryVault to memory’s sealed store instead (ProtectedBodies, PqProtected, which files, reads and erases only through receipt_file, receipt_read and receipt_erase; GH #606, #985).

The record has an API of its own (GH #646 stage 0). Record is the boundary the organism’s readers and writers stand on, with a vocabulary that never names the store: a chain of rows appended at its head (journal), bodies kept by the digest of their content, the signer of a row, and the dna.* settings. MemoryRecord is the implementation over memory, MemRecord the one for fixtures; what the interface kept for git’s transport (chains built beside a head and swapped, cells, pointers, families shared with a remote) answers that the record is memory’s. hale habitat graft and hale dna upgrade seed the record through the host (graph-ingest, design-upgrade): the driver keeps no record code of its own.

A reload never shortens what a reader holds (GH #748). The record only grows, and a reader is held to the same rule: a read that comes back shorter than the rows a reader holds is a read that failed, never a shorter record, and leaves the rows, the head and the text as they were.

A head read tells an absent chain from a read that failed (GH #961). Record.head(chain) answers a Head: ok when the read happened, then absent for a chain that does not exist or id for its head; a read that did not happen is ok: false with why, and no caller takes it for an absent chain. MemoryRecord reads absent only for a record memory answers with no rows. Every caller that compares a head it read earlier — an admission fence, an API response’s final check, a writer’s compare-and-swap — refuses on a failed read with the reason (record_unavailable, or an io: append error), never treating it as a Record that did not move. A CLI verb says the record could not be read rather than that there is none, record-head answers none only for a record known absent (the driver seeds on it), and a receipt is not written again on a read that did not happen. Both used to be the empty string. hale dna sync refuses, with the chain untouched, when either head cannot be read, or when a round of its reconcile reads either head behind where the previous round left it; it pulls into an empty clone only while the ref is still absent.

Every external dependency of the DNA is declared the same way, one interface in the core with one implementation over the real thing and one in memory: Infrastructure (a body’s database, supervisor and credentials; GH #647) and Transport (how a head reaches a body) in infrastructure.hl, Forge (a code-review host; GH #648) in forge.hl. Their memory implementations exist; the host still drives compose, systemd, ssh and gh directly until each is moved behind its interface.

The organism is an organization written in Hale (GH #566 F2): a program at dna/org that hale habitat graft generates and hale dna run runs. The application it oversees is not modified by the graft 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). Its work system hands the agent kind to the legs’ relay (agent: dna::LegRelay, with agent_reconciler: dna::RelayReplay beside it): a judgment or an analysis is answered pending and a leg claims it through hale dna work (GH #946); no agent performer runs in process, and the law’s positions group names the relay. 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. That required authority is the fallback: it decides only a Review whose route defines no signers.
  • Signers from the route (GH #1089). A Review is opened by a definition, and the definition says what it requires: change-deliver’s Review (a mutation’s, a prepared source’s) requires what hale dna route computes for the paths its candidate changes against its base; practice-ratify’s (every proposal the Board decides — a practice, a hold, a hole, a binding, an edge) requires the board position under the purpose. The organization asks memory’s graph when it opens the Review and records the answer once as review.routed <review> {definition, signers, gates}; that row is what the Review requires from then on, a restart included. signers is every position the route names with the holders it has then, <position>=<holder>,<holder> … — a position an organization holds is held, for signing, by that organization’s members (GH #1123: the route expands the organization into its members, each once), so any one of them signs for it; gates is the gates guarding the change, and fallback whether the route left some path to the fallback. A route that names no held position defines no signers: with no memory named, or before anyone holds a signing position (the Board’s own first holder is ratified this way), the Review keeps its required authority. A change-deliver Review’s fallback is recorded too, and final — review.routed with no signers and why (no memory is named, no position the route names is held), which hale dna review <id> shows; a practice-ratify Review writes nothing on its fallback and is routed again each time it is rebuilt (a restart), so a Board seated since signs it, and rebuilding a record’s proposals appends nothing. The Review is not opened — and reconciliation asks again — when memory is named and does not answer, when git cannot name the paths the candidate changes (no base, or a failed diff), or when the record does not take its routing; so no Review opens on its claimed authority alone because memory blinked, and no change routes as one that touches nothing. The route is recorded before a post-review candidate is applied. With signers, a verdict is admitted only from a holder of a required position, whatever authority it claims (reviewer <who> holds no position this Review requires (<positions>)) — or, where the route left a path to the fallback, from one claiming the Review’s own required authority; a verdict counts toward every requirement it meets. The author is still refused, except on a governance Review (a practice, a hold, an opening, a graph proposal) requiring the Board, which admits its author’s verdict; a mutation’s Review refuses its author whatever authority it requires; one rejection or revision from a required position settles it; approval settles only once every required position has approved — one holder of two positions signs for both — the required authority where the route left it a path, and every gate has a passing run at the candidate, as the forge reported it. An approval that settles nothing is answered all the same, review.signed <review> {by, awaiting}, and hale dna review <id> shows what it awaits. Holders are routed again: while a routed Review is open the organization reads who holds its positions on each reconciliation, and when that changed — a position filled after it opened, a holder retired — writes review.routed again (the latest row is the requirement) and the Review takes the new holders; the positions stay the route’s, and what was signed stays signed. The Leader never decides a routed Review. A gate’s run is the forge’s (GH #1161): a gate is a workflow’s job, and its run is the job’s check run at the candidate, which the host reads from the forge (Forge.checks: GitHub’s check runs for the commit, a file forge’s <n>.checks in fixtures) when it syncs the forge, and records once per run as gate.observed <review> {gate, sha, conclusion, url, forge, run} — completed runs of the gates the Review’s route names, matched by job name (a matrix leg job (…) and a reusable workflow’s job / … are the job’s). The host’s forge sync writes that row, and it is the only run a Review weighs, and only from the forge its github.pr row names: a run at another commit, one whose conclusion is not success (skipped included), another gate’s, or another forge’s counts for nothing, and a verdict cites no run (a receipt anyone files is no gate’s word; --evidence is refused). The row is trusted as every record row is, by who may write the record. When such a row lands, the organization tells the Review (ReviewRouted, on its reconciliation, once per row), and the approvals it holds are weighed again: with every gate’s passing run in, it settles on the last of them. A Review rebuilt at a restart admits the plain verdicts the record holds for it again, so the approvals given before the runs survive it. A prepared source’s Review (an organization change) takes the route’s positions and no gate. A command verdict (command_id) is decided once by the same rules: redelivered after a lost response, or asked of the Review a restart rebuilds from review.command_decided, it answers the decision it made and writes nothing. The typed dna.review.verdict command applies the same standing, over the latest review.routed row, in every Review profile (a practice, a binding, an edge, a prepared source), naming its reviewer by the principal; its refusal is forbidden on the wire, and the admission’s result carries a why naming what the Review requires, as the Review’s own refusal does; the plain path (hale dna review <id> approve --as <who>) names its reviewer as the row’s writer says, so under dna.trust = signed the position check is as strong as the writer’s signature.
  • Owners (stage B2, GH #664; the graph’s, GH #1123). A shared record’s org chart has one owner per organism position: the firm whose controller admits intents for it. The org chart is the graph’s (The repository’s graph): a firm is an organization node, it owns a position by holding it (holds(position:org, organization:acme), holds(position:org/collections, organization:north)), and a person is its member by holding the organization (holds(organization:acme, alice)). A position no organization holds takes the owner of its nearest held ancestor by path (org/collections/late takes org/collections’s, up to org), and one under no held ancestor is unowned — admitted by no controller. A graph in which no organization holds anything is one owner, the organization itself, and nothing about a single-owner organism changes. The organism’s Ownership is built from memory’s graph_ownership() (JSON [node, holder] pairs of every live holds edge an organization is in; memory not answering is no ownership, so a view that may have moved is dropped), read again at rehydrate and on every reconciliation, so the Ledger’s gate (org_members) and every admission read one projection. A body over a shared record says which organization it is (git config dna.owner acme, the bare name of its organization node, carried as HALE_DNA_OWNER); a body that names its organization admits nothing until memory says what it holds — it fails closed. The host’s run and dev read which organizations exist straight from the record’s graph.node rows of kind organization (the record is the membership’s source): over a record that states organizations, a body that names none does not run, nor one that names an organization the record does not state. The host’s admission helpers read memory as a head does (HALE_DNA_MEMORY_DSN_HEAD, else the spine’s DSN). A body admits an intent (Intent.to, the position it is for; “” is org) only for a position its organization owns, refusing one offered to it for another’s by name (intent.refused: “not this organization’s to admit”). It refuses only on the graph as of the record’s end: it reads the ownership again, and while memory’s projection has record rows still to apply the intent stays unanswered, for the relay to offer it again. A head never offers such an intent to the body beside it: hale dna task create --to writes it to the record for the owner’s controller, the host relays only intent.requested rows for positions its owner holds, and status lists the rest as [unadmitted] with the owner. Ownership and membership change like any other hold: proposed (hale dna fill <position>|organization:<name> <holder>|organization:<name>) and ratified by the Board (practice-ratify, which requires the board position); no quorum of owners approves anything — every approval is the route’s (Signers from the route), and a position an organization holds is signed by any of its members (a Review’s wire form keeps its approvers field, always empty). There is no verb yet to propose a new organization node: a fixture or an operator writes its graph.node row. Ids and effect claims carry their owner (GH #666): a controller mints in its own namespace — acme:t3, acme:m2 — and claims an apply as acme:apply:<candidate>, so two controllers never contend for one id, each restores its count from its own births alone, and the ledger’s unique constraint on a claim kind decides a first claim with no coordination between them: a task.born the Ledger answers claimed is minted again under the next id, and nothing is refused. With one owner nothing is prefixed. Work that crosses owners is a transfer inside the one ledger (GH #667), #615’s rule applied between owners: a plan that hands a job to another owner’s member appends task.transfer_requested <task> {owner, to, assignee, …} in place of task.handed, and the Task waits; a member of that owner accepts it (hale dna task accept <id> --as <who>, task.transfer_accepted, which memory takes only in a member of to‘s name) and their controller then appends task.handed in its own name, naming transferred_from and who accepted, so completion is admitted in the assignee’s name as always. Nothing settles on the request alone. Every owner runs a body over a shared record, and every body projects, carries out and erases, none coordinating (The spine). Each owner’s heads write as their owner’s role, which writes only in its owner’s members’ names (The owners’ roles); who the person is remains the head’s to establish — its issuer under dna.principal = oidc. With one owner, the head’s role writes as any person. The organization that owns org is ownership.host_owner in the API’s organization read (empty with one owner), which builds ownership from memory under the head’s role; the owners map’s host = <owner> (GH #669) went with the map. An organization source published by a named source owner is published only when that owner owns org in the graph.
  • 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 nerves. 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.
  • One body per owner: the body lease, and its epoch (GH #665). A record admits one body per owner at a time — one body when the organization is the one owner (bounded attachment; the initial controller model). Over a shared record (Owners, above) the lease is a row of memory’s claims table per owner, owner/<owner>, taken like the single body’s body row, so two firms’ controllers run side by side over one record and a firm’s second host waits on its own firm’s lease alone. The lease’s token is an epoch: it rises when the lease is taken (a takeover, a forced claim, an expiry) and holds through renewals, and the host hands the body it starts its lease and epoch (HALE_DNA_LEASE, HALE_DNA_LEASE_TOKEN), which the body’s MemoryLedger checks before every append: a write under a lease that is not held, is held at another epoch, or has expired lands nothing (fenced: …), so a controller that survived a partition writes nothing after its replacement took over, whatever it still believes. A head’s or a host’s write is a request, which the spine admits. Failover is within an owner; no owner’s positions pass to another on a timeout. The rest of this entry describes the lease as one body’s; it holds per owner. A record admits one body of each. The host takes the body lease in memory (the row body, owner/<owner> over a shared record, swapped by its token) before it builds or runs anything, so two hosts on two clones fence each other through memory itself. The lease’s fields are (holder\ntoken\nexpires\npresent), the holder user@host:<clone>, so the same clone restarting is the same holder and takes its lease back at once; a second clone is refused with who holds it and exits 3. The lease lives 30s and is renewed when a third of that is gone; the host asserts it at the top of every tick, before it relays, restarts or applies, and stops itself (exit 3, the organization with it) when the lease is someone else’s or released. It proves the lease again, renewing it, the moment before it starts a process — the organization and the expression at startup and at every restart — and once more before relaying after the tick’s sync: a build or a sync that outlasts a takeover starts and relays nothing, and the host exits 3. The renewal and the fence do not wait on the host. Beside the host runs the body fence (the host binary’s body-fence), which renews the lease every 10s with every call bounded to 8s — and to the deadline it last proved (the lease’s expiry less 5s, .hale/dna/body.deadline), so the calls of one renewal together cannot outlast the lease — and writes what it proved to .hale/dna/body.fence.status, each line prefixed with its host’s pid; the host reads its own lines there instead of renewing. The fence’s mandate is .hale/dna/body.fence, naming its host’s pid: a fence whose mandate is gone or names another host (a host restarted on the same clone) stops and kills nothing. When the lease is someone else’s or released, when the fence cannot prove it within 5s of its expiry, or when the host is gone, the fence kills the organization and the expression (by their pid files), each with every process it started — the tree and the session frozen, then killed. Every process the host starts begins a session of its own (dna::in_new_session: setsid(1), or perl’s POSIX::setsid where there is none), and a tool keeps its session through everything a process group does not survive: run_tool gives each tool a group of its own, and a tool whose organism died is reparented, but both stay in the session its organism began, and only a tool that calls setsid itself leaves it (#970). Then every process carrying the body’s mark (HALE_DNA_BODY=<holder>#<token>#<host pid>#, set in the environment of everything the host starts and inherited by every tool they start) — the second net, for that setsid tool, where the machine discloses environments — then a repository’s compose services this body brought up (The body runs a repository’s compose services: they live in docker, outside every tree and mark above, so the fence takes them down by the body’s ownership record) — and says why (the host does the same, before it stops the fence, on every exit it takes once it holds the lease: a failed build, a refused start, its organization’s exit), and the host exits 3 when it next looks. The session reaches a reparented tool on every POSIX machine; the mark needs the machine to disclose another process’s environment, and what a machine will give differs (#638). body_scan says which it has: proc — Linux’s /proc/<pid>/environ; ps — ps -E, proved at the time of asking on a child of the call’s, since the answer is what the kernel discloses about another process and not whether the flag parses (macOS shows a process its own environment and, with SIP, no one else’s — GitHub’s runners do show it); or none, where the fence still stops the sessions — every tool the organization and the expression started, orphaned or not — and only a tool that began a session of its own is out of reach, which the host says at startup (#970). A host blocked in a sync, a build or an observation window therefore cannot keep its organism executing past the lease; while memory cannot be reached the lease is kept unrenewed until then: a partitioned body executes nothing past its TTL. Taking the lease is a row (body.claimed <holder> {token, forced, by}), giving it up at exit another (body.released). hale dna body reads the lease; hale dna body claim --force releases a live lease that belongs to a body that is gone, as a body.claimed row with forced: true in the forcer’s name (that body stops when it next asserts; the next hale dna run takes the lease); hale dna body release [--force] releases it explicitly.
  • The combination is detected, never stored. An organism is one point in a space of independent capabilities — where the body runs, whether a hosted head exists, whether a fleet expresses it, which records it is connected to — declared by its pieces. hale dna profile prints one line per axis from the pieces alone: the record’s remote, the body lease and its last tick, [dna] fleet, the knowledge store’s environment, dna.trust, the forge allocation; the status projection carries a profile: and a body: line. hale habitat graft <name> --profile local|remote-body [--remote <url>] [--body <user@host>] sets pieces (a remote, dna.body); the profile name is not written anywhere. Profiles are examples of combinations, not a closed set; the toolchain refuses only what breaks a commitment (a second body on one record).
  • The body on a server. hale dna body provision <user@host> [--dsn <url>] [--dir <path>] [--dry-run] makes a body over ssh, in order: the toolchain hale.lock pins (installed with the site’s installer at that version, verified, or stop), the record’s remote cloned (fetched into a clone that exists), vendor/dna and the record brought up, memory’s database from the body’s local habitat or a DSN (which goes into the body’s env file as HALE_DNA_MEMORY_DSN_OWNER, never the record), the organism’s memory and nerves migrated once by hale habitat migrate . in the script (GH #1653: the unit’s hale dna dev migrates nothing, and hands the host only the spine’s DSN), and a systemd user unit hale-dna-<project>-<record> supervising hale dna dev . --no-iris — on one server the body is the organization and the application under one host — with Restart=always (GH #1026): a failure flows up one more level, and a node that exits to take a moved genome is started again on it (The genome pull). It stops before writing anything when the record has no remote or one local to this machine, when ssh cannot reach the host, or when the host lacks git, curl, systemd, or (without a DSN) docker compose. It records dna.body and dna.body.dir here and a body.provisioned <user@host> {dir, toolchain, knowledge, by} row. The default directory is $HOME/dna/<project> on the body, expanded by the body’s shell (the unit’s WorkingDirectory=%h/dna/<project> is the same place). The script can carry the DSN, so its file here is created empty with mode 600 before it is written, and removed once ssh has read it. --dry-run prints the exact script. hale dna body start|stop|logs [--body …] reach the unit over ssh; stop succeeds when the unit is no longer active, and says so. Postgres, Docker, ssh and systemd are the reference setup; the definition admits other implementations, and names where they plug in (GH #647): Infrastructure — a body’s knowledge database, its supervisor (a unit installed, started, stopped, asked, read) and its credentials (put by name, never read back) — and Transport — how a head reaches a body (probe, execute) — are interfaces in the core. ReferenceInfrastructure in the host is compose, a systemd user unit and the env file, reached through SshTransport (HALE_DNA_TRANSPORT=ssh, the default) or LocalTransport (local: every script runs in this machine’s shell under HALE_DNA_TRANSPORT_HOME, which is how the fixtures exercise a body with nothing stubbed on PATH for ssh); MemInfrastructure and MemTransport are the core’s fixtures. The provisioning script is composed from the database’s and the supervisor’s own fragments, so a body without systemd gets another supervisor’s unit by implementation, not by if. Nothing in the host spells ssh, systemctl, journalctl or docker compose outside that one implementation.
  • Secrets. hale dna secret set <NAME> [--body <user@host>] reads the value from stdin — never argv (a NAME=value argument is refused), never the record — and fills that name’s slot of the vault (The organism’s secrets; mode 600, the newest value) on the body over ssh’s stdin, or on this machine. A name is a slot the organism requires — FORGE_TOKEN, OIDC_CLIENT_SECRET, or a credential dna/org/models.hl or dna/org/work.hl names (HostedCredential { key }; the body’s own credential check reads models.hl alone, so a legs key never stands in for the organization’s) — and any other is refused. No process’s environment carries the value: the part that sends it reads it from the vault. Off a terminal the value is the whole of stdin with one trailing newline (\n, or one \r\n pair) taken off; at a terminal, one line typed after a prompt; an empty value is refused by the secret’s name and nothing is stored. secret rotate <NAME> is the same for a name already set. The record gets secret.rotated <NAME> {where, by} and nothing else. At start the host checks the credentials its catalog names (HostedCredential { key } in dna/org/models.hl): when none is in its slot of the vault (model-<key>, the one source a model key has; no environment variable is read) it says so and appends body.credential_missing model {any_of}; status and board carry “no credential for the model” until a start finds one and appends body.credential_present.
  • What a body holds on a machine is bound to its record (#635). <record> in the unit’s and the env file’s names is the first twelve hex digits of the record’s identity (its first row’s digest), so two records whose directories share a name on one machine never share a unit or a credential. Everything else a body reads lives in its clone: the dna.* git configuration (the OIDC member mapping among it) and the knowledge schema, whose ownership is checked against the record (GH #613); the hosted head and the node serve the clone they run in.

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> appends one pressure.requested row, which a node relays on PressureRaised (dna.pressure.raised) until the organization answers it.

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 that holds the edited files, 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 listing is the .hl files under the grant’s root, by path from it, four directories deep; hidden directories and the organization’s own (dna, vendor) are not listed, so a grant over a repository’s root reaches the seeds under it (api/todo.hl). When no file is found the Mutation fails naming the cause, not the grant alone: the grant lists no .hl file (no model is asked), the model could not be asked, or the model named a file the listing does not hold (SourceEditor.located_why).

Which seed is checked. A root that is itself a seed (it holds a .hl file of its own: an application’s organization; the root hale.toml that hale habitat graft writes in repository mode is the organization’s environments, not source, and does not make a seed) is checked and formatted whole. Otherwise (a repository, whose code sits under its seeds) each edited file’s seed is its nearest ancestor directory under the root that holds a hale.toml and a .hl file, else the file’s own directory; each seed is formatted and checked once, in order of the first file edited in it, the first failure’s diagnostics (named from the seed, not the worktree) feed the retry. The candidate’s verification follows the same rule: when the Mutation’s seed is the repository’s root (.) and the root is not a seed, fmt --check, check, verify, test and the base topology run in the seed of the first file the candidate changed.

The law in force on a Hale heart (GH #1591). When the practice standard/logs-are-structured is in force as law (ratified, not retired, its document saying law: true), the candidate’s check adopts std::law::Standard from outside, as hale check --adopt std::law::Standard (spec/verification.md, “Adopting from outside the source”): a heart with a bare println on a served or attached path fails the check, naming the clause logs_structured, and one that writes through std::log passes. Nothing is written into the heart’s source, and with the practice absent, advice or retired the check adopts nothing.

The organization’s models are a catalog in source (GH #583 M1): dna/org/models.hl, generated by the graft and owned by the project.

  • The seam. ModelBackend (identity, adapter, price_micros, model_name, allows, capacity_bytes, complete) is what every adapter implements; every call publishes ModelCalled, which the substrate journals as model.called with the time of the call (at). One vocabulary (GH #1131): a row’s adapter is the adapter that answered the call — openai-chat, anthropic-messages, local, harness, fake, recorded; empty when the router permitted no backend, so none answered — whether the call was made in process or by a leg (the router records it, last_adapter); legs is only a leg’s wait row, a call that was not made (refused says why, waited_ms for how long). The prompt is a receipt (GH #611): the evidence carries the prompt and the context as sent, and the substrate files each as a receipt in memory under the prompt_digest / context_digest the row names — only a body that hashes to its digest, and receipts are content-addressed, so an identical brief is stored once — recording bodies: stored (or none for a call with neither). An organism whose receipts are not the record’s (a file store: a fixture, an embedded use with no record) files nothing and records bodies: none (receipts are not the record's: file). A customer-class call files no body: bodies: withheld (data class customer), and its digests are all the record keeps. hale dna history <attempt> renders, under each model.called row of that attempt, prompt <digest> (<n> bytes), exactly those bytes from the receipt, and end of prompt (the same for the context) — never a re-composition; a withheld body says so, and a body memory lacks says so. A wider history (hale dna history m1) names the calls and leaves their bodies to each attempt’s own, since an editor’s prompt carries whole files. The row says stored only when the store gives the body back. Routers return data, never a backend. Adapters: OpenAiChat (the OpenAI chat shape: OpenAI, OpenRouter, vLLM, Ollama; adapter: openai-chat), AnthropicMessages (the native Messages API: system beside messages, max_tokens required and defaulted to 4096, the reply’s text blocks joined, anthropic-version sent; adapter: anthropic-messages), LocalModel (the OpenAI shape to this machine; no credential, no external_model, any data class), FakeModel (scripted: answer, answer_file, answers_dir, answer_role; fail_after refuses after that many calls; turns_dir answers a request that offers tools from <dir>/<turn>.json, a tool loop’s scripted turns). A request names the model its mapping resolved (ModelRequest.model, else the backend’s own), evidenced as requested_model; it carries metadata (the attempt, the position and the holder: attribution only), which OpenAiChat sends only with send_metadata: true, and an idempotency_key, sent as Idempotency-Key (a model leg’s is <attempt>:call:<turn>). A model leg declines a Work whose mapping named a model and refused it (a tools or do Work on a model that returns no tool calls) before it asks for spend or calls anything. A hosted adapter’s complete carries external_model; its credential is a sealed HostedCredential that names its key (key, whose vault slot is model-<key>; GH #989, the vault is the one source a model key has, and no environment variable is read) and its scheme — bearer (an Authorization: Bearer header) or x-api-key — and never returns the bytes: the credential reads its slot and composes the header in the one statement that sends the request, no adapter does. An API error is refused as http <status> <the API's message>; a transport failure as http 0 <kind> <detail>.
  • The harness. HarnessModel runs an installed coding harness (command: claude, or codex with output: text and exec --skip-git-repo-check --sandbox workspace-write, because a non-interactive codex is read-only by default and would answer without ever editing its export) per request with its own tools on, as a backend that works_in_place: for a source-editing request the editor makes an EXPORT of the Mutation’s worktree (a plain directory with the same files, never .git, never .hale), runs the harness with the export as its cwd and the whole objective as its prompt, and imports the export’s diff back under the grant — a file that differs or is new is written through the tools, one that is gone is removed, a change outside the grant is counted (outside_grant) and left behind; files_changed is derived from that diff, never from the harness’s answer. A file the worktree does not have is new whatever it holds, empty included. A written file’s parent directories are made under the grant first, so a harness may add a module in a directory the worktree does not have yet, and an in-grant write or removal that fails is the attempt’s failure (the import was incomplete: …), never a candidate carrying part of the change. Then the same fmt, check, retry (the diagnostics in the next prompt) and assessment as the file-by-file flow. An answer-only request (a review) runs in an empty directory of its own. complete carries external_model and harness_run; evidence is one model.called row per call with the harness’s own cost summary (total_cost_usd, usage), tool_grant: harness @export, and confinement=<kind> in params.
  • The boundary is stated exactly. The export is not a sandbox: a process whose cwd is the export can still reach whatever the operator’s account can. What DNA guarantees is that the genome is unreachable from the harness process. A Confinement (kind, available, wrap(argv, cwd, mask)) supplies it: Bubblewrap (Linux: the filesystem as the operator sees it — the harness’s own home state, the toolchain, the network — with the repository and the worktree replaced by empty tmpfs mounts, --die-with-parent) or NoConfinement. The genome is the model’s own, not the request’s: HarnessModel { genome, genome_env } reads it at birth (the host exports HALE_DNA_GENOME), every call masks it, and a request’s mask adds what that call knows besides — the worktree an editor exported. Every verb the host runs is given HALE_DNA_GENOME, not only the organization under run and dev: hale dna models probes the catalog in a process of its own, and a confined harness with nothing to mask is refused. An answer-only role (a review, a classification, the catalog’s probe) carries no request mask, so a genome on the model itself is what makes the guarantee true for them. A confinement with nothing to mask is refused, not run, so the claim and the fact cannot diverge. A harness whose confinement is unavailable is refused (unconfined harness not allowed) unless the org chart’s allow_unconfined says otherwise; the evidence records which it was, and masked=<n> how many paths the boundary covered. On every platform the assembly also checks that the repository’s head and the worktree’s head did not move during the attempt and fails the Mutation (mutation.failed: the genome moved during the attempt) if they did: not a boundary, but a bypass becomes a loud refusal. Outside the genome the harness has exactly the operator’s account, which is what running it by hand has. The Repository interface names its root for the mask.
  • The tape. RecordedModel { dir, mode, inner } wraps any backend (dir_env / mode_env name environment variables it reads into dir / mode at birth, so a catalog function computes no string: dna/FRICTION.md F.17). The key is sha256 over the fields that identify a request — role, the inner’s name, the model the request asks for (the one its mapping resolved, else the inner’s), the prompt and context digests, the data class, the grant normalized (… @grant: its path is where it ran, not what it was) and, for a backend that works in place, a digest of the workspace’s starting tree; a tool loop’s request adds digests of its tools and its turns so far, and its entry keeps the tool calls the model asked for, so a replay follows the same turns. An identical request made again in one run — a retried Work planned a second time — is keyed by its occurrence as well (occurrence=n for the n-th; the first keeps the plain key), so a recording that got two answers replays both in order, and a miss on a later occurrence says it was never recorded. record makes the tape’s directory before it writes anything into it, and an entry is counted only once every file of it has landed: a patch or an entry that could not be written is a refusal (cannot write the tape: …), because an entry claiming a patch that is not there replays as a miss on a request the tape appears to hold. record forwards to inner (whose evidence is the call’s) and writes <key>.json with every keyed field in clear and the answer, and for a workspace <key>.patch: what the backend changed there, before any import, so a replay reproduces the edit and everything after it runs for real. replay answers from the directory (evidence adapter: recorded, params: tape=<key> mode=replay), applies the patch to the workspace, and refuses a miss naming the request and, when an entry shares the prompt digest, the fields that differed (tape miss <key> (role …, backend …, …): nearest <key> differs in: context_digest tree). Replay is pure; record reaches whatever inner reaches. Anything that varies between runs must stay out of the key, and recording finds what does: the editor therefore strips the worktree’s path from the diagnostics it feeds back. The tape is a checked-in fixture, not a store. It proves how the organization handles recorded outcomes; model quality is only ever tested by a fresh run.
  • The fixture. dna/acceptance/trio: a gateway (the root seed), an api and a worker under a fleet seed (trio/fleet) of four instances on two hosts, with the routes its bindings derive and claims across them (require publishes on the gateway and the api, require subscribes on the api for the gateway’s orders, forbid reaches gateway → worker avoiding the api). Its organization, with every backend a RecordedModel over the real adapter (dna/acceptance/trio.fixture/catalog.hl; mode from HALE_DNA_TAPE, the tape from HALE_DNA_TAPE_DIR), is driven by scripted asks end to end: a change to the gateway approved and deployed to both nodes, the organization grown by a supervisor under pressure from the worker and restarted with it, and a change to one service that still checks but breaks the fleet’s claim denied — every model call answered from the tape, keyless. It is the acceptance for every later change to the organization; HALE_DNA_TAPE=record with a key re-records it.
  • The catalog is source. A backend is a constructor function (frontier(), fast(), desk() …); a position’s router is a function composed from them (leader_models(), editor_models(), agent_models(), the model leg’s, behind the performer in dna/org/work.hl); the organization’s main takes each position’s router from the catalog and names no adapter inline. A new position is one more function; a provider switch is one file; hale check validates it and the law keeps the concrete types at every boundary.
  • Discovery. The graft looks at the environment (ANTHROPIC_API_KEY, OPENAI_API_KEY) and PATH (ollama, whose first listed model becomes the desk model; claude, else codex, becomes harness()), write the catalog for what is there (AnthropicMessages with claude-opus-5 / claude-haiku-4-5 when its key is present, else OpenAiChat with gpt-4o / gpt-4o-mini, a placeholder key when none is set; with a harness the editor’s and the agent’s quick tier is the harness, and with no key every model-backed slot is), and print what each position was given. Nothing found is fine: a hosted backend without its key is not permitted, and every Review waits for the Board. upgrade writes a catalog for an organization that predates it and says what to point at it; it never edits the organization’s main.
  • The budget has one owner. The catalog declares org_budget(), a BudgetPolicy (window: day, week or none; allowance_micros, 0 = unmetered) as data. The substrate owns the one Budget: every model.called row is accounted at its time, the counters are rehydrated from the record at birth, and when a window is exhausted intent is refused (intent.refused: budget exhausted (spent … of … micro-dollars this day in … call(s))), budget.exhausted is journaled once per window (detail, spent_micros, allowance_micros, window, at), the Board is told, and a Review is not announced to the model-backed positions — it waits for the Board, which needs no model. A ModelRouter counts what its calls cost and has no allowance of its own.
  • One gate for spend (GH #1131). Budget.admit(ceiling, now) is the gate every model-backed spend passes, in process and out: the editor’s attempt and the leader’s Review are admitted by it before a call is made, and a leg asks it for its attempt (Legs). It refuses an exhausted window and otherwise admits the smaller of the work’s own cost ceiling and what the window has left (-1 when neither bounds it: an unmetered budget, no ceiling). A refusal is the same row wherever it is asked — attempt.allowance_refused <entity> (why, ceiling_micros, spent_micros, window_micros, window, at) on the editor’s attempt, which is declined and edits nothing, on review:<id> for the leader’s Review, which waits for the Board, or on a leg’s attempt. Intent admission and the optimize pass ask it too (their answers stay intent.refused and optimize.refused). Nothing is reserved, so the window can be overrun, by a bound: legs admitted against one remainder may together pass the window’s allowance by at most the sum of the allowances they were granted plus one call each — a leg makes no call whose known price is past what is left of its grant, nor any once its calls have spent it, and only a call whose cost is learned from its answer can cross it; the owner settles such an attempt failed, naming the overrun, and journals the calls. In process the gate admits the editor’s attempt and the leader’s Review whole: their calls within are not held to an allowance, so their spend is bounded only by the window being open when they are admitted.
  • Tokens per task (GH #946). The attempt id on a model.called row is the tag its cost is attributed by: <ask>/plan to the task born of the ask (the task.born row whose body starts <ask>: ); <work>/a<n> to the Work’s task — a wf1 attempt’s admission names the task and the performer kind, a Mutation’s request names the task (the editor’s; an older record says it in the proposal’s task <id> summary); <review>/review to the task the Review’s request names, or its Mutation’s, or knowledge for a knowledge Review (k:<12hex>); optimize/<n> to org. The probe leaves no row. A call of no known shape is its own and no task’s. The projection (dna/operations/usage.hl, Usage) sums calls, input and output tokens and cost_micros under an id — the attempt itself, the ask, Work, Mutation or Review it is of, or the task it serves and every task under it (by workflow.admitted’s parent_task) — by position, keyed by the graph’s id for it, position:<name> (position:leader for a plan, a verdict and the optimize pass; position:editor for a Mutation’s attempts; position:<performer kind> for a wf1 attempt, until an attempt’s claim names the position that took it) and by <backend>/<model>. hale dna history <entity> prints usage of <entity>: <calls> call(s) · <in> in · <out> out · <cost> µ$ and the two breakdowns under the history of line, nothing when no call was made, and usage: … for the whole record with no entity; the API carries the same sums as usage on every execution, each of its attempts and every administered Task. The editor’s locate and plan calls carry the Work’s attempt id (the tape key excludes it).
  • hale dna models rule <selector> <size> [--permit <sizes>] appends model.rule <selector> in the setter’s name (--as): a selector is default, position:<name>, contract:<Name> or task:<id>, a size quick, standard or deep, and --permit the sizes a more specific selector may move it to. models category <mode>.<size> <model> [--tools] [--price-in N] [--price-out N] [--endpoint <url>] [--key-vault <slot>] appends model.category (prices in micro-dollars per 1k tokens); a tools or do category without --tools is refused. --endpoint names where the model is asked, an OpenAI-compatible chat URL (voice’s on a host, say), and --key-vault the vault slot of that endpoint’s key, a slot’s name and never a key: a Work the category resolves for is asked there by its openai-chat performer, with that slot’s key or, with none, no key at all (the performer’s own key never goes to another endpoint). The habitat’s head sets it for a tenant as Commands::set_model_map (spec/habitat.md). models map lists the latest row per selector and per category. A graft (hale habitat graft) seeds a starting mapping when a hosted model’s key is in the vault: model.rule default at standard (permitting every size), and every category <mode>.<size> filled by the hosted pair the catalog writes, quick by the fast model and standard and deep by the frontier one, each with tool calls and the catalog’s prices, in the name hale habitat graft; with no hosted key it seeds none. hale dna task create --size <size> appends model.rule task:<intent id>, an override that holds only inside the sizes the rule beneath it permits.
  • hale dna models builds the catalog beside a one-line main in .hale/dna/probe, runs its probe_catalog() from the project root with the organization’s environment, and prints one line per backend: the catalog name, the router-facing slot, the model, and the answer — ok, elapsed, cost and the first line of the reply; refused: …; or not permitted (no credential present). The organization is not started, the nerves not touched, and nothing is journaled: a probe is nobody’s Attempt.

The knowledge graph has two halves and one authority (GH #583 K1). The live half is memory’s, projected from the record by the spine (GH #985).

  • The record holds the decided half. A proposal is a document — canonical JSON with a fixed field order (kind, text, author, target, provenance) — filed as a receipt under its sha256, so a digest in the record always resolves to recoverable content. Dna.propose_knowledge(idea, target) files the receipt, appends knowledge.proposed <digest> (body: digest, review_id, kind, author, target, class, at), and births a Review pinned to that digest (review_id = k: + the digest’s first twelve hex digits; required_authority: board — ratified knowledge is the organization’s; the review.requested body carries knowledge_digest). The tower rule classifies the binding from who proposes and where it binds — a parent’s idea bound to a child is a goal, a child’s bound to a parent a concern, one’s own an initiative — and a lateral proposal (siblings) is refused with knowledge.refused and no Review. A verdict naming another digest is refused by the Review; a Leader’s does not satisfy the Board’s requirement. The authoritative event is knowledge.ratified <digest>, appended by the assembly on review.settled with approve (body: digest, review_id, outcome, decided_by, kind, target, class, at); any other outcome appends knowledge.declined. A pending knowledge Review is re-born from the record at birth like a mutation’s.
  • Binding changes preserve the item. The reviewed dna.knowledge.binding.bind@1 and .unbind@1 operations address one (idea, target, author) tuple. Independent explicit grants admit a knowledge.binding.requested fact. The Body records delivery before creating the canonical dna.knowledge-binding-change/1 candidate and its exact Review (knowledge_binding_digest, not knowledge_digest). The candidate pins the original command, grant and latest Record directive for the tuple. Approval produces knowledge.binding.bound or .unbound; an intervening tuple change or newly inactive bind subject produces a separate apply refusal. Each consequence belongs to the original request identity. Projection changes future package applicability, preserving the item receipt and earlier Work packages. A visible retired item permits exact unbinding. This is the reviewed command profile the API admits (The API, under The surface), not a universal Review requirement on the in-process Knowledge.bind primitive.
  • Relationship policy selects direct or reviewed admission. The existing dna.knowledge.edge.link@1 and .unlink@1 commands retain their exact directed (from, to, rel) identity and command encoding. A review grant instead admits knowledge.edge.requested, pinning the latest direct or reviewed directive for that tuple in a canonical dna.knowledge-edge-change/1 candidate. Its Review carries knowledge_edge_digest; the candidate is not a Knowledge node. The Body derives ownership from both endpoint receipts and requires its owner to admit all four author/target positions in this reference profile. An independent approval produces knowledge.edge.reviewed_linked or .reviewed_unlinked only while the pinned tuple basis and endpoint eligibility still hold. A changed basis or ineligible endpoint produces a separate apply refusal. A retired, ratified endpoint permits exact unlinking. Recovery follows the original admitted variant even if policy later changes mode. Native effects replay in Record order; approval alone does not establish a graph change. This policy does not add a universal Review requirement to the graph primitives.
  • Memory holds the live half. dna/core/memory_store.hl declares KnowledgeStore: scope / record, open, healthy / reopen, watermark (the next record seq to apply), begin_row(seq, prev, head) / commit_row / abort_row (one record row’s transaction, below), rebuild(watermark, head), upsert(idea, ratified_seq) (idempotent by id, which is the digest), retire, bind, unbind, link, unlink, idea(id), context_ids(target, budget) (accepted ideas bound to the target or any prefix of its path — goals flow down, initiatives stay local — in ratification order; target is one target or a space-separated set, Targets are paths or nodes, below), ranked_ids, count(what), and the structure and signals of K3, and the repository’s graph (below). Pq is Postgres (nine tables: knowledge_meta, knowledge_ideas, knowledge_bindings, knowledge_edges, knowledge_structure, knowledge_signals, graph_nodes, graph_edges, graph_members, which the migration creates and open only checks; node writes are upserts and removals delete exact tuples; rows come back as JSON built by the server, since the driver’s tab- and newline-separated rows cannot carry an ordinary paragraph; a signal counts once per record row, keyed on that row’s sequence). The projection is a consumer of the record: apply_record(store, journal, receipts) in dna/operations/memory_tail.hl walks the record’s rows from the watermark, resolves each knowledge digest to its receipt, upserts (ratified accepted with its binding; proposed and declined kept as such, never over a ratification). Each record row is one transaction (GH #1026): begin_row moves the stamp first — the watermark from n to n + 1, then graph_projection_head from row n - 1’s digest to row n’s, each by compare-and-swap on its knowledge_meta row — then the row’s effects, and commit_row lands them together; a failed effect lands nothing (abort_row), and the next pass begins the row again. A projector whose compare finds the stamp moved rolls back having written nothing and ends its pass (yielded): it blocked on the watermark’s row while another projector held it, and that projector committed the row. So any number of projectors may apply one record at once, with no coordinator, and converge on one graph, each row applied once; after a crash a projector resumes at the stamp. Nothing in memory becomes ratified except from a knowledge.ratified row: the record is the sole authority; the projection can lag, never disagree. The observed and inferred tier (K3) is derived and rebuilds from the record.
  • The record is the scope of its memory. Memory is opened for one record, named by the record’s identity — the digest of its first row, record.genesis, which carries a nonce, so two projects created alike are two records (GH #1068, #1590) (scope(record) on the store before open, record() to read it back).store before open, record() to read it back). Pq keeps one Postgres schema per record, dna_<identity>, selected for the session at open (with public behind it for the vector type), so two records on one database each see only their own ideas, bindings, structure, signals and watermark; a memory allocation in the local habitat is one record’s anyway. The schema’s knowledge_meta carries a record row naming its owner, written by the migration and checked by the migration and by every open: a schema that names another record is refused (scope: schema … belongs to record …, not …), never read. A record with no rows yet has no identity: its projection opens under dna_unscoped, where nothing is ever applied, and moves to the record’s own schema once its first row lands.
  • The genome pins the identity (GH #1590). hale habitat graft composes the record’s first row, writes [dna] record = "<identity>" into the repository’s hale.toml, and only then provisions memory and appends the row, so a clone knows which record — which schema, roles and nerves token — it belongs to from its genome. The graft writes it into the dna table wherever the manifest keeps it (a [dna] header in any spelling, dotted keys, an inline table), editing the parsed document so the rest of the file survives. record_identity_at (dna/core/memory_spine.hl) reads it wherever TOML lets it stand, as a basic or a literal string: with no pin there is no record; a pin present but not readable there (not a string, a multi-line string, a basic string with escapes) is refused, never taken for no pin; and a pin that is not 40 or 64 lowercase hex digits is none. A record whose first row names a genome this clone’s history does not hold is not opened (the hale.toml naming it was copied from another project). A graft in a clone whose pinned record memory does not hold refuses: it would start a second record under the first one’s name.
  • The spine projects; readers read. MemoryProjection (dna/operations/memory_tail.hl) applies the record into the graph on the host’s tick, as the record’s spine role, on every node (The spine, above), so the projection advances without anyone asking. The fence compares the stamp with the record: memory’s stamp is a row of the record (its watermark and that row’s digest), and the record only grows (GH #1590). The stamped row at its place: the rows after it are the delta. A watermark past the record this projector holds: another projector is ahead, and there is nothing to project until it reads again. A stamp that does not match the row at its place is a graph this record cannot vouch for, so it is rebuilt from row 0 — rebuild(watermark, head) empties it only while the stamp is still the one read, so of several projectors one rebuilds, and a projector rebuilds at most once per pass. A question memory cannot answer is a refusal, never an empty answer: a package built from failing reads, or from a projection behind the record, is indistinguishable from “there is no knowledge here”. The projection’s store is opened on the first tick rather than at birth, and the connection is asked on each tick afterwards (healthy), because an open that succeeded once is not a connection that still answers: a session dropped by a restart or a failover is re-established (reopen). A reader — MemoryKnowledge in the organism, the API as a head — never projects. A package (MemoryKnowledge.package_for(target, query): the ids included, their ideas with kind, text and author, the projection’s watermark as its revision, and a digest over the target and the ids — what is handed over, never the revision, which varies between runs and would make a tape unable to answer the same request twice) waits, bounded at 20 s (PACKAGE_WAIT_SECS), for the projection to pass the record’s last row that changes a package (an idea ratified or retired, a binding made or undone), and otherwise refuses: memory's projection is at row <n>, before the record's knowledge row <m>; the spine applies it on its tick. The organization’s knowledge package is read from memory under the spine role. The API reads the graph under the head’s role (The API, below).
  • The design, as proposals (GH #596 C). The graft seeds the toolchain’s practices about how a DNA organization works — the design principles, the evolution pattern, structure follows intent, standard equipment, the structural signals, signaling, the optimize pass, software delivery — as knowledge.proposed rows with receipts bound to org, one Review per practice (k:<12hex>, grouped design in hale dna review’s listing; hale dna review design approve|reject is a convenience that decides each pending one in turn, never a batch object in the record). A Review pins one digest and settles with one outcome, so the Board decides practice by practice, and nothing is ratified by the toolchain. Each receipt carries a stable name (design/<slug>) and the toolchain version. Supersession is the Board’s decision, not a side effect. Declining a proposal retires nothing (the projection keeps a ratified idea a later knowledge.declined names). A proposal whose receipt carries supersedes: <digest> retires that digest when it is ratified — the assembly appends knowledge.retired <old> — and leaves it in force when it is declined; a retirement proposal carries only that. A replacement is ratified only while what it replaces is still active: an approval of a proposal whose supersedes names a digest already retired is refused (knowledge.refused <digest>, with what retired it) rather than ratified — otherwise two replacements of one version would both be served, the first never retired. hale dna upgrade proposes each practice whose current text is not the latest proposed under its name, superseding the active version under that name (a pending or declined proposal is not a predecessor), one replacement at a time: a practice whose latest proposal is still before the Board waits (upgrade says so), and a refused one is proposed again against what is active now. The projection retires an idea by marking it not accepted: it leaves every package and stays readable by digest.
  • The operating practices, as proposals (GH #994). Beside the design, the graft seeds a second family, operating/<slug>: how the organism runs, which is what a leader plans within and a reviewer cites, where the design says how an organization is shaped — one store per step, row first, readings never act, legs hold nothing, deploy settles on pulse, the forge decides. Each is one paragraph, a rule a person applies. The family is seeded, listed (grouped operating; hale dna review operating approve|reject), decided and superseded exactly as the design, and upgrade seeds it into an organization whose record never held it. Its receipts carry provenance design, which marks a toolchain-seeded practice of either family; HALE_DNA_DESIGN_SUFFIX (fixtures only) appends to every practice of both. A receipt may also carry a schedule, which the practice declares when it is ratified (Schedules): operating/optimize-cadence carries the optimize pass’s, convened by the Board once a day (The optimize pass).
  • Targets are paths or nodes (seed/pull). A binding’s target is a locus path (org, org/<child>) or a graph node id (language:hale, system:dna, position:leader: The repository’s graph, below). The match rule is one query, ranked_ids and context_ids taking a space-separated set of targets: a word holding : is a node id and matches a binding to exactly that id (language:hale is not language:hale-x, and a prefix of an id is nothing); a word without one is a path and matches a binding to it or to any ancestor, as it always has. An accepted idea is in the package once however many words match it, and a set of one path is the tower rule unchanged. A node is bound from above, never beside: classify_binding calls a binding to a node a goal when its author is a path, so it is never lateral. A proposal or a binding request whose target is a node the record holds no graph.node row for (and no later graph.retired took back) is refused naming it: knowledge.refused for a proposal, knowledge.binding.refused with reason unknown_node for a binding request. The projection’s knowledge_bindings rows carry the target unchanged — no migration; a node id is just another value of the column — and ranked_ids keeps their ordering and budget.
  • The hat’s target set (seed/pull). The hat’s package is asked for a set, in this order (hat_knowledge_targets, dna/core/hat.hl): the Work’s locus path (hat_knowledge_target: org or org/…, else org); language:<l> for every written_in edge of the attached application (application.attached’s name, the node application:<name>), unless the Work is an organization change; then system:dna in their place when it is one — the Work’s Task admission carries the leader’s plan with kind organism (class organization), the same word edit_class_of reads to edit the organization’s own seed; and position:<name> for the position the hat names (hat_position_of), so a mandate bound to the position arrives with the Work. The hat’s JSON names them as targets (a space-separated string) and its digest covers them, so a hat is a different hat when its set is. The owner’s edit path asks the same set for its package, and PRACTICES (ratified knowledge for <targets>, package <digest>) names it. A reviewer’s brief is the same read: hale dna review <id> prints, under knowledge for <targets>, the names of the ideas ratified for the subject’s targets — the node a knowledge Review is bound to when it is one, system:dna for a mutation of class organization — ranked by the Review’s question and bounded by the package’s budget; without memory (HALE_DNA_MEMORY_DSN_HEAD) it says the knowledge was not read, and a subject with no node prints nothing.
  • The nodes knowledge is about, and the library (seed/pull, seed/library). The graft writes graph.node rows for language:hale (kind language, text Hale <toolchain version>…) and system:dna (kind system) and, for an attached application, application:<account name> (kind application, the name application.attached carries — the record named the application by that row alone before) with a written_in edge from it to the language (written_in:application:<name>|language:hale); a repository with no application gets the two nodes and no edge. Beside them it proposes the toolchain’s library: the book per chapter and the spec per section, one idea each. crates/hale-cli/build.rs generates the table (LIBRARY) from the tree at build time — no hand list, so a new chapter joins the library by existing: every docs/src/**/*.md is an idea named library/<path without docs/src and .md>, titled by the chapter’s first heading, and every ## section of spec/*.md is an idea named library/spec/<file>#<slug>, titled by the section’s heading, its text running to the next ## . An idea is bound to language:hale, except the DNA chapters (docs/src/dna/**, docs/src/parts/**, spec/dna.md, spec/model.md), which are bound to system:dna. Each is a knowledge.proposed row of kind idea, provenance toolchain, whose receipt carries the text, the title, the target, the toolchain version and the text’s digest (content_digest). The proposal’s target is the binding: ratifying it binds the idea there, so a library idea has no separate knowledge.binding.proposed row (a binding request needs a ratified subject).
  • A family is ratified by one Review (seed/library). The Board does not review hundreds of ideas one by one. The library is proposed as two families, one per node — library/language@<version> and library/design@<version> — and each family is one review.requested (group library, kind family) whose subject is the family’s own document, a receipt of kind family naming the ideas it ratifies, <name> <digest> a line, in the order they were proposed (the Review’s body carries the same list and a count). hale dna review lists a family as one entry; hale dna review <id> renders the ideas it ratifies; hale dna review library approve|reject decides each pending family in turn, and its verdict is the verdict of every idea it names. Approving writes, for each idea, the rows it would have had of its own — knowledge.ratified (kind, target and class, supersedes when the idea replaces one, the retirement of what it replaces) — and then the family’s own row, library.family.ratified <family digest> when every member was ratified, or library.family.refused <family digest> (with refused, a <name> <digest> line per refused member, and why) when the record refused any — a member whose predecessor another approved revision replaced — so the family is never reported ratified while incomplete; rejecting writes knowledge.declined for each and library.family.declined. A family’s rows land as one batch: the journal is read once, every member’s outcome is decided against that read (a member’s ratification is what a later member’s supersession sees), and the rows — the members’ in the family’s order, each retirement after the ratification it follows, then the family’s own row — are written by one append_batch at the revision they were decided at. A batch that finds the record moved is decided again against what landed, and a row already there is not written twice; a practice ratified alone is still its own one-row write. The family’s own row is what its practice-ratify execution waits for (its hat step is done once memory’s projection holds the last idea, ratified); the per-idea rows are the shape the lanes already project, unchanged. Practices (design, operating, using) keep one Review per practice, and the record stays append-only.
  • Upgrade is supersession (seed/library). upgrade proposes the library of the new toolchain version, for a record that has one, as new families (library/<node>@<new version>) whose ideas each name the active digest under their name in supersedes; approving the family ratifies the new ideas and retires the old, by the existing supersession rows. Until the Board approves it, nothing is retired: an application pinned to the old toolchain keeps the bindings of the version it was seeded at. A family whose earlier version still awaits the Board waits (upgrade says so), and a family already proposed at this version proposes nothing. A record seeded with --no-library gets no library from an upgrade. HALE_DNA_LIBRARY_VERSION and HALE_DNA_LIBRARY_SUFFIX (fixtures only) name another version and append to every text: how a test makes a later toolchain out of the one binary it has. graft --no-library seeds none of the nodes, the edge or the ideas (the practices stay). language, system and application are node kinds and written_in(application, language) an edge kind (The repository’s graph).
  • The using practices (seed/library). A third seeded family, using/<slug>, beside design and operating: how a person or an agent works with the organism — propose-review-ratify, change-classes, ask-the-leader, cut-structure, read-the-record, evidence-first, bind-knowledge. One Review each, grouped using, seeded, decided and superseded exactly as the other two (hale dna review using approve|reject), and kept by --no-library: they are practices, not the library.
  • The charter (GH #596 L). The graft writes dna/org/charter.hl, a function returning text like purpose: the leader’s brief, saying that it is the organism’s architect — it proposes, the Board decides — and what it must know before it plans an ask or decides a Review. Project-owned; a change is a reviewed change to the organism. upgrade writes it for an organization from before it.
  • The leader reads its brief at both moments it thinks. The org chart hands the Leader its charter and purpose (the program’s own text) and a MemoryKnowledge; before a review and before a plan it composes its brief — CHARTER, PURPOSE, the LAW as the genome holds it at HEAD, and PRACTICES from the package for org when memory has any — and puts it above the diffs. The model call carries knowledge_bindings naming the package, so the evidence of every decision that read it says so.
  • HALE_DNA_DISCOVER=off makes the graft’s discovery find nothing — no key, no local model, no harness — so a fixture on a developer’s machine makes the organization CI makes: one whose leader has no model that answers, whose plans take the defaults, and which spends nothing. The CLI fixtures set it.
  • An ask is admitted as a workflow, in the one engine (card 18). Dna.ask(Intent) — intent through the Board, an optimizer’s — passes the Board’s gate, the owner’s (GH #664) and the budget’s, journals intent.offered, and admits a workflow for it (workflow.admitted, the one positive discriminator, under the intent’s id as the admission’s identity — offered again it is the same execution) which the engine the substrate owns for its scope runs. With planned: true on the substrate (the generated org chart says so) the leader’s word comes first: the substrate publishes PlanRequested for the ask, the leader answers TaskPlanned, and the admission binds that word in its inputs — the objective, the kind (organism, appendage, product or person), the class applied (an organism change is class organization whatever the plan called it: the Board’s), the target, whom and under what obligation, the narrative, planned. A person’s job is admitted under the ask-person definition (one human leaf, a case the substrate hands to whom the leader named, with no second word asked — cards 16 and 17 complete it); anything else under ask-edit (one edit leaf under the Patch contract, performed by the substrate’s editor as class and target the word names — the target only when it names a file the genome has under the seed the class edits; a model’s prose is not a path — else class application at the ask’s own target; card 15 reports its outcome). An ask for a judgment (hale dna task create --judgment, the head’s TaskCreate with kind: judgment, or the face’s task form; the intent row carries kind: judgment, and the receipt echoes it, GH #946, #1144) is the asker’s word: no plan is asked, and it is admitted under ask-judge — one leaf requiring judgment under the Assessment contract, which capability-first routing hands to an agent on the first attempt (the legs’ relay: a leg claims it through hale dna work) and to a person on the second. A change routed to a position (hale dna task create --to position:<name>; the intent row carries position, and to stays the ask’s owner, the organization) is the asker’s word too: no plan is asked, and it is admitted under ask-position — one leaf requiring position under the Patch contract, the routed position bound into its requirement at admission (position position:<name>). Such a Work is an agent’s, the legs’; a claim takes it only when its lease’s position is that one: a position the graph states, taken as an agent’s, claims with position and its own id among its capabilities, which no other claimant has: the head first drops any position or position:<name> word the claimant supplied, and grants them from the verified seat alone. Its hat is the routed position’s (charter, mandate, model rule), and its outcome is a leg’s Patch, applied, verified and reviewed as any. Class organization for a child that is not the organism is a contradiction and is refused before anything is admitted (intent.refused). The execution is asked of the engine with the performer kind of every leaf of the whole bound tree, named for the exact Work — its bound id, never its key, which two Works of different steps or workflows may share — the editor’s for an edit leaf, the human kind for a person’s, else the routing policy’s choice for the Work’s own request under the Task that owns it. The tooling’s answer to an ask (hale dna task create) is the execution whose admission names that ask as its request, never the next birth in the record, which may be another ask’s. An answer that names no kind and no class leaves the defaults standing — class application, the ask’s own target — and the admission says it did not parse. On the solo path (below, Workflow execution: human cases), an organism with no model asks no plan: the ask is a person’s job, admitted under ask-person and handed unassigned, for a person to take, and never reaches the editor, which is a model too (spec/habitat.md). Without a leader the ask is an application change. An ask whose leader has not answered yet is in planning, and asked again meanwhile is not asked of the leader twice. No task.planned, task.pending or task.resumed row is written: the admission is the plan, and the execution’s facts are the engine’s rows.
  • Work survives a restart (GH #604, card 18). The record holds an execution’s admission before anything runs; a birth the record refuses stops there (intent.refused, workflow.refused), and nothing has run. On restart the substrate asks its engine again for every admitted root of its own that is not finished — settled AND drained: a cancellation settles the root before its admitted Works have settled, and a root whose tree (its own workflow’s Works and every child’s, by their admissions’ parent_task, and every admitted child itself, before its first attempt or after its last Work settled) still owes a settlement is asked for again, so the engine’s own recovery drains it (cards 12a, 13: the responsibilities still owed anywhere in the tree are counted, and a cancelled parent whose step holds only a child member drains the child through that step, born fenced). A child reborn under a cancelled ancestor reads that cancellation from the record at its own state question — a fence published before it existed reached nobody — proposes its own cancellation, and drains like any cancelled execution; a drained record is asked for nothing and written nothing. The engine restores each from its admission and the record’s facts — under the word bound at admission, never replanned; an attempt whose outcome is recorded is answered from it and never runs again; a case handed stays a person’s and waits. A Mutation’s recovery is derived from every row it has, from its first — its request row, or its proposal when no request names it: one whose last word is the request, the proposal, an open worktree or a located file was in flight and fails, once (mutation.failed); one with a candidate, a Review or an outcome after them is left to that. Its id is counted at the restart and never minted again. A Task with no admission — a row of the shape from before card 18 — is left where it is: nothing resumes it, plans it or settles it. An intent.offered with no admission naming it is noted (intent.unrecovered) and never re-offered: work may already have run.
  • The local habitat holds the organism’s infrastructure (GH #1653). A machine has one local habitat, under ${XDG_DATA_HOME:-~/.local/share}/hale/habitat/local, run as one docker compose project named for the habitat: hale-habitat-local-<h>, <h> the first eight hex digits of the sha256 of the habitat’s directory. No seed and no checkout appears in a project, container, volume or port name. An organism is a tenant of the habitat: its memory, its nerves and its senses’ store are allocations, services of the habitat’s project named for the organism’s record identity and the role — memory-<id>, nerves-<id>, senses-<id>, <id> the first twelve hex digits of the identity [dna] record pins. Two organisms are two sets of allocations in the one project; two checkouts of one organism (two worktrees, two clones) are the same organism, with the same allocations. An allocation’s files live beside it, never in a checkout: allocations/<id>/ holds its compose.yaml (pgvector/pgvector:pg16, nats:2, the senses’ Prometheus; each service on docker’s default bridge, so the habitat makes no network per tenant), its ports.yaml (the host ports, memory’s in the 54000s, the nerves’ in the 42000s, the senses’ in the 39000s, a candidate from the service’s name stepped past any port something already listens on, chosen once for the habitat and kept), postgres.secrets, nats.conf, nats.secrets.conf and senses.yml, the directory mode 700 and every secrets file mode 600. Compose runs as docker compose -p <habitat project> --project-directory allocations/<id> -f compose.yaml -f ports.yaml, with COMPOSE_IGNORE_ORPHANS=1, since the other tenants are the same project’s. A checkout holds no compose file and no server configuration; upgrade removes the ones an older toolchain wrote (dna/compose.yaml, dna/nats.conf, dna/nats.secrets.conf, dna/postgres.secrets, dna/senses.yml) and migrates no container an older toolchain started: there is no migration.
  • The habitat is an organism the toolchain ships (GH #1653). Its genome is the toolchain’s, as the foundational law and the nursery are (hale_dna::HABITAT_FILES, dna/habitat/README.md): a repository organism whose heart slot is empty, its document’s marked law items its law with the nursery’s standard pack, and its fixed positions the nursery’s habitat pack (habitat/placement, habitat/backends, habitat/identity, habitat/admission). It is the one organism born without a parent. hale habitat up [--foreground] [--profile local] births it the first time into <habitat>/genome (written from the toolchain’s files, a repository, then the birth’s seed on it, its record in its own memory allocation, and both packs proposed to its Board), then migrates its own memory and nerves (the habitat’s schema is its own to move, at bootstrap and at a toolchain’s upgrade), brings every tenant’s recorded allocation up through the compose provider — migrating none: an organism’s allocation moves at its own upgrade or with hale habitat migrate <organism>, never under ongoing work — records its placement (the host local the compose provider produces with its offers, the local profile’s policy, and its own head and face placed on that host: spec/habitat.md, Hosts, providers and placement) and starts its body, hale dna dev <genome>: a systemd user unit named for the habitat’s project, or with --foreground in the process itself (what a fixture runs, to ready). up is idempotent, and the hosted profile is not built. A habitat’s backends are chosen at its birth (--backends own|operator, kept beside it and as its habitat.profile row, which names no DSN): own, the default, provisions memory, the nerves and the senses’ store as allocations of its project; operator adopts the Postgres and the NATS HALE_DNA_MEMORY_DSN_OWNER and HALE_DNA_NATS_URL_OWNER name, starts no service of its project, and records each allocation with the service operator (what a suite run on an owner’s DSN and NATS is). up refuses when the environment disagrees with them: an own habitat with an operator’s server named, an operator habitat with either unnamed, or a --backends other than the habitat’s. A unit of an operator habitat reads them from <habitat>/backends.env (mode 600), never from its own lines. hale habitat status reads the habitat, its genome and body, and each allocation: whether each role’s service runs, and the schema version it records against this toolchain’s, naming hale habitat migrate <organism> (or hale dna upgrade) for one behind. hale habitat down stops the body and every service of the project and keeps their data, after the body has drained: it is told to stop and waited for, up to its drain grace (LOTUS_DRAIN_GRACE_MS, the node’s 30 s by default) and a margin, so its drain still reaches its backends; one that outlives that is said and killed, and only then are the services stopped (a unit’s TimeoutStopSec is the same window). A birth that stops part way — memory or docker failing after the identity is pinned — is resumed by the next up; <habitat>/birth.pending says a birth is not whole yet (and which of its profile and packs are done), but whether its record was born is memory’s to say, never the file’s. A pinned identity whose record.genesis memory holds is resumed: the rest of the birth, each step of which keeps what is done, against that record — every seed step checks its own completion and runs when it is missing (the seed when the record holds no application.attached, the seat when its git config keys are absent, each family for what the record does not hold, the library and the models map when their rows are absent), so a graft that stopped part way is finished the same way. Only when memory answers that it holds no genesis for the pin, or the identity’s memory allocation was never made (docker answers it has no such volume), is the identity drawn again and what was made for it taken down. Memory that cannot be reached is a refusal that destroys nothing. Allocation rows wait for the genesis memory confirms before they are written into the record.
  • Every other organism is grafted from the habitat (GH #1653). hale habitat graft <dir> [--attach] [--no-library] [--no-pack] [--with <pack>] births an organism as the habitat’s child: a new application at <dir>, or with --attach the application or repository already there. An attached application’s organism is its workspace’s: the manifest the birth reads (the nearest hale.toml above the seed) is the one its pin and its needs are read from. A graft is complete when the habitat’s record holds its habitat.grafted row; one that stopped part way (a pinned genome whose genesis names this habitat, and no such row yet) goes on where it stopped when run again, each step keeping what is done (the birth’s seed, the allocations, the row, the standard pack, each once). Memory decides whose child a pinned record is, never a guess: its genesis is read from memory (with the clone’s kept head DSN, else one derived from memory that is there, else after the local setup the birth makes), and a genesis that cannot be read is refused, naming why. A genome pinning a record born of another parent, or of none, is refused, and one memory holds no record for is refused by the birth, naming it. The habitat must be born and its record readable, and the environment must agree with its backends. Graft has two halves. Needs by role come first: the genome declares the roles it needs from the habitat in its manifest, [dna] needs = ["memory", "nerves", "senses"]; memory and the nerves are always among them (the record is memory’s, and the organization reads the nerves). A genome that declares none, foreign heart or not (a foreign heart declares nothing), gets exactly the organism’s own roles — memory, the nerves and the senses’ store — and never a heart role (database, broker, cache, object-store): a heart’s infrastructure is its own, and the habitat provides nothing for a heart that did not ask. A Hale heart that needs one declares it. The local profile provisions database: a tenant database of the organism’s own memory server (its memory allocation’s Postgres, or the operator’s on operator backends), heart_<id>, owned by a role of the same name that it alone reaches (PUBLIC revoked), its password drawn into the vault slot postgres-heart_<id>; it is recorded as the organism’s database allocation (habitat.allocated <id>/database), provisioned again by hale habitat migrate, and handed to the heart as HALE_DNA_DATABASE_DSN, a DSN that names its vault slot and carries no password (the attach contract’s name for it; hale dna dev derives it, provisioning nothing). The local profile provisions broker too: two users of the organism’s own nerves server, both confined to its own subject space heart.<id>.>, and neither granted _INBOX, so no grant reaches another user’s replies (the heart’s replies come back under its own heart.<id>._INBOX): heart-<id>, the heart, which publishes and subscribes on all of heart.<id>.>, and heart-<id>-node, the narrower user its nodes connect as, which subscribes on heart.<id>.node.> and publishes on heart.<id>.reply.> and heart.<id>._INBOX.>; no JetStream; their passwords the vault slots nats-<org>-heart and nats-<org>-heart-node, drawn with the organism’s other secrets as every drawn secret is — 32 lowercase hex digits, so a password is safe in a JSON document and a URL as it is; recorded as its broker allocation. broker, and any heart role that needs server configuration, requires own backends: a habitat on operator backends refuses it at graft, by name (the operator’s NATS is not the habitat’s to configure), so a heart that needs one runs in a habitat on own backends — a fixture’s own compose stack, taken down by the sweep — while database, a tenant database and a role, is provisioned on either. cache and object-store are refused by name at graft until a heart needs one, as is a role the habitat does not know.
  • A heart may hand its migrations to the habitat (GH #1653). It is the heart’s choice. A Hale heart that owns the schema of a heart role it declares may say how to migrate it, in its genome’s manifest: [dna.migrations.<role>] with command, the argv of its own migration tool, run in the organism’s directory with that role’s handover (HALE_DNA_DATABASE_DSN, the broker’s) and the rest of the attach handover in its environment. The habitat then runs it at graft, at hale habitat migrate <organism> and at hale dna upgrade, every time: the tool is idempotent and keeps its own ledger, so it decides whether anything moved, and the habitat keeps no version of the heart’s schema. Each run is a habitat act: the role’s allocation is brought up and waited for first, as hale habitat migrate brings it up, so a hale dna upgrade that restarts the nerves for new passwords runs the migration once they are back. Each run is a habitat.migrated <id>/<role> row with the command, its exit status and its output (its last 4096 bytes); a run that fails fails the act that ran it, naming its command, and the next act runs it again. A heart that declares none owns its schema however it likes, migrating on boot included; a foreign heart declares none; and the habitat migrates nothing of a heart’s on its own. A declaration for a role the genome does not need, one with no command, or one with any other key (schema_version among them) is refused by name. A heart role’s allocation carries no schema version: “behind the toolchain” is said only of the organism’s own memory and nerves, whose schemas are the toolchain’s.
  • The attach handover (GH #1653). What the habitat hands a habitat-scoped heart, a secret only ever by its vault slot’s name: HALE_DNA_APPLICATION_ID (the record’s identity), HALE_DNA_HEAD_URL (git config dna.head.url, the local head’s http://127.0.0.1:8792 by default), HALE_DNA_OIDC_ISSUER (dna.oidc.issuer, the local stub’s http://127.0.0.1:8794 by default), HALE_DNA_OIDC_CLIENT (the service client the heart signs in as, its application’s name) and HALE_DNA_OIDC_CLIENT_VAULT (oidc-service-<client>); for an allocated database, HALE_DNA_DATABASE_DSN; for an allocated broker, HALE_DNA_BROKER_URL (the nerves server, no credential), HALE_DNA_BROKER_USER, HALE_DNA_BROKER_VAULT, HALE_DNA_BROKER_SUBJECTS, and the nodes’ HALE_DNA_BROKER_NODE_USER and HALE_DNA_BROKER_NODE_VAULT, which the heart passes a node at its enrollment (a client names its reply inbox heart.<id>._INBOX: pond’s NatsClient.inbox_prefix). Graft maps the service client in the child’s record (git config --local --add dna.oidc.service <client>=<client>) and draws its secret, so none of it is written by hand. hale dna dev hands the set to the host, which the heart inherits, derived (nothing provisioned); hale habitat handover [organism] prints it. The manifest is read once, by graft (a TOML parser: needs on one line or several, with comments and a trailing comma); every reader after it (the handover, the organism’s secrets, its nerves server’s users) reads the roles the habitat allocated, its rows as the allocation keeps them (allocation.json), never the manifest again. Where a heart runs and on what port is the deployment’s (#1477), not the handover’s. Then birth satisfies them: the record’s genesis names its parent (parent, the habitat’s record identity, in the first row’s body, and so in the child’s identity), each role it needs is allocated to it and recorded in the habitat’s record (habitat.allocated, habitat.migrated), the graft is a habitat.grafted <child> row (child, parent, needs, backends), and the standard pack is proposed to the child’s Board (--no-pack leaves it for hale dna pack apply standard, as --no-library leaves the library: a fixture that counts the record’s requests says so). --with <pack> [--set <param>=<value>]… [--as <who>] proposes a nursery pack as well, planned on the holes the birth proposed (hale dna pack apply <pack> --on-holes), so one review all approve ratifies the starting organization whole; a pack the holes refuse is the graft’s failing exit, its organism born. A new application takes the profile flags --profile local|remote-body [--remote <url>] [--body <user@host>]; an attached one keeps its own, and graft refuses them with --attach. hale dna new and hale dna init are retired: each exits 2 naming the graft that replaces it (hale habitat graft <name>, `hale habitat graft --attach`, with the flags it was given). A graft of an organism this habitat's record already holds as its child goes on where its birth stopped; one whose genesis names another parent, or none, is refused.
  • A test’s organisms are grafted too (GH #1653). A DNA fixture makes an organism with dna::grafted(dir, args), into dna::test_habitat(): the DNA suite’s slice habitat when the suite runs it — born once per slice whose fixtures graft, with no body (hale habitat up --no-body), under a scratch root the slice sweeps, on the operator’s backends when the slice has an owner’s DSN and NATS (CI’s: no compose) and its own otherwise, its secrets in a vault of its own that test_habitat lends into the fixture’s (HALE_DNA_TEST_HABITAT, HALE_DNA_TEST_HABITAT_VAULT) — or, run by hand, a habitat of the test file’s own, born the first time. A CLI test does the same through tests/support/habitat.rs (habitat(), graft(cwd, dir, args)), in the habitat of its process’s vault. HALE_DNA_HABITAT names a habitat’s directory outright. A fixture whose subject is the allocations themselves (their compose project, the nerves’ or the senses’ servers) births the test file’s own habitat under its own vault (hale habitat up --no-body --backends own, or operator when it only reads the allocations’ files) and grafts into that.
  • An allocation is a row of the habitat’s record. Every provisioning act is: habitat.allocated <id>/<role> the first time an organism’s role is provisioned (organism, role, service, project, the provider that placed it and the host it is on — compose on local, or on operator backends operator and no host — and the vault slots it holds by name, never a value) and habitat.migrated <id>/<role> each time an organism role’s schema version moves (from, schema_version: memory’s MEMORY_SCHEMA_VERSION, the nerves’ NERVES_STREAM_VERSION), or each time a heart’s declared migration runs (command, status, output; a heart role carries no version). A row is keyed by the organism and the role alone, never a checkout: two checkouts of one organism are one tenant. An allocation made before the habitat’s record exists (the bootstrap’s own memory and nerves, or an organism migrated before the habitat is born) waits in allocations/<id>/allocation.json, the local profile’s cache of the rows, and is written at birth. status reads the rows, and the cache only while the habitat’s memory is not up. hale dna upgrade migrates an organism’s allocation whose recorded version is behind this toolchain’s.
  • The habitat is long-lived, and only its acts provision. Nothing tears the local habitat down when work ends, and organisms are reused tenants from run to run: hale dna dev and hale dna work next run against whatever is up. Provisioning and migration are the habitat’s acts alone: birth (hale habitat up, hale habitat graft) brings the organism’s memory and nerves allocations up, applies memory’s schema and creates the organization’s stream; hale habitat migrate [organism] brings its allocations up and moves memory’s schema and the nerves’ stream to this toolchain’s, idempotently; upgrade moves the schema of an operator’s own memory (HALE_DNA_MEMORY_DSN_OWNER); and the explicit verbs hale dna memory migrate, nerves migrate and senses up remain until the graft retires them. No other command migrates or provisions as a side effect: dev, run, head, show, work and the host’s verbs derive the roles’ DSNs and URLs from what is up (memory-migrate --derive, nerves-migrate --derive, senses-up --derive: nothing brought up, no schema applied, no stream created, nothing written into the habitat) and, when it is not, say so and name hale habitat migrate <organism>. A schema that is behind is the store’s refusal when it opens (memory_fence), naming the same act. With an operator’s HALE_DNA_MEMORY_DSN_OWNER or HALE_DNA_NATS_URL_OWNER, that server is the organism’s, and the habitat’s allocation for it is not used. The owner’s DSN of the memory allocation names the vault’s postgres-owner-<id> (The organism’s secrets), which a database made with another password adopts first. hale dna run brings up nothing: beyond one machine memory is a Postgres of the operator’s. Only a test’s habitat is ever taken down: a test’s hale runs under a habitat of its own (hale test gives each file one beside its vault, the CLI tests one per process), which comes down with the vault. hale test, the CLI tests’ reap and scripts/compose-down-under.sh share one teardown, hale habitat sweep <root>: a project is rooted in the root when a container of it ran from under it, or when a directory under it names it whether or not a container is left (a project whose containers went keeps its network and volumes): each <home>/.hale/habitat/<project> an ssh host was handed a render in, and each habitat born there (…/hale/habitat/local, its project named from that path), never a project one of whose containers ran from outside the root. It names every project rooted in the root in the root’s .dna-compose-projects before it takes any down, and drops a name only once the project’s containers, volumes and networks are confirmed gone, so a teardown that did not finish (a volume docker would not remove) is found again by name and retried by the next — a vault whose habitat did not come down is kept for the next sweep. It, and dna::scratch_done, refuse the developer’s habitat’s project whatever a file under a root names. The test vaults of gone processes are swept by hale habitat sweep --test-vaults [<root>] (hale test‘s first vault runs it, as do the CLI tests’): each vault’s habitat comes down before the vault goes, and a habitat whose vault went first — every toolchain before this one removes a gone process’s vault without its habitat, and they share <tmp>/hale-test-vaults-<uid> — is found by its compose working directory under a gone process’s vault and taken down by its project’s name.
  • Knowledge changes later work (K2). The substrate holds a MemoryKnowledge (budget, 8 by default; memory opened under the spine’s role), the owner’s line to memory; the editor never holds one, which the law states (group knowledge = { dna::Knowledge, dna::MemoryKnowledge }, editors_never_learn). When a Mutation opens, the substrate asks memory for the package of the change’s target in the tower — org for an organization change, org/<child> for the application, org/<child>/<seed> for a seed inside it — and journals knowledge.consulted <mutation> (target, hat, digest, revision, included_n, included, error) whenever memory is named (HALE_DNA_MEMORY_DSN_SPINE), answer or not. The package becomes the hat the attempt wears (GH #946): built by the owner, kept in memory by its digest, and named on the request as its context_digest; the editor is briefed with the package’s ideas (SourceEditor.brief: what a model is shown is the ask, then a PRACTICES (ratified knowledge for <target>, package <digest>): block, one idea per line) while the record, the commit message and the Mutation keep the ask itself; and the request names the package (knowledge_bindings: package:<digest> <id>…), which every model call of the attempt carries into its model.called row. No memory named is an empty package that says so, and nothing waits on it; memory that cannot answer, or whose projection has not reached the record’s head within 20 s, is a package refused with the reason (The spine projects; readers read).
  • Concerns (K2). ConcernRaised (source, what, severity) is a child’s signal about the part above it: an application’s own concern.raised event, which the heart lands and the spine puts into the record (K4), or hale dna concern raise <source> <what…> [--severity N]’s, each a concern.requested row a node relays onto the nerves, and the substrate admits it as an execution of concern-escalate (GH #995: The workflow catalog) whose step journals concern.raised <source> (an object: what, severity, occurrence, request, parent, task), and when one source has raised it concern_threshold times (3) it becomes a knowledge proposal by that source bound to its parent path — a concern by the tower rule — through propose_knowledge, with concern.proposed <source> naming the digest, once. A source with no parent (no /) is refused with knowledge.refused concern:<source>. Which occurrence a concern is counted from the record, never from a number the organism keeps, so a restart continues the count where the record left it; a count that starts again is a record read short, not a restart (GH #748). An execution a restart interrupts is resumed with every other; a source over the threshold with no concern.proposed of its own and no concern-escalate running for it — rows that reached the record another way — is owed a proposal that nothing but a further concern would make. At birth every such source is escalated once, by an execution whose raise step finds its raises in the record (rehydrate); a source with no parent was refused where it was raised and is not refused again.
  • The repository’s graph (GH #1085). An organization and its product are one recursive hypergraph; the org chart and the deployed process model are two perspectives over it, neither its trunk. The record holds it as rows and memory projects it with everything else: one record, one projection, no second store. The vocabulary is dna/operations/graph.hl. Node kinds: purpose, axiom, process, seed, contract, noun, deployment, practice, gate, document, witness, position, work, organization (a firm, GH #1123), definition (a workflow catalog definition a schedule convenes, GH #1143), and what knowledge is about (seed/pull): language (language:hale), system (system:dna) and application (application:<name>), which the graft seeds and an ingest of a repository never reads; a node’s id is <kind>:<name>. Hyperedge kinds, arity two or more, each a list of members {role, node} whose first is its anchor: unfold(parent, child), meets(contract; server…, consumer…, carrier…), names(contract; noun…), refers(from, to), constrains(axiom; shaped…), runs(deployment; process…), gates(gate; guarded…), binds, witnesses(witness; about…), holds(position, holder), reviews(position, subject) — the subject a contract, a document, a seed or a deployment: what the position signs a change to, a design document being signed too — and convenes(position, definition) (GH #1143), a position convening a definition on a cadence, written when a schedule is declared (Schedules), its body carrying the cadence (every 1d, every 90s, cron <expr>) and the schedule; its position is a position node and its definition a definition node, and written_in(application, language) (seed/pull), the codebase an application is written in — an application node and a language node, keyed by both, the edge a hat reads its language targets from. A holds edge is a seat or a membership (GH #1123): holds(position:<p>, <person>) is a person’s seat, holds(position:<p>, organization:<o>) is organization o owning position p, and holds(organization:<o>, <person>) is the person’s membership of o; its position role takes a position or an organization node, its holder is a person or, for a position, an organization node, and an organization never holds another organization (Owners). A position has one owning organization and a person one: memory refuses a second of either as a hold.refused row, and hale dna fill refuses to propose it. A pair kind (unfold, refers, holds, reviews, convenes, written_in) is exactly its two members and is keyed by both, so an unfold is one edge per child; any other kind is keyed by its anchor, so there is one meets per contract and the latest row says who meets there. An edge’s id is <kind>:<anchor> or <kind>:<anchor>|<second>, and no name or holder holds a |, so an id reads one way. A role may require a node kind (a meets contract is a contract: node, a runs process a process:); a holder is a person or an organization node; meets also carries its transport (via) and the parties it reaches that are not nodes (outside, e.g. callers). Two kinds are never graph rows: a practice is a knowledge idea of kind practice — advice unless the Board ratifies it as law (its document says law: true; one ratified as advice stays advice) — and binds is its knowledge binding; a member names a practice as practice:<digest>. The rows are graph.node (entity the node’s id, body {kind, name, text?, source?}), graph.edge (entity the edge’s id, body {kind, members, via?, outside?}) and graph.retired (entity the id). The tail checks a graph row whole before memory takes it — the kind is the vocabulary’s, the anchor comes first, every member names a node of the kind its role requires and no node twice, the arity is two or more, and the entity is the id the body implies. A row that fails is refused as a row, as a refused hold is: memory appends graph.refused <row id> (kind, why, row, by: memory) once, says it on stderr, and the projection goes on past it, so one bad row never holds back every read waiting on the watermark. A graph row inside a ratified proposal is checked and refused the same way, at the row that ratified it, and the proposal’s other rows land. A store that fails to take a valid row still stops the projection at that row: that is the database’s failure, retried, not the row’s. So does a refusal the record cannot take (an I/O failure; a race with another writer is retried first): the row is never passed without its graph.refused or hold.refused, and the next pass writes it. A node or edge is replaced by a later row with its id (an edge’s members with it) and taken out by graph.retired; a retired node’s edges stay as the record left them, and the perspectives leave its memberships out (an edge whose anchor is gone is not shown). There is one org chart, and it is the graph: its position and organization nodes and their holds edges, proposed and ratified as holes and holds (below). Who owns a position and who is a firm’s member are holds edges, never a file: the owners map (dna/org/owners) is retired, not rendered (GH #1123) — a graft writes none, and hale dna upgrade removes one, noting the lines it named to be stated again as holds edges, and takes the generated ownership: field out of dna/org/main.hl (or notes it where the file was edited by hand). The organization’s generated dna/org files and the organism’s program are written beside the graph, never its source, and nothing reads a position from them that the graph states. Perspectives are queries over memory: KnowledgeStore.graph_perspective("org") is the positions in the record’s order, each with the node it unfolds from (under), its holders and the contracts and documents it reviews; graph_perspective("processes") is the processes, every meets (contract, via, servers, consumers, carriers, outside), every runs (deployment, processes) and every convenes (position, definition, cadence, schedule), each as JSON the server builds. graph_nodes_count(kind) and graph_edges_count(kind) count them; practice counts the practices proposed or ratified and binds the ratified practices that bind something, one hyperedge each. Pq.graph_ownership() is JSON [node, holder] pairs of every live holds edge an organization is in (Owners).
  • Ingest: hale habitat graft <dir> --attach on a repository (GH #1090). A directory with no Hale source of its own that is no workspace’s seed is a repository, not one application: the graft makes the organization there as it would for an application — vendor/dna, dna/org, the manifest with [claims] no_base and [environments.org] alone (there is no application entrypoint) — cuts no topology artifact, and seeds the record with the repository’s application attached, then what the repository holds, as the graph, through the host verb graph-ingest, then proposes the declared purpose (GH #995) (dna/operations/graph_ingest.hl). It never derives one perspective from the other. A directory holding .hl files is a seed: with a main locus it is an application, attached as before and read into the graph too (the next bullet), and without one it is refused as before. Ingest reads the tracked files (git ls-files, paths as git holds them with core.quotePath off) and recognizes: the purpose — the README.md’s title and first paragraph, and none without a README.md, so an empty tree is an empty graph; processes — the compose file’s services (compose.yaml, compose.yml, docker-compose.*), then the compose deployment that runs them (built) — processes first, since a perspective lists in the record’s order; seeds — each directory holding hale.toml, package.json, Cargo.toml, go.mod or pyproject.toml, the outermost only (a workspace’s members are its own), never the root; a process unfolds into the seed of its name. A ui/ at the root holding no manifest is no seed (ruling 11b, 2026-10-12): when the repository references it — a link in a tracked markdown document outside it resolves into it, or the compose file names ./ui (a volume or a build) — the standard face serves it as the application’s static assets under /app/ (/app/ its index.html, /app/<path> the file ui/<path>, nothing outside it, for the attached project, which the face asks the head’s read for: ops::application_asset; the head serves none; a file’s bytes are written as they are, with their length and the extension’s content type, so an image or a font arrives whole), and the graft says so on a note line; when nothing references it the graft says once that it is ignored. Neither writes a row. contracts — spec/*.yaml, spec/*.yml, spec/*.md and each directory under spec/vendor/; nouns — a YAML contract’s components.schemas and a markdown contract’s CREATE TABLEs, named by the contract; a noun is its name, so two contracts that name one merge into one node, which both names; then refers, from a contract’s local $ref: '#/components/schemas/<X>' (never another key that holds such a path, never a reference that leaves the contract) or REFERENCES <table>, only to a noun the same contract names — every noun of a contract is written before its refers; documents — every .md; gates — each CI job (.github/workflows/*), named <workflow>/<job name>, guarding the seeds, contracts and compose deployment its steps name; witnesses — each FRICTION.md entry (a ## heading) and each issue it links (<owner>/<repo>#<n>), about the seeds, contracts and documents it names. What structure cannot say is written in markdown, in two forms. A marked list item: a code span opening a list item names its kind — axiom, derived, practice or law — and its first bold span is its name; an axiom is a node under the purpose, and the repository files it links to are what it constrains. (derived, practice and law are practices, proposed at record birth, GH #1091.) A declaring table: a table whose columns include Served by and Consumed by declares one meets per row — the row’s first link to a contract is its contract, Over its transport — and one whose first column is Deployment and has Runs declares a named deployment and what it runs. In a cell a name in code is a process, else a seed, else a repository path; a link is the node its target is (relative to the document); plain words are a party outside the repository; a code span inside a link is the link’s text, not a name; an Over that names a process makes it the carrier. Links resolve to a contract, else a document, else the compose deployment, else the seed holding the path. Every row comes out once, a node before an edge that names it, and the host checks every graph.* row against the vocabulary before it appends any (record-seed does the same for a seed file), so an ingest the graph refuses leaves the record as it was. A YAML mapping’s children are indented as its first child line is, never by an assumed two. Input the conventions cannot take is refused, with the reason, never dropped: an axiom item with no bold name; any name the vocabulary refuses (a |, a control byte, longer than 512 bytes) — a witness’s title, a schema’s, an axiom’s; a meets row that links no contract; a runs row that names no deployment; a name in code in a declaring table that is no process, seed or file of the repository. Nothing is appended then, the purpose’s Review included (the graft seeds it and the graph in one graph-ingest call), so the record does not exist and the graft can be run again once the document is fixed. A job’s own name: names its gate and is not a word its steps say. The graft says what it read: graph <n> node(s), <m> edge(s): <count> <kind>, …; <p> proposal(s). A record that exists is not reseeded.
  • Ingest: hale habitat graft <dir> --attach on an application (seed/structure-2). An grafting an application enriches this one path; there is no second proposer and no starter chart. The artifact’s rows are unchanged (application.attached, structure.observed per locus, topic, binding and effect class, responsibility.proposed per locus: facts the Board reads, from which nothing is derived into the graph), and they land in the same graph-ingest call as the purpose’s proposal and the graph — graph-ingest --rows <file> --app <name> --description <file> — so a graph the vocabulary refuses leaves no record. The ingest then runs over the application’s workspace root as for a repository (README purpose, compose, spec/* contracts and their description roles, documents, CI gates, FRICTION witnesses, marked lists and declaring tables), with two additions the application’s own structure gives. The application is the root seed (seed:<name>, the project directory’s name, or the application’s directory when it sits below the workspace root, where the manifest walk finds it as before): the root that is never a seed for a repository is one here, a compose process of its name unfolds into it, a CI step naming . guards it, and an application with no README.md takes the purpose the graft declares (dna/org/purpose.hl) as its purpose node, so the board has a place. And each API surface it serves is a contract the seed meets as server: api:<Surface>, from the toolchain’s own export (hale check --api, handed to the host as a file), one node per surface; a stream served over a hub is stream:<Topic>. A surface whose lower-cased name is the stem of a spec/<stem>.md or .yaml contract (the stem rule that reads spec/<Stem>.description.json) is that contract’s node, not a second one; a contract a declaring table already gives a meets keeps the table’s row. The requires of the surface’s members, and of a stream, are the contract’s roles: the equipment of the positions that serve it (Standard equipment, below), never an organization and never authority. The graft says what it read on the same graph … line, and writes dna/org/structure.hl as for a repository. A fresh application whose export is empty has two nodes (its purpose and its seed) and no edge.
  • Holes as proposals (GH #1091). What a repository cannot imply and a delivery needs, the graft proposes at record birth, each on its own with one Board Review, in the same checked seed as the graph (so a repository whose holes cannot be proposed leaves no record either). The holes are derived from the graph’s edges, never enumerated; a part is a process, or a seed no process unfolds into (a UI, a validator); a cell naming a seed a process unfolds into (a link, a path inside it) names that process. The graft proposes: a board under the purpose; under each part that serves or consumes a contract (meets) — and under an application’s own seed whether or not it serves one — a reviewer whose proposal carries a reviews edge to the part’s seed and to every contract it serves or consumes; under each part whose seed a gate guards, or that serves a contract a gate validates (gates) — and an application’s own seed always — a dev, and a work item where a gate names it; a dev and a work item for each validated contract no part of the repository serves; under each deployment an operator with a reviews edge to the deployment; and each item a document marks derived, practice or law, proposed as advice (a law item’s question asks for law). The one structure policy is the project’s: dna/org/structure.hl, which the graft writes for a repository or an application unless one is there already, and whose fn operational_roles() -> String names, space-separated, the operational roles each deployment also gets (support, accounts, billing, on-call), proposed empty and signing nothing — "", none, by default. It is read once, when the graft seeds the record, so a project opts in by writing the file before grafting; a word that is no operational role refuses the seed, and a role named twice is proposed once. upgrade rewrites an existing file to the current shape, keeps the roles it names, and notes every other line the rewrite dropped. A work item is an unfold of its part (or its contract), named for the part — or for the contract’s file when the gate guards a contract (store.md), its whole path where a part already has that name; two parts delivering one contract give one work item, the first’s — its node’s done_when the first gate guarding it: the question proposes “done when <gate> passes”, and the Board amends that wording at ratification. A hole is a knowledge proposal: its receipt (kind: graph, rows) carries the graph rows it would add, each checked by the vocabulary before it is proposed; knowledge.proposed and review.requested (required_authority: board, group: holes or practices) are the rows a practice is proposed with. The existing review path ratifies it (knowledge.ratified); the projection, seeing a ratified document of kind graph, puts its rows into the graph at the row that proposed it (its knowledge.proposed), checking each again, so the org chart lists positions in the order the holes were proposed whatever order the Board ratifies them in; the proposal stays in memory as the idea it was, bound to nothing, so it never enters a context package. Ratifying them fills the org chart. hale dna review lists the groups holes, practices, holds, positions, changes and ingest together, as it does design and operating, and hale dna review <group> approve|reject decides each pending one in turn. A seeded group is listed, and decided by its name, in the order the record requested its Reviews (a position, then its mandate), never by digest. Opening a position at run time (hale dna position open <name> --mandate "<text>" [--text <what it is>] [--under <part>] [--as <who>]). The CLI refuses a position the record already states or proposes, resolves a bare --under to the seed, work part, position or process the graph states by that name, and appends position.requested in the asker’s name, relayed as PositionOpenRequested until it is answered. The organization refuses a name with a space or |, no mandate, no author, a name the graph states, or an under the graph does not state (position.refused); otherwise it proposes the position’s node (the body the graft writes for a hole, and an unfold edge from the part with --under) and its mandate as two members of one family Review in the group positions (position.proposed), which ratifying ratifies both. A batch of changes (hale dna apply <file> [--dry-run] [--as <who>], dna/core/batch.hl). Several changes to the structure as ONE Board Review, applied all or nothing. The file is one step a line, in the language the scenario runner reads: open <name> [under <node>] | <mandate>, fill <name> <holder>, fill * <holder>, vacate <name> <holder>, practice <name> [for <position>] | <text>, law <name> [for <position>] | <text> (a practice its document proposes as law, law: true, which the Board ratifies as law or as advice; GH #1591), idea <name> [for <position>] | <text> (knowledge, such as an external source), retire-practice <name>, nursery <source>, assign|unassign <name> reviews <kind>:<node>, close <name>, merge <name> into <name>, split <name> into <name> <name>, expose <kind>:<node> as contract:<name>, depend <kind>:<node> on contract:<name>, externalize seed:<name> and internalize contract:<name> as seed:<name>; # starts a comment, and a name is a position’s, with or without position:. The host plans the batch against the record (batch_plan): a line that is no step, a name with a space, | or tab, an open with no mandate, a position the graph states or the batch opens twice, an under that is neither a node the graph states nor a position the batch opens, a fill of a position the graph does not state and the batch does not open, a fill of a stated position with no ratified mandate, a hold the organization would then hold twice, or a vacate of a hold the record does not state, asked twice or filled in the same batch, a practice or an idea named twice, one for a position the graph does not state and the batch does not open, or one already in force with another text or target is refused whole, naming the step, and nothing is written. A practice or an idea for a position is bound to it (its document’s and its member row’s target), and to org without one. One in force with the same text and target is kept: the step leaves the batch, so a pack adopted again proposes only what it adds, and a batch left with no step is refused, nothing to propose: every step is in force already. Opens are taken first, so a step may name a position a later line opens; every other step is taken in the order written, against the organization the steps before it leave. fill * <holder> is taken last: every position the organization would then state with a ratified mandate (or one the batch opens), no holder and no vacate in the batch is filled by the holder, in the chart’s order, and the step is written into the batch’s normalized steps as the fills it made, so the Review and the plan at ratification name each seat; a wildcard that finds no vacant seat is refused. Planned on pending proposals (--on-holes, batch_plan_on). hale dna apply <file> --on-holes and hale dna pack apply … --on-holes (what graft --with passes) plan the batch on the holes hale habitat graft proposed that are not decided yet (batch_init_holes: each review.requested in the group holes authored by hale habitat graft whose digest has no knowledge.ratified, knowledge.declined or knowledge.refused): the record is read with a knowledge.ratified row for each appended (PlannedOn, a read-only journal), so a hole’s position, its edges and its mandate are stated for the plan. The pending proposals the plan stands on — a graph proposal one of whose row ids the batch’s rows or steps name, and a mandate whose target they name — are its planned_on, one <review id> <digest> a line, carried in the family document and in batch.proposed; the question adds ; planned on <n> pending Review(s), it is ratified after them, and the printed chart a line naming them. A pending proposal the batch does not touch (a work hole) is no ground of it. With no planned_on the family’s documents are byte for byte what they were. Ratifying a batch with planned_on (resume_family, batch_ground) first reads its ground: while one of them is not decided, the ratification waits (the verdict settles; its execution is performed again on the next verdict or tick), so approving the batch before its holes is not a refusal; when one was declined or refused, the batch is refused whole as stale (the batch is stale: the proposal it was planned on, k:…, was not ratified); when all are ratified, it is planned again against the record and its effects compared, as every batch is. A pack that opens a position the graft already proposes is refused when it is proposed ( `position:…` is a position the graph already states), not at ratification. The operations, as graph rows. assign adds a reviews edge (reviews:<position>|<node>; the node a contract, document, seed or deployment the graph states) and unassign retires it. close retires a position: its holds, what it reviews, the unfold edge it hangs from, then its node; never position:board, and never one the batch opens. merge a into b fills b with every holder of a it lacks and assigns it what a reviews, then closes a. split a into b c opens b and c, each with a’s ratified mandate to start from (the Board amends either later; refused when a has none), fills both with a‘s holders, assigns both what a reviews, and closes a. expose n as c states contract:c if the graph does not and writes its meets edge with n its server (refused when the contract is met already); depend n on c restates that edge with n a consumer. externalize seed:s: every contract the part serves is restated without it, the part’s name added to the edge’s outside parties (refused when nothing in the organism consumes the contract, or when the part serves none), every position unfolding from it closes, and its node retires. internalize c as seed:s states the part again and restates the edge with it the server and its name out of outside. A batch’s rows are each id once, at the last write its steps make (an assign after an unassign of the same edge stands; a retirement of an id only the batch added is dropped), filed as its nodes, then its edges (each restated meets once, at its last body), then its holds, then what it retires. The family carries effects, a digest of those rows and the mandates, retirements and nurseries it proposes; ratifying plans the batch again and refuses it as stale when the steps would now do anything else (a contract that gained a consumer meanwhile keeps it). Knowledge bound to a closed position stays bound to its node; rebinding it is not built. The org chart a batch is planned against also lists reviews <position> <node>, part seed:<name>, serves|consumes|carries <node> <contract> and outside <contract> <party>. Planned, the batch prints the org chart before and after (position <id>, mandate <id>, holds <id> <holder> lines, + where the batch adds one, - where it takes one out), and --dry-run stops there. Otherwise the host keeps the receipts and appends, in the asker’s name: one graph proposal (kind: graph, name batch/<request id>, target org) whose rows are the opened positions’ nodes, then their unfold edges, then the holds, then a graph.retired for each hold vacated; one mandate per opened position (mandate/<name>); one practice per practice step (kind practice, its name, target org, the proposer its author); each a member (knowledge.proposed, no Review of its own); the family (kind: family, its ideas the members in that order, and batch, the normalized steps), with its review.requested in the group changes under the Board; batch.proposed <request id> (digest, review_id, by, steps); and graph.requested <request id>, which a running organization answers by opening and routing the Review (graph.proposed), as it does a re-ingest. The same steps while a batch of them is pending are answered with that batch’s Review. The Board’s one verdict decides every member in one append (resume_family); an approval first plans the batch again against the record as it then stands, and a batch whose ground moved (a position it opens was opened since, one it fills was retired) is refused whole: every member knowledge.refused with why the batch is stale: …, the family library.family.refused, nothing of it projected. Memory still admits each ratified hold as it admits any (hold.refused for a holder it does not know), so the batch checks structure, and the store stays the gate on who may hold what. hale dna vacate <position> <holder> [--dry-run] [--as <who>] is a one-step batch: the holder leaves, the position stays. Every reader of holds takes a retirement carried by a ratified proposal as it takes a graph.retired row: the graph, memory’s projection, the head’s seats (HoldsReader) and the CLI’s and the head’s structure commands’ (held_state_of, dna/operations/structure_command.hl). The org chart a batch is planned against also lists practice <name> for each named practice in force. Packs and the nursery (hale dna pack list | show <name> | apply <name> [--set <param>=<value>]… [--from <dir|git-url[#rev]>] [--dry-run] [--as <who>] [project]). A pack is a batch with {param} placeholders; a nursery is a directory or a git repository whose nursery.toml lists its packs ([[pack]]: name, version, path, summary, params with each one’s default, "" for none). With no --from the nursery is the toolchain’s (dna/nursery, embedded: dev-team, small-startup, content-team, founder, fill * {founder}, one person in every vacant seat, what graft --with founder --set founder=<who> proposes, and standard, the standard practices every organism is to adopt at birth, each a law step that extends the foundational law once ratified: its first clause, standard/logs-are-structured, is that every line a running part logs is a record, held by the Board in review until the compiler classes the stdio streams, the law can name a program group and std::log is the logger (GH #1591); beside the clauses it holds a template, two ? lines kept only when integration, position, source, kind and every are all set, that propose one watched upstream: the practice standard/upstream-watched/<integration> and the idea standard/upstream-source/<integration>, both for the position (Upstream watches)); a git repository is cloned into $XDG_CACHE_HOME/hale/nursery/ and read at the revision after #, its default branch’s head without one. The CLI renders the pack: every parameter replaced by its --set value or its default, a line beginning ? kept only when every parameter it names has a value, and a parameter with neither refused by name. apply proposes the steps as a batch whose family and graph proposal are named pack/<name>@<version>, its question ratify adopting the pack <name>@<version> …, and batch.proposed carries pack: the name and version, sha256: of the pack’s text, and its source (toolchain <version>, the directory, or <url>@<commit>). hale dna pack retire <name> [--dry-run] [--as <who>] undoes the latest adoption of the pack the Board ratified, as one batch (pack/<name>/retired): every position it opened that is still stated closes (its holds and reviews with it), every hold it filled and assignment it made on a position that stays is taken back, and every practice it proposed that is in force retires (retire-practice <name>, a member of kind retirement superseding the practice, which ratifying retires); whatever has gone since is passed over. A pack supersedes nothing, so there is nothing to restore. A git nursery is trusted by the Board before its packs are adopted: hale dna nursery add <source> proposes the batch step nursery <source> (a knowledge idea of kind nursery, named by the source, target org), and once it is ratified the org chart lists nursery <source>; hale dna nursery list prints the ones in force. pack apply --from <git URL> is refused, before anything is fetched, while the URL is not one of them, unless the habitat’s Board trusts it (hale habitat nursery add, below); the toolchain’s nursery and a directory need no trust, and reading a nursery (list, show) needs none either. Saplings (phase 4) are a second kind of nursery entry beside packs, and packs stay what they are: [[sapling]] with name (one word, no /), summary (its purpose), repo (a git URL or path), revision, digest (sha256: over git ls-tree -r --full-tree <revision>, the tree’s listing, so one tree is one digest at any commit), packs (the packs it adopts at graft, by name, from its own nursery or else the toolchain’s) and set (their parameters). A sapling is grafted by name into the habitat (spec/habitat.md, Nurseries and saplings); the toolchain’s nursery offers hale, and a repository may be its own nursery (repo = "."). Every mutation is a head command; the UI is a client. A change to the structure asked of the head is not a second code path: the head’s surface HeadCommands carries Commands::open_position (PositionOpening { request_id, record_head, position, text, mandate, under }), Commands::fill (HoldFill { request_id, record_head, position, holder }) and Commands::apply_batch (BatchApplication { request_id, record_head, steps, pack }), each requires: [position], and the read Commands::preview_batch (BatchDraft { steps }, ungated). The commands are operations of the composed head’s governance service (dna.position.open@1, dna.hold.fill@1, dna.batch.apply@1, dna/operations/structure_command.hl), admitted and recovered by request id as every command is, and their handlers write the rows the CLI verbs write, in the caller’s person’s name: position.requested and hold.requested with the CLI’s keys in the CLI’s order, and a batch’s receipts kept and its rows (members, review.requested, batch.proposed, graph.requested) landed together by one ref move, batch_filing’s bytes; each request row’s id is the one the CLI mints for the same ask by the same person at the same head (p, h or b and twelve hex digits of the digest of the head and the ask), and the command’s fields (command_format: dna.structure-command/1, command_id, command_payload, command_fingerprint, command_authority, command_authority_basis, command_record_head) follow the CLI’s keys in the request row (batch.proposed for a batch), so a relay and the organization read it as the CLI’s. Before a row the head checks what the CLI checks, over the same state: a position the record states or proposes is not opened again, a fill takes the CLI’s fill refusals word for word, and a batch is planned whole; a refusal is a receipt (state: refused, structure.why) and no row, and the same steps still pending are answered already_proposed with the Review they await. A command is conditioned on record_head (stale_subject once the Record moved). The receipt’s structure branch (state, request_id, review_id, event_id, why, warning) reads requested until the organization answers the request row, then proposed with its Review or refused with why; the Board decides through Commands::verdict. vacate is a one-step batch through apply_batch. Commands::preview_batch plans a batch against the Record’s head and writes nothing: BatchPreview { ok, code, why, application_id, head, revision, steps, before, after, added, removed, unchanged, pending }, the org chart’s lines before and after and those the batch adds, takes out and keeps, one a line, and pending the Review the same steps await. The habitat UI is a client of the generated surface (hale api client --surface HeadCommands), and wires its affordances to these members. Mandates (seed/structure). Each position the graft proposes carries a mandate, proposed in the same seed right after its hole: a knowledge idea of kind mandate (family mandate, author: hale habitat graft, name mandate/<position name>, target the position’s node id, position:<name>), whose text states what the position decides, what it may not, what it cites, how it writes and what it escalates. review lists it under holes directly after its position (question: ratify the mandate of <p>: what it decides, what it may not, what it cites, how it writes, what it escalates), so hale dna review holes approve ratifies the position and then its mandate; the ordinary ratification binds the idea to the position’s node (a mandate is not a graph document, so it is bound, unlike a hole). A mandate ratified before its position lands is a binding whose node does not exist yet: nothing refuses it, and the hat finds it once the position does (hat_knowledge_targets names the performer’s position:<name>, so a mandate bound to it arrives in the hat’s package and brief). The texts are the toolchain’s (dna/operations/graph_mandates.hl), one per position kind the proposer knows (board; a part’s dev and reviewer; a deployment’s operator; each operational role), the part’s or deployment’s name substituted. The authority defaults are text in the mandate: which change classes and magnitudes the position may ratify alone, which need a second signer, which are the Board’s, in the law’s names (OrgPolicy: the classes organization, constitutional, process-policy and topology, and any change that touches law, widens effects or crosses ownership, are the Board’s; the rest, inside the grant, the Leader’s), cited and not re-implemented; the charter refines them. The organism’s own positions the hat already names (position:leader, position:editor, position:agent) get theirs at the graft and upgrade under design beside the design practices (design/mandate-<name>, a seeded practice with a target, proposed as a mandate bound to it and superseded on upgrade the same way). Standard equipment. A proposed position’s node body carries equipment, a JSON object with roles (the API roles the position is fitted with on fill) and nothing else for now. It is derived from the graph the ingest read and the toolchain’s own export: for a contract whose stem matches a spec/<Stem>.description.json (what hale api describe / hale check --api writes) case-insensitively, the ingest keeps the requires roles of its members (GraphIngest.roles, <contract>\t<role> lines; the description is the contract’s roles, not a node of its own; an application’s own surfaces and streams are contracts whose roles are read from the same export, § Ingest on an application). A role named like a position’s last segment (reviewer, dev, operator, an operational role) is that position’s equipment for the part that serves the contract (for operator and the operational roles, the deployment that runs the part); every other role (editor, reader, …) is the equipment of the part’s dev. A repository with no description proposes the same holes with empty equipment. The ruling that governs this: an API surface’s requires informs contract relationships and equipment; it never determines the organization and never grants authority. The reviews edge over the surface’s contract is what it was; no position exists because a role does. The seeded practice design/standard-equipment states the list: API roles now; the broker account and the vault names of the application a repository attaches are not proposed as equipment yet. hale dna fill <position>|organization:<name> <holder>|organization:<name> [project] [--as <who>], once it has proposed the hold, prints equipment: <roles> when the position’s node names API roles (read from the node the record states or proposes); granting them, a token that carries those roles, is deferred, and fill grants nothing. It asks the running organization for a holder — a person’s seat, a firm owning a position (fill org/collections organization:north) or a person’s membership of a firm (fill organization:north bob), each a holds edge (GH #1123) — as hale dna practice propose asks for a practice (the graft alone writes proposals directly, since no organism exists yet): a hold.requested <id> {request_id, position, holder, by} row in the asker’s name, relayed as HoldRequested until the organization answers hold.proposed (digest, review_id) or hold.refused (why). The organization proposes it like any hole (group: holds), in the asker’s name. Whoever holds the Board may ratify it, the asker included: a governance proposal requiring the Board’s authority (the organization marks its knowledge Reviews governance) is the one exception to a Review’s independence from its author (a solo organization seats itself), which every change to the work keeps, a mutation requiring the Board included. The CLI checks first that the position or organization is one the record states or proposes and has not retired (graph.retired); a firm holding a position needs no person checks — only that the firm is an organization the record states or proposes, and that it holds a position, never another organization; a person must not be a name the organism writes as, must not have retired (person.retired), and, for a seat, must be a person the record knows — who wrote a row in their own name, or an organization’s member — under the same-holder rule. A membership’s holder need not have written a row before: joining an organization is how a person becomes known to a shared record. The store is the gate: a hold is checked again where memory projects it, however it reached the record — a seat whose holder the record never knew before the row (no row in their name, and no organization’s member) is not projected, nor is a retired holder’s, nor a membership of a second organization (a person belongs to at most one), and a part’s reviewer held by its dev’s holder is a warning on stderr under dna.trust = local (one person holds every role) and not projected under any other trust. A hold the store refuses is a row, once: hold.refused <hold id> (why, row, by: memory). A person retired after their hold was projected gives every seat and membership back where the retirement is projected. fill and practice propose name a request by its digest at the record’s head (h…, p…), so two requests in one millisecond never share an id.
  • A repository record builds its organization alone (GH #1091). When the record’s seed holds no Hale source of its own, hale dna dev and hale dna run check and build the organization only: there is no application to cut, build or restart on a Mutation’s request (the body says so). Its expression is the repository’s compose services (below).
  • A repository’s application is its compose services (ruling 11, 2026-10-12). The compose file is a repository’s expression, and the record attaches its application all the same: the graft lands application.attached (entity .) with the project’s name as name, repository: true and no main, artifact or shape (nothing was cut), before the purpose and the graph. Its broker account is drawn as any attached application’s (The application’s account): app-<name> in the vault and the nerves allocation’s nats.conf, and hale dna nerves migrate prints HALE_DNA_NATS_URL_APP, HALE_DNA_NATS_USER_APP and HALE_DNA_NATS_VAULT_APP for it, so what the repository’s processes publish under <org>.app.<name>. reaches the heart. hale dna status says expression: the repository's compose services, attached as <name>. Such a row is not an application’s artifact: a repository is re-ingested (hale dna ingest) with it.
  • The body runs a repository’s compose services (ruling 11). Under dev and run, a repository body whose root holds a compose file (compose.yaml, compose.yml, docker-compose.yaml or docker-compose.yml, compose’s own order) brings its services up once the organization reads its nerves, as the organism’s own compose project, <record schema>-<application> (dna_<id>-todo): two organisms on one machine, or a person’s own docker compose up of the same file, never share a container. up -d --wait (300 s, scaled by HALE_DNA_WAIT_SCALE) waits for every service to be running, and healthy where it declares a healthcheck; the body says the repository's compose services are up (compose project <p>), or that they did not all come up and why, and the organization stays up either way. Every 10 s the body’s tick looks at them again: a service with no container, or whose container stopped other than by finishing (exit 0), is brought up again (compose: <service> <state> <code>; brought up again); one compose is restarting under its own restart: policy is left to it. Every exit of the body once its lease is held takes them down (the repository's compose services are down). Before up the body labels every service with its mark (hale.dna.body=<holder>#<token>#<host pid>#, an override .hale/dna/compose.body.<token>_<host pid>.yaml beside the compose file) and records who owns them in .hale/dna/compose.owner.<token>_<host pid> (the host’s pid, the mark, the project, the compose file, the project directory and the override, a line each); without the record nothing is brought up. Both files are the body’s own: a body creates, reads and removes only its own, so a replacement host on the same clone never loses its files to the cleanup of the body before it. Nothing ever takes the project down whole: docker compose down acts on whatever the project holds when it runs, and the project is the organism’s, so another clone of the record on the same docker daemon — a replacement body, once this one’s lease is lost — brings its services up in it under a mark of its own. The body’s services go by the daemon’s own label filter, evaluated container by container: the project’s containers that carry this body’s mark are stopped and removed (docker rm -f), then the project’s networks are removed, which the daemon refuses while any container is attached, so a network a replacement uses stays; volumes are never removed. The host does this on every exit, stopping them within 10s first — one of its exits (stop_body) is taken after its lease is lost, when a replacement may already be running, so its own exit is no safer for a project-wide down than the fence’s. A host that dies cannot: when the body fence stops a lost body (its host gone, its lease lost or expired) it reads the record, and one that names its host and its mark has that body’s containers removed at once, the same way; a record another body wrote (a replacement host on this clone) is left, with its services. The fence’s status line says compose removed this body's containers of <project> or compose not this body's: <project>, and the body’s own record and override go once its containers are gone, by the host or by the fence. A host starting its services first sweeps the records of other bodies of its clone whose host is gone (a fence that died with it): their containers go by their mark, then their files (compose: removed the containers of earlier bodies whose host is gone (<marks>)); a record whose host still runs is left to that body and its fence. Compose runs without the organism’s credentials in its environment (the ones an application’s start removes: the spine’s, owner’s, head’s and reflexes’ URLs and DSNs), so a compose file cannot interpolate them, and with nerves here it is handed the attached application’s: HALE_DNA_NATS_URL_APP (the broker as the body reaches it, its own HALE_DNA_NATS_URL_APP if set, else the spine’s server), HALE_DNA_NATS_USER_APP, HALE_DNA_NATS_VAULT_APP, HALE_DNA_NATS_ORG, and HALE_DNA_NATS_SECRET_APP, the path of the local vault’s entry holding the application’s password. The compose file mounts that file read-only as a secret (secrets: <name>: file: ${HALE_DNA_NATS_SECRET_APP:-/dev/null}) and the service reads it as a vault of one entry (HALE_VAULT_DIR: /run/secrets, HALE_DNA_NATS_VAULT_APP: <name>): the vault stays the one ingress, and the password is never copied, never in an environment and never in the repository. A vault served over HTTP (HALE_VAULT_ADDR) has no file to hand; the body says so and hands none. The broker’s address is handed as the body sees it, and a service on the host’s network (network_mode: host) reaches it there; on Linux a broker on the host’s loopback is out of a bridge network’s reach (host.docker.internal through host-gateway reaches only what listens beyond the loopback), so the address is not rewritten. A mounted entry is the file the vault held when the container was created: a password drawn anew reaches the service when the body next brings it up. HALE_DNA_COMPOSE=off leaves the services to the operator, as before ruling 11, and the body says so; a repository with no compose file runs no application under the body, which says that too. Not yet: a Mutation’s restart request does not bring a changed compose file up (the body says the repository has no application to restart), and the services’ logs stay compose’s (docker compose -p <project> logs).
  • Review routing (GH #1087). A change set goes to the positions that must sign it, read from the graph, never guessed: hale dna route [--json] (<path>… | --diff <range>) (the host verb route, reading memory as the head, over graph_perspective("routing"); the routing is dna/core/route.hl). A path is the repository’s: one given from a subdirectory is taken from there, and the organization’s root is the nearest directory holding dna/org (a seed’s own hale.toml is not it). A path names a node — a contract (its file, or a file under its directory), else a document, else a deployment (the file it was read from, when that is no document: compose.yaml), else the longest seed holding it — and the reviews edges say who signs, whatever a position is called. Any node: every position that reviews it — a part’s reviewer reviews its seed and the contracts it serves or consumes, a deployment’s operator the deployment (compose.yaml goes to compose/operator, against the gates guarding compose). A contract, beside those: the positions reviewing a consumer that is no part (a contract, a document, a deployment), and the board where the contract is law: a practice the Board ratified as law is bound to it (its receipt says law: true); one ratified as advice is not law, and the board does not sign for it. A position that reviews nothing — a derived dev, an operational role — signs nothing; one a reviews edge names signs, whatever its name. The evidence a verdict is given against is the run of every gate guarding a node the change touches. A consumer no position reviews is listed as such (unsigned), never dropped: a consuming process or seed with no position under it reviewing the contract; a path that names no node, or whose node no position signs (or operates), is left to the fallback — the Review’s own required authority, the task routers of GH #697 when they exist, which the route names. Text lists signed by, against (no gate guards it when no gate does), reviewed by no position and left to the fallback, <what it falls back to>, each signer with its holders and why it signs; --json is one object: signers (position, because), evidence (gate ids), unsigned (node, because), fallback (path, because), fallback_to. A Review takes its signers and gates from the same computation when it opens (Signers from the route, above).
  • Perspectives: hale dna show (GH #1086). hale dna show org | processes [--json] [project] is the host verb show. It takes --json and at most one project directory, and refuses any other flag, a second project or one that is not a directory. It reads memory under the head’s role alone (HALE_DNA_MEMORY_DSN_HEAD), never the spine’s, even in a process that holds both, scoped to the record, and prints what graph_perspective answers. Nothing is edited through it: a change moves through the record’s rows, and the spine projects it into both perspectives. A record with no rows is the empty perspective, with no memory asked, and stderr says a graft births a record; with rows and no memory named it refuses, and says the graph is memory’s. The projection’s stamp is read before the graph, so the rows it reports never overstate what the graph holds. Text (dna/operations/graph_show.hl) shows a node by the last segment of its name. The org chart is nothing when there are no positions; otherwise purpose, then a line per node positions sit under, in the record’s order: a position under the purpose is a line of its own (board), and any other node is its name, padded in code points to the widest, then its positions’ roles. A role is the position’s name less its node’s (api/reviewer under api is reviewer), followed by (<the contracts and documents it reviews>) and, when it has holders, [<holders>]; a position under no node is under (unplaced), after the rest. Each role ends with {mandate: ratified|pending|none[; roles: <API roles>]}: ratified when a ratified idea of kind mandate is bound to the position’s node (memory), pending when one is proposed and undecided (read from the record), none otherwise; roles are the node’s equipment.roles, when it has any. --json carries each position’s roles and mandate (ratified, or empty) beside its holders. The process model is a line per meets — its outside parties and consumers, the arrow --<via>: <contract>-->, its servers or (nobody here), and (carried by <carriers>) after — then one <deployment> runs { <processes> } per runs, then one <position> convenes <definition> <cadence> per convenes (board convenes optimize-walk every 1d). --json prints one object: perspective, record_rows (the record’s length), projected_rows (memory’s watermark), stamp_valid and graph, the query’s answer as memory built it. How memory stands to the record is said on stderr, never in the perspective: behind it (memory has projected <m> of the record's <n> rows), past it (a read that raced a writer: read again), or with a stamp that vouches for nothing (hale habitat migrate <organism>). On voice’s record with its holes filled the two texts are GRAPH.md’s renderings, byte for byte (dna/tests/graph_show_test.hl).
  • Projections and ranking (K3). The tail also projects the record’s structure.observed rows (the graft’s loci, topics, bindings, effect classes and claims) into memory by kind and name, the latest row winning, and its pressure.raised and concern.raised rows into signals counted per (kind, source, what) with the last row’s seq (knowledge_structure, knowledge_signals), so the graph has what ideas bind to and what the fleet is saying. Retrieval is by binding and provenance first: the bounded set is the accepted ideas bound to the target or above it, and nothing outside it is retrieved. Inside it, a query — the objective, which the substrate passes from the ask, its first 2000 characters — ranks by similarity so the budget keeps the most relevant (KnowledgeStore.ranked_ids(target, budget, query_vec); no query is ratification order). The embedding is lexical — embed_text: a hashed bag of words in 64 dimensions, normalized, deterministic, rendered as a pgvector literal — so Pq ranks with <=> over a vector(64) column (the migration creates the vector extension and says so when the Postgres has none).
  • The application side (K4). A concern is one of the application’s own events (GH #986, #987). It declares it as its own topic, concern.raised under its own subject app.<app>., with a codec that writes one JSON object: its id, and the concern’s source, what and severity. It publishes it through pond’s NATS library with the application credential its node handed it, and imports nothing of the DNA. Its id is that concern’s alone: a repeat is a replay, refused. Its source is a /-path of letters, digits and -_., at most 128 bytes, one of whose parts is the application the subject names: an application speaks for its own parts, never another’s, and a concern that does not is refused (acknowledged, recorded nowhere). NatsPublisher, the publish-only adapter, dispatches nothing into the program: its artifact stays exact, so a fleet claim over it can still be certified (a NatsConn hands whatever arrives to __local_dispatch, which no checker can bound). The heart lands it as a reading first, reading.recorded <app>/concern.raised/<id>. The spine then appends concern.requested <source> (request the reading’s entity, source, what, severity, app) before the event is acknowledged, once for that request, whichever delivery reads it; a concern the record refuses leaves the event unacknowledged, to come again. A node relays concern.requested rows onto the nerves like intent.requested and review.verdict until the record holds the answer, and the organization journals concern.raised, admitted once by its request. hale dna concern raise writes the same concern.requested row by hand.
  • The learning scenario is the acceptance (K4), in the fixture: the worker on the second node observes its mail backlog and raises the concern three times; each travels as the worker’s own event → the heart’s reading → concern.requested → the relay → the nerves → concern.raised; the third makes a proposal by org/trio/worker bound to org/trio (a concern by the tower rule); the Board ratifies the exact digest (hale dna review k:… approve --authority board); the spine projects it into memory on its tick; and the next change to the trio consults memory (knowledge.consulted m1 with the digest and the hat’s digest), briefs the editor with the concern under the objective, and every model.called row of the attempt names the package and the digest. Something observed and ratified today informs the work done tomorrow, and the receipt says so.

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

  • Record — Journal: RoutedJournal over RecordJournal (memory) and MemoryLedger, MemJournal (tests).
  • Board (the board field, a Membrane) — where humans see and decide: Board / LocalHumanMembrane, reached over the nerves on one machine; the record itself across clones (intent.requested, review.verdict rows, relayed by the host); the organism’s forge, mirrored by the host: the forge is a gate, not a store, and which forge stands in it is the organism’s forge allocation (spec/habitat.md, Forge and identity) — the habitat’s own Forgejo by default in the local profile, GitHub when a Board names it (hale habitat forge <organism> github --repo owner/repo). Against whichever it is, every pending mutation Review becomes a pull request (github.pr, naming the forge; 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 — both once the Mutation is applied (or has failed), since a Review settles before its Mutation is applied. GitHub is a projection of the record and the record wins: a review whose head moved is refused here and shows as refused there. The forge is an implementation (GH #648): Forge in the core is the vocabulary of a code-review host and nothing of GitHub’s — open_review, verdicts (one per line: who, outcome, when, the forge’s own key so each is admitted once), comment, close_review, present, name. GitHubForge (gh) is one implementation; the FileForge (.hale/dna/forge/<n>.review, its verdicts the lines of <n>.verdicts) is the fixtures’; ForgejoForge (Forgejo’s API, a token the vault slot of the organism’s own Forgejo user holds) is the local profile’s; NoForge is a bare remote’s honest behaviour, the no-infrastructure fallback, and hale dna profile‘s github: line names which the host found: the organism’s forge allocation (or HALE_DNA_FORGE, the fixtures’ override). hale dna forge sync syncs with the allocated forge; with none allocated it answers forge: no forge allocated. (hale dna github sync is gone.) The forge’s pass is written against the interface; the rows it appends (github.pr, github.commented, review.verdict keyed by the forge’s key) do not change with the forge. Only the github.pr rows of the forge in the gate now are read: after the allocation (and the remote) moves to another forge, a Review still open is opened again there and nothing migrates. Nothing in the core or the host spells gh outside that one implementation, and no practice, workflow or standard names a forge by kind: “open a review” is the verb, and the allocation answers it.
  • 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.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 skin’s (GH #989). A secret is provisioned when its owner is declared and consumed when it runs. Organism-level secrets are provisioned by a graft and hale dna upgrade, whether or not the part that uses one runs; per-member secrets are provisioned at that member’s admission, and are not on the organism’s list: an owner’s head role on a shared record (postgres-<schema>_head_<owner>) where the owner is declared to memory (memory migrate under HALE_DNA_OWNER_KEYS); a node’s own account, not yet built. The application the record attaches is the exception on the list: its broker account is drawn once application.attached lands, and revoked by application.detached (The application’s account). One skin-owned function (dna/host/secrets.hl, organism_secrets) holds the list, and one (provision_secrets) provisions it; every other part only consumes a secret, by its vault name, and never creates one.

vault name kind whose
postgres-dna_<identity>_spine, _head drawn memory’s roles (Memory)
postgres-owner-<id>, when the organism has a memory allocation drawn the memory allocation’s superuser in the local habitat
nats-<org>-<role>, for owner spine head reflexes drawn the nerves’ accounts (The nerves)
nats-<org>-app-<name>, while the record attaches the application drawn the application’s broker account (The application’s account)
oidc-client-<dna.oidc.client> (dna-local when unset) drawn for an issuer on the loopback, else a slot the head’s OIDC client
oidc-service-<service>, per dna.oidc.service drawn a service client (The principal source)
forge-token slot the forge’s token: with none, nothing is done at the forge, never as gh’s own login
model-<NAME>, per credential dna/org/models.hl or dna/org/work.hl names slot a model’s key (Models); hale dna secrets lists a work.hl key as the legs’ model key
  • Drawn or a slot. A secret the organism owns the value of is drawn from urandom (32 hex characters) into the vault when the vault lacks it; the nerves’ are drawn anew on every graft and upgrade, since the server’s include is written from the same draw. The habitat’s allocations read theirs from files written from the same draw as the vault’s entries, beside the allocation and never in a checkout (GH #1653): nats.secrets.conf for the nerves and postgres.secrets for memory (its POSTGRES_PASSWORD_FILE), each mode 600 in allocations/<id>/. No committed file holds a secret. A secret a person supplies is a named empty entry that hale dna secret set <NAME> fills (FORGE_TOKEN, OIDC_CLIENT_SECRET, or a model key’s name); an entry that is empty is not held.
  • The vault. The local vault is std::secret::vault_local_dir() (HALE_VAULT_DIR, else the toolchain’s cache), each entry a file of the secret’s exact bytes, mode 600. A real vault (HALE_VAULT_ADDR) is provisioned out of band: there the bootstrap only checks, and says what the vault lacks. The local vault is one directory per user, not a boundary between the parts one user runs: any of them can read any entry by name. What keeps a part to its own credential is what it is handed (an application gets its own account’s vault name and none of the others’), not what it could read.
  • Presented, never held. A credential is presented by the adapter that puts it on the wire, and never exists as a String elsewhere: a part carries a std::secret::Credential { vault: … } (a sealed locus), and reveal_text() is called on the line that writes the secret out — pond’s NATS client on CONNECT, the head’s token request, the model’s request header. No field, URL, argument or environment variable carries one; memory’s DSNs name their role’s entry and pq presents it in its handshake. Two are not sealed yet: the migration sets each role’s password in the SQL it runs as the owner (CREATE / ALTER ROLE … PASSWORD, an SQL literal on pq’s ordinary query path), and the forge’s token reaches gh, which takes it no other way, as GH_TOKEN in that one child’s environment (through a file unlinked at once).
  • Fail closed. hale dna run (and dev, which starts it) refuses when the vault lacks a drawn secret, naming each; nerves migrate refuses a role with no entry; nothing falls back to a default.
  • hale dna secrets lists every secret the organism requires, whether the vault holds it, and the remedy for each missing one; never a value. The board says so for each missing one (a model key is reported by the body, since the vault that matters is the one on the machine that calls the model).

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

  • The fleet seed describes the workspace’s own services. Its params are the workspace’s seeds’ mains, and composition cuts each one’s artifact from its seed, so a candidate is checked as the fleet it would deploy. An instance’s placement host (host("edge-1")) is the node that expresses it. [dna] fleet = "<name>" in hale.toml names which entry of [fleets] (a fleet seed) the DNA expresses. The DNA reads the fleet as its rows: hale fleet export <seed> (hale dna fleet-of resolves the name to the seed in a checkout’s manifest); a plan JSON is read nowhere.
  • 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 fleet seed’s source (a .hl file in a directory one of whose own files declares fleet locus, so an import-only change counts) 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 fleet’s name (plan), the revision, the seed the change edits ("" for the whole genome), the instances touched — every instance with a host whose seed is that directory, read from the fleet’s export at that revision (checked out into a scratch worktree for it) — and the reason (apply, rollback, or the operator’s). The revision is published first on the genome’s forge (the record’s remote) as the branch dna/revision/<rev>, and every node fetches it by its sha, as hale fetch fetches a pinned dependency (GH #1590: a revision is the genome’s, not the record’s). 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 by its sha from the forge (git fetch <remote> <sha>; a fetch that fails is said and tried next tick), checks it out, reads the fleet’s export there, and for each instance the fleet places on the node’s host 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 fleet 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 fleet, its node, whether it is up, the revision and model hash it last came up at, and the last deploy.

The senses are the organism’s perception, and the reflexes are its reactions that need no plan (GH #988). Both are their own parts: one telemetry store every part emits into, and a program beside it that reads it and says what fired onto the nerves. The spine records and routes; it never plans on a reading.

  • Readings. Every long-running part serves its readings as a Prometheus scrape target (std::metrics::Endpoint), namespace dna. It listens on the port its environment names in HALE_DNA_SENSES_PORT, else the one the seed names for it: the spine (the node’s host) on 9464, a node on 9465, the head on 9466 (dna/core/senses.hl). It binds every interface, or HALE_DNA_SENSES_HOST, from a dna::Senses its program’s main locus holds on a cooperative pool of its own (senses), whose shutdown ends the accept, so the part drains at once. A port another process holds is the server’s listen_failed, which Senses absorbs, as the only handler it has: the part runs on, unread, and says so.
    • Labels. Every series carries part=<name>, and a node’s node as well. No label is per event, per task or per revision: a series cannot be retired, so the set a part serves stays bounded by its parts and instances. A task joins senses, spend and receipts through its rows (a model.called row names its attempt, and the attempt its task), not through a label.
    • Series. dna_pulse_seconds{part} is each part’s tick. dna_instance_up{part, node, instance} is 1 while the instance runs and 0 once it exited. dna_instance_since_seconds{part, node, instance} says since when (a down transition writes its since first, an up its state first, so no scrape pairs a new state with an old since). dna_model_calls_total{part} counts the model calls whose rows the spine announces landed (with its nerves up).
    • A part never learns where its readings go.
  • hale node runs as the host program’s main locus (Host, verb node), the one place its Senses can be placed on a pool of its own. It is no spine: its nerves connection has no server and stops at once, and it takes no application’s events. It drains on SIGTERM.
  • The store is the organism’s senses-<id> allocation in the local habitat (GH #1653): Prometheus, scraping what the allocation’s senses.yml names over the host gateway (honor_labels, so a part’s own labels are the readings’), and keeping what it scraped for its retention (--storage.tsdb.retention.time, a week). Its port is in the 39000s, on 127.0.0.1, kept in the allocation’s ports.yaml as memory’s and the nerves’ are.
    • The allocation’s compose.yaml and senses.yml are written the first time a habitat act brings an allocation of the organism up.
    • hale habitat migrate [organism] and hale dna senses up [dir] bring the store up; senses up prints HALE_DNA_SENSES_URL, its read URL.
    • hale dna senses url [dir] prints the same line for a store that is up, bringing nothing up, and names hale habitat migrate <organism> when it is not.
    • hale dna dev never brings the store up: it says the store’s URL when it is up, or that it is not, unless the environment names its own memory or nerves (an owner’s DSN or URL), and nothing stops for it.
    • A reading never acts.
  • The reflexes (dna/reflexes) are a program on the private-services tier. They hold the store’s read URL and a publish-only credential, the reflexes user of the nerves (HALE_DNA_NATS_URL_REFLEXES, which hale dna nerves migrate prints). That user may publish on <org>.app.reflexes.> alone, and an application’s user may publish only under its own name, so no application can speak as the reflexes. They hold nothing else: never the record, memory or the vault. To the organism they are one more application.
    • The rule. One is built in, instance.down: an instance its node reads dna_instance_up 0, asked of the store each tick. It fires once per outage, reflex.fired { id, rule, node, target, action: "restart", since, fired_at } on <org>.app.reflexes.reflex.fired. The id names the outage (instance.down:<node>:<instance>:<since>, each segment kept to the heart’s id token).
    • The row. The heart lands the firing as a reading row first, reading.recorded reflexes/reflex.fired/<id> (The heart’s events), so a second publish of the same outage is refused as a duplicate. They remember what they published only while it is down.
    • Launch. The face’s launcher (hale dna head, start.sh) builds and starts them beside the stub OpenID provider when a project is attached, with that project’s store and nerves (hale dna senses url, hale dna nerves urls), and stops them with the launcher. The launcher brings nothing up: a store or nerves that is not up leaves the face running without them, and the launcher names hale habitat migrate <project>.
  • The node acts on the row. On its tick a node reads every firing that names it and has no reflex.acted row yet. It restarts that one instance at the latest deploy’s revision, through the path a fleet.deploy takes, touching no other instance and not marking the deploy expressed. It then appends reflex.acted (entity: the firing’s id; rule, target, action, outcome, node), whatever the outcome was: restarted, already up, or why not. A restart that did not take becomes a concern as well (concern.requested, request reflex-failed:<id>), not a quiet retry. A node restarts; any other action is refused on the row.
  • The spine’s read. The host that lands a firing, or finds it landed already, reads the firings for its node and target. A second firing within the window (HALE_DNA_REFLEX_WINDOW_SECS, 600 s) becomes a concern: concern.requested (source reflexes/<target>, request reflex:<id>). There is one such concern a window, however many firings fall in it, and it lands before the firing is acknowledged; a concern the record refuses leaves the firing unacknowledged, to come again. The organization raises it, concern.raised, and that is what a workflow answers to. Only the rule fires, only the node acts, and both leave rows.

Everything hale dna does beside the compiler that is DNA behaviour rather than manifest or scaffolding is the host, dna/host, embedded in the toolchain beside the core and the surface. It is two programs over one seed, one command per process (GH #1490):

  • the body, dna/host itself: run and dev (the supervision, in which the host is its program’s main locus and binds the nerves), node (the node agent, hale node) and body-fence (the renewer a body starts beside itself, from its own binary). Each runs until it is stopped.
  • the verb program, dna/host/verb: every other verb, one-shot — the projections (status, history, review, board, report, pressure, fleet), the writers (task create, a verdict, pressure raise, sync, deploy, rollback, forge sync) and the rest. It imports the host’s seed and starts no locus that runs or handles the bus, so it runs to exit, and the memory-bound survey judges it as it judges a script (spec/verification.md).

hale dna <verb> resolves the project from the manifest (the root, the application’s seed, the fleet and its fleet seed under [dna]), builds both programs once into the toolchain cache (side by side, the first time any host verb needs either), and execs the one the verb belongs to in place — the pid that ran hale dna <verb> is the host: <program> <verb> <root> <seed> <fleet> <fleet seed> …, with HALE_BIN (the toolchain) and HALE_DNA_TOOLCHAIN in the environment. Either program refuses a verb that is the other’s. The host reads the record through the core’s RecordJournal, appends in a person’s or a node’s name through memory’s append function, 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 the graft and upgrade (they embed the sources), hale fleet check and export, and the exec shims. The host decides nothing.

A seed is built once per content fingerprint

Section titled “A seed is built once per content fingerprint”

The host builds the organization’s seed before it starts it, and the application’s seed too under dev; it rebuilds both on a restart. A build is a pure function of what it reads, so the host does it once per content fingerprint and starts the stored binary thereafter. The cache is $XDG_CACHE_HOME/hale/dna-build (or ~/.cache/hale/dna-build), a sibling of the toolchain cache the host and the surface are themselves built into; it holds the 32 most recently used entries and prunes the rest. An entry is a directory, <fingerprint>/<seed name>, so the process keeps the seed’s own name.

A build never writes into the seed’s directory. The host passes hale build -o: with a cache the binary is built into a private directory beside the entries and renamed whole to its entry, and the entry’s path is what the host starts; without one (HALE_DNA_NO_BUILD_CACHE, no cache home, a fingerprint that could not be taken) it is built into .hale/dna/build/<seed>/<seed name> under the project root. Either way a checkout has no binary in it afterwards.

The fingerprint is a SHA-256 over:

  • every file the build reads, each as its path relative to the project and the SHA-256 of its bytes. The list is the compiler’s own — hale inputs <seed>, which resolves imports exactly as a build does — so a seed that grows an import, or a vendored core that is upgraded, is a different fingerprint without anything here having to be kept in step;
  • the project’s hale.toml and hale.lock;
  • the toolchain version, and the hale binary’s own size and mtime, so a compiler rebuilt in place without a version bump is a different fingerprint;
  • every environment variable that changes codegen;
  • the seed’s own path, because a binary carries the paths of the sources it was built from.

A hit is the entry used where it lies, and nothing else is done. A store is one rename of a whole directory into the cache, so a reader sees a whole entry or none, and a binary that is still running keeps its inode when its entry is replaced or pruned. Two hosts building one thing at once need no lock: the second rename finds the entry there and drops its own copy, and both are right. Anything that stops the fingerprint being taken whole — no cache home, hale inputs failing — is a miss, and a miss builds exactly as it did before. HALE_DNA_NO_BUILD_CACHE=1 makes every build a miss.

A graft and hale dna upgrade materialize vendor/dna from the DNA source embedded in the hale binary, so an organism runs the core that binary carries and not the one in any checkout. A version does not identify that source — two builds of one version can embed different dna/ source — so the toolchain names it (GH #726): EMBEDDED_DIGEST is a length-framed SHA-256 over the sorted (path, content) pairs of every embedded file (dna/core, dna/host, dna/host/verb, dna/operations, dna/organization_runtime, dna/organization_source, dna/ui, and pond’s driver and NATS client pinned under dna/core/pond), computed when the toolchain is built. It is:

  • the second line of hale --version (embedded dna: <first 16 hex>; the first line stays the version alone, since a provisioning script reads field 2 of it);
  • all 64 digits on stdout, alone, from hale dna --embedded-digest, and — with --from-tree <dir>, a checkout holding dna/core — the digest that checkout would embed, by the same algorithm. Unequal digests mean the binary predates the tree: what it materializes is the older core, and a test of an edit to the tree measures the wrong source. A missing directory is refused, never digested as a smaller set;
  • the first line of hale dna status, with the toolchain version, and a refusal to stay quiet when vendor/dna was materialized by another build (hale dna status --json is unchanged: the projection is the host’s);
  • recorded where a materialized tree can be asked about it: vendor/dna/README.md and .hale/dna/embedded.digest (hale <version> then embedded dna: <digest>), both toolchain-owned and git-ignored, both refreshed by upgrade. It is deliberately not written into the generated organization’s source: dna/org/main.hl is project-owned source that is reviewed, diffed and read by models, and a line that changes with every DNA edit belongs in neither a review nor a recorded baseline.

The build enforces its own snapshot: the digest is computed over the on-disk tree by the build script, the crate digests what the compiler actually embedded, and the two must agree — source edited while a build runs, or a file added without being embedded, fails the build rather than shipping a digest that names nothing.

hale dna ui [project] [--port N] serves the DNA surface in a browser from the record in memory (GH #1590: a head reads no record without memory): a Hale program (dna/ui, embedded in the toolchain like the core and the host, 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), a task (POST /api/task/create) and a pressure signal (POST /api/pressure) are the CLI’s own verbs sent and not waited for: rows in the record, in the name the form gives, which a node relays. 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.

The head (dna/api/project_service), which the face fronts, is the surface’s counterpart for the operator’s machine. hale dna head [project] [--port P] [--head-port P] [--api-port P] [--oidc-port P] [--commands-port P] [--source-drafts] starts it from an installed toolchain: the toolchain embeds the head’s seeds (dna/api, dna/api/practice_review, dna/api/project_service, dna/face/standard, dna/oidc, dna/oidc/serve, dna/reflexes), the face’s assets (dna/face/web) and the launcher (dna/face/start.sh) beside the host’s sources — not in the embedded DNA digest, which names what vendor/dna is materialized from — materializes them into the toolchain cache, whose key covers them, builds the five seeds there side by side under the cache’s build lock once per key, and execs the materialized launcher with every binary handed in (HALE_API_BIN, HALE_HEAD_BIN, HALE_FACE_BIN, HALE_OIDC_BIN, HALE_REFLEXES_BIN) and HALE_BIN the running toolchain. With a project and no HALE_DNA_MEMORY_DSN_HEAD, it derives the head’s memory DSN alone, onto the launcher’s environment and never its output, when memory is up and migrated: an owner’s server (HALE_DNA_MEMORY_DSN_OWNER), or the organism’s memory allocation in the local habitat already running. Otherwise it says memory is not up, names hale habitat migrate <project>, and starts the head with Knowledge unsupported; it brings nothing up and migrates nothing (GH #1653). The launcher reads the reflexes’ store and nerves with hale dna senses url and hale dna nerves urls, which bring nothing up either. From a checkout, dna/face/start.sh [project] is the same launcher, building each seed it is not handed from the checkout into temporary storage on every start. The head is one loopback process that serves surfaces only, keeps a registry of projects and a journal of receipts under a state directory, and proxies the Record’s read and command routes to a per-project API child it starts and replaces. Its own operations — create, init, attach, detach, forget, sync, publish, forge, the local body, the remote body, secrets by source name, the model probe, connections, handoffs — are the CLI’s verbs run detached with a pid, an exit code and a log as files, and each answers a receipt (recorded → admitted|refused → running → succeeded|failed|outcome_unknown) under a command-<digest> identity that replays on an identical retry and conflicts on a changed one. A run that passes its deadline is outcome_unknown when the verb reaches beyond this machine, never failed; the head appends nothing to any record itself. It is trusted-local and refuses an oidc project.

Faces (Phase 3). A face is a role an application fills: the browser’s door to the organism. This slice has one, the standard face (dna/face/standard), which the toolchain ships and grafts as it grafts the habitat genome and the packs: its sources and the browser’s assets are part of the head’s files, built into the toolchain cache beside the head’s seeds. A genome names others as below.

Named faces. An organism names a face by a param of its main locus of type dna::Face (dna/core/face.hl, in the embedded core), recognised by the type it resolves to (whatever alias imports the core; a look-alike Face is no face) as std::observe::Probes is and built by its literal, never a manifest table:

admin: dna::Face = dna::Face { app: "faces/admin", roles: "operator", consumes: "Orders", port: 8801 };

The param’s name is the face’s; app is the seed of the application that fills it (relative to the application’s seed), roles the roles it acts under and consumes the surfaces it calls (space-separated names), port the loopback port it is served on, a literal 1..65535. The compiler reads the literal into hale check --api: the inventory carries a faces array (name, app, roles, consumes, port), present only when the program declares one, so an inventory without faces is as before. The check refuses a face with no app, no port or a port another face holds, a role the program does not declare, or a surface it does not declare.

The record states a face from that description, composing the graph’s vocabulary and adding none: an application:face/<name> node (the application and port on its row), and the face as a consumer on the meets edge of the contract of each surface it consumes, the one edge the contract has (beside the seed that serves it). A face is a consumer, never a part: no position is proposed for it. The head’s Commands::index row answers them as a faces section (name, app, port, roles, consumes), and the standard face lists them in /index, each a link to http://<host>:<port>/ on the host the browser reached the face by. Mounting is navigation to the face’s URL, not a reverse proxy.

hale dna dev and hale dna run build each declared face’s seed and start it as <binary> <port> with HALE_DNA_FACE=<name> and HALE_DNA_FACE_PORT=<port>, beside the application and holding none of the organism’s credentials (as the application holds none); the pids are .hale/dna/face-<name>.pid, listed in .hale/dna/faces, and the body’s stop and fence stop them with it. A face that does not build is said, and the organism does not wait on it. An organism that names none is served by the standard face alone, unchanged. In both kinds of face the contract is the surfaces: headers carry only identity and the digest. A face is not restarted by an apply in this slice.

The head serves surfaces only. The standard face stands in front of it, on the port the browser opens (8792; --port), the head on the loopback behind it (--head-port, 8796; it learns the face’s as HALE_DNA_PUBLIC_PORT, its origin for sign-in, host checks and commands). The face serves GET / and the shell’s assets from dna/face/web and GET /app/… from the attached project’s ui/, and passes every other request to the head as the browser sent it: method, path and query, headers (cookie, origin, host) and body, the answer written back as it came, an event stream held open until either end closes. / is the head’s gate: the head answers it 204 when the person may open the shell (a session under OIDC, the launch-token cookie under a fixture’s trusted-local session), naming them on X-Hale-Person (the session’s person, or the head’s own principal), and the face then serves the shell; any other answer (the sign-in redirect, the launch token’s cookie and redirect to /, a refusal) is passed on unchanged. A face given HALE_DNA_GRAFANA_PORT and HALE_DNA_GRAFANA_HEADER serves the habitat’s Grafana under /grafana/ behind the same gate, in the person’s name (spec/habitat.md, Readings); without them /grafana/ is a 404. /app/… asks the head’s read (GET /api/hale/v1/head) as the same session which project is attached, and serves its ui/ (ops::application_asset), or the head’s refusal. The head has no static file and no /app/: those paths are no route of it. The browser’s traffic keeps relaying as the browser sent it (the head’s browser routes are not rows of a surface); the face’s own reads go through a client.

The index. GET /index on the face is the record’s index: a static page, plain HTML in the shell’s stylesheet and no script, generated on each request. Behind the head’s read as the person’s session (the same gate as /app/: the head’s refusal is passed on; a trusted-local session reads bare, as every head read does), the face reads the record through a client of the head’s surface (api::client, spec/api.md § Calling a surface; the face declares the one row it calls, Commands::index, whose member digest is the row’s request and response shapes, so the two sides agree by shape). The row is Commands::index of HeadCommands (dna/api/record_index.hl), a read any authenticated caller the record knows may ask: it takes {limit} (at most that many entries of each section, 200 by default, 1000 at most) and answers the record’s revision and six sections, each a JSON array the face renders and the purpose a line: the declared purpose (the purpose node of the graph the record states); the parts and positions (each position node with the holders of its holds edges); the attached codebases (name, repository, branch, build, test, toolchain); the watched upstreams (each ratified, unretired standard/upstream-watched/<name> practice with its position, source, kind and codebase, its terms refused if they cannot stand, the result of its last occurrence — the snapshot’s digest, short, or unreached — and the instant of that occurrence in UTC; the occurrences are the Ledger’s rows, read beside the record); the contracts (each contract node with its source and text); and every outside link the record holds as knowledge, each http:// or https:// URL in the text or source of a graph node or in the text of a ratified, unretired practice, with what holds it. The row writes nothing and adds no manifest table, record family or row kind: the sections are read from the rows that exist. The face’s client is http::Client on the head’s /api/hale/v1/head/surface (POST …/call/Commands::index), its bearer the requesting visitor’s own Cookie header, which the face received and presents on the visitor’s behalf. The head reads that bearer as the visitor’s cookies, runs the request through its own gates (a session under OIDC, the loopback host check, the trusted-local session), and relays the call to the API child’s commands port under the visitor’s credential: the session’s ID token under OIDC, the child’s launch token in a trusted-local session. So the index is the visitor’s read: a visitor who is not signed in, or whose session is forged, is refused as the head refuses every read (401), the face passing the refusal on. A page the face serves (the habitat’s operator face) calls the same row from the browser: the face relays that one POST with the page’s own Cookie header as the Authorization bearer, which is where the head reads the visitor’s session, so the generated client of the head’s rows needs no token of its own.

The habitat’s face. The habitat is an organism, so its face is this one: hale habitat up starts face (hale dna head on the habitat’s genome, the standard face over the habitat’s own head) beside body (hale dna dev <genome> --no-iris) as services of the habitat’s compose project, its head’s state in face-state under the habitat’s directory; hale habitat status prints its address on a face line, and down takes the compose project down. The habitat’s index is the same page and the same Commands::index row, with one more section in the answer: a record that grafted organisms lists them under Organisms (the organisms array, empty and then no section in any other record). Each entry is read from a habitat.grafted row and says the organism’s name (the directory it was grafted into), its state (up while its face accepts connections on the loopback, grafted otherwise), the address of its face and the address of its head, its declared purpose (the PURPOSE of its dna/org/purpose.hl, taken at its graft) and its pulse (the worst state the providers of its allocations observe now, each where it was last applied: down over degraded over up, "" when none is observed; its forge and identity, the habitat’s services it is given, are observed with the allocations and are not its pulse). No manifest table, record family or row kind is added: the addresses are fields of the existing habitat.grafted row (name, face, head), taken at the graft. An organism’s address is a block of HEAD_BLOCK (8) consecutive loopback ports, found free from a starting point of the habitat’s own and distinct from every block already handed out, and kept in the organism’s dna.head.url (its face is the block’s base, its head the next port, then its API child, provider and commands). hale dna head <organism> serves on that block unless a port flag says otherwise, so the recorded address is the one the head answers on. The habitat’s own address is assigned the same way, in its genome’s dna.head.url, the first time up needs it.

The habitat’s placement. Three more reads of HeadCommands (dna/api/habitat_rows.hl), each taking {limit} as the index does and answering the record’s revision and one JSON array, are what the operator face’s hardware and infrastructure screens read: Commands::hosts (each habitat.host row: the host’s name, the provider that produced it and what it offers), Commands::policy (each habitat.policy row: the role, its provider and the profile it came from) and Commands::allocations (each habitat.allocated row: the allocation (<instance>/<role>), its role, instance, organism, provider, host, service and address, and the state its provider observes now with the detail it saw: up, degraded or down from the compose provider; "" for the operator’s, which the habitat does not observe). The latest row of an entity is the one listed. A host is a machine the habitat places on (spec/habitat.md, Hosts, providers and placement), never the program dna/host. They are reads any authenticated caller the record knows may ask, they write nothing, and the head relays them from the face as it relays the index: POST /api/hale/v1/head/surface/call/<member> for the surface’s reads (Commands::index, ::hosts, ::allocations, ::policy) and no other member. habitat.host and habitat.policy are row kinds of the habitat’s record, written by hale habitat up; an allocation’s provider and host are fields of the existing habitat.allocated row. Commands::hosts leaves out a removed host. Four Board rows (requires: [owner]) manage the hosts: Commands::add_host ({name, user, port}) and Commands::probe_host ({name}), each answering {ok, code, why, host, public_key, authorize}: the host as its row now states it, and for add_host the habitat’s public key and the line the host’s ~/.ssh/authorized_keys takes; Commands::plan_remove_host ({name}), a read, answering {ok, code, why, plan, confirm}: the removal’s plan, a JSON array of items (spec/habitat.md, Destroying: plan, then confirm), and the name to type back to confirm it ("" when the plan stops and deletes nothing on the host); and Commands::remove_host ({name, confirm}), answering {ok, code, why, host, plan, report}: it plans again with the same auditor and, when the plan stops or deletes anything on the host, executes it only when confirm is the host’s name, item by item, report saying of each item done, failed or left. A refusal is code with why (invalid_host, host_exists, unreachable, key_unavailable, not_found, host_key_changed, not_removable, confirm_required, confirm_mismatch, plan_changed, partly_removed, record_refused). The plan’s reply carries its digest, and remove_host takes it ({name, confirm, digest}), refusing plan_changed when the plan is another now. Two more Board rows remove an organism the habitat grafted, the same way: Commands::plan_remove_organism ({name}, a read answering {ok, code, why, plan, confirm, digest}) and Commands::remove_organism ({name, confirm, digest}, answering {ok, code, why, plan, report}; refusals not_found, ambiguous, not_removable, confirm_required, confirm_mismatch, plan_changed, partly_removed); an organism removed is its habitat.grafted and habitat.allocated rows written again with "state": "removed". Each row they write is in the name of the person the caller maps to. They are what hale habitat host add|probe|remove calls, over the head’s socket (spec/habitat.md, Hosts, providers and placement). Two more Board rows place and apply the allocations: Commands::place_allocation ({allocation, host}, answering {ok, code, why, allocation}, the row it wrote) and Commands::apply_allocations ({host, fresh}, answering {ok, code, why, restart, applied}, applied a JSON array of each allocation’s allocation, host, provider, ok, why, state and detail): hale habitat place and hale habitat apply call them (spec/habitat.md, Placing and applying). Two more reads list the habitat’s nurseries and saplings for the operator face’s organisms screen: Commands::nurseries (source, kind: the toolchain’s, bundled, then each habitat.nursery row the Board trusts) and Commands::saplings (each habitat.sapling row: name, purpose, repo, revision, digest, nursery, packs, and proposed, the revision a newer habitat.sapling.proposed row files, "" when none waits); the head relays both from the face. Two Board rows act on them: Commands::add_nursery ({source}: a habitat.nursery <source> row, state trusted) and Commands::ratify_sapling ({name}: the sapling pinned at the revision its proposal filed), each answering {ok, code, why}, each row written in the name of the person the caller maps to. The services an organism’s host command placed on a host (spec/habitat.md, Host commands) are three rows: Commands::record_placed ({organism, command, host, id, service, platform, readings, toolchain}, the Board’s, answering {ok, code, why, readings}: a habitat.placed row keyed <organism>/<command>/<host>/<id>, and readings the endpoint it registered with the habitat’s senses, "" for none), Commands::record_unplaced ({organism, command, host, id}, the Board’s: a habitat.unplaced row keeping what the placement said), each refusing invalid_placement, not_found or record_refused, and Commands::placements, a read the head relays from the face, answering {ok, why, revision, placements, history}: the placements that hold (each key’s latest row) and every one the record names with its state. hale habitat organism place and unplace write the first two once the command ran. Commands::brief is the habitat’s brief for an agent pairing with a person (spec/habitat.md, Pairing), a read. The head serves the surface over mcp::Rpc too, under the commands port’s bearer and roles, on a port the system gives it and reports as mcp=<port> (HALE_DNA_API_PORTS).

The head tells the browser when to read again, and the browser does not poll it (GH #986). GET /api/hale/v1/head/events is a text/event-stream the head writes event: changed to — data: rows when a row landed in the active project’s organism, data: head when the head’s own state moved: a receipt journaled, a run or a child started or ended, the API child answering. An event carries nothing to render; the face reads the API, the one source of truth, again, and once more on every reconnect. A comment line every 15 s keeps a quiet stream open and finds one whose browser left; a write that does not complete within a second closes its stream. At most 16 streams are held; one more is refused 503 busy. The rows come from the nerves (Rows landing): the head subscribes to the active project’s organization and every owner’s, as the head’s user, on HALE_DNA_NATS_URL_HEAD, else on the server of HALE_DNA_NATS_URL_OWNER (which it hands a body it starts), else on the organism’s nerves allocation in the local habitat while it is up (asked for, never brought up; asked again every 5 s). With none, only the head’s own state is pushed. The application view still reads on a timer: the application’s readings (The heart’s events) do not reach it yet.

The API (dna/api, source-built; the head starts one per project) is a head of memory. It reads the Knowledge graph in its own process under the head’s role (HALE_DNA_MEMORY_DSN_HEAD), through one handle opened on the first read and held for the process’s life; a projection that has not reached the record’s head answers knowledge_projection_unavailable until the spine’s tick applies it, and without the DSN Knowledge reads are unsupported (knowledge_unsupported). It admits Knowledge commands — the *_knowledge members of its surface, requiring position, reached over its HTTP exposure like its other commands — into the record in the same process, under the explicit authority policy in the file HALE_DNA_KNOWLEDGE_COMMAND_POLICY names (dna.knowledge-authority/1, at most 64 KiB, decoded by KnowledgePolicyCodec in dna/operations/knowledge_policy.hl against the record’s identity and dna.trust). The policy is read once, when the API starts: an edit to the file does not change a running API’s basis, and a policy that does not decode stops the API (exit 2). dna/api/README.md describes the reads and the topics.

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.

Every HALE_DNA_* variable the DNA reads, and the few others its programs read. The host, the CLI and the programs they start pass most of these to each other: a variable the CLI sets for a child is listed under the process that reads it. Variables that name a role’s memory or nerves are never inherited by a process that is not that role; the harness strips them from everything it starts (see Memory and The senses and the reflexes). crates/hale-cli/tests/env_knobs_are_documented.rs fails if a variable the tree names is missing here or in the runtime’s tables (runtime.md), or a row names one the tree no longer does.

A boolean is on for any value but empty or 0 unless its row says otherwise.

Variable Default Effect
HALE_DNA_DISCOVER=off discover off makes the graft’s discovery (harnesses on PATH, local model servers) find nothing; for fixtures. The api head passes it on to the graft it runs.
HALE_DNA_TAPE=record|replay the catalog’s mode Whether a RecordedModel whose mode_env names it records answers or replays them; the trio fixture’s catalog and scripts/dna-cycle name it.
HALE_DNA_TAPE_DIR=<dir> the catalog’s dir The tape directory of a RecordedModel whose dir_env names it.
HALE_DNA_TOOLCHAIN=<path> the running hale The toolchain binary the host and the processes it starts use; hale dna sets it to the binary it is. It is part of a seed’s build fingerprint.
HALE_DNA_GENOME=<dir> unset The record root the organization takes its genome from; hale dna and the host set it for the organization they start, and the model adapters read it at birth like a credential names its source.
HALE_DNA_GENOME_POLL=<s> 300 How often the host polls the record’s remote for a changed genome, to restart the organization on it.
HALE_DNA_NO_BUILD_CACHE off Turns the content-fingerprint seed build cache off, so every seed is built.
HALE_DNA_FORGE=<kind> inferred Names the forge (github, the file forge) instead of inferring it from github: in the record’s profile or the remote.
HALE_DNA_TRANSPORT=<kind> ssh local runs every infrastructure script on this machine instead of over ssh; for fixtures.
HALE_DNA_TRANSPORT_HOME=<dir> unset With local, the body’s home directory.
HALE_DNA_GIT_TIMEOUT=<s> 8 Seconds a record fetch, push or ls-remote may take before it is killed.
HALE_DNA_NODE=<name> <hostname>:<pid> This node’s name in its claims (GH #1026).
HALE_DNA_EXCHANGE_PER_SENDER=<n> 10000 How many envelopes of a kind one sender’s subject of this record’s exchange stream holds (max_msgs_per_subject), set when hale dna nerves migrate creates or updates the stream; a fixture lowers it to test a full inbox (GH #1590).
HALE_DNA_OWNER=<org> unnamed The organization this body is, for the ownership graph.
HALE_DNA_LEASE=<name> unset The lease a body holds; set by the body for the processes it starts.
HALE_DNA_LEASE_TOKEN=<n> unset The lease’s fencing token; set with HALE_DNA_LEASE.
HALE_DNA_MEMORY_DSN_OWNER=<dsn> the memory allocation A Postgres of your own instead of the organism’s memory allocation in the local habitat: the owner role that migrates memory and creates the record’s roles. Migrated by hale habitat migrate, memory migrate and upgrade; read, never migrated, by dev, head and show; and by the fixtures that migrate or drop a record.
HALE_DNA_MEMORY_DSN_SPINE=<dsn> unset The spine role’s DSN, printed by memory migrate and handed to the process that is the spine.
HALE_DNA_MEMORY_DSN_HEAD=<dsn> unset The head role’s DSN, handed to the head and to nothing else.
HALE_DNA_APPLICATION_ID=<identity> set by hale dna dev The attach handover (GH #1653): the record’s identity, the application id a habitat-scoped heart presents to its head.
HALE_DNA_HEAD_URL=<url> set by hale dna dev The attach handover: the head the heart reaches (git config dna.head.url, http://127.0.0.1:8792 by default). Also read by hale habitat and hale dna verbs as the head URL when --head <url> is not given (precedence: --head, HALE_DNA_HEAD_URL, <habitat>/head.url, habitat.profile).
HALE_DNA_OIDC_ISSUER=<url> set by hale dna dev The attach handover: the OIDC issuer the heart’s service client asks (dna.oidc.issuer, http://127.0.0.1:8794 by default).
HALE_DNA_OIDC_CLIENT=<id> set by hale dna dev The attach handover: the heart’s service client id, mapped at graft as dna.oidc.service <id>=<id>.
HALE_DNA_OIDC_CLIENT_VAULT=<slot> set by hale dna dev The attach handover: the vault slot holding the service client’s secret (oidc-service-<id>), never the secret.
HALE_DNA_BROKER_URL=<url> set for a heart the habitat allocated broker The attach handover: the organism’s nerves server, with no credential.
HALE_DNA_BROKER_USER=<user> set for a heart the habitat allocated broker The attach handover: the heart’s broker user, heart-<id>.
HALE_DNA_BROKER_VAULT=<slot> set for a heart the habitat allocated broker The attach handover: the vault slot of the broker user’s password, nats-<org>-heart.
HALE_DNA_BROKER_SUBJECTS=<subjects> set for a heart the habitat allocated broker The attach handover: the heart’s own subject space, heart.<id>.>.
HALE_DNA_BROKER_NODE_USER=<user> set for a heart the habitat allocated broker The attach handover: the user the heart’s nodes connect as, heart-<id>-node.
HALE_DNA_BROKER_NODE_VAULT=<slot> set for a heart the habitat allocated broker The attach handover: the vault slot of the nodes’ broker password, nats-<org>-heart-node, which the heart passes a node at enrollment.
HALE_DNA_HABITAT=<dir> ${XDG_DATA_HOME:-~/.local/share}/hale/habitat/local The local habitat’s directory, named outright (GH #1653): a test’s or a DNA suite slice’s habitat; the developer’s stays the default. A test never inherits it: hale test sets each file’s to the habitat beside its vault, and the CLI tests’ hale() its process’s, whatever the caller exported.
HALE_HABITAT_IMAGE=<tag> built from Dockerfile and hale.pin An existing local docker image for the habitat’s body and face compose services, skipping the Dockerfile build (--image <tag> on hale habitat up takes precedence). hale habitat image build [<checkout>] builds it once from a local checkout and hale test and the DNA suite pass its tag to fixture children.
HALE_DNA_TEST_HABITAT=<dir> unset Set by the DNA suite for a slice whose fixtures graft (GH #1653): the slice’s habitat, born once with no body, which dna::test_habitat hands a fixture; unset, a test file births a habitat of its own.
HALE_DNA_TEST_HABITAT_VAULT=<dir> unset Set beside HALE_DNA_TEST_HABITAT: the vault the slice’s habitat was born with, whose entries dna::test_habitat lends into the fixture’s own vault.
HALE_DNA_DATABASE_DSN=<dsn> set for a heart whose genome declares database The heart’s own database, a tenant of the organism’s memory server provisioned at graft (GH #1653): a DSN naming the role heart_<id> and its vault slot postgres-heart_<id>, never a password. hale dna dev hands it to the host, and the heart inherits it.
HALE_DNA_MEMORY_FROM=owner|habitat <project>/memory-<id> written by every migration Not set by hand: the line beside the head’s DSN in a clone’s .hale/dna/memory.env that says where the DSN was derived, an operator’s own Postgres (owner) or the organism’s memory allocation in the local habitat. A kept DSN whose origin is anything else (another habitat’s allocation, an older toolchain’s database, or no origin) is removed and refused, naming hale habitat migrate (GH #1653).
HALE_DNA_OWNER_KEYS=<keys> one owner <owner>=<key> …, space-separated: the owners memory migrate writes for a shared record. The keys stay the record’s; memory never holds signature bytes.
HALE_DNA_RECEIPT_KEY=<key> unset Sixteen characters or more: the key evidence bodies are sealed under. The first key given is written; a different one later is refused, because bodies sealed under the first would no longer open.
HALE_DNA_NATS_URL_OWNER=<url> the nerves allocation The owner user’s URL: it creates the organization’s stream (hale habitat migrate, nerves migrate); dev and nerves urls derive the roles’ URLs on it.
HALE_DNA_NATS_URL_SPINE=<url> unset The spine user’s URL, handed to the process that is the spine.
HALE_DNA_NATS_URL_HEAD=<url> unset The head user’s URL: subscribes, publishes nothing.
HALE_DNA_NATS_URL_APP=<url> unset The application’s URL: publishes on its own subjects only.
HALE_DNA_NATS_URL_REFLEXES=<url> unset The reflexes’ URL: publishes on <org>.app.reflexes.> alone.
HALE_DNA_NATS_USER_APP=<user> unset The attached application’s own nerves user, app-<name> (hale dna nerves migrate prints it; GH #989).
HALE_DNA_NATS_VAULT_APP=<name> unset The vault entry holding the application user’s nerves password (hale dna nerves migrate prints it): presented on CONNECT, never carried in a URL (GH #989).
HALE_DNA_NATS_SECRET_APP=<path> unset Handed by a repository body to its compose services alone: the path of the local vault’s entry holding the application’s nerves password, for the compose file to mount as a secret (ruling 11).
HALE_DNA_COMPOSE=off on A repository body leaves its compose services to the operator: it neither brings them up nor down, and says a repository: its compose services are left to the operator (HALE_DNA_COMPOSE=off) (ruling 11). The DNA suite sets it for every fixture; a fixture about compose opts in with on.
HALE_DNA_NATS_VAULT_REFLEXES=<name> unset The same for the reflexes user; dna/reflexes refuses to start without it.
HALE_DNA_COMMANDS_PORT=<port> unset The loopback port of the API child’s HTTP commands transport, pinned by dna/face/start.sh (8795 by default) for the head and by the head for its API child; unset, the child’s transport listens where the system puts it and the child reports the port it got (HALE_DNA_API_PORTS).
HALE_DNA_API_PORTS=<file> unset Where an API child writes the ports it serves on once they are bound (api=<port>, commands=<port> and mcp=<port> lines, renamed into place; the MCP port is always the system’s): set by the head for its child, which it starts with port 0 for whatever it does not pin, and reads the ports back from there.
HALE_DNA_NATS_ORG=<token> unset The organization’s token, the prefix of its subjects; dev hands it to the host and the host to the parts.
HALE_DNA_SENSES_URL=<url> unset The senses store’s read URL, for the reflexes.
HALE_DNA_SENSES_PORT=<n> the seed’s per-part port The port a part serves its scrape endpoint on; 0 serves nothing.
HALE_DNA_SENSES_HOST=<addr> 0.0.0.0 The address a part’s scrape endpoint listens on.
HALE_DNA_REFLEX_TICK_MS=<ms> 5000 How often the reflexes read the store.
HALE_DNA_REFLEX_WINDOW_SECS=<s> 600 How close two firings for one target are a pattern, not an accident.
HALE_DNA_ORGANIZATION_LAUNCH=<attempt> unset Set by the host on the organization process it starts for a workflow attempt: which attempt launched it.
HALE_DNA_RESTART_FOR=<mutation> unset Set by the host when it restarts an expression on a Mutation’s request; the expression journals expression.restarted for it (GH #529 D6).
HALE_DNA_EXPRESSION=<name> unset The expression that was restarted; set with HALE_DNA_RESTART_FOR.
HALE_DNA_DESIGN_SUFFIX=<text> unset Appended to the design practice hale habitat graft seeds, so a fixture can exercise upgrade’s supersession against real record history.
HALE_DNA_LIBRARY_VERSION=<version> the toolchain’s The version the library is proposed under (library/<node>@<version>, and on each idea): a fixture’s way to make a later toolchain out of the one binary it has, so upgrade’s supersession is exercised against real record history.
HALE_DNA_LIBRARY_SUFFIX=<text> unset Appended to every library idea’s text, so a later version’s ideas differ from the earlier’s (fixtures only).
HALE_DNA_LIBRARY_EARLIER_NAMES=1 unset The two moved chapters are proposed under their earlier names (library/api, library/shaping): a fixture’s way to make a record the earlier toolchain would have seeded, so an upgrade is seen finding them (fixtures only).
HALE_DNA_INSTANCE=<id> unset Set by a node on each instance it starts: which instance of its shape the process is.
HALE_DNA_BODY=<mark> unset The body’s mark: the host puts it in the environment of the processes it starts as a body (body_mark: holder, lease token, pid), and the body fence finds them by it.
HALE_DNA_SCAN_NEEDLE=<mark> unset Set by the body fence’s scan on a probe process: the mark it looks for in every other process’s environment (ps -E) to find the body’s processes.
HALE_DNA_SCAN_PROBE=yes unset Set on a probe process by the fence’s environment scan, which checks that this operating system shows one process another’s environment before relying on HALE_DNA_BODY.
HALE_VERSION=v<pin> unset Not read by the DNA: the host sets it for hale-lang.org’s install.sh when it installs the toolchain version a body is pinned to.
HALE_DNA_ONESHOT off A hale dna run application returns after the ping instead of staying up.
HALE_DNA_API=<url> http://127.0.0.1:8793 The head API a leg’s verbs (hale dna work …) talk to when --api is not given.
HALE_DNA_ID_TOKEN=<token> unset The bearer token a leg presents to the head; sent only over https:// or loopback, never plain http off this machine.
HALE_DNA_HOST_NAME=<name> unset The name of the ssh host a compose project is applied on, handed to that project’s environment by the habitat (spec/habitat.md, The handover); a project maps it in its compose file. Unset on this machine.
HALE_DNA_HOST_LISTEN=<addr> unset That host’s listen address (the first IPv4 address its name resolves to on the habitat’s machine), handed beside HALE_DNA_HOST_NAME, through the toolchain’s own resolver (hale habitat resolve); an apply on a host whose name resolves to none is refused. Unset on this machine.
HALE_DNA_HABITAT_LISTEN=<addr> 127.0.0.1 The address a habitat declares its face, head and OIDC stub on: what the face service binds (HALE_DNA_LISTEN_HOST) and where up probes them. Not set by hand: hale habitat up --host <name> sets it on every container it runs on that machine to the address <name> resolves to on the admin machine.
HALE_DNA_HABITAT_EXPECT=<record> unset The record a hale habitat down run on a habitat’s machine through a pointer must find there: when it is set and the habitat’s genome pins another record, or none, nothing is stopped or removed. Not set by hand: hale habitat down on the admin machine sets it to the pointer’s identity, after checking the same over SSH.
HALE_DNA_HABITAT_ADDRESS=<name> 127.0.0.1 The host a habitat’s face and head are recorded at (http://<name>:<port>, on its habitat.profile row and in head.url). Not set by hand: hale habitat up --host <name> sets it to <name> on the containers it runs there.
HALE_HABITAT_SSH_CONFIG=<file> unset An SSH config file hale habitat up --host and the verbs of a habitat on another machine hand ssh -F; unset, ssh reads the person’s own.
HALE_DNA_IDENTITY=publish unset The face’s launch whose sign-in provider is the habitat’s, shared with every organism it grafts (dna/face/start.sh): its key is kept in the face’s state (HALE_DNA_HEAD_STATE/oidc.key, made once) and the provider is published at <habitat>/identity.env (issuer, the key’s public half, the client, where it answers). A launch under a habitat that published one starts no provider and signs in through it. Not set by hand: the habitat’s compose render sets it on its face service.
HALE_DNA_GRAFANA=off on hale habitat up runs no Grafana behind the habitat’s face (and removes one an earlier up started); status names none. The DNA suite sets it for every fixture; habitat_operator_face_test opts in on its own up.
HALE_DNA_GRAFANA_PORT=<port> unset The loopback port of the habitat’s Grafana, which the standard face serves under /grafana/ behind the head’s gate. Not set by hand: the habitat’s compose render sets it on its face service (the head block’s sixth port). Unset, /grafana/ is a 404.
HALE_DNA_GRAFANA_HEADER=<name> unset The header the habitat’s Grafana trusts for the signed-in person’s name, drawn once per habitat (X-Hale-Grafana-<32 hex>, <habitat>/grafana.secrets, 0600): the face sets it on what it relays to Grafana, and nothing else on the machine knows it. Not set by hand.
HALE_DNA_FACE_HOME=<path> unset The path the standard face lands a signed-in visitor on in place of the organization shell at / (dna::face_home); the shell is then at /organization. Not set by hand: the habitat’s compose render sets it on its face service to /app/, the operator face. A value that is not a path of the face is ignored.
HALE_DNA_LISTEN_HOST=<addr> 127.0.0.1 The address the face, the head and the OIDC stub bind (dna::listen_host). Not set by hand: the habitat’s compose render sets it on its face service from the address the habitat declares (127.0.0.1 for a local habitat); on macOS, where face runs on the project’s network, it is 0.0.0.0 inside the container and compose publishes the ports on the declared address alone.
HALE_DNA_LOOPBACK_HOST=<host> 127.0.0.1 The host a process dials a port published on this machine’s loopback at (dna::loopback_host, dna::reach_url): an allocation’s recorded 127.0.0.1:<port> is dialled there. Not set by hand: the habitat’s compose render sets host.docker.internal on its body and face on macOS, where they run on the project’s network; on Linux they share the host’s network and it is unset. A listener in the process’s own container is dialled at 127.0.0.1 regardless.
HALE_DNA_HEAD_STATE=<dir> ${XDG_STATE_HOME:-$HOME/.local/state}/hale/dna/head The project head’s state directory (its launch token and key).
HALE_DNA_COMMAND_POLICY=<path> unset The explicit authority policy the native command lanes and the governance verbs require; with none, they refuse.
HALE_DNA_KNOWLEDGE_COMMAND_POLICY=<path> empty policy The policy file for Knowledge commands.
HALE_DNA_ORGANIZATION_POLICY=<path> unset An optional, independent policy for organization-source proposals and Review writes; configuring evidence reads does not enable them.
HALE_DNA_TASK_POLICY=<path> unset The policy for task administration commands.
HALE_DNA_RPC_SOCKET=<path> the record’s socket under XDG_RUNTIME_DIR/hale/dna (else .hale/dna) Where the head serves its surface (HeadCommands, GH #1417) over unix::Rpc; the capabilities name it to the legs.
HALE_DNA_ORG_DRAFTS=1 off Lets the api accept organization-source drafts (1 only).
HALE_DNA_OIDC_ISSUER=<url> unset The project head’s OIDC issuer; plain http is accepted on this machine, or on exactly the habitat’s declared address (HALE_DNA_HABITAT_ADDRESS), only. A project head needs the four HALE_DNA_OIDC_* below or it refuses to start.
HALE_DNA_OIDC_CLIENT=<id> unset The head’s client id at the issuer.
HALE_DNA_OIDC_MEMBER=<subject>=<member> unset Which member a subject is.
HALE_DNA_OIDC_KEY=<pin> unset The pin of the issuer’s signing key.
HALE_DNA_OIDC_KEY_FILE=<path> unset The stub issuer’s private signing key.
HALE_DNA_OIDC_KEY_X=<x> unset The x coordinate of the stub issuer’s public key.
HALE_DNA_OIDC_KEY_Y=<y> unset The y coordinate of the stub issuer’s public key.
HALE_DNA_OIDC_SERVICES=<clients> unset The service clients the stub issuer accepts.
HALE_DNA_NODE_EXPORTER=off on No machine’s node exporter or container exporter is installed or removed by this run: not by hale habitat up on the habitat’s machine, host add on a host, host remove, or a forget. The DNA suite sets it for every fixture (habitat_operator_face_test opts in on Linux); the run up --host makes inside the habitat’s image always has it, since the person’s machine installs the habitat machine’s exporter over SSH instead.
HALE_DNA_OIDC_DIAL=<url> unset Where the habitat’s sign-in provider answers on its own machine (http://<listen>:<port>), as its face published it in <habitat>/identity.env: an organism’s face launch probes it there before it signs in through the provider. Not set by hand.
HALE_DNA_OIDC_SERVICES_FILE=<path> unset A file of more service clients’ ids the stub issuer accepts, read at each request (dna/oidc). Not set by hand: the habitat’s face hands its provider <habitat>/identity.services, which a graft appends each organism’s heart’s service client to.
HALE_DNA_FACE=<name> set by the host The name of the named face (dna::Face param of the organism’s main) an application is started as.
HALE_DNA_FACE_PORT=<port> set by the host The loopback port a named face serves on, the port of its literal (also its one argument).
HALE_DNA_CHILD_PROOF=<proof> unset The proof a face gave its child; the child answers an unauthenticated request with X-Hale-Child: <digest of the proof>, so the face knows its own child answered before it forwards a token.
HALE_DNA_TRUSTED_LOCAL=1 off The trusted-local session, a fixture’s mode alone (1 only). A project serves its heads under OIDC.
HALE_DNA_KEEP_SCRATCH off Fixtures and the suite’s sweep keep their scratch roots, for looking at what a fixture built, and the compose projects run from under them. Without it, dna::scratch_done and the sweep take every compose project whose working directory is under a root they remove down with it (docker compose -p <project> down -v, found by the com.docker.compose.project.working_dir label on containers, or by name in the root’s .dna-compose-projects), and confirm it by the project’s label on containers and volumes. Neither ever takes down the developer’s local habitat, nor the habitat XDG_DATA_HOME names outside the root (GH #1653). A teardown that fails or leaves anything fails the fixture (or the slice), and keeps the root, its registry entry, the slice’s ledger and the projects it names for another attempt.
HALE_DNA_SUITE_TAG=<tag> unset The stamp a DNA suite slice puts on the processes and scratch roots it starts, so its sweep collects only its own.
HALE_DNA_WAIT_SCALE=<n> 1 A whole number of at least 1 that widens every fixture wait.
HALE_API_BIN=<path> built from the checkout dna/face/start.sh: a prebuilt api binary to launch, as --api does. A binary handed in is checked before anything is built.
HALE_HEAD_BIN=<path> built from the checkout dna/face/start.sh: a prebuilt head (project service) binary to launch, as --head does.
HALE_FACE_BIN=<path> built from the checkout dna/face/start.sh: a prebuilt standard face (dna/face/standard) to start in front of the head, as --face does. hale dna head hands in the one it built.
HALE_DNA_PUBLIC_PORT=<port> unset (the head’s own) The port the browser reaches the head on, when the standard face stands in front of it (dna/face/start.sh sets it to the face’s): the origin the head signs in under, answers as and checks a command against.
HALE_OIDC_BIN=<path> built from the checkout dna/face/start.sh: a prebuilt stub OpenID provider (dna/oidc/serve) to start. hale dna head hands in the one it built.
HALE_REFLEXES_BIN=<path> built from the checkout dna/face/start.sh: a prebuilt reflexes binary (dna/reflexes) to start beside an attached project. hale dna head hands in the one it built.