How Generation Works
Generation takes two inputs and produces files. The model is yours; the rules come from a pack. Nothing in the middle is specific to Data Vault, to JSON Schema or to any other output — that is why installing a different pack changes what you get without changing the application.
The two questions a definition answers
A pack's definitions are the rules. Each one answers two questions, and everything about
generation follows from them:
Which model elements does this apply to? A definition names a type_key, and an entity
whose type carries that key matches. It can narrow further, and these are the conditions
available — the same set the Explain why dialog reports on:
| Condition | Passes when |
|---|---|
type_key | the entity's type has this key |
cp_equals / cp_not_equals | a custom property has, or does not have, this value |
cp_exists | the custom property is set at all |
effective_cp_equals / effective_cp_not_equals | the same, after inheritance is applied |
name_ends_with | the entity's name ends with this text |
group_path_not_contains | the entity's group path avoids this text |
They combine with ALL, ANY and NOT, so one entity type can be sent down different templates depending on how it is configured — the Data Vault pack does exactly that. What there is not is priority, fallback, or a default rule: a definition either matches or it does not.
How often does it run? That is the scope:
| Scope | Runs | Produces |
|---|---|---|
entity | once per matching entity | one file per element |
model | once, for the whole model | one file over everything |
Every definition whose match applies generates. They do not compete and they do not know about each other, so two packs installed together simply both run — each quietly ignoring the types belonging to the other. "No template found for this type" is the correct answer, not a failure.
What runs per match
For each match the engine calls the definition's context builder — a Python function in the pack — and hands the result to its template. The builder is where the model is read: it walks attributes, follows relations, reads custom properties, and returns a plain data structure. The template only formats what it is given.
That split is deliberate. A template that had to traverse the model would mix two hard things — deciding what is true, and deciding how it looks — and neither would be testable alone. With the split, a pack's tests exercise the builder against a stub model and never render anything.
The engine's Jinja delimiters are not the usual ones: an expression is {@ … @} and a
block is {= … =}. That is so a template can emit dbt or Jinja code into the generated file
without the engine consuming it. Standard delimiters do not raise — they render as literal
text and the value comes out empty, so the failure looks like a context problem.
Where to go next
- Generating & Downloading Artifacts — running it, and what the phases mean.
- Writing a Template Pack — the three files of a pack, and the contracts each one has to satisfy.
- Show Templates — asking the application which definitions match one entity, without generating.