Finding
The Cardigann engine parses every template twice: once at definition load for validation (template::validate / template::validate_row_scoped in crates/eksetasis/src/client/cardigann/template.rs), and again at render time (render_specs / the extractor path). The load-time functions return Result<(), _> and discard the parsed AST.
Evidence
crates/eksetasis/src/client/cardigann/template.rs:108 (validate) and :116 (validate_row_scoped) both call parse(template)? then discard the nodes.
- Call sites in
definition.rs:365,371,381,383 use them purely as validators; the definition struct stores raw template strings.
RUST/validate-returns-unit fires on validate_row_scoped (the validate_ prefix); the sibling validate escapes the pattern but has the same shallow shape.
Why this matters
Parse-don't-validate (RUST.md#validation-constructors): a parsed template is the constrained type. Storing parsed ASTs in the definition would make load-time validation produce the value the render path consumes — one parse, no re-parse per search, and invalid states unrepresentable past load. This is the same class as aitesis#301.
Desired correction
Refactor the definition lifecycle: CardigannDefinition stores parsed templates (or a ParsedTemplate newtype over the node AST) produced once at load; the render path consumes them. validate/validate_row_scoped fold into the parser.
Done when: templates parse exactly once per definition load; no validate_* function returns Result<(), _>; the .kanon-lint-ignore entry added under #513 is removed.
Finding
The Cardigann engine parses every template twice: once at definition load for validation (
template::validate/template::validate_row_scopedincrates/eksetasis/src/client/cardigann/template.rs), and again at render time (render_specs/ the extractor path). The load-time functions returnResult<(), _>and discard the parsed AST.Evidence
crates/eksetasis/src/client/cardigann/template.rs:108(validate) and:116(validate_row_scoped) both callparse(template)?then discard the nodes.definition.rs:365,371,381,383use them purely as validators; the definition struct stores raw template strings.RUST/validate-returns-unitfires onvalidate_row_scoped(thevalidate_prefix); the siblingvalidateescapes the pattern but has the same shallow shape.Why this matters
Parse-don't-validate (RUST.md#validation-constructors): a parsed template is the constrained type. Storing parsed ASTs in the definition would make load-time validation produce the value the render path consumes — one parse, no re-parse per search, and invalid states unrepresentable past load. This is the same class as aitesis#301.
Desired correction
Refactor the definition lifecycle:
CardigannDefinitionstores parsed templates (or aParsedTemplatenewtype over the node AST) produced once at load; the render path consumes them.validate/validate_row_scopedfold into the parser.Done when: templates parse exactly once per definition load; no
validate_*function returnsResult<(), _>; the.kanon-lint-ignoreentry added under #513 is removed.