Generating record models¶
The pub.layers.* record models are generated from the vendored Layers
lexicons and committed to the repository. No model is hand-written. A
new Layers version is adopted by re-vendoring the lexicon tree,
regenerating the models, and confirming the drift gate is clean. The
generated modules carry descriptions, optionality, refined value types,
integer ranges, knownValues, and union discriminators that a lossy
schema round-trip would drop, so editing a generated file by hand is
both forbidden and pointless: the next regeneration overwrites it.
This guide drives the two CLI commands that own the pipeline:
lairs vendor and lairs gen. For the rationale (why generation
rather than authoring) see Generated models.
Vendor a lexicon tree¶
lairs vendor copies a lexicons/pub/layers tree from a local Layers
checkout into lairs/lexicons and rewrites MANIFEST.toml. Point
--from at the tree to vendor:
lairs vendor --from /path/to/layers/lexicons/pub/layers \
--layers-version 0.7.0 \
--layers-git-sha 3f2a1c9 \
--vendored-at 2026-06-16
The three provenance flags are optional. When omitted, the existing
manifest values are kept. To vendor a specific upstream ref or tag,
check it out first, then point --from at its
lexicons/pub/layers. Fetching by ref is a thin wrapper over the
same copy-and-manifest step, not a separate command.
Vendoring recomputes a content hash over the new tree. When the copied
content is byte-identical to the tree already vendored, the existing
hash and vendoring date are retained, so a no-op re-vendor does not
invalidate the committed generated models. The command prints the file
count and the resulting lexicon_tree_hash, then reminds you to run
lairs gen.
Regenerate the models¶
lairs gen reads the vendored tree and writes one module per
pub.layers.* namespace into lairs/records/_generated, plus the
package __init__:
Each lexicon document is parsed through panproto, mapped to the codegen
intermediate representation, and emitted as module text. Permission-set
(OAuth scope) lexicons are skipped, and a namespace whose lexicons are
all method-only (query, procedure, subscription) contributes no records
and emits no module, so the vendored tree can mirror the full Layers tree
without forcing a module for every namespace. Cross-namespace embeds (for
example annotation embedding defs#anchor) are resolved to imports so
every module type-checks in isolation. Emission is
deterministic. Classes and fields are ordered stably, and the output is
run through ruff format, ruff check --fix, and ruff format again so
the committed modules converge to a lint-clean canonical form. That
determinism is what makes the drift gate meaningful.
The generated modules are the import surface of lairs.records. A class
imports as from lairs.records._generated.<namespace> import <Class> and
is also re-exported through the namespace attribute, so
lairs.records.expression.Expression and
lairs.records.defs.Anchor resolve the same classes. A lexicon union
becomes a dx.TaggedUnion whose discriminator is kind and whose
variants pin a Literal discriminator value.
Run the drift gate¶
lairs gen --check regenerates into a temporary directory and compares
the result byte-for-byte against the committed modules. It exits zero
when they match and non-zero when they are stale:
This is the CI gate that keeps the committed models honest. It catches
two failure modes: a vendored tree that was changed without
regenerating, and a generated module that was hand-edited. When it
reports staleness, run lairs gen and commit the result.
What the manifest records¶
lairs/lexicons/MANIFEST.toml is the provenance of the vendored tree.
Its runtime form is the Manifest model, and the TOML file is its
serialized, reviewable form, written by lairs vendor. It records, under
[provenance], the upstream layers_git_sha, the layers_version, the
vendored_at date, and the lexicon_tree_hash, and under [counts],
the lexicon_files and record_types totals. The Manifest model is
flat: the [provenance] and [counts] groupings are a property of the
TOML serialization, not of the model fields.
The lexicon_tree_hash is the deterministic SHA-256 over the sorted
per-file hashes of every lexicon JSON document, so it depends only on
file paths and contents and is stable across machines. That hash is
stamped into the header of every emitted module, so a generated file
records the exact lexicon revision it came from:
# generated by lairs gen; do not edit
# lexicon tree hash cedfb9f34cc55fb5be1abb81dda63fd6a6671af2efd6a5e5923fca0c77ba0fd1
See also¶
- Codegen reference for the pipeline, spec, and emitter symbols.
- Generated models for why the models are generated and committed rather than authored.