Seal generated children deeply
Close both the set of `pack`-generated children and each child's shape, or seal from the side with a hidden guard.
Rendered from
docs/how-to/seal-generated-children.md
in the engine repository, where a correction belongs, and where the
test suite executes every example on this page.
close seals exactly the node it wraps: it is deliberately
shallow. Around a
pack
generator, that means close(pack(...)) forbids adding children
to the generated map while each child’s own keys stay open. A typo’d
override is then absorbed instead of refused:
names: hide({ web: {} auth: {} })
deploy: close(pack($.names, { replicas: *1|integer tier: *standard|string }))
deploy: web: replicaz: 3
{
"deploy": {
"auth": { "replicas": 1, "tier": "standard" },
"web": { "replicas": 1, "replicaz": 3, "tier": "standard" }
}
}
Exit 0, and web ships with the default replicas: 1, the
misspelled replicaz riding along beside it, doing nothing. The
deep-seal spelling closes the template as well, so every generated
child is sealed too:
names: hide({ web: {} auth: {} })
deploy: close(pack($.names, close({
replicas: *1|integer
tier: *standard|string
})))
deploy: web: replicas: 3
{
"deploy": {
"auth": { "replicas": 1, "tier": "standard" },
"web": { "replicas": 3, "tier": "standard" }
}
}
The legitimate override composes exactly as before: web gets its
replicas: 3, auth keeps the defaults. Now misspell it against
the same sealed shape: the same document with deploy: web: replicaz: 3, as deploy.aon:
names: hide({ web: {} auth: {} })
deploy: close(pack($.names, close({
replicas: *1|integer
tier: *standard|string
})))
deploy: web: replicaz: 3
$ aontu deploy.aon
[aontu/closed]: Cannot resolve value at path $.deploy.web.replicaz
...
$ echo $?
1
The child refuses, naming the key. The rule generalises: close
never travels, so seal each level you mean to seal: the outer
close(...) pins the set of children, the inner close({...})
pins each child’s shape.
Seal the set without closing the tree
Sometimes the generated map itself has to stay open: other statements merge into it, or overlays you do not control land on it. A hidden guard seals from the side: meet a clone of the tree with a closed pack of the same table and an empty template:
environments: hide({ dev: {} prod: {} })
deploy: pack($.environments, { replicas: *1|integer })
envguard: hide($.deploy & close(pack($.environments, {})))
deploy: prod: replicas: 3
{ "deploy": { "dev": { "replicas": 1 }, "prod": { "replicas": 3 } } }
envguard evaluates on every run and emits nothing. An environment
that exists nowhere in the table now has nowhere to land: change
the last line of guard.aon to invent one:
environments: hide({ dev: {} prod: {} })
deploy: pack($.environments, { replicas: *1|integer })
envguard: hide($.deploy & close(pack($.environments, {})))
deploy: prod2: replicas: 3
$ aontu guard.aon
[aontu/closed]: Cannot resolve value at path $.envguard
...
$ echo $?
1
The annotated site is the overlay line that invented prod2; the
reported path is the guard’s own, which is the cost of guarding from
the side rather than in the tree.
For the shallow basics of close on a plain map, start at forbid
unexpected keys; the semantics are
specified in Closed values: close /
open. Both
recipes run live: the Kubernetes golden
path seals its service set
with close(pack(...)) as its drift guard, and the deployment
fleet keeps the environment
guard as a worked example in stack.aon.