| 1 | # Collector Taxonomy |
| 2 | |
| 3 | Collector chart taxonomy is authored in public repo source files and |
| 4 | generated into a dashboard-consumable JSON artifact. |
| 5 | |
| 6 | ## Source Files |
| 7 | |
| 8 | - Collector taxonomy authoring file: |
| 9 | `<collector>/taxonomy.yaml`, sibling to `metadata.yaml`. |
| 10 | - Section registry: |
| 11 | `integrations/taxonomy/sections.yaml`. |
| 12 | - Icon registry: |
| 13 | `integrations/taxonomy/icons.yaml`. |
| 14 | - Schemas: |
| 15 | `integrations/schemas/taxonomy_collector.json`, |
| 16 | `integrations/schemas/taxonomy_sections.json`, |
| 17 | `integrations/schemas/taxonomy_output.json`. |
| 18 | - Generator: |
| 19 | `integrations/gen_taxonomy.py`. |
| 20 | - Touched-collector checker: |
| 21 | `integrations/check_collector_taxonomy.py`. |
| 22 | - Seed helper: |
| 23 | `integrations/gen_taxonomy_seed.py`. |
| 24 | |
| 25 | ## Authoring Contract |
| 26 | |
| 27 | `taxonomy.yaml` v1 is a closed schema. Unknown core keys fail |
| 28 | validation; extension keys must be prefixed with `x_`. |
| 29 | |
| 30 | Required top-level fields: |
| 31 | |
| 32 | - `taxonomy_version: 1` |
| 33 | - `plugin_name` |
| 34 | - `module_name` |
| 35 | - either `placements` or `taxonomy_optout`, not both |
| 36 | |
| 37 | Each placement requires `id`, `section_id`, `title`, and `items`. |
| 38 | `section_id` is the only accepted v1 section reference. |
| 39 | `section_path` is rejected in authoring files. Section IDs are stable |
| 40 | opaque handles; hierarchy is defined by `parent_id` in |
| 41 | `sections.yaml`. |
| 42 | |
| 43 | `items:` is an ordered recursive tree. The allowed item kinds are: |
| 44 | |
| 45 | - context-string shorthand such as `mysql.queries`; this is an owning |
| 46 | structural context leaf and normalizes to `type: owned_context`; |
| 47 | - `type: owned_context` with one literal `context`; |
| 48 | - `type: group` with stable hand-authored `id`, `title`, and nested |
| 49 | structural `items`; |
| 50 | - `type: flatten`, the structural equivalent of the legacy FE |
| 51 | `properties.justGroup` behavior; |
| 52 | - `type: selector` with one selector mechanism; |
| 53 | - `type: context`, a display widget that references contexts and |
| 54 | requires `contexts` plus `chart_library`; |
| 55 | - `type: grid`, `type: first_available`, and `type: view_switch` for |
| 56 | dashboard widget composition. |
| 57 | |
| 58 | Strings are allowed only in structural positions: placement `items`, |
| 59 | `group.items`, and `flatten.items`. Grid bodies, first-available |
| 60 | alternatives, and view-switch branches must use explicit object |
| 61 | items. Nested `flatten` under `flatten.items` is rejected. The |
| 62 | recursion matrix is exhaustive: unlisted container/item combinations |
| 63 | are invalid. |
| 64 | |
| 65 | `single_node:` is a sparse same-kind delta only. It may override |
| 66 | display, selector, or renderer fields allowed on the same item type. |
| 67 | It may not contain `type`, `items`, `multi_node`, `single_node`, or |
| 68 | change an owning item into a display widget. Whole-body single-vs- |
| 69 | multi differences use `type: view_switch`; `view_switch` and sparse |
| 70 | `single_node` are mutually exclusive on the same item. |
| 71 | |
| 72 | Renderer-private payloads live only under `renderer:`. Current known |
| 73 | keys are `renderer.overlays`, `renderer.url_options`, and |
| 74 | `renderer.toolbox_elements`; future renderer-only additions must use |
| 75 | `x_*` under `renderer`. `toolbox_elements`, `overlays`, and |
| 76 | `url_options` are not valid item-body siblings. |
| 77 | |
| 78 | ## Selectors And Context References |
| 79 | |
| 80 | Every literal context owned by `owned_context` or referenced by a |
| 81 | `context` widget must exist in the owning collector's `metadata.yaml` |
| 82 | under `metrics.scopes[].metrics[].name`, unless the exact reference |
| 83 | uses the explicit unresolved-reference escape hatch: |
| 84 | |
| 85 | ```yaml |
| 86 | contexts: |
| 87 | - context: mysql.future_context |
| 88 | unresolved: |
| 89 | reason: staged downstream rollout |
| 90 | owner: cloud-frontend |
| 91 | expires: "2026-08-01" |
| 92 | ``` |
| 93 | |
| 94 | Dynamic collectors must opt in from metadata: |
| 95 | |
| 96 | ```yaml |
| 97 | metrics: |
| 98 | dynamic_context_prefixes: |
| 99 | - prefix: snmp. |
| 100 | reason: SNMP profiles emit vendor-specific contexts at runtime. |
| 101 | dynamic_collect_plugins: |
| 102 | - plugin: statsd.plugin |
| 103 | reason: statsd synthetic charts are operator-defined. |
| 104 | ``` |
| 105 | |
| 106 | `type: selector` owns the contexts it resolves from `context_prefix` |
| 107 | or `collect_plugin`. Selector objects inside a widget `contexts:` |
| 108 | array reference contexts but do not own them. `context_prefix_exclude` |
| 109 | is valid only on the same item/reference that also has |
| 110 | `context_prefix`. A `context_prefix:` value may narrow a declared |
| 111 | metadata dynamic namespace; for example, a collector that declares |
| 112 | `dynamic_context_prefixes: [{prefix: snmp., ...}]` may use |
| 113 | `context_prefix: [snmp.device_prof_]` in taxonomy authoring. |
| 114 | |
| 115 | `collect_plugin:` selects by Agent `_collect_plugin` label. It is for |
| 116 | dynamic contexts that do not share a stable context-name prefix. |
| 117 | |
| 118 | ## Output Artifact |
| 119 | |
| 120 | `integrations/gen_taxonomy.py` emits gitignored |
| 121 | `integrations/taxonomy.json`: |
| 122 | |
| 123 | - `taxonomy_schema_version` |
| 124 | - `source.netdata_commit` |
| 125 | - `source.generated_at` |
| 126 | - normalized `sections` |
| 127 | - normalized `placements` |
| 128 | - `opted_out_collectors` |
| 129 | |
| 130 | Each placement and item includes: |
| 131 | |
| 132 | - `resolved_contexts`: contexts owned by structural strings, |
| 133 | `owned_context`, and selector items. |
| 134 | - `referenced_contexts`: contexts referenced by display widgets, |
| 135 | grids, first-available alternatives, and view-switch widget |
| 136 | branches. |
| 137 | - `unresolved_references`: explicit unresolved-reference escape |
| 138 | hatches with `context`, `reason`, `owner`, `expires`, and |
| 139 | `item_path`. This is the durable signal that a widget reference is |
| 140 | intentionally unresolved instead of accidentally missing. `expires` |
| 141 | uses `YYYY-MM-DD`. |
| 142 | |
| 143 | The snapshots are deterministic for identical repository input and |
| 144 | preserve author item order for the recursive tree. |
| 145 | |
| 146 | ## CI Contract |
| 147 | |
| 148 | Pull requests run `integrations/check_collector_taxonomy.py` from |
| 149 | `.github/workflows/check-markdown.yml`. The checker: |
| 150 | |
| 151 | - validates all committed `taxonomy.yaml` files; |
| 152 | - validates the generated artifact shape; |
| 153 | - fails when a PR adds/removes a collector `taxonomy.yaml`; |
| 154 | - fails when a PR edits a collector `metadata.yaml` metrics block |
| 155 | without a sibling `taxonomy.yaml`. |
| 156 | |
| 157 | The master regeneration workflow runs `gen_taxonomy.py` and removes |
| 158 | the gitignored artifact during cleanup. |
| 159 | |
| 160 | ## Finding Codes |
| 161 | |
| 162 | Active v1 codes: |
| 163 | |
| 164 | | Code | Severity | Meaning | |
| 165 | |---|---|---| |
| 166 | | TAX001 | fatal | Schema/load failure or missing matching metadata. | |
| 167 | | TAX002 | reserved | Reserved for a future empty-effective-node lint; current empty authoring shapes fail schema/load validation as TAX001. | |
| 168 | | TAX003 | fatal | Literal context is not declared by the owning collector metadata. | |
| 169 | | TAX006 | fatal | Duplicate section or placement ownership key. | |
| 170 | | TAX021 | fatal | Invalid `single_node` override key or shape. | |
| 171 | | TAX022 | fatal | `multi_node:` used outside `type: view_switch`. | |
| 172 | | TAX023 | fatal | List-merge syntax such as `*_extend` used; v1 lists replace. | |
| 173 | | TAX024 | warning | Empty `single_node:` block. | |
| 174 | | TAX025 | warning | `single_node` override equals the top-level value. | |
| 175 | | TAX028 | fatal | Unknown/deprecated section, unknown icon, or invalid section authoring shape. | |
| 176 | | TAX029 | fatal | Invalid dynamic declaration location or `context_prefix_exclude` usage. | |
| 177 | | TAX030 | fatal | Touched collector needs `taxonomy.yaml` coverage. | |
| 178 | | TAX031 | fatal | `context_prefix` used without metadata opt-in. | |
| 179 | | TAX032 | reserved | Reserved for a future narrower prefix-overlap diagnostic; current selector ownership overlap conflicts emit TAX036. | |
| 180 | | TAX033 | fatal | Resolved context owned by more than one placement. | |
| 181 | | TAX034 | warning | Literal context is redundant because a prefix already covers it. | |
| 182 | | TAX035 | fatal | `collect_plugin` used without metadata opt-in. | |
| 183 | | TAX036 | fatal | Selector overlap conflict across collector/type boundaries. | |
| 184 | | TAX037 | fatal | Literal context is referenced by a widget but not owned by any structural item. | |
| 185 | | TAX038 | warning | `unresolved` escape hatch is stale because the context now resolves. | |
| 186 | |
| 187 | Removed v1 codes: |
| 188 | |
| 189 | - TAX026 and TAX027 were removed with `only_views`. |
| 190 | - TAX040 through TAX042 were removed with chart-recipe manifests. |
| 191 | |
| 192 | ## Contributor Rule |
| 193 | |
| 194 | Collector context changes and taxonomy changes move together. A PR |
| 195 | that adds, removes, or renames chart contexts must update |
| 196 | `metadata.yaml` and `taxonomy.yaml` in the same change unless the |
| 197 | collector uses a declared dynamic selector that covers the context. |