Per-Renderer Config and Significance as a Renderer Concern¶
Date: 2026-03-09 Status: Decided (implemented)
Problem¶
The original design placed significance classification config in a flat output.significance section of the dataset config:
This had two problems:
-
It "knew too much" for the config layer. The significance mapping is inherently format-specific — it only matters to renderers that render human-readable changelogs (Markdown, potentially HTML). JSON changeset output doesn't use it. A CI-check renderer might want a completely different mapping. Putting it at the top level of
outputimplied it was a universal output concern. -
No mechanism for renderer-specific config. The
Renderertrait'srendermethod received a monolithicOutputConfigstruct. Every new renderer-specific knob would require modifying this shared struct — the opposite of a plugin architecture. A future HTML renderer wanting athemesetting, or a CI renderer wanting afail_onlist, would all crowd into the same type.
Options Considered¶
A: Significance as a transformer (rejected)¶
Make significance classification a transformer that annotates IR nodes with a significance field. Renderers read annotations.
Rejected because it pushes a renderer concern into the IR phase, violating the design principle that the IR carries factual tags and the renderer interprets them. It would bake a significance judgment into the changeset JSON, which is supposed to be format-agnostic and judgment-free. Different renderers couldn't apply different significance mappings to the same changeset.
B: Per-renderer config sections (chosen)¶
The output section becomes a map from renderer names to renderer-specific config objects. Each renderer defines its own config schema and defaults.
output:
markdown:
significance:
clerical:
- binoc.column-reorder
substantive:
- binoc.column-addition
The Renderer::render method receives a serde_json::Value (the renderer's own config section) instead of a monolithic OutputConfig. The renderer deserializes it into its own config type with #[serde(default)] for missing fields.
Decision¶
Option B. The implementation:
-
OutputConfigis now a struct wrappingBTreeMap<String, serde_json::Value>with#[serde(flatten)].get_for_renderer(name)resolves both short names ("markdown") and qualified names ("binoc.markdown"). -
Renderer::renderreceives&serde_json::Value— the renderer's own config section, or an empty object if absent. Renderers deserialize this into their own config types and apply their own defaults. -
MarkdownRendererConfigis a new public type inbinoc-core::outputwith asignificance: BTreeMap<String, Vec<String>>field. Default significance (the standard clerical/substantive mapping) lives here, not in the global config. -
The changeset IR is unchanged. Tags remain factual; significance classification remains a renderer concern. Different renderers can apply different significance mappings to the same changeset.
Consequences¶
- The same pattern naturally extends to future renderer-specific config (HTML themes, CI failure rules, etc.) without modifying shared types.
- Third-party renderers define their own config schemas — no coordination with core needed.
- The existing config format changes:
output.significance.*becomesoutput.markdown.significance.*. This is a breaking change, acceptable since the project is pre-release. - Per-plugin config for comparators and transformers is not yet implemented but could follow the same pattern (
comparator_config,transformer_configsections) if needed.