Documentation/Architecture

Multi-tenancy boundary

The main security invariant: every PromQL query goes through `composeQuery` or `rewritePromQL`. Both inject organization_id as a mandatory label matcher. There is no raw-query path for user input.

composeQuery

Takes a bare metric name + filters → builds PromQL of the form:

metric{organization_id="<org-id>", host="web-01"}

Mandatory filters always win. Optional ones (from URL params) are added if they don't conflict.

rewritePromQL

Parses the user's PromQL via prometheus/promql/parser, walks the AST via parser.Inspect. On each VectorSelector and MatrixSelector the LabelMatchers are replaced with a merged set where organization_id wins.

A user cannot "bypass" organization_id even by stating it explicitly in the query — the rewrite overrides it.