aontu

Pin what a document means

Pin a document's meaning to one string with aontu hash, and detect when the meaning moves.

Rendered from docs/how-to/pin-a-document-hash.md in the engine repository, where a correction belongs, and where the test suite executes every example on this page.

A file hash moves when a comment moves. aontu hash prints one string that identifies a document’s meaning, so a lockfile, a registry entry or an agent can say “this definition, this version” and check the claim later. Write a small model as system.aon:

services: { &: { replicas: *1|integer tier: *standard|string } }
services: auth: replicas: 3
services: billing: tier: premium
$ aontu hash system.aon
aon1-kmZi3pPU2hnWQfwLnaFoC5iUtlrt6vbUzU7og-KxWJE

The aon1- prefix is a scheme id; the rest is a digest of the document’s canonical meaning after evaluation. Reorder the keys, add comments and split entries apart, as system-reordered.aon does:

# same meaning: keys reordered, comments added, one entry split
services: billing: tier: premium
services: auth: replicas: 3
services: { &: { replicas: *1|integer tier: *standard|string } }
$ aontu hash system-reordered.aon
aon1-kmZi3pPU2hnWQfwLnaFoC5iUtlrt6vbUzU7og-KxWJE

Same pin. The document is evaluated standalone before hashing, so even splitting it across files leaves the pin alone. Move the template into its own defaults.aon:

services: { &: { replicas: *1|integer tier: *standard|string } }

and load it from a two-line system-split.aon:

@"./defaults.aon"
services: auth: replicas: 3
services: billing: tier: premium
$ aontu hash system-split.aon
aon1-kmZi3pPU2hnWQfwLnaFoC5iUtlrt6vbUzU7og-KxWJE

The include is part of the evaluation, which also means the pin is transitive: an edit two includes deep moves it.

When the pin moves

Change what the document means and the string changes. Flip the replicas default from 1 to 2, as system-changed.aon does:

services: { &: { replicas: *2|integer tier: *standard|string } }
services: auth: replicas: 3
services: billing: tier: premium
$ aontu hash system-changed.aon
aon1-2kHqTOm6-XLy1j322NI3Wje3AEAgdN5K6OEZdLpor84

A stored pin is therefore a one-string staleness check: re-run the verb, compare, and only re-read the document when the strings differ. When one has moved and you want to see what moved, --form prints the exact text the digest is taken over, which is the thing to diff:

$ aontu hash --form system.aon
{"services":{&:{"replicas":*1|integer,"tier":*"standard"|string},"auth":{"replicas":3,"tier":*"standard"|string},"billing":{"replicas":*1|integer,"tier":"premium"}}}

The form carries marks the display canon omits (close(), hide()), so two documents that render alike but close differently pin differently. That is the point.

A broken document has no pin

A document that does not evaluate has no meaning to identify, and a hash of the wreck would agree with every other wreck. Give broken.aon an outright contradiction:

port: 8080
port: 9090
$ aontu hash broken.aon
aontu: broken.aon does not evaluate on its own; nothing to hash
...
$ echo $?
4

Exit 4 with the engine’s own refusal attached, so a pipeline that pins on release fails on the release that broke.

The hash form and its guarantees are specified under aontu hash. A pin also holds still across a recursive definition’s unrollings (Define a recursive schema shows one), and it pairs with the breaking gate: hash says whether the meaning moved, breaking says whether the move breaks anyone.