5. The oracle and the store

How every write is cross-checked against Unison, and where the results live.

The write path

clay add (or Save in the editor) does this, in order:

1. parse strictly            -> AST
2. lower                     -> Core
3. Rust kernel: typecheck    -> types, or a Clay-worded error   (nothing written)
4. Rust kernel: hash         -> hash H_k
5. emit Unison text          -> UCM: typecheck + hash            -> hash H_u
6. if the verdicts or hashes differ: TYPECHECK DISAGREEMENT / HASH DISAGREEMENT, write nothing
7. store objects + names + journal entry, atomically

The emitted text for the running example (this is exactly what UCM receives):

add : builtins.Int -> builtins.Int -> builtins.Int
add a b =
  (builtins.Int.+) a b

twice : builtins.Int -> builtins.Int
twice n =
  add n n

Note the fully qualified builtins. names and explicit signatures: the emitter is boring on purpose. Nothing about UCM’s behaviour is assumed from memory. To add a language feature you first write the Unison program, record UCM’s answer as a fixture, and only then make the kernel agree (the “oracle-first rule”).

Why keep UCM online

The kernel could be trusted alone after enough fixtures, but each write is a fresh, free differential test against the reference implementation. The cost is that writing needs Node and UCM; reading, checking and hashing run in the browser.

The store

packages/compiler/src/store.ts. Three ideas:

  • Objects are write-once and never deleted, keyed by hash. That makes rename, undo and forking nearly free.
  • Names are labels on hashes, kept per branch. Rename repoints a label and rewrites typed source text scope-aware, refusing anything that would change what a name refers to.
  • Every change is a journaled transaction, atomic, one undo step. Undo repoints names back.
objects   : hash -> { core, type, source text }          (immutable, grow-only)
branches  : name -> { names: label -> hash, journal: [...] }

Files and folders are a view: a “file” is an unhashed label on a term. Saving a file means “this file now contains exactly these terms”: editing re-derives dependents, removal is refused while a term is in use.

clay verify re-hashes every stored object without UCM and compares to its key, so a corrupted store is detectable offline. Because objects are immutable and unions of object sets are valid, the same design maps onto a bring-your-own object bucket with one compare-and-swap pointer per branch (packages/storage).

Next: Data types.